Skip to content
This repository has been archived by the owner on Dec 1, 2023. It is now read-only.

Replace subprocess with direct python calls #169

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2e6540b
lower level refactor
andrew-fleming Aug 19, 2022
39a8772
update branch Merge remote-tracking branch 'origin' into remove-subpr…
andrew-fleming Aug 20, 2022
4bbaa67
simplify deploy/declare
andrew-fleming Aug 21, 2022
e5787cc
finished cli
andrew-fleming Aug 22, 2022
82ff53f
fix logging
andrew-fleming Aug 22, 2022
73a3ffb
fix format
andrew-fleming Aug 22, 2022
52a281e
remove comment
andrew-fleming Aug 22, 2022
4b5dba0
fix error msg
andrew-fleming Aug 22, 2022
6c2e7f6
add asyncclick to deps
andrew-fleming Aug 25, 2022
f2da517
add gateway feeder
andrew-fleming Aug 25, 2022
84e532d
add test event loop
andrew-fleming Aug 25, 2022
9aa6483
fix response and logging
andrew-fleming Aug 25, 2022
1c02c26
fix feeder and gateway response
andrew-fleming Aug 25, 2022
4e7d86a
fix gateway response
andrew-fleming Aug 25, 2022
6a58eae
give import func alias
andrew-fleming Aug 25, 2022
de8938f
finish declare tests
andrew-fleming Aug 25, 2022
f4a3739
add kwargs to response
andrew-fleming Aug 26, 2022
219b36b
add tests
andrew-fleming Aug 26, 2022
b4d1f83
start account tests
andrew-fleming Aug 26, 2022
709b241
remove auto asyncio mode
andrew-fleming Aug 29, 2022
ae03ebe
fix tests
andrew-fleming Aug 29, 2022
4d37dce
add docstrings
andrew-fleming Aug 29, 2022
b968564
replace click with asyncclick
andrew-fleming Aug 29, 2022
f6054aa
add docstrings, fix gateway response
andrew-fleming Aug 29, 2022
14af4e1
add async Account cls
andrew-fleming Aug 29, 2022
69319bd
fix invoke response
andrew-fleming Aug 29, 2022
d9e0395
add async to nre section
andrew-fleming Aug 29, 2022
826d7a6
fix conflicts
andrew-fleming Aug 31, 2022
e5d1c9e
remove async from nre class methods
andrew-fleming Sep 1, 2022
38ab7a9
update nre examples
andrew-fleming Sep 1, 2022
885e37b
fix return vals
andrew-fleming Sep 1, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ For example:
```sh
nile send <private_key_alias> ownable0 transfer_ownership 0x07db6...60e794

Calling transfer_ownership on ownable0 with params: ['0x07db6...60e794']
Invoke transaction was sent.
Contract address: 0x07db6b52c8ab888183277bc6411c400136fe566c0eebfb96fffa559b2e60e794
Transaction hash: 0x1c
Expand Down Expand Up @@ -218,13 +219,13 @@ Please note:

### `run`

Execute a script in the context of Nile. The script must implement a `run(nre)` function to receive a `NileRuntimeEnvironment` object exposing Nile's scripting API.
Execute a script in the context of Nile. The script must implement an asynchronous `run(nre)` function to receive a `NileRuntimeEnvironment` object exposing Nile's scripting API.

```python
# path/to/script.py

def run(nre):
address, abi = nre.deploy("contract", alias="my_contract")
async def run(nre):
address, abi = await nre.deploy("contract", alias="my_contract")
print(abi, address)
```

Expand Down Expand Up @@ -374,18 +375,18 @@ Retrieves a list of ready-to-use accounts which allows for easy scripting integr
Next, write a script and call `get-accounts` to retrieve and use the deployed accounts.

```python
def run(nre):
async def run(nre):

# fetch the list of deployed accounts
accounts = nre.get_accounts()
accounts = await nre.get_accounts()

# then
accounts[0].send(...)
await accounts[0].send(...)

