kopia lustrzana https://github.com/bugout-dev/moonworm
WIP brownie interface
rodzic
234d44985e
commit
76ac5cac51
|
@ -133,3 +133,5 @@ venv/
|
|||
.venv/
|
||||
.moonworm/
|
||||
.vscode/
|
||||
.secrets/
|
||||
generated/
|
|
@ -0,0 +1,32 @@
|
|||
# Code generated by moonworm : https://github.com/bugout-dev/moonworm
|
||||
# Moonworm version : {moonworm_version}
|
||||
|
||||
import argparse
|
||||
from typing import Any, List, Optional
|
||||
from eth_typing.evm import ChecksumAddress
|
||||
import os
|
||||
import json
|
||||
|
||||
from brownie import Contract, network, project
|
||||
from brownie.network.contract import ContractContainer
|
||||
|
||||
|
||||
PROJECT_DIRECTORY = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
BUILD_DIRECTORY = os.path.join(PROJECT_DIRECTORY, "build", "contracts")
|
||||
PROJECT = project.load(PROJECT_DIRECTORY)
|
||||
|
||||
|
||||
def contract_from_build(abi_name: str) -> ContractContainer:
|
||||
abi_full_path = os.path.join(BUILD_DIRECTORY, f"{{abi_name}}.json")
|
||||
if not os.path.isfile(abi_full_path):
|
||||
raise IOError(
|
||||
f"File does not exist: {{abi_full_path}}. Maybe you have to compile the smart contracts?"
|
||||
)
|
||||
|
||||
with open(abi_full_path, "r") as ifp:
|
||||
build = json.load(ifp)
|
||||
|
||||
return ContractContainer(PROJECT, build)
|
||||
|
||||
|
||||
{contract_body}
|
|
@ -13,6 +13,7 @@ from moonworm.watch import watch_contract
|
|||
from .contracts import CU, ERC20, ERC721
|
||||
from .crawler.networks import Network
|
||||
from .generator import (
|
||||
generate_brownie_interface,
|
||||
generate_contract_cli_content,
|
||||
generate_contract_interface_content,
|
||||
)
|
||||
|
@ -80,6 +81,27 @@ def handle_generate(args: argparse.Namespace) -> None:
|
|||
print(f"Files are successfully generated to:{args.outdir}")
|
||||
|
||||
|
||||
def handle_brownie_generate(args: argparse.Namespace):
|
||||
|
||||
Path(args.outdir).mkdir(exist_ok=True)
|
||||
|
||||
project_directory = args.project
|
||||
build_directory = os.path.join(project_directory, "build", "contracts")
|
||||
|
||||
build_file_path = os.path.join(build_directory, f"{args.name}.json")
|
||||
if not os.path.isfile(build_file_path):
|
||||
raise IOError(
|
||||
f"File does not exist: {build_file_path}. Maybe you have to compile the smart contracts?"
|
||||
)
|
||||
|
||||
with open(build_file_path, "r") as ifp:
|
||||
build = json.load(ifp)
|
||||
|
||||
abi = build["abi"]
|
||||
interface = generate_brownie_interface(abi, args.name)
|
||||
write_file(interface, os.path.join(args.outdir, args.name + ".py"))
|
||||
|
||||
|
||||
def handle_watch(args: argparse.Namespace) -> None:
|
||||
if args.abi == "erc20":
|
||||
contract_abi = ERC20.abi()
|
||||
|
@ -268,6 +290,29 @@ def generate_argument_parser() -> argparse.ArgumentParser:
|
|||
|
||||
watch_cu_parser.set_defaults(func=handle_watch_cu)
|
||||
|
||||
generate_brownie_parser = subcommands.add_parser(
|
||||
"generate-brownie", description="Moonworm code generator for brownie projects"
|
||||
)
|
||||
generate_brownie_parser.add_argument(
|
||||
"-o",
|
||||
"--outdir",
|
||||
required=True,
|
||||
help=f"Output directory where files will be generated.",
|
||||
)
|
||||
generate_brownie_parser.add_argument(
|
||||
"--name",
|
||||
"-n",
|
||||
required=True,
|
||||
help="Prefix name for generated files",
|
||||
)
|
||||
generate_brownie_parser.add_argument(
|
||||
"-p",
|
||||
"--project",
|
||||
required=True,
|
||||
help=f"Path to brownie project directory",
|
||||
)
|
||||
generate_brownie_parser.set_defaults(func=handle_brownie_generate)
|
||||
|
||||
generate_parser = subcommands.add_parser(
|
||||
"generate", description="Moonworm code generator"
|
||||
)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import keyword
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, Dict, List, Union, cast
|
||||
from typing import Any, Dict, List, Sequence, Union, cast
|
||||
|
||||
import libcst as cst
|
||||
from libcst._nodes.statement import BaseCompoundStatement
|
||||
|
@ -27,8 +27,20 @@ except Exception as e:
|
|||
logging.warn(f"WARNING: Could not load cli template from {CLI_TEMPLATE_PATH}:")
|
||||
logging.warn(e)
|
||||
|
||||
BROWNIE_INTERFACE_TEMPLATE_PATH = os.path.join(
|
||||
os.path.dirname(__file__), "brownie_contract.py.template"
|
||||
)
|
||||
try:
|
||||
with open(BROWNIE_INTERFACE_TEMPLATE_PATH, "r") as ifp:
|
||||
BROWNIE_INTERFACE_TEMPLATE = ifp.read()
|
||||
except Exception as e:
|
||||
logging.warn(
|
||||
f"WARNING: Could not load cli template from {BROWNIE_INTERFACE_TEMPLATE_PATH}:"
|
||||
)
|
||||
logging.warn(e)
|
||||
|
||||
def make_annotation(types: list):
|
||||
|
||||
def make_annotation(types: list, optional: bool = False):
|
||||
if len(types) == 1:
|
||||
return cst.Annotation(annotation=cst.Name(types[0]))
|
||||
union_slice = []
|
||||
|
@ -40,10 +52,18 @@ def make_annotation(types: list):
|
|||
)
|
||||
),
|
||||
)
|
||||
return cst.Annotation(
|
||||
annotation = cst.Annotation(
|
||||
annotation=cst.Subscript(value=cst.Name("Union"), slice=union_slice)
|
||||
)
|
||||
|
||||
if optional:
|
||||
annotation = cst.Annotation(
|
||||
annotation=cst.Subscript(
|
||||
value=cst.Name("Optional"),
|
||||
slice=cast(Sequence[cst.SubscriptElement], annotation),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def normalize_abi_name(name: str) -> str:
|
||||
if keyword.iskeyword(name):
|
||||
|
@ -66,7 +86,56 @@ def python_type(evm_type: str) -> List[str]:
|
|||
elif evm_type == "tuple[]":
|
||||
return ["list"]
|
||||
else:
|
||||
raise ValueError(f"Cannot convert to python type {evm_type}")
|
||||
return ["Any"]
|
||||
|
||||
|
||||
def generate_brownie_contract_class(
|
||||
abi: List[Dict[str, Any]],
|
||||
contract_name: str,
|
||||
) -> cst.ClassDef:
|
||||
class_name = contract_name
|
||||
class_constructor = cst.FunctionDef(
|
||||
name=cst.Name("__init__"),
|
||||
body=cst.IndentedBlock(
|
||||
body=[
|
||||
cst.parse_statement("self.address = contract_address"),
|
||||
cst.parse_statement(
|
||||
f"self.contract = contract_from_build({contract_name})"
|
||||
),
|
||||
]
|
||||
),
|
||||
params=cst.Parameters(
|
||||
params=[
|
||||
cst.Param(name=cst.Name("self")),
|
||||
cst.Param(
|
||||
name=cst.Name("contract_address"),
|
||||
annotation=make_annotation(["ChecksumAddress"], optional=True),
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
contract_constructors = [c for c in abi if c["type"] == "constructor"]
|
||||
if len(contract_constructors) == 1:
|
||||
contract_constructor = contract_constructors[0]
|
||||
elif len(contract_constructors) == 0:
|
||||
contract_constructor = {"inputs": []}
|
||||
else:
|
||||
raise ValueError("Multiple constructors found in ABI")
|
||||
|
||||
contract_constructor["name"] = "constructor"
|
||||
class_functions = (
|
||||
[class_constructor]
|
||||
+ [generate_brownie_constructor_function(contract_constructor)]
|
||||
+ [
|
||||
generate_brownie_contract_function(function)
|
||||
for function in abi
|
||||
if function["type"] == "function"
|
||||
]
|
||||
)
|
||||
return cst.ClassDef(
|
||||
name=cst.Name(class_name), body=cst.IndentedBlock(body=class_functions)
|
||||
)
|
||||
|
||||
|
||||
def generate_contract_class(
|
||||
|
@ -196,6 +265,95 @@ def generate_contract_function(func_object: Dict[str, Any]) -> cst.FunctionDef:
|
|||
)
|
||||
|
||||
|
||||
def generate_brownie_constructor_function(
|
||||
func_object: Dict[str, Any]
|
||||
) -> cst.FunctionDef:
|
||||
|
||||
default_param_name = "arg"
|
||||
default_counter = 1
|
||||
func_params = []
|
||||
func_params.append(cst.Param(name=cst.Name("self")))
|
||||
param_names = []
|
||||
for param in func_object["inputs"]:
|
||||
param_name = normalize_abi_name(param["name"])
|
||||
if param_name == "":
|
||||
param_name = f"{default_param_name}{default_counter}"
|
||||
default_counter += 1
|
||||
param_type = make_annotation(python_type(param["type"]))
|
||||
param_names.append(param_name)
|
||||
func_params.append(
|
||||
cst.Param(
|
||||
name=cst.Name(value=param_name),
|
||||
annotation=param_type,
|
||||
)
|
||||
)
|
||||
func_params.append(cst.Param(name=cst.Name("signer")))
|
||||
|
||||
func_name = "deploy"
|
||||
param_names.append("{'from': signer}")
|
||||
proxy_call_code = (
|
||||
f"deployed_contract = self.contract.deploy({','.join(param_names)})"
|
||||
)
|
||||
|
||||
func_body = cst.IndentedBlock(
|
||||
body=[
|
||||
cst.parse_statement(proxy_call_code),
|
||||
cst.parse_statement("self.address=deployed_contract.address"),
|
||||
]
|
||||
)
|
||||
|
||||
return cst.FunctionDef(
|
||||
name=cst.Name(func_name),
|
||||
params=cst.Parameters(params=func_params),
|
||||
body=func_body,
|
||||
)
|
||||
|
||||
|
||||
def generate_brownie_contract_function(func_object: Dict[str, Any]) -> cst.FunctionDef:
|
||||
|
||||
default_param_name = "arg"
|
||||
default_counter = 1
|
||||
func_params = []
|
||||
func_params.append(cst.Param(name=cst.Name("self")))
|
||||
|
||||
param_names = []
|
||||
for param in func_object["inputs"]:
|
||||
param_name = normalize_abi_name(param["name"])
|
||||
if param_name == "":
|
||||
param_name = f"{default_param_name}{default_counter}"
|
||||
default_counter += 1
|
||||
param_type = make_annotation(python_type(param["type"]))
|
||||
param_names.append(param_name)
|
||||
func_params.append(
|
||||
cst.Param(
|
||||
name=cst.Name(value=param_name),
|
||||
annotation=param_type,
|
||||
)
|
||||
)
|
||||
|
||||
func_raw_name = normalize_abi_name(func_object["name"])
|
||||
func_name = cst.Name(func_raw_name)
|
||||
if func_object["stateMutability"] == "view":
|
||||
proxy_call_code = (
|
||||
f"return self.contract.{func_raw_name}.call({','.join(param_names)})"
|
||||
)
|
||||
else:
|
||||
func_params.append(cst.Param(name=cst.Name(value="signer")))
|
||||
param_names.append(f"{{'from': signer}}")
|
||||
proxy_call_code = (
|
||||
f"return self.contract.{func_raw_name}({','.join(param_names)})"
|
||||
)
|
||||
func_body = cst.IndentedBlock(body=[cst.parse_statement(proxy_call_code)])
|
||||
func_returns = cst.Annotation(annotation=cst.Name(value="Any"))
|
||||
|
||||
return cst.FunctionDef(
|
||||
name=func_name,
|
||||
params=cst.Parameters(params=func_params),
|
||||
body=func_body,
|
||||
returns=func_returns,
|
||||
)
|
||||
|
||||
|
||||
def generate_argument_parser_function(abi: List[Dict[str, Any]]) -> cst.FunctionDef:
|
||||
def generate_function_subparser(
|
||||
function_abi: Dict[str, Any],
|
||||
|
@ -340,3 +498,15 @@ def generate_contract_cli_content(abi: List[Dict[str, Any]], abi_file_name: str)
|
|||
)
|
||||
|
||||
return content
|
||||
|
||||
|
||||
def generate_brownie_interface(abi: List[Dict[str, Any]], contract_name: str) -> str:
|
||||
contract_body = cst.Module(
|
||||
body=[generate_brownie_contract_class(abi, contract_name)]
|
||||
).code
|
||||
|
||||
content = BROWNIE_INTERFACE_TEMPLATE.format(
|
||||
contract_body=contract_body,
|
||||
moonworm_version=MOONWORM_VERSION,
|
||||
)
|
||||
return content
|
||||
|
|
Ładowanie…
Reference in New Issue