kopia lustrzana https://github.com/bugout-dev/moonworm
WIP brownie interface
rodzic
234d44985e
commit
76ac5cac51
|
@ -133,3 +133,5 @@ venv/
|
||||||
.venv/
|
.venv/
|
||||||
.moonworm/
|
.moonworm/
|
||||||
.vscode/
|
.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 .contracts import CU, ERC20, ERC721
|
||||||
from .crawler.networks import Network
|
from .crawler.networks import Network
|
||||||
from .generator import (
|
from .generator import (
|
||||||
|
generate_brownie_interface,
|
||||||
generate_contract_cli_content,
|
generate_contract_cli_content,
|
||||||
generate_contract_interface_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}")
|
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:
|
def handle_watch(args: argparse.Namespace) -> None:
|
||||||
if args.abi == "erc20":
|
if args.abi == "erc20":
|
||||||
contract_abi = ERC20.abi()
|
contract_abi = ERC20.abi()
|
||||||
|
@ -268,6 +290,29 @@ def generate_argument_parser() -> argparse.ArgumentParser:
|
||||||
|
|
||||||
watch_cu_parser.set_defaults(func=handle_watch_cu)
|
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_parser = subcommands.add_parser(
|
||||||
"generate", description="Moonworm code generator"
|
"generate", description="Moonworm code generator"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import keyword
|
import keyword
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from typing import Any, Dict, List, Union, cast
|
from typing import Any, Dict, List, Sequence, Union, cast
|
||||||
|
|
||||||
import libcst as cst
|
import libcst as cst
|
||||||
from libcst._nodes.statement import BaseCompoundStatement
|
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(f"WARNING: Could not load cli template from {CLI_TEMPLATE_PATH}:")
|
||||||
logging.warn(e)
|
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:
|
if len(types) == 1:
|
||||||
return cst.Annotation(annotation=cst.Name(types[0]))
|
return cst.Annotation(annotation=cst.Name(types[0]))
|
||||||
union_slice = []
|
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)
|
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:
|
def normalize_abi_name(name: str) -> str:
|
||||||
if keyword.iskeyword(name):
|
if keyword.iskeyword(name):
|
||||||
|
@ -66,7 +86,56 @@ def python_type(evm_type: str) -> List[str]:
|
||||||
elif evm_type == "tuple[]":
|
elif evm_type == "tuple[]":
|
||||||
return ["list"]
|
return ["list"]
|
||||||
else:
|
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(
|
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_argument_parser_function(abi: List[Dict[str, Any]]) -> cst.FunctionDef:
|
||||||
def generate_function_subparser(
|
def generate_function_subparser(
|
||||||
function_abi: Dict[str, Any],
|
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
|
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