# or
alice, bob, *_ = accounts
alice.send(...)
bob.send(...)
await alice.send(...)
await bob.send(...)
```

> Please note that the list of accounts include only those that exist in the local `<network>.accounts.json` file.
Expand Down
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ python_requires = >=3.7

install_requires =
click>=8.0,<9.0
asyncclick>=8.0,<9.0
anyio
cairo-lang
importlib-metadata>=4.0
python-dotenv>=0.19.2

Expand Down
53 changes: 33 additions & 20 deletions src/nile/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"""Nile CLI entry point."""
import logging

import click
import asyncclick as click

from nile.core.account import Account
from nile.core.call_or_invoke import call_or_invoke as call_or_invoke_command
Expand Down Expand Up @@ -72,55 +72,64 @@ def install():
@cli.command()
@click.argument("path", nargs=1)
@network_option
def run(path, network):
async def run(path, network):
"""Run Nile scripts with NileRuntimeEnvironment."""
run_command(path, network)
await run_command(path, network)


@cli.command()
@click.argument("artifact", nargs=1)
@click.argument("arguments", nargs=-1)
@network_option
@click.option("--alias")
def deploy(artifact, arguments, network, alias):
@click.option("--salt")
@click.option("--token")
async def deploy(artifact, arguments, network, alias, salt, token):
"""Deploy StarkNet smart contract."""
deploy_command(artifact, arguments, network, alias)
return await deploy_command(artifact, arguments, network, alias, salt, token)


@cli.command()
@click.argument("artifact", nargs=1)
@network_option
@click.option("--alias")
def declare(artifact, network, alias):
@click.option("--signature", nargs=2)
@click.option("--token")
async def declare(artifact, network, alias, signature, token):
"""Declare StarkNet smart contract."""
declare_command(artifact, network, alias)
return await declare_command(artifact, network, alias, signature, token)


@cli.command()
@click.argument("signer", nargs=1)
@network_option
def setup(signer, network):
async def setup(signer, network):
"""Set up an Account contract."""
Account(signer, network)
await Account(signer, network)


@cli.command()
@click.argument("signer", nargs=1)
@click.argument("contract_name", nargs=1)
@click.argument("method", nargs=1)
@click.argument("params", nargs=-1)
@click.option("--nonce", nargs=1)
@click.option("--max_fee", nargs=1)
@network_option
def send(signer, contract_name, method, params, network, max_fee=None):
async def send(signer, contract_name, method, params, network, nonce, max_fee):
"""Invoke a contract's method through an Account. Same usage as nile invoke."""
account = Account(signer, network)
account = await Account(signer, network)
print(
"Calling {} on {} with params: {}".format(
method, contract_name, [x for x in params]
)
)
out = account.send(contract_name, method, params, max_fee=max_fee)
print(out)
address, tx_hash = await account.send(
contract_name, method, params, nonce=nonce, max_fee=max_fee
)
logging.info("Invoke transaction was sent.")
logging.info(f"Contract address: {address}")
logging.info(f"Transaction hash: {tx_hash}")


@cli.command()
Expand All @@ -129,23 +138,27 @@ def send(signer, contract_name, method, params, network, max_fee=None):
@click.argument("params", nargs=-1)
@click.option("--max_fee", nargs=1)
@network_option
def invoke(contract_name, method, params, network, max_fee=None):
async def invoke(contract_name, method, params, network, max_fee=None):
"""Invoke functions of StarkNet smart contracts."""
out = call_or_invoke_command(
address, tx_hash = await call_or_invoke_command(
contract_name, "invoke", method, params, network, max_fee=max_fee
)
print(out)
logging.info("Invoke transaction was sent.")
logging.info(f"Contract address: {address}")
logging.info(f"Transaction hash: {tx_hash}")


@cli.command()
@click.argument("contract_name", nargs=1)
@click.argument("method", nargs=1)
@click.argument("params", nargs=-1)
@network_option
def call(contract_name, method, params, network):
async def call(contract_name, method, params, network):
"""Call functions of StarkNet smart contracts."""
out = call_or_invoke_command(contract_name, "call", method, params, network)
print(out)
result = await call_or_invoke_command(
contract_name, "call", method, params, network
)
logging.info(result)


@cli.command()
Expand Down Expand Up @@ -243,4 +256,4 @@ def get_accounts(network):


if __name__ == "__main__":
cli()
cli(_anyio_backend="asyncio")
66 changes: 44 additions & 22 deletions src/nile/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
import json
import os
import re
import subprocess

from starkware.starknet.cli.starknet_cli import NETWORKS, assert_tx_received
from starkware.starknet.services.api.feeder_gateway.feeder_gateway_client import (
FeederGatewayClient,
)
from starkware.starknet.services.api.gateway.gateway_client import GatewayClient

CONTRACTS_DIRECTORY = "contracts"
BUILD_DIRECTORY = "artifacts"
Expand Down Expand Up @@ -46,30 +51,41 @@ def get_all_contracts(ext=None, directory=None):
return files


def run_command(
contract_name, network, overriding_path=None, operation="deploy", arguments=None
):
"""Execute CLI command with given parameters."""
base_path = (
overriding_path if overriding_path else (BUILD_DIRECTORY, ABIS_DIRECTORY)
)
contract = f"{base_path[0]}/{contract_name}.json"
command = ["starknet", operation, "--contract", contract]

if arguments:
command.append("--inputs")
command.extend(prepare_params(arguments))

if network == "mainnet":
os.environ["STARKNET_NETWORK"] = "alpha-mainnet"
elif network == "goerli":
os.environ["STARKNET_NETWORK"] = "alpha-goerli"
async def get_gateway_response(network, tx, token):
"""Execute transaction and return response."""
gateway_url = get_gateway_url(network)
gateway_client = GatewayClient(url=gateway_url)
gateway_response = await gateway_client.add_transaction(tx=tx, token=token)
assert_tx_received(gateway_response)

return gateway_response


async def get_feeder_response(network, tx):
"""Execute transaction and return response."""
gateway_url = get_feeder_url(network)
gateway_client = FeederGatewayClient(url=gateway_url)
gateway_response = await gateway_client.call_contract(invoke_tx=tx)

return gateway_response["result"]


def get_gateway_url(network):
"""Return gateway URL for specified network."""
if network == "localhost":
return GATEWAYS.get(network)
else:
command.append(f"--gateway_url={GATEWAYS.get(network)}")
network = "alpha-" + network
return f"https://{NETWORKS[network]}/gateway"

command.append("--no_wallet")

return subprocess.check_output(command)
def get_feeder_url(network):
"""Return feeder gateway URL for specified network."""
if network == "localhost":
return GATEWAYS.get(network)
else:
network = "alpha-" + network
return f"https://{NETWORKS[network]}/feeder_gateway"


def parse_information(x):
Expand All @@ -92,3 +108,9 @@ def prepare_params(params):
if params is None:
params = []
return stringify(params)


def prepare_return(x):
"""Unpack list and convert hex to integer."""
for y in x:
return int(y, 16)
60 changes: 41 additions & 19 deletions src/nile/core/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,24 @@
load_dotenv()


class Account:
class AsyncObject(object):
"""Base class for Account to allow async initialization."""

async def __new__(cls, *a, **kw):
"""Return coroutine (not class so sync __init__ is not invoked)."""
instance = super().__new__(cls)
await instance.__init__(*a, **kw)
return instance

async def __init__(self):
"""Support Account async __init__."""
pass


class Account(AsyncObject):
"""Account contract abstraction."""

def __init__(self, signer, network):
async def __init__(self, signer, network):
"""Get or deploy an Account contract for the given private key."""
try:
self.signer = Signer(int(os.environ[signer]))
Expand All @@ -33,27 +47,27 @@ def __init__(self, signer, network):
)
return

if accounts.exists(str(self.signer.public_key), network):
signer_data = next(accounts.load(str(self.signer.public_key), network))
if accounts.exists(str(self.signer.public_key), self.network):
signer_data = next(accounts.load(str(self.signer.public_key), self.network))
self.address = signer_data["address"]
self.index = signer_data["index"]
else:
address, index = self.deploy()
address, index = await self.deploy()
self.address = address
self.index = index

def deploy(self):
async def deploy(self):
"""Deploy an Account contract for the given private key."""
index = accounts.current_index(self.network)
pt = os.path.dirname(os.path.realpath(__file__)).replace("/core", "")
overriding_path = (f"{pt}/artifacts", f"{pt}/artifacts/abis")

address, _ = deploy(
"Account",
[str(self.signer.public_key)],
self.network,
f"account-{index}",
overriding_path,
address, _ = await deploy(
contract_name="Account",
arguments=[str(self.signer.public_key)],
network=self.network,
alias=f"account-{index}",
overriding_path=overriding_path,
)

accounts.register(
Expand All @@ -62,15 +76,13 @@ def deploy(self):

return address, index

def send(self, to, method, calldata, max_fee, nonce=None):
async def send(self, to, method, calldata, max_fee=None, nonce=None):
"""Execute a tx going through an Account contract."""
target_address, _ = next(deployments.load(to, self.network)) or to
calldata = [int(x) for x in calldata]

if nonce is None:
nonce = int(
call_or_invoke(self.address, "call", "get_nonce", [], self.network)[0]
)
nonce = await self.get_nonce()

if max_fee is None:
max_fee = 0
Expand All @@ -89,12 +101,22 @@ def send(self, to, method, calldata, max_fee, nonce=None):
params.extend(calldata)
params.append(nonce)

return call_or_invoke(
return await call_or_invoke(
contract=self.address,
type="invoke",
method="__execute__",
params=params,
network=self.network,
signature=[str(sig_r), str(sig_s)],
max_fee=str(max_fee),
signature=[sig_r, sig_s],
max_fee=max_fee,
)

async def get_nonce(self):
"""Return nonce from account contract."""
return await call_or_invoke(
contract=self.address,
type="call",
method="get_nonce",
params=[],
network=self.network,
)
Loading