Skip to content

Commit

Permalink
Merge pull request #66 from zomglings/watch-more-keenly
Browse files Browse the repository at this point in the history
Improved "moonworm watch", added "moonworm find-deployment"
  • Loading branch information
Yhtiyar authored Apr 19, 2022
2 parents 4653ea6 + fa7ccac commit 67ab68f
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 9 deletions.
49 changes: 49 additions & 0 deletions moonworm/cli.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import argparse
import json
import os
from multiprocessing.sharedctypes import Value
from pathlib import Path
from shutil import copyfile

Expand All @@ -12,6 +13,7 @@

from .contracts import CU, ERC20, ERC721, CULands
from .crawler.networks import Network
from .deployment import find_deployment_block
from .generators.basic import (
generate_contract_cli_content,
generate_contract_interface_content,
Expand Down Expand Up @@ -138,6 +140,7 @@ def handle_watch(args: argparse.Namespace) -> None:
contract_abi=contract_abi,
num_confirmations=args.confirmations,
start_block=args.start,
end_block=args.end,
outfile=args.outfile,
)
finally:
Expand All @@ -152,6 +155,7 @@ def handle_watch(args: argparse.Namespace) -> None:
contract_abi=contract_abi,
num_confirmations=args.confirmations,
start_block=args.start,
end_block=args.end,
outfile=args.outfile,
)

Expand Down Expand Up @@ -184,6 +188,16 @@ def handle_watch_cu(args: argparse.Namespace) -> None:
)


def handle_find_deployment(args: argparse.Namespace) -> None:
web3_client = Web3(Web3.HTTPProvider(args.web3))
result = find_deployment_block(web3_client, args.contract, args.interval)
if result is None:
raise ValueError(
f"Address does not represent a smart contract: {args.contract}"
)
print(result)


def generate_argument_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Moonworm: Manage your smart contract")

Expand Down Expand Up @@ -233,6 +247,14 @@ def generate_argument_parser() -> argparse.ArgumentParser:
help="Block number to start watching from",
)

watch_parser.add_argument(
"--end",
"-e",
type=int,
default=None,
help="Block number at which to end watching",
)

watch_parser.add_argument(
"--poa",
action="store_true",
Expand Down Expand Up @@ -363,6 +385,33 @@ def generate_argument_parser() -> argparse.ArgumentParser:
help="Force rewrite generated files",
)
generate_parser.set_defaults(func=handle_generate)

find_deployment_parser = subcommands.add_parser(
"find-deployment",
description="Find the block where a smart contract was deployed",
)
find_deployment_parser.add_argument(
"-w",
"--web3",
required=True,
help="Web3 provider",
)
find_deployment_parser.add_argument(
"-c",
"--contract",
type=Web3.toChecksumAddress,
required=True,
help="Contract address",
)
find_deployment_parser.add_argument(
"-t",
"--interval",
type=float,
default=1.0,
help="Number of seconds (float) to wait between web3 calls",
)
find_deployment_parser.set_defaults(func=handle_find_deployment)

return parser


Expand Down
104 changes: 104 additions & 0 deletions moonworm/deployment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""
This module allows users to inspect the conditions under which a smart contract was deployed.
"""
import logging
import os
import time
from typing import Dict, Optional

from eth_typing.evm import ChecksumAddress
from web3 import Web3

CONFIG_KEY_WEB3_INTERVAL = "web3_interval"
CONFIG_KEY_WEB3_LAST_CALL = "web3_last_call"

logger = logging.getLogger("moonworm.deployment")
VERBOSE = os.environ.get("MOONWORM_VERBOSE", "f").lower() in {
"y",
"yes",
"t",
"true",
"1",
}
logger.setLevel(logging.INFO if VERBOSE else logging.WARNING)


def was_deployed_at_block(
web3_client: Web3,
contract_address: ChecksumAddress,
block_number: int,
config: Optional[Dict[str, float]],
) -> bool:
if config is not None:
interval = config.get(CONFIG_KEY_WEB3_INTERVAL)
if interval is not None:
last_call = config.get(CONFIG_KEY_WEB3_LAST_CALL)
current_time = time.time()
if last_call is not None and current_time < last_call + interval:
time.sleep(last_call + interval - current_time + 1)

code = web3_client.eth.get_code(contract_address, block_identifier=block_number)

if config is not None:
config[CONFIG_KEY_WEB3_LAST_CALL] = time.time()

code_hex = code.hex()
was_deployed = not (code_hex == "0x" or code_hex == "0x0" or code_hex == "")
return was_deployed


def find_deployment_block(
web3_client: Web3,
contract_address: ChecksumAddress,
web3_interval: float,
) -> Optional[int]:
"""
Note: We will assume no selfdestruct for now.
This means that, if the address does not currently contain code, we will assume it never contained
code and is therefore not a smart contract address.
"""
log_prefix = f"find_deployment_block(web3_client, contract_address={contract_address}, web3_interval={web3_interval}) -- "

logger.info(f"{log_prefix}Function invoked")
config = {CONFIG_KEY_WEB3_INTERVAL: web3_interval}

max_block = int(web3_client.eth.block_number)
min_block = 0
middle_block = int((min_block + max_block) / 2)

was_deployed_at_max_block = was_deployed_at_block(
web3_client, contract_address, max_block, config=config
)
if not was_deployed_at_max_block:
logger.warn(f"{log_prefix}Address is not a smart contract")
return None

was_deployed: Dict[int, bool] = {
max_block: was_deployed_at_max_block,
min_block: was_deployed_at_block(
web3_client, contract_address, min_block, config=config
),
middle_block: was_deployed_at_block(
web3_client, contract_address, middle_block, config=config
),
}

while max_block - min_block >= 2:
logger.info(
f"{log_prefix}Binary search -- max_block={max_block}, min_block={min_block}, middle_block={middle_block}"
)
if not was_deployed[min_block] and not was_deployed[middle_block]:
min_block = middle_block
else:
max_block = middle_block

middle_block = int((min_block + max_block) / 2)

was_deployed[middle_block] = was_deployed_at_block(
web3_client, contract_address, middle_block, config=config
)

if was_deployed[min_block]:
return min_block
return max_block
2 changes: 1 addition & 1 deletion moonworm/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
MOONWORM_VERSION = "0.1.18"
MOONWORM_VERSION = "0.1.19"
20 changes: 12 additions & 8 deletions moonworm/watch.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def watch_contract(
num_confirmations: int = 10,
sleep_time: float = 1,
start_block: Optional[int] = None,
end_block: Optional[int] = None,
outfile: Optional[str] = None,
) -> None:
"""
Expand All @@ -77,19 +78,22 @@ def watch_contract(
ofp = None
if outfile is not None:
ofp = open(outfile, "a")

try:
while True:
while end_block is None or current_block <= end_block:
time.sleep(sleep_time)
end_block = min(
until_block = min(
web3.eth.blockNumber - num_confirmations, current_block + 100
)
if end_block < current_block:
if end_block is not None:
until_block = min(until_block, end_block)
if until_block < current_block:
sleep_time *= 2
continue

sleep_time /= 2

crawler.crawl(current_block, end_block)
crawler.crawl(current_block, until_block)
if state.state:
print("Got transaction calls:")
for call in state.state:
Expand All @@ -104,7 +108,7 @@ def watch_contract(
web3,
event_abi,
current_block,
end_block,
until_block,
[contract_address],
)
for event in all_events:
Expand All @@ -115,10 +119,10 @@ def watch_contract(
ofp.flush()

progress_bar.set_description(
f"Current block {end_block}, Already watching for"
f"Current block {until_block}, Already watching for"
)
progress_bar.update(end_block - current_block + 1)
current_block = end_block + 1
progress_bar.update(until_block - current_block + 1)
current_block = until_block + 1
finally:
if ofp is not None:
ofp.close()

0 comments on commit 67ab68f

Please sign in to comment.