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

Commit

Permalink
Add Account.declare (#207)
Browse files Browse the repository at this point in the history
* refactor run_command. add Account.declare

* test account.declare

* tidy up

* fix cli's declare. add documentation

* fix test

* Update README.md

Co-authored-by: Andrew Fleming <[email protected]>

* apply review suggestions

* fix param

* fix linter

* improve callorinvoke

* update udc address

* fix linting

* apply eric's review comments

* remove unnecessary compilation. fix overriding paths

* fix linter

* fix linter

* apply review suggestions

* fix tests

Co-authored-by: Andrew Fleming <[email protected]>
  • Loading branch information
martriay and andrew-fleming authored Oct 21, 2022
1 parent 7e36c82 commit 01ddf87
Show file tree
Hide file tree
Showing 13 changed files with 358 additions and 119 deletions.
39 changes: 22 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ Creating artifacts/abis/ to store compilation artifacts

### `deploy`

> NOTICE: this method doesn't use an account, which will be deprecated very soon as StarkNet makes deployments from accounts mandatory.
```sh
nile deploy contract --alias my_contract

Expand All @@ -125,23 +127,6 @@ A few things to notice here:
4. By default Nile works on local, but you can use the `--network` parameter to interact with `mainnet`, `goerli`, and the default `localhost`.
5. By default, the ABI corresponding to the contract will be registered with the deployment. To register a different ABI file, use the `--abi` parameter.

### `declare`

```sh
nile declare contract --alias my_contract

🚀 Declaring contract
⏳ Declaration of contract successfully sent at 0x07ec10eb0758f7b1bc5aed0d5b4d30db0ab3c087eba85d60858be46c1a5e4680
📦 Registering declaration as my_contract in localhost.declarations.txt
```

A few things to notice here:

1. `nile declare <contract_name>` looks for an artifact with the same name
2. This created a `localhost.declarations.txt` file storing all data related to my declarations
3. The `--alias` parameter lets me create a unique identifier for future interactions, if no alias is set then the contract's address can be used as identifier
4. By default Nile works on local, but you can use the `--network` parameter to interact with `mainnet`, `goerli`, and the default `localhost`.

### `setup`

Deploy an Account associated with a given private key.
Expand Down Expand Up @@ -188,6 +173,26 @@ Some things to note:
- `max_fee` defaults to `0`. Add `--max_fee <max_fee>` to set the maximum fee for the transaction
- `network` defaults to the `localhost`. Add `--network <network>` to change the network for the transaction

### `declare`

Very similar to `send`, but for declaring a contract based on its name through an account.

```sh
nile declare <private_key_alias> contract --alias my_contract

🚀 Declaring contract
⏳ Successfully sent declaration of contract as 0x07ec10eb0758f7b1bc5aed0d5b4d30db0ab3c087eba85d60858be46c1a5e4680
🧾 Transaction hash: 0x7222604b048632326f6a016ccb16fbdea7e926cd9e2354544800667a970aee4
📦 Registering declaration as my_contract in localhost.declarations.txt
```

A few things to notice here:

1. `nile declare <private_key_alias> <contract_name>` looks for an artifact with name `<contract_name>`
2. This creates or updates a `localhost.declarations.txt` file storing all data related to your declarations
3. The `--alias` parameter lets you create a unique identifier for future interactions, if no alias is set then the contract's address can be used as identifier
4. By default Nile works on local, but you can use the `--network` parameter to interact with `mainnet`, `goerli`, and the default `localhost`.

### `call`

Using `call`, we can perform read operations against our local node or the specified public network. The syntax is:
Expand Down
20 changes: 15 additions & 5 deletions src/nile/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from nile.core.call_or_invoke import call_or_invoke as call_or_invoke_command
from nile.core.clean import clean as clean_command
from nile.core.compile import compile as compile_command
from nile.core.declare import declare as declare_command
from nile.core.deploy import deploy as deploy_command
from nile.core.init import init as init_command
from nile.core.install import install as install_command
Expand Down Expand Up @@ -95,12 +94,23 @@ def deploy(artifact, arguments, network, alias, abi=None):


@cli.command()
@click.argument("artifact", nargs=1)
@network_option
@click.argument("signer", nargs=1)
@click.argument("contract_name", nargs=1)
@click.option("--max_fee", nargs=1)
@click.option("--alias")
def declare(artifact, network, alias):
@click.option("--overriding_path")
@network_option
def declare(
signer, contract_name, network, max_fee=None, alias=None, overriding_path=None
):
"""Declare StarkNet smart contract."""
declare_command(artifact, network, alias)
account = Account(signer, network)
account.declare(
contract_name,
alias=alias,
max_fee=max_fee,
overriding_path=overriding_path,
)


@cli.command()
Expand Down
89 changes: 79 additions & 10 deletions src/nile/common.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
"""nile common module."""
import json
import logging
import os
import re
import subprocess

from starkware.crypto.signature.fast_pedersen_hash import pedersen_hash
from starkware.starknet.core.os.class_hash import compute_class_hash
from starkware.starknet.services.api.contract_class import ContractClass

from nile.utils import normalize_number, str_to_felt

CONTRACTS_DIRECTORY = "contracts"
Expand All @@ -15,6 +20,10 @@
ACCOUNTS_FILENAME = "accounts.json"
NODE_FILENAME = "node.json"
RETRY_AFTER_SECONDS = 30
UNIVERSAL_DEPLOYER_ADDRESS = (
# subject to change
"0x1a8e53128903a412d86f33742d7f907f14ee8db566a14592cced70d52f96222"
)


def get_gateway():
Expand Down Expand Up @@ -49,29 +58,72 @@ def get_all_contracts(ext=None, directory=None):


def run_command(
contract_name, network, overriding_path=None, operation="deploy", arguments=None
operation,
network,
contract_name=None,
arguments=None,
inputs=None,
signature=None,
max_fee=None,
overriding_path=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]
command = ["starknet", operation]

if contract_name is not None:
base_path = (
overriding_path if overriding_path else (BUILD_DIRECTORY, ABIS_DIRECTORY)
)
contract = f"{base_path[0]}/{contract_name}.json"
command.append("--contract")
command.append(contract)

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

if signature is not None:
command.append("--signature")
command.extend(prepare_params(signature))

if max_fee is not None:
command.append("--max_fee")
command.append(max_fee)

if arguments is not None:
command.extend(arguments)

if network == "mainnet":
os.environ["STARKNET_NETWORK"] = "alpha-mainnet"
elif network == "goerli":
os.environ["STARKNET_NETWORK"] = "alpha-goerli"
else:
command.append(f"--feeder_gateway_url={GATEWAYS.get(network)}")
command.append(f"--gateway_url={GATEWAYS.get(network)}")

command.append("--no_wallet")

return subprocess.check_output(command)
try:
return subprocess.check_output(command).strip().decode("utf-8")
except subprocess.CalledProcessError:
p = subprocess.Popen(command, stderr=subprocess.PIPE)
_, error = p.communicate()
err_msg = error.decode()

if "max_fee must be bigger than 0" in err_msg:
logging.error(
"""
\n😰 Whoops, looks like max fee is missing. Try with:\n
--max_fee=`MAX_FEE`
"""
)
elif "transactions should go through the __execute__ entrypoint." in err_msg:
logging.error(
"\n\n😰 Whoops, looks like you're not using an account. Try with:\n"
"\nnile send [OPTIONS] SIGNER CONTRACT_NAME METHOD [PARAMS]"
)

return ""


def parse_information(x):
Expand Down Expand Up @@ -120,5 +172,22 @@ def is_string(param):


def is_alias(param):
"""Identiy param as alias (instead of address)."""
"""Identify param as alias (instead of address)."""
return is_string(param)


def get_contract_class(contract_name, overriding_path=None):
"""Return the contract_class for a given contract name."""
base_path = (
overriding_path if overriding_path else (BUILD_DIRECTORY, ABIS_DIRECTORY)
)
with open(f"{base_path[0]}/{contract_name}.json", "r") as fp:
contract_class = ContractClass.loads(fp.read())

return contract_class


def get_hash(contract_name, overriding_path=None):
"""Return the class_hash for a given contract name."""
contract_class = get_contract_class(contract_name, overriding_path)
return compute_class_hash(contract_class=contract_class, hash_func=pedersen_hash)
66 changes: 57 additions & 9 deletions src/nile/core/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@
from dotenv import load_dotenv

from nile import accounts, deployments
from nile.common import is_alias
from nile.common import (
UNIVERSAL_DEPLOYER_ADDRESS,
get_contract_class,
is_alias,
normalize_number,
)
from nile.core.call_or_invoke import call_or_invoke
from nile.core.declare import declare
from nile.core.deploy import deploy
from nile.utils import normalize_number
from nile.utils.get_nonce import get_nonce_without_log as get_nonce

try:
Expand Down Expand Up @@ -77,16 +82,59 @@ def deploy(self):

return address, index

def send(self, address_or_alias, method, calldata, max_fee, nonce=None):
"""Execute a tx going through an Account contract."""
if not is_alias(address_or_alias):
address_or_alias = normalize_number(address_or_alias)
def declare(
self, contract_name, max_fee=None, nonce=None, alias=None, overriding_path=None
):
"""Declare a contract through an Account contract."""
if nonce is None:
nonce = get_nonce(self.address, self.network)

target_address, _ = (
next(deployments.load(address_or_alias, self.network), None)
or address_or_alias
if max_fee is None:
max_fee = 0
else:
max_fee = int(max_fee)

contract_class = get_contract_class(
contract_name=contract_name, overriding_path=overriding_path
)

sig_r, sig_s = self.signer.sign_declare(
sender=self.address,
contract_class=contract_class,
nonce=nonce,
max_fee=max_fee,
)

return declare(
sender=self.address,
contract_name=contract_name,
signature=[sig_r, sig_s],
alias=alias,
network=self.network,
max_fee=max_fee,
)

def deploy_contract(
self, class_hash, salt, unique, calldata, max_fee=None, deployer_address=None
):
"""Deploy a contract through an Account contract."""
return self.send(
to=deployer_address or UNIVERSAL_DEPLOYER_ADDRESS,
method="deployContract",
calldata=[class_hash, salt, unique, len(calldata), *calldata],
max_fee=max_fee,
)

def send(self, to, method, calldata, max_fee=None, nonce=None):
"""Execute a tx going through an Account contract."""
if not is_alias(to):
to = normalize_number(to)

try:
target_address, _ = next(deployments.load(to, self.network))
except StopIteration:
target_address = to

calldata = [int(x) for x in calldata]

if nonce is None:
Expand Down
62 changes: 10 additions & 52 deletions src/nile/core/call_or_invoke.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
"""Command to call or invoke StarkNet smart contracts."""
import logging
import os
import subprocess

from nile import deployments
from nile.common import GATEWAYS, prepare_params
from nile.common import run_command
from nile.core import account
from nile.utils import hex_address

Expand All @@ -20,9 +17,7 @@ def call_or_invoke(
address, abi = next(deployments.load(contract, network))

address = hex_address(address)
command = [
"starknet",
type,
arguments = [
"--address",
address,
"--abi",
Expand All @@ -31,48 +26,11 @@ def call_or_invoke(
method,
]

if network == "mainnet":
os.environ["STARKNET_NETWORK"] = "alpha-mainnet"
elif network == "goerli":
os.environ["STARKNET_NETWORK"] = "alpha-goerli"
else:
command.append(f"--feeder_gateway_url={GATEWAYS.get(network)}")
command.append(f"--gateway_url={GATEWAYS.get(network)}")

params = prepare_params(params)

if len(params) > 0:
command.append("--inputs")
command.extend(params)

if signature is not None:
command.append("--signature")
command.extend(signature)

if max_fee is not None:
command.append("--max_fee")
command.append(max_fee)

command.append("--no_wallet")

try:
return subprocess.check_output(command).strip().decode("utf-8")
except subprocess.CalledProcessError:
p = subprocess.Popen(command, stderr=subprocess.PIPE)
_, error = p.communicate()
err_msg = error.decode()

if "max_fee must be bigger than 0" in err_msg:
logging.error(
"""
\n😰 Whoops, looks like max fee is missing. Try with:\n
--max_fee=`MAX_FEE`
"""
)
elif "transactions should go through the __execute__ entrypoint." in err_msg:
logging.error(
"\n\n😰 Whoops, looks like you're not using an account. Try with:\n"
"\nnile send [OPTIONS] SIGNER CONTRACT_NAME METHOD [PARAMS]"
)

return ""
return run_command(
operation=type,
network=network,
inputs=params,
arguments=arguments,
signature=signature,
max_fee=max_fee,
)
Loading

0 comments on commit 01ddf87

Please sign in to comment.