kopia lustrzana https://github.com/bugout-dev/moonworm
commit
18cb7e55e9
105
moonworm/cli.py
105
moonworm/cli.py
|
@ -23,11 +23,17 @@ from .version import MOONWORM_VERSION
|
|||
|
||||
|
||||
def write_file(content: str, path: str):
|
||||
"""
|
||||
Write content to filesystem at the specified path.
|
||||
"""
|
||||
with open(path, "w") as ofp:
|
||||
ofp.write(content)
|
||||
|
||||
|
||||
def copy_web3_util(dest_dir: str, force: bool = False) -> None:
|
||||
"""
|
||||
Copy the web3_util.py file to the given destination directory.
|
||||
"""
|
||||
dest_filepath = os.path.join(dest_dir, "web3_util.py")
|
||||
if os.path.isfile(dest_filepath) and not force:
|
||||
print(f"{dest_filepath} file already exists. Use -f to rewrite")
|
||||
|
@ -36,6 +42,9 @@ def copy_web3_util(dest_dir: str, force: bool = False) -> None:
|
|||
|
||||
|
||||
def create_init_py(dest_dir: str, force: bool = False) -> None:
|
||||
"""
|
||||
Create __init__.py file in destination directory.
|
||||
"""
|
||||
dest_filepath = os.path.join(dest_dir, "__init__.py")
|
||||
if os.path.isfile(dest_filepath) and not force:
|
||||
print(f"{dest_filepath} file already exists. Use -f to rewrite")
|
||||
|
@ -44,6 +53,10 @@ def create_init_py(dest_dir: str, force: bool = False) -> None:
|
|||
|
||||
|
||||
def handle_generate(args: argparse.Namespace) -> None:
|
||||
"""
|
||||
Handler for the "moonworm generate" command, which generates web3.py-compatible interfaces to a
|
||||
given smart contract.
|
||||
"""
|
||||
if not args.interface and not args.cli:
|
||||
print("Please specify what you want to generate:")
|
||||
print("--interface for smart contract interface")
|
||||
|
@ -85,7 +98,10 @@ def handle_generate(args: argparse.Namespace) -> None:
|
|||
|
||||
|
||||
def handle_brownie_generate(args: argparse.Namespace):
|
||||
|
||||
"""
|
||||
Handler for the "moonworm generate-brownie" command, which generates brownie-compatible interfaces
|
||||
to a given smart contract.
|
||||
"""
|
||||
Path(args.outdir).mkdir(exist_ok=True)
|
||||
|
||||
project_directory = args.project
|
||||
|
@ -118,6 +134,10 @@ def handle_brownie_generate(args: argparse.Namespace):
|
|||
|
||||
|
||||
def handle_watch(args: argparse.Namespace) -> None:
|
||||
"""
|
||||
Handler for the "moonworm watch" command, which records all events and transactions against a given
|
||||
smart contract between the specified block range.
|
||||
"""
|
||||
if args.abi == "erc20":
|
||||
contract_abi = ERC20.abi()
|
||||
elif args.abi == "erc721":
|
||||
|
@ -177,35 +197,11 @@ def handle_watch(args: argparse.Namespace) -> None:
|
|||
)
|
||||
|
||||
|
||||
def handle_watch_cu(args: argparse.Namespace) -> None:
|
||||
from moonworm.cu_watch import watch_cu_contract
|
||||
|
||||
MOONSTREAM_DB_URI = os.environ.get("MOONSTREAM_DB_URI")
|
||||
if not MOONSTREAM_DB_URI:
|
||||
print("Please set MOONSTREAM_DB_URI environment variable")
|
||||
return
|
||||
|
||||
if args.abi is not None:
|
||||
with open(args.abi, "r") as ifp:
|
||||
contract_abi = json.load(ifp)
|
||||
else:
|
||||
print("Using CUContract abi since no abi is specified")
|
||||
contract_abi = CU.abi()
|
||||
|
||||
web3 = Web3(Web3.HTTPProvider(args.web3))
|
||||
web3.middleware_onion.inject(geth_poa_middleware, layer=0)
|
||||
|
||||
watch_cu_contract(
|
||||
web3,
|
||||
web3.toChecksumAddress(args.contract),
|
||||
contract_abi,
|
||||
args.confirmations,
|
||||
start_block=args.deployment_block,
|
||||
force_start=args.force,
|
||||
)
|
||||
|
||||
|
||||
def handle_find_deployment(args: argparse.Namespace) -> None:
|
||||
"""
|
||||
Handler for the "moonworm find-deployment" command, which finds the deployment block for a given
|
||||
smart contract.
|
||||
"""
|
||||
web3_client = Web3(Web3.HTTPProvider(args.web3))
|
||||
result = find_deployment_block(web3_client, args.contract, args.interval)
|
||||
if result is None:
|
||||
|
@ -216,6 +212,9 @@ def handle_find_deployment(args: argparse.Namespace) -> None:
|
|||
|
||||
|
||||
def generate_argument_parser() -> argparse.ArgumentParser:
|
||||
"""
|
||||
Generates the command-line argument parser for the "moonworm" command.
|
||||
"""
|
||||
parser = argparse.ArgumentParser(description="Moonworm: Manage your smart contract")
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
|
@ -327,51 +326,6 @@ def generate_argument_parser() -> argparse.ArgumentParser:
|
|||
|
||||
watch_parser.set_defaults(func=handle_watch)
|
||||
|
||||
watch_cu_parser = subcommands.add_parser(
|
||||
"watch-cu", help="Watch a Crypto Unicorns contract"
|
||||
)
|
||||
watch_cu_parser.add_argument(
|
||||
"-i",
|
||||
"--abi",
|
||||
default=None,
|
||||
help="ABI file path, default is abi in fixtures/abis/CU.json",
|
||||
)
|
||||
watch_cu_parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
action="store_true",
|
||||
help="Force start from given block",
|
||||
)
|
||||
watch_cu_parser.add_argument(
|
||||
"-c",
|
||||
"--contract",
|
||||
required=True,
|
||||
help="Contract address",
|
||||
)
|
||||
|
||||
watch_cu_parser.add_argument(
|
||||
"-w",
|
||||
"--web3",
|
||||
required=True,
|
||||
help="Web3 provider",
|
||||
)
|
||||
|
||||
watch_cu_parser.add_argument(
|
||||
"--confirmations",
|
||||
default=10,
|
||||
type=int,
|
||||
help="Number of confirmations to wait for. Default=12",
|
||||
)
|
||||
|
||||
watch_cu_parser.add_argument(
|
||||
"--deployment-block",
|
||||
"-d",
|
||||
type=int,
|
||||
help="Block number of the deployment",
|
||||
)
|
||||
|
||||
watch_cu_parser.set_defaults(func=handle_watch_cu)
|
||||
|
||||
generate_brownie_parser = subcommands.add_parser(
|
||||
"generate-brownie", description="Moonworm code generator for brownie projects"
|
||||
)
|
||||
|
@ -471,6 +425,9 @@ def generate_argument_parser() -> argparse.ArgumentParser:
|
|||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Handler for the "moonworm" command.
|
||||
"""
|
||||
parser = generate_argument_parser()
|
||||
args = parser.parse_args()
|
||||
args.func(args)
|
||||
|
|
|
@ -1,130 +0,0 @@
|
|||
import json
|
||||
from dataclasses import dataclass
|
||||
from re import L
|
||||
|
||||
from moonstreamdb.db import yield_db_session_ctx
|
||||
from moonstreamdb.models import PolygonLabel
|
||||
from sqlalchemy.orm.session import Session
|
||||
from web3 import Web3
|
||||
|
||||
from .contracts import CU
|
||||
|
||||
ADDRESS = "0xdC0479CC5BbA033B3e7De9F178607150B3AbCe1f"
|
||||
MUMBAI_ADDRESS = "0xA993c4759B731f650dfA011765a6aedaC91a4a88"
|
||||
|
||||
|
||||
def get_all_DNA_events(session: Session):
|
||||
labels = (
|
||||
session.query(PolygonLabel)
|
||||
.filter(PolygonLabel.label == "moonworm")
|
||||
.filter(PolygonLabel.address == ADDRESS)
|
||||
.filter(PolygonLabel.label_data["name"].astext == "DNAUpdated")
|
||||
.all()
|
||||
)
|
||||
max_token = -1
|
||||
token_ids = []
|
||||
for label in labels:
|
||||
token_ids.append(label.label_data["args"]["tokenId"])
|
||||
max_token = max(max_token, label.label_data["args"]["tokenId"])
|
||||
|
||||
print(len(token_ids))
|
||||
return token_ids
|
||||
|
||||
|
||||
def filter_ids(_id, labeled_ids):
|
||||
with open(f"unicorn-classes-{_id}.json", "r") as ifp:
|
||||
original_ids = json.load(ifp)
|
||||
|
||||
token_ids = [item["tokenId"] for item in original_ids]
|
||||
result = []
|
||||
for token in token_ids:
|
||||
if token not in labeled_ids:
|
||||
result.append({"tokenId": token, "class": _id})
|
||||
|
||||
with open(f"processes-{_id}.json", "w") as ofp:
|
||||
json.dump(result, ofp)
|
||||
|
||||
|
||||
action_map = {
|
||||
0: "add",
|
||||
1: "replace",
|
||||
2: "remove",
|
||||
}
|
||||
|
||||
cu_contract = Web3().eth.contract(abi=CU.abi())
|
||||
|
||||
|
||||
def get_all_diamond_cuts(session: Session):
|
||||
labels = (
|
||||
session.query(PolygonLabel.label_data)
|
||||
.filter(PolygonLabel.label == "moonworm")
|
||||
.filter(PolygonLabel.address == ADDRESS)
|
||||
.filter(PolygonLabel.label_data["name"].astext == "diamondCut")
|
||||
.filter(PolygonLabel.label_data["status"].astext == "1")
|
||||
.order_by(PolygonLabel.block_number.asc())
|
||||
.all()
|
||||
)
|
||||
|
||||
return labels
|
||||
|
||||
|
||||
def get_function_name_by_selector(selector: str):
|
||||
try:
|
||||
|
||||
name = cu_contract.get_function_by_selector(selector).function_identifier
|
||||
return name
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print(selector)
|
||||
return "UNKNOWN"
|
||||
|
||||
|
||||
def run():
|
||||
|
||||
with yield_db_session_ctx() as session:
|
||||
diamond_cuts_events = get_all_diamond_cuts(session)
|
||||
|
||||
selector_actions = {}
|
||||
current_index = 0
|
||||
for event in diamond_cuts_events:
|
||||
diamond_cuts = event[0]["args"]["_diamondCut"]
|
||||
for diamond_cut in diamond_cuts:
|
||||
for selector in diamond_cut[2]:
|
||||
if selector not in selector_actions:
|
||||
selector_actions[selector] = []
|
||||
selector_actions[selector].append(
|
||||
{
|
||||
"name": get_function_name_by_selector(selector),
|
||||
"action": action_map[diamond_cut[1]],
|
||||
"address": diamond_cut[0],
|
||||
"action_index": current_index,
|
||||
}
|
||||
)
|
||||
current_index += 1
|
||||
|
||||
current_cuts = {}
|
||||
for selector, actions in selector_actions.items():
|
||||
last_cut = None
|
||||
last_cut_index = -1
|
||||
for action in actions:
|
||||
if action["action_index"] > last_cut_index:
|
||||
last_cut_index = action["action_index"]
|
||||
if action["action"] == "remove":
|
||||
last_cut = {"name": action["name"], "address": None}
|
||||
else:
|
||||
last_cut = {"name": action["name"], "address": action["address"]}
|
||||
current_cuts[selector] = last_cut
|
||||
|
||||
grouped_by_name = {}
|
||||
for selector, cut in current_cuts.items():
|
||||
if grouped_by_name.get(cut["name"]) is None:
|
||||
grouped_by_name[cut["name"]] = []
|
||||
grouped_by_name[cut["name"]].append(
|
||||
{"selector": selector, "address": cut["address"]}
|
||||
)
|
||||
|
||||
with open("current_cuts.json", "w") as ofp:
|
||||
json.dump(grouped_by_name, ofp)
|
||||
|
||||
|
||||
run()
|
|
@ -1,344 +0,0 @@
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import pprint as pp
|
||||
import re
|
||||
import time
|
||||
from os import stat
|
||||
from re import S
|
||||
from typing import Any, Dict, List, Optional, cast
|
||||
|
||||
import web3
|
||||
from eth_typing.evm import ChecksumAddress
|
||||
from moonstreamdb.db import yield_db_session_ctx
|
||||
from moonstreamdb.models import EthereumLabel, PolygonLabel
|
||||
from sqlalchemy.orm import Query, Session
|
||||
from sqlalchemy.sql.expression import delete
|
||||
from tqdm import tqdm
|
||||
from web3 import Web3
|
||||
from web3.middleware import geth_poa_middleware
|
||||
|
||||
from moonworm.crawler.moonstream_ethereum_state_provider import (
|
||||
MoonstreamEthereumStateProvider,
|
||||
)
|
||||
from moonworm.crawler.utils import Network
|
||||
|
||||
from .contracts import CU, ERC721
|
||||
from .crawler.ethereum_state_provider import EthereumStateProvider
|
||||
from .crawler.function_call_crawler import (
|
||||
ContractFunctionCall,
|
||||
FunctionCallCrawler,
|
||||
FunctionCallCrawlerState,
|
||||
Web3StateProvider,
|
||||
utfy_dict,
|
||||
)
|
||||
from .crawler.log_scanner import _fetch_events_chunk
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_last_crawled_block(
|
||||
session: Session, contract_address: ChecksumAddress
|
||||
) -> Optional[int]:
|
||||
"""
|
||||
Gets the last block that was crawled.
|
||||
"""
|
||||
|
||||
query = (
|
||||
session.query(PolygonLabel)
|
||||
.filter(
|
||||
PolygonLabel.label == "moonworm",
|
||||
PolygonLabel.address == contract_address,
|
||||
)
|
||||
.order_by(PolygonLabel.block_number.desc())
|
||||
)
|
||||
if query.count():
|
||||
return query.first().block_number
|
||||
return None
|
||||
|
||||
|
||||
def _add_function_call_labels(
|
||||
session: Session,
|
||||
function_calls: List[ContractFunctionCall],
|
||||
address: ChecksumAddress,
|
||||
) -> None:
|
||||
"""
|
||||
Adds a label to a function call.
|
||||
"""
|
||||
|
||||
existing_function_call_labels = (
|
||||
session.query(PolygonLabel)
|
||||
.filter(
|
||||
PolygonLabel.label == "moonworm",
|
||||
PolygonLabel.log_index == None,
|
||||
PolygonLabel.address == address,
|
||||
PolygonLabel.transaction_hash.in_(
|
||||
[call.transaction_hash for call in function_calls]
|
||||
),
|
||||
)
|
||||
.all()
|
||||
)
|
||||
# deletin existing labels
|
||||
for label in existing_function_call_labels:
|
||||
session.delete(label)
|
||||
|
||||
try:
|
||||
if existing_function_call_labels:
|
||||
logger.info(
|
||||
f"Deleting {len(existing_function_call_labels)} existing tx labels"
|
||||
)
|
||||
session.commit()
|
||||
except Exception as e:
|
||||
try:
|
||||
session.commit()
|
||||
except:
|
||||
logger.error(f"Failed!!!\n{e}")
|
||||
session.rollback()
|
||||
|
||||
for function_call in function_calls:
|
||||
label = PolygonLabel(
|
||||
label="moonworm",
|
||||
label_data={
|
||||
"type": "tx_call",
|
||||
"name": function_call.function_name,
|
||||
"caller": function_call.caller_address,
|
||||
"args": function_call.function_args,
|
||||
"status": function_call.status,
|
||||
"gasUsed": function_call.gas_used,
|
||||
},
|
||||
address=function_call.contract_address,
|
||||
block_number=function_call.block_number,
|
||||
transaction_hash=function_call.transaction_hash,
|
||||
block_timestamp=function_call.block_timestamp,
|
||||
)
|
||||
session.add(label)
|
||||
try:
|
||||
session.commit()
|
||||
except Exception as e:
|
||||
try:
|
||||
session.commit()
|
||||
except:
|
||||
logger.error(f"Failed!!!\n{e}")
|
||||
session.rollback()
|
||||
|
||||
|
||||
def _add_event_labels(
|
||||
session: Session, events: List[Dict[str, Any]], address: ChecksumAddress
|
||||
) -> None:
|
||||
"""
|
||||
Adds events to database.
|
||||
"""
|
||||
|
||||
transactions = [event["transactionHash"] for event in events]
|
||||
log_indexes = [event["logIndex"] for event in events]
|
||||
for ev in events:
|
||||
print(ev)
|
||||
existing_event_labels = (
|
||||
session.query(PolygonLabel)
|
||||
.filter(
|
||||
PolygonLabel.label == "moonworm",
|
||||
PolygonLabel.address == address,
|
||||
PolygonLabel.transaction_hash.in_(transactions),
|
||||
PolygonLabel.log_index != None,
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
# deletin existing labels
|
||||
deleted = 0
|
||||
for label in existing_event_labels:
|
||||
if label.log_index in log_indexes:
|
||||
deleted += 1
|
||||
session.delete(label)
|
||||
|
||||
try:
|
||||
if deleted > 0:
|
||||
logger.error(f"Deleting {deleted} existing event labels")
|
||||
session.commit()
|
||||
except Exception as e:
|
||||
try:
|
||||
session.commit()
|
||||
except:
|
||||
logger.error(f"Failed!!!\n{e}")
|
||||
session.rollback()
|
||||
|
||||
for event in events:
|
||||
label = PolygonLabel(
|
||||
label="moonworm",
|
||||
label_data={
|
||||
"type": "event",
|
||||
"name": event["event"],
|
||||
"args": event["args"],
|
||||
},
|
||||
address=event["address"],
|
||||
block_number=event["blockNumber"],
|
||||
transaction_hash=event["transactionHash"],
|
||||
block_timestamp=event["blockTimestamp"],
|
||||
log_index=event["logIndex"],
|
||||
)
|
||||
session.add(label)
|
||||
try:
|
||||
session.commit()
|
||||
except Exception as e:
|
||||
try:
|
||||
session.commit()
|
||||
except:
|
||||
logger.error(f"Failed!!!\n{e}")
|
||||
session.rollback()
|
||||
|
||||
|
||||
class MockState(FunctionCallCrawlerState):
|
||||
def __init__(self) -> None:
|
||||
self.state: List[ContractFunctionCall] = []
|
||||
|
||||
def get_last_crawled_block(self) -> int:
|
||||
"""
|
||||
Returns the last block number that was crawled.
|
||||
"""
|
||||
return 0
|
||||
|
||||
def register_call(self, function_call: ContractFunctionCall) -> None:
|
||||
"""
|
||||
Processes the given function call (store it, etc.).
|
||||
"""
|
||||
self.state.append(function_call)
|
||||
|
||||
def flush(self) -> None:
|
||||
"""
|
||||
Flushes cached state to storage layer.
|
||||
"""
|
||||
self.state = []
|
||||
|
||||
|
||||
def watch_cu_contract(
|
||||
web3: Web3,
|
||||
contract_address: ChecksumAddress,
|
||||
contract_abi: List[Dict[str, Any]],
|
||||
num_confirmations: int = 60,
|
||||
min_blocks_to_crawl: int = 10,
|
||||
sleep_time: float = 1,
|
||||
start_block: Optional[int] = None,
|
||||
force_start: bool = False,
|
||||
use_moonstream_web3_provider: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
Watches a contract for events and calls.
|
||||
"""
|
||||
if force_start and start_block is None:
|
||||
raise ValueError("start_block must be specified if force_start is True")
|
||||
|
||||
with yield_db_session_ctx() as session:
|
||||
function_call_state = MockState()
|
||||
|
||||
eth_state_provider: Optional[EthereumStateProvider] = None
|
||||
if use_moonstream_web3_provider:
|
||||
eth_state_provider = MoonstreamEthereumStateProvider(
|
||||
web3, network=Network.polygon, db_session=session
|
||||
)
|
||||
else:
|
||||
eth_state_provider = Web3StateProvider(web3)
|
||||
|
||||
crawler = FunctionCallCrawler(
|
||||
function_call_state,
|
||||
eth_state_provider,
|
||||
contract_abi,
|
||||
[web3.toChecksumAddress(contract_address)],
|
||||
)
|
||||
|
||||
last_crawled_block = _get_last_crawled_block(session, contract_address)
|
||||
if start_block is None:
|
||||
if last_crawled_block is not None:
|
||||
current_block = last_crawled_block
|
||||
logger.info(f"Starting from block {current_block}, last crawled block")
|
||||
else:
|
||||
current_block = web3.eth.blockNumber - num_confirmations * 2
|
||||
logger.info(f"Starting from block {current_block}, current block")
|
||||
else:
|
||||
current_block = start_block
|
||||
if not force_start and last_crawled_block is not None:
|
||||
if start_block > last_crawled_block:
|
||||
logger.info(
|
||||
f"Starting from block {start_block}, last crawled block {last_crawled_block}"
|
||||
)
|
||||
else:
|
||||
current_block = last_crawled_block
|
||||
logger.info(f"Starting from last crawled block {current_block}")
|
||||
|
||||
event_abis = [item for item in contract_abi if item["type"] == "event"]
|
||||
|
||||
while True:
|
||||
try:
|
||||
session.execute("select 1")
|
||||
time.sleep(sleep_time)
|
||||
end_block = min(
|
||||
web3.eth.blockNumber - num_confirmations, current_block + 100
|
||||
)
|
||||
if end_block < current_block + min_blocks_to_crawl:
|
||||
logger.info(
|
||||
f"Sleeping crawling, end_block {end_block} < current_block {current_block} + min_blocks_to_crawl {min_blocks_to_crawl}"
|
||||
)
|
||||
sleep_time += 1
|
||||
continue
|
||||
|
||||
sleep_time /= 2
|
||||
|
||||
logger.info("Getting txs")
|
||||
crawler.crawl(current_block, end_block)
|
||||
if function_call_state.state:
|
||||
_add_function_call_labels(
|
||||
session, function_call_state.state, contract_address
|
||||
)
|
||||
logger.info(
|
||||
f"Got {len(function_call_state.state)} transaction calls:"
|
||||
)
|
||||
function_call_state.flush()
|
||||
|
||||
logger.info("Getting events")
|
||||
all_events = []
|
||||
for event_abi in event_abis:
|
||||
raw_events = _fetch_events_chunk(
|
||||
web3,
|
||||
event_abi,
|
||||
current_block,
|
||||
end_block,
|
||||
[contract_address],
|
||||
)
|
||||
|
||||
for raw_event in raw_events:
|
||||
raw_event["blockTimestamp"] = (
|
||||
crawler.ethereum_state_provider.get_block_timestamp(
|
||||
raw_event["blockNumber"]
|
||||
),
|
||||
)
|
||||
all_events.append(raw_event)
|
||||
|
||||
if all_events:
|
||||
print(f"Got {len(all_events)} events:")
|
||||
_add_event_labels(session, all_events, contract_address)
|
||||
logger.info(f"Current block {end_block + 1}")
|
||||
current_block = end_block + 1
|
||||
except Exception as e:
|
||||
logger.error(f"Something went wrong: {e}")
|
||||
logger.info(f"Trying to recover from error")
|
||||
for i in range(10):
|
||||
logger.info(f"Attempt {i}:")
|
||||
try:
|
||||
time.sleep(10)
|
||||
logger.info("Trying to reconnect to database")
|
||||
session.rollback()
|
||||
session.execute("select 1")
|
||||
logger.info("Trying to reconnect to web3")
|
||||
web3.eth.block_number
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(f"Failed: {e}")
|
||||
continue
|
||||
|
||||
try:
|
||||
session.execute("select 1")
|
||||
web3.eth.block_number
|
||||
continue
|
||||
except Exception as e:
|
||||
logger.error("Moonworm is going to die")
|
||||
raise e
|
|
@ -1,5 +1,7 @@
|
|||
"""
|
||||
This module allows users to inspect the conditions under which a smart contract was deployed.
|
||||
Allows users to inspect the conditions under which a smart contract was deployed.
|
||||
|
||||
The entrypoint for this functionality is [`find_deployment_block`][moonworm.deployment.find_deployment_block].
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
|
@ -53,10 +55,27 @@ def find_deployment_block(
|
|||
web3_interval: float,
|
||||
) -> Optional[int]:
|
||||
"""
|
||||
Note: We will assume no selfdestruct for now.
|
||||
Performs a binary search on the blockchain to discover precisely the block when a smart contract was
|
||||
deployed.
|
||||
|
||||
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.
|
||||
Note: Assumes no selfdestruct. 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.
|
||||
|
||||
## Inputs
|
||||
|
||||
1. `web3_client`: A web3 client through which we can get block and address information on the blockchain.
|
||||
An instance of web3.Web3.
|
||||
|
||||
2. `contract_address`: Address of the smart contract for which we want the deployment block. If this
|
||||
address does not represent a smart contract, this method will return None.
|
||||
|
||||
3. `web3_interval`: Number of seconds to wait between requests to the web3_client. Useful if your
|
||||
web3 provider rate limits you.
|
||||
|
||||
## Outputs
|
||||
|
||||
Returns the block number of the block in which the smart contract was deployed. If the address does
|
||||
not represent an existing smart contract, returns None.
|
||||
"""
|
||||
log_prefix = f"find_deployment_block(web3_client, contract_address={contract_address}, web3_interval={web3_interval}) -- "
|
||||
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
import copy
|
||||
"""
|
||||
Generates [`web3.py`](https://github.com/ethereum/web3.py)-compatible bindings to Ethereum smart contracts from their ABIs.
|
||||
|
||||
The entrypoints to code generation are:
|
||||
|
||||
- [`generate_contract_interface_content`][moonworm.generators.basic.generate_contract_interface_content]
|
||||
- [`generate_contract_cli_content`][moonworm.generators.basic.generate_contract_cli_content]
|
||||
"""
|
||||
|
||||
import keyword
|
||||
import logging
|
||||
import os
|
||||
|
@ -479,6 +487,19 @@ def generate_argument_parser_function(abi: List[Dict[str, Any]]) -> cst.Function
|
|||
def generate_contract_interface_content(
|
||||
abi: List[Dict[str, Any]], abi_file_name: str, format: bool = True
|
||||
) -> str:
|
||||
"""
|
||||
Generates a Python class designed to interact with a smart contract with the given ABI.
|
||||
|
||||
## Inputs
|
||||
1. `abi`: The ABI to the smart contract. This is expected to be the Python representation of a JSON
|
||||
list that conforms to the [Solidity ABI specification](https://docs.soliditylang.org/en/v0.8.16/abi-spec.html#json).
|
||||
2. `abi_file_name`: Path where the contract ABI will be stored as part of codegen.
|
||||
3. `format`: If True, uses [`black`](https://github.com/psf/black) to format the generated code before
|
||||
returning it.
|
||||
|
||||
## Outputs
|
||||
The generated code as a string.
|
||||
"""
|
||||
contract_body = cst.Module(body=[generate_contract_class(abi)]).code
|
||||
|
||||
content = INTERFACE_FILE_TEMPLATE.format(
|
||||
|
@ -496,6 +517,19 @@ def generate_contract_interface_content(
|
|||
def generate_contract_cli_content(
|
||||
abi: List[Dict[str, Any]], abi_file_name: str, format: bool = True
|
||||
) -> str:
|
||||
"""
|
||||
Generates a command-line interface designed to interact with a smart contract with the given ABI.
|
||||
|
||||
## Inputs
|
||||
1. `abi`: The ABI to the smart contract. This is expected to be the Python representation of a JSON
|
||||
list that conforms to the [Solidity ABI specification](https://docs.soliditylang.org/en/v0.8.16/abi-spec.html#json).
|
||||
2. `abi_file_name`: Path where the contract ABI will be stored as part of codegen.
|
||||
3. `format`: If True, uses [`black`](https://github.com/psf/black) to format the generated code before
|
||||
returning it.
|
||||
|
||||
## Outputs
|
||||
The generated code as a string.
|
||||
"""
|
||||
cli_body = cst.Module(body=[generate_argument_parser_function(abi)]).code
|
||||
|
||||
content = CLI_FILE_TEMPLATE.format(
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
"""
|
||||
Generates [`brownie`](https://github.com/eth-brownie/brownie)-compatible bindings to Ethereum smart contracts from their ABIs.
|
||||
|
||||
The entrypoint to code generation is [`generate_brownie_interface`][moonworm.generators.brownie.generate_brownie_interface].
|
||||
"""
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import os
|
||||
|
@ -865,6 +871,17 @@ def generate_brownie_cli(
|
|||
) -> List[cst.FunctionDef]:
|
||||
"""
|
||||
Generates an argparse CLI to a brownie smart contract using the generated smart contract interface.
|
||||
|
||||
## Inputs
|
||||
|
||||
1. `abi`: The ABI to the smart contract. This is expected to be the Python representation of a JSON
|
||||
list that conforms to the [Solidity ABI specification](https://docs.soliditylang.org/en/v0.8.16/abi-spec.html#json).
|
||||
|
||||
2. `contract_name`: Name for the smart contract
|
||||
|
||||
## Outputs
|
||||
|
||||
Concrete syntax tree representation of the generated code.
|
||||
"""
|
||||
get_transaction_config_function = generate_get_transaction_config()
|
||||
add_default_arguments_function = generate_add_default_arguments()
|
||||
|
@ -900,6 +917,35 @@ def generate_brownie_interface(
|
|||
format: bool = True,
|
||||
prod: bool = False,
|
||||
) -> str:
|
||||
"""
|
||||
Generates Python code which allows you to interact with a smart contract with a given ABI, build data, and a given name.
|
||||
|
||||
The generated code uses the [`brownie`](https://github.com/eth-brownie/brownie) tool to interact with
|
||||
the blockchain.
|
||||
|
||||
## Inputs
|
||||
|
||||
1. `abi`: The ABI to the smart contract. This is expected to be the Python representation of a JSON
|
||||
list that conforms to the [Solidity ABI specification](https://docs.soliditylang.org/en/v0.8.16/abi-spec.html#json).
|
||||
|
||||
2. `contract_build`: `brownie` build information for the contract (e.g. its bytecode).
|
||||
|
||||
3. `contract_name`: Name for the smart contract
|
||||
|
||||
4. `relative_path`: Path to brownie project directory (relative to target path for the generated code).
|
||||
|
||||
5. `cli`: Set to True if a CLI should be generated in addition to a Python class.
|
||||
|
||||
6. `format`: If True, uses [`black`](https://github.com/psf/black) to format the generated code before
|
||||
returning it.
|
||||
|
||||
7. `prod`: If True, creates a self-contained file. Generated code will not require reference to an
|
||||
existing brownie project at its runtime.
|
||||
|
||||
|
||||
## Outputs
|
||||
The generated code as a string.
|
||||
"""
|
||||
contract_class = generate_brownie_contract_class(abi, contract_name)
|
||||
module_body = [contract_class]
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
MOONWORM_VERSION = "0.4.0"
|
||||
MOONWORM_VERSION = "0.5.0"
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
"""
|
||||
Implements the moonworm smart contract crawler.
|
||||
|
||||
The [`watch_contract`][moonworm.watch.watch_contract] method is the entrypoint to this functionality
|
||||
and it is what powers the "moonworm watch" command.
|
||||
"""
|
||||
|
||||
import json
|
||||
import pprint as pp
|
||||
import time
|
||||
|
@ -60,7 +67,50 @@ def watch_contract(
|
|||
outfile: Optional[str] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Watches a contract for events and calls.
|
||||
Watches a contract for events and method calls.
|
||||
|
||||
Currently supports crawling events and direct method calls on a smart contract.
|
||||
|
||||
It does *not* currently support crawling internal messages to a smart contract - this means that any
|
||||
calls made to the target smart contract from *another* smart contract will not be recorded directly
|
||||
in the crawldata. If the internal message resulted in any events being emitted on the target
|
||||
contract, those events *will* be reflected in the crawldata.
|
||||
|
||||
## Inputs
|
||||
|
||||
1. `web3`: A web3 client used to interact with the blockchain being crawled.
|
||||
2. `state_provider`: An [`EthereumStateProvider`][moonworm.crawler.ethereum_state_provider.EthereumStateProvider]
|
||||
instance that the crawler uses to access blockchain state and event logs.
|
||||
3. `contract_address`: Checksum address for the smart contract
|
||||
4. `contract_abi`: List representing objects in the smart contract ABI. It does not need to be an
|
||||
exhaustive ABI. Any events not present in the ABI will not be crawled. Any methods not present
|
||||
in the ABI will be signalled as warnings by the crawler but not stored in the crawldata.
|
||||
5. `num_confirmations`: The crawler will remain this many blocks behind the current head of the blockchain.
|
||||
6. `sleep_time`: The number of seconds for which to wait between polls of the state provider. Useful
|
||||
if the provider rate limits clients.
|
||||
7. `start_block`: Optional block number from which to start the crawl. If not provided, crawl will
|
||||
start at block 0.
|
||||
8. `end_block`: Optional block number at which to end crawl. If not provided, crawl will continue
|
||||
indefinitely.
|
||||
9. `min_blocks_batch`: Minimum number of blocks to process at a time. The crawler adapts the batch
|
||||
size based on the volume of events and transactions it parses for the contract in its current
|
||||
range of blocks.
|
||||
10. `min_blocks_batch`: Minimum number of blocks to process at a time. The crawler adapts the batch
|
||||
size based on the volume of events and transactions it parses for the contract in its current
|
||||
range of blocks.
|
||||
11. `max_blocks_batch`: Maximum number of blocks to process at a time. The crawler adapts the batch
|
||||
size based on the volume of events and transactions it parses for the contract in its current
|
||||
range of blocks.
|
||||
12. `batch_size_update_threshold`: Adaptive parameter used to update batch size of blocks crawled
|
||||
based on number of events processed in the current batch.
|
||||
13. `only_events`: If this argument is set to True, the crawler will only crawl events and ignore
|
||||
method calls. Crawling events is much, much faster than crawling method calls.
|
||||
14. `outfile`: An optional file to which to write events and/or method calls in [JSON Lines format](https://jsonlines.org/).
|
||||
Data is written to this file in append mode, so the crawler never deletes old data.
|
||||
|
||||
## Outputs
|
||||
|
||||
None. Results are printed to stdout and, if an outfile has been provided, also to the file.
|
||||
"""
|
||||
|
||||
def _crawl_events(
|
||||
|
|
Ładowanie…
Reference in New Issue