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

Deploy through Accounts #276

Merged
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
3ea6b8d
feat: add main logic
ericnordelo Nov 2, 2022
0dc42e5
Merge branch 'main' of github.com:OpenZeppelin/nile into feat/deploy-…
ericnordelo Nov 3, 2022
4d60c2a
feat: finish main logic
ericnordelo Nov 3, 2022
7e93f8c
feat: format files
ericnordelo Nov 3, 2022
c06dd64
feat: add deprecation warning and update README
ericnordelo Nov 3, 2022
f061781
fix: format and linter
ericnordelo Nov 3, 2022
f9166bc
feat: format code
ericnordelo Nov 3, 2022
7b6b9af
feat: organize code
ericnordelo Nov 3, 2022
2dba075
Merge branch 'main' of github.com:OpenZeppelin/nile into feat/deploy-…
ericnordelo Nov 7, 2022
6d3897b
feat: update tests
ericnordelo Nov 7, 2022
a6e919f
refactor: format files
ericnordelo Nov 7, 2022
ae32dba
Update README.md
ericnordelo Nov 8, 2022
7a4b484
feat: add tests
ericnordelo Nov 8, 2022
b88b44d
Merge branch 'feat/deploy-through-account-#259' of github.com:ericnor…
ericnordelo Nov 8, 2022
f648d89
fix: linter issues
ericnordelo Nov 8, 2022
9969311
fix: linter issue
ericnordelo Nov 8, 2022
bf3ea83
Merge branch 'main' of github.com:OpenZeppelin/nile into feat/deploy-…
ericnordelo Nov 8, 2022
d969342
update README
ericnordelo Nov 9, 2022
dba3394
feat: parse get_class_hash
ericnordelo Nov 10, 2022
9856871
refactor: format files
ericnordelo Nov 10, 2022
34704c7
refactor: remove misleading comment
ericnordelo Nov 11, 2022
953e831
Update src/nile/core/deploy.py
ericnordelo Nov 11, 2022
8777ed0
refactor: update comment
ericnordelo Nov 11, 2022
6281b3c
Merge branch 'feat/deploy-through-account-#259' of github.com:ericnor…
ericnordelo Nov 11, 2022
2f62263
Merge branch 'main' of github.com:OpenZeppelin/nile into feat/deploy-…
ericnordelo Nov 11, 2022
3cface7
feat: add more tests
ericnordelo Nov 11, 2022
a53ec32
refactor: tests
ericnordelo Nov 11, 2022
f41c22e
feat: refactor udc to deployer
ericnordelo Nov 14, 2022
ce03ca1
feat: change deploy cli api
ericnordelo Nov 15, 2022
048568e
feat: update README
ericnordelo Nov 15, 2022
bb949c6
Update src/nile/core/account.py
ericnordelo Nov 15, 2022
24f396a
Update README.md
ericnordelo Nov 15, 2022
9d6b9e5
Merge branch 'main' of github.com:OpenZeppelin/nile into feat/deploy-…
ericnordelo Nov 17, 2022
08cd4f2
fix: test and linter
ericnordelo Nov 17, 2022
7c97c42
Merge branch 'feat/deploy-through-account-#259' of github.com:ericnor…
ericnordelo Nov 17, 2022
4df6e18
fix: bug in debug
ericnordelo Nov 17, 2022
21e85eb
feat: fix comment
ericnordelo Nov 17, 2022
a480804
fix: bug in address generation
ericnordelo Nov 17, 2022
765a7c2
feat: fix tests
ericnordelo Nov 17, 2022
f544ad5
feat: remove _tx in sign methods
ericnordelo Nov 18, 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
21 changes: 12 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ 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.
> NOTICE: Calling this method will require an account very soon (using the `--account` param) as StarkNet will make deployments from accounts mandatory.

> Token for deployments to Alpha Mainnet can be set with the `--token` option.

Expand All @@ -128,21 +128,24 @@ nile deploy <contract> [--alias ALIAS] [--network NETWORK] [--track | --debug]
```

```sh
nile deploy contract --alias my_contract
nile deploy contract --account setup_account_alias --alias my_contract
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

although i like the --account flag much better, for consistency with other commands we should use pk aliases

nile deploy PK_ALIAS contract ...

#212 partially addresses it. having an --account flag is also the way because it would maintain the API even after having default accounts with #73

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i understand this makes much more difficult to identify therefore support both deployments (with/without account), unclear what's best here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My first thought was the same (to follow the same format of send), but as you state, the difference here is that we need to support both with and without account options. Click parameters (instead of options) are not optional (they could be, but with important restrictions I think are not worth to address).

IMO the best approach would be to let account an option in this PR (because it is potentially the desired behavior anyway) and fix (or improve) pk handling later in the issues you mentioned.

Copy link
Contributor

@martriay martriay Nov 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the difference here is that we need to support both

i'm not sure about this. "accountless" deployments are already discouraged and will be prohibited soon. UDC-like deployments are already functional, so we don't really need to support it.

i think it's preferable to have a consistent, expectable API than an inconsistent one with perfect/final portions. i think we should aim for consistency and eventually migrate all methods to use --account.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you suggesting then removing the support for deploys without accounts? If we remove this support, the current starknet-devnet version we support in Nile (pre 0.10.1) won't work for deployments because it has not predeployed UDC.

Copy link
Contributor

@martriay martriay Nov 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes that's the suggestion, since this release will require [email protected] already. happy to keep the functionality as long as it doesn't compromise API consistency if there's such alternative

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the API to match the send one, but let a --ignore_account flag to deploy without an account with the deprecation warning until the direct deploys are fully removed.

Copy link
Contributor

@martriay martriay Nov 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, i appreciate the change and i know it's quirky. i asked the starkware team, they expect to remove it by this week.


🚀 Deploying contract
🌕 artifacts/contract.json successfully deployed to 0x07ec10eb0758f7b1bc5aed0d5b4d30db0ab3c087eba85d60858be46c1a5e4680
⏳ ️Deployment of contract successfully sent at 0x07ec10eb0758f7b1bc5aed0d5b4d30db0ab3c087eba85d60858be46c1a5e4680
🧾 Transaction hash: 0x79e596c39cfec555f2d17253d043a0defd64a851a268b68c13811f328baf123
📦 Registering deployment as my_contract in localhost.deployments.txt
```

A few things to note here:

- `nile deploy <contract_name>` looks for an artifact with the same name.
- This creates or updates the `localhost.deployments.txt` file storing all data related to deployments.
- The `--alias` parameter creates a unique identifier for future interactions, if no alias is set then the contract's address can be used as identifier.
- By default Nile works on local, but you can use the `--network` parameter to interact with `mainnet`, `goerli`, and the default `localhost`.
- By default, the ABI corresponding to the contract will be registered with the deployment. To register a different ABI file, use the `--abi` parameter.
- `--track` and `--debug` flags can be used to watch the status of the deployment transaction. See `status` below for a complete description.
1. `nile deploy <contract_name>` looks for an artifact with the same name.
2. This creates or updates the `localhost.deployments.txt` file storing all data related to deployments.
3. The `--account` parameter deploys using the provided account and the UniversalDeployer contract.
martriay marked this conversation as resolved.
Show resolved Hide resolved
4. The `--alias` parameter creates a unique identifier for future interactions, if no alias is set then the contract's address can be used as identifier.
5. The `--deployer_address` parameter lets you specify the deployer contract address if needed.
6. By default Nile works on local, but you can use the `--network` parameter to interact with `mainnet`, `goerli`, `goerli2`, `integration`, and the default `localhost`.
7. By default, the ABI corresponding to the contract will be registered with the deployment. To register a different ABI file, use the `--abi` parameter.
8. `--track` and `--debug` flags can be used to watch the status of the deployment transaction. See `status` below for a complete description.

### `setup`

Expand Down
68 changes: 53 additions & 15 deletions src/nile/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,24 +91,62 @@ def run(path, network):


@cli.command()
@click.argument("artifact", nargs=1)
@click.argument("arguments", nargs=-1)
@click.argument("signer", nargs=1)
@click.argument("contract_name", nargs=1)
@click.argument("params", nargs=-1)
@click.option("--max_fee", nargs=1)
@click.option("--salt", nargs=1, default=0)
@click.option("--unique", is_flag=True)
@click.option("--alias")
@click.option("--abi")
@network_option
@click.option("--deployer_address")
@click.option(
"--ignore_account",
is_flag=True,
help="Deploy without Account.",
)
@mainnet_token_option
@network_option
@watch_option
def deploy(artifact, arguments, network, alias, watch_mode, abi, token):
"""Deploy StarkNet smart contract."""
deploy_command(
contract_name=artifact,
arguments=arguments,
network=network,
alias=alias,
abi=abi,
mainnet_token=token,
watch_mode=watch_mode,
)
def deploy(
signer,
contract_name,
salt,
params,
max_fee,
martriay marked this conversation as resolved.
Show resolved Hide resolved
unique,
alias,
abi,
deployer_address,
ignore_account,
token,
network,
watch_mode,
):
"""Deploy a StarkNet smart contract."""
if not ignore_account:
account = Account(signer, network)
account.deploy_contract(
contract_name,
salt,
unique,
params,
alias,
deployer_address=deployer_address,
max_fee=max_fee,
abi=abi,
martriay marked this conversation as resolved.
Show resolved Hide resolved
watch_mode=watch_mode,
)
else:
deploy_command(
contract_name,
params,
network,
alias,
abi=abi,
mainnet_token=token,
watch_mode=watch_mode,
)


@cli.command()
Expand All @@ -130,7 +168,7 @@ def declare(
overriding_path,
token,
):
"""Declare StarkNet smart contract."""
"""Declare a StarkNet smart contract through an Account."""
account = Account(signer, network)
account.declare(
contract_name,
Expand Down
2 changes: 1 addition & 1 deletion src/nile/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ def get_contract_class(contract_name, overriding_path=None):
return contract_class


def get_hash(contract_name, overriding_path=None):
def get_class_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 hex_class_hash(
Expand Down
62 changes: 37 additions & 25 deletions src/nile/core/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from nile.core.call_or_invoke import call_or_invoke
from nile.core.declare import declare
from nile.core.deploy import deploy
from nile.core.deploy import deploy_contract as deploy_with_deployer
from nile.utils.get_nonce import get_nonce_without_log as get_nonce

try:
Expand Down Expand Up @@ -64,6 +65,8 @@ def __init__(self, signer, network, predeployed_info=None, watch_mode=None):
self.address = address
self.index = index

assert type(self.address) == int
martriay marked this conversation as resolved.
Show resolved Hide resolved

def deploy(self, watch_mode=None):
"""Deploy an Account contract for the given private key."""
index = accounts.current_index(self.network)
Expand Down Expand Up @@ -95,20 +98,14 @@ def declare(
mainnet_token=None,
watch_mode=None,
):
"""Declare a contract through an Account contract."""
if nonce is None:
nonce = get_nonce(self.address, self.network)

if max_fee is None:
max_fee = 0
else:
max_fee = int(max_fee)
"""Declare a contract through an Account."""
_, max_fee, nonce = self._process_arguments([], max_fee, nonce)

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

sig_r, sig_s = self.signer.sign_declare(
sig_r, sig_s = self.signer.sign_declare_tx(
sender=self.address,
contract_class=contract_class,
nonce=nonce,
Expand All @@ -127,14 +124,33 @@ def declare(
)

def deploy_contract(
self, class_hash, salt, unique, calldata, max_fee=None, deployer_address=None
self,
contract_name,
salt,
unique,
calldata,
alias,
max_fee=None,
deployer_address=None,
abi=None,
watch_mode=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,
"""Deploy a contract through an Account."""
deployer_address = normalize_number(
deployer_address or UNIVERSAL_DEPLOYER_ADDRESS
)

deploy_with_deployer(
self,
contract_name,
salt,
unique,
calldata,
alias,
deployer_address,
max_fee,
abi=abi,
watch_mode=watch_mode,
)

def send(
Expand All @@ -147,17 +163,14 @@ def send(
query_type=None,
watch_mode=None,
):
"""Execute a query or invoke call for a tx going through an Account contract."""
# get target address with the right format
"""Execute a query or invoke call for a tx going through an Account."""
ericnordelo marked this conversation as resolved.
Show resolved Hide resolved
target_address = self._get_target_address(address_or_alias)

# process and parse arguments
calldata, max_fee, nonce = self._process_arguments(calldata, max_fee, nonce)

# get tx version
tx_version = QUERY_VERSION if query_type else TRANSACTION_VERSION

calldata, sig_r, sig_s = self.signer.sign_transaction(
calldata, sig_r, sig_s = self.signer.sign_invoke_tx(
sender=self.address,
calls=[[target_address, method, calldata]],
nonce=nonce,
Expand Down Expand Up @@ -193,10 +206,9 @@ def _get_target_address(self, address_or_alias):
if not is_alias(address_or_alias):
address_or_alias = normalize_number(address_or_alias)

target_address, _ = (
next(deployments.load(address_or_alias, self.network), None)
or address_or_alias
)
target_address, _ = next(
deployments.load(address_or_alias, self.network), None
) or (address_or_alias, None)

return target_address

Expand Down
78 changes: 75 additions & 3 deletions src/nile/core/deploy.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
"""Command to deploy StarkNet smart contracts."""
import logging

from starkware.cairo.common.hash_chain import compute_hash_chain
from starkware.starknet.core.os.contract_address.contract_address import (
calculate_contract_address_from_hash,
)

from nile import deployments
from nile.common import ABIS_DIRECTORY, BUILD_DIRECTORY, parse_information, run_command
from nile.common import (
ABIS_DIRECTORY,
BUILD_DIRECTORY,
get_class_hash,
parse_information,
run_command,
)
from nile.utils import hex_address
from nile.utils.status import status

Expand All @@ -17,8 +28,12 @@ def deploy(
mainnet_token=None,
watch_mode=None,
):
"""Deploy StarkNet smart contracts."""
logging.info(f"🚀 Deploying {contract_name}")
"""Deploy StarkNet smart contracts (DEPRECATED)."""
logging.info(
f"🚀 Deploying {contract_name} without Account. "
+ "This method is deprecated and will be removed soon. "
+ "Use the --account option."
)
base_path = (
overriding_path if overriding_path else (BUILD_DIRECTORY, ABIS_DIRECTORY)
)
Expand Down Expand Up @@ -47,3 +62,60 @@ def deploy(
return

return address, register_abi


def deploy_contract(
account,
contract_name,
salt,
unique,
calldata,
alias,
deployer_address,
max_fee,
overriding_path=None,
abi=None,
watch_mode=None,
):
"""Deploy StarkNet smart contracts."""
logging.info(f"🚀 Deploying {contract_name}")

base_path = (
overriding_path if overriding_path else (BUILD_DIRECTORY, ABIS_DIRECTORY)
)
register_abi = abi if abi is not None else f"{base_path[1]}/{contract_name}.json"
deployer_for_address_generation = 0

if unique:
# Match UDC address generation
salt = compute_hash_chain(data=[account.address, salt])
deployer_for_address_generation = deployer_address
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about including a random salt generator for deployments without --salt like StarkWare does? => https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/cli/starknet_cli.py#L846-L847

Otherwise, trying to deploy multiple instances of the same contract will result in:

raise StarkException(code=code, message=message) starkware.starkware_utils.error_handling.StarkException: (500, {'code': <StarknetErrorCode.CONTRACT_ADDRESS_UNAVAILABLE: 1>, 'message': 'Requested contract address 1999693730423262048654525999365022023777068478432229193013554800567845966099 is unavailable for deployment.'})

Users can add salt to avoid this, but automating salt seems preferable

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we agree^, I definitely suggest adding some salt tests

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this salt should be part of the protocol (like nonce in Ethereum deployments), but I am not sure why it is left to be passed from the end users (am I missing an important reason?). With this said, agreeing on a format for the automatic generation of this salt, I think, is content enough for another issue/PR. Shouldn't be any problem with releasing this version, right? I don't see a reason to delay it to another milestone.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this salt should be part of the protocol

I agree in general haha but since we're deploying contracts through an account.send tx, the burden seems to fall on Nile. When Nile interacts with the starknet_cli directly (and when Nile most likely bypasses the cli in the future), I imagine defining a salt generator will not be necessary

agreeing on a format for the automatic generation of this salt, I think, is content enough for another issue/PR

That sounds reasonable to me

I don't see a reason to delay it to another milestone.

I don't disagree


# This needs to be fixed following the Hex/Int Pattern
martriay marked this conversation as resolved.
Show resolved Hide resolved
class_hash = int(get_class_hash(contract_name=contract_name), 16)

address = calculate_contract_address_from_hash(
salt, class_hash, calldata, deployer_for_address_generation
)

output = account.send(
deployer_address,
method="deployContract",
calldata=[class_hash, salt, unique, len(calldata), *calldata],
max_fee=max_fee,
)

_, tx_hash = parse_information(output)
logging.info(
f"⏳ ️Deployment of {contract_name} successfully sent at {hex_address(address)}"
)
logging.info(f"🧾 Transaction hash: {hex(tx_hash)}")

deployments.register(address, register_abi, account.network, alias)

if watch_mode is not None:
if status(tx_hash, account.network, watch_mode).status.is_rejected:
deployments.unregister(address, account.network, alias, abi=register_abi)
return

return address, register_abi
10 changes: 5 additions & 5 deletions src/nile/signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ def sign(self, message_hash):
"""Sign a message hash."""
return sign(msg_hash=message_hash, priv_key=self.private_key)

def sign_declare(self, sender, contract_class, nonce, max_fee):
def sign_declare_tx(self, sender, contract_class, nonce, max_fee):
"""Sign a declare transaction."""
if isinstance(sender, str):
sender = int(sender, 16)

transaction_hash = get_declare_hash(
transaction_hash = get_declare_tx_hash(
sender=sender,
contract_class=contract_class,
max_fee=max_fee,
Expand All @@ -44,10 +44,10 @@ def sign_declare(self, sender, contract_class, nonce, max_fee):

return self.sign(message_hash=transaction_hash)

def sign_transaction(
def sign_invoke_tx(
martriay marked this conversation as resolved.
Show resolved Hide resolved
self, sender, calls, nonce, max_fee, version=TRANSACTION_VERSION
):
"""Sign a transaction."""
"""Sign an invoke transaction."""
call_array, calldata = from_call_to_call_array(calls)
execute_calldata = [
len(call_array),
Expand Down Expand Up @@ -93,7 +93,7 @@ def from_call_to_call_array(calls):
return (call_array, calldata)


def get_declare_hash(sender, contract_class, max_fee, nonce, chain_id):
def get_declare_tx_hash(sender, contract_class, max_fee, nonce, chain_id):
"""Compute the hash of a declare transaction."""
return calculate_declare_transaction_hash(
contract_class=contract_class,
Expand Down
Loading