Skip to content

Commit

Permalink
Add Account.deploy_account_v3 static method (#1265)
Browse files Browse the repository at this point in the history
* Add `_prepare_account_to_deploy` function

* Rename `Account.deploy_account` to have `v1` postfix

* Add `Account.deploy_account_v3` function

* Make if condition shorter

* Rename parse calls related methods

* Rename `execute` to `execute_v1`

* Update docstrings
  • Loading branch information
ddoktorski authored Jan 31, 2024
1 parent 4d6175c commit df9a651
Show file tree
Hide file tree
Showing 15 changed files with 184 additions and 74 deletions.
4 changes: 2 additions & 2 deletions starknet_py/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ async def deploy(
calldata=constructor_args,
cairo_version=self._cairo_version,
)
res = await self._account.execute(
res = await self._account.execute_v1(
calls=deploy_call, nonce=nonce, max_fee=max_fee, auto_estimate=auto_estimate
)

Expand Down Expand Up @@ -728,7 +728,7 @@ async def deploy_contract(
calldata=constructor_args,
cairo_version=cairo_version,
)
res = await account.execute(
res = await account.execute_v1(
calls=deploy_call, nonce=nonce, max_fee=max_fee, auto_estimate=auto_estimate
)

Expand Down
172 changes: 126 additions & 46 deletions starknet_py/net/account/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ async def sign_deploy_account_v3_transaction(
signature = self.signer.sign_transaction(deploy_account_tx)
return _add_signature_to_transaction(deploy_account_tx, signature)

async def execute(
async def execute_v1(
self,
calls: Calls,
*,
Expand Down Expand Up @@ -592,7 +592,7 @@ def verify_message(self, typed_data: TypedData, signature: List[int]) -> bool:
return verify_message_signature(message_hash, signature, self.signer.public_key)

@staticmethod
async def deploy_account(
async def deploy_account_v1(
*,
address: AddressRepresentation,
class_hash: int,
Expand All @@ -605,53 +605,41 @@ async def deploy_account(
max_fee: Optional[int] = None,
auto_estimate: bool = False,
) -> AccountDeploymentResult:
# pylint: disable=too-many-locals
"""
Deploys an account contract with provided class_hash on Starknet and returns
an AccountDeploymentResult that allows waiting for transaction acceptance.
Provided address must be first prefunded with enough tokens, otherwise the method will fail.
If using Client for either TESTNET or MAINNET, this method will verify if the address balance
is high enough to cover deployment costs.
If using Client for MAINNET, GOERLI, SEPOLIA or SEPOLIA_INTEGRATION, this method will verify
if the address balance is high enough to cover deployment costs.
:param address: calculated and prefunded address of the new account.
:param class_hash: class_hash of the account contract to be deployed.
:param salt: salt used to calculate the address.
:param address: Calculated and prefunded address of the new account.
:param class_hash: Class hash of the account contract to be deployed.
:param salt: Salt used to calculate the address.
:param key_pair: KeyPair used to calculate address and sign deploy account transaction.
:param client: a Client instance used for deployment.
:param chain: id of the Starknet chain used.
:param constructor_calldata: optional calldata to account contract constructor. If ``None`` is passed,
:param client: Client instance used for deployment.
:param chain: Id of the Starknet chain used.
:param constructor_calldata: Optional calldata to account contract constructor. If ``None`` is passed,
``[key_pair.public_key]`` will be used as calldata.
:param nonce: Nonce of the transaction.
:param max_fee: max fee to be paid for deployment, must be less or equal to the amount of tokens prefunded.
:param max_fee: Max fee to be paid for deployment, must be less or equal to the amount of tokens prefunded.
:param auto_estimate: Use automatic fee estimation, not recommend as it may lead to high costs.
"""
address = parse_address(address)
calldata = (
constructor_calldata
if constructor_calldata is not None
else [key_pair.public_key]
)

if address != (
computed := compute_address(
salt=salt,
class_hash=class_hash,
constructor_calldata=calldata,
deployer_address=0,
)
):
raise ValueError(
f"Provided address {hex(address)} is different than computed address {hex(computed)} "
f"for the given class_hash and salt."
)

account = Account(
account = _prepare_account_to_deploy(
address=address,
client=client,
class_hash=class_hash,
salt=salt,
key_pair=key_pair,
client=client,
chain=chain,
calldata=calldata,
)

deploy_account_tx = await account.sign_deploy_account_v1_transaction(
Expand All @@ -663,12 +651,7 @@ async def deploy_account(
auto_estimate=auto_estimate,
)

if chain in (
StarknetChainId.SEPOLIA_TESTNET,
StarknetChainId.SEPOLIA_INTEGRATION,
StarknetChainId.GOERLI,
StarknetChainId.MAINNET,
):
if chain in StarknetChainId:
balance = await account.get_balance()
if balance < deploy_account_tx.max_fee:
raise ValueError(
Expand All @@ -681,6 +664,70 @@ async def deploy_account(
hash=result.transaction_hash, account=account, _client=account.client
)

@staticmethod
async def deploy_account_v3(
*,
address: AddressRepresentation,
class_hash: int,
salt: int,
key_pair: KeyPair,
client: Client,
chain: StarknetChainId,
constructor_calldata: Optional[List[int]] = None,
nonce: int = 0,
l1_resource_bounds: Optional[ResourceBounds] = None,
auto_estimate: bool = False,
) -> AccountDeploymentResult:
"""
Deploys an account contract with provided class_hash on Starknet and returns
an AccountDeploymentResult that allows waiting for transaction acceptance.
Provided address must be first prefunded with enough tokens, otherwise the method will fail.
:param address: Calculated and prefunded address of the new account.
:param class_hash: Class hash of the account contract to be deployed.
:param salt: Salt used to calculate the address.
:param key_pair: KeyPair used to calculate address and sign deploy account transaction.
:param client: Client instance used for deployment.
:param chain: Id of the Starknet chain used.
:param constructor_calldata: Optional calldata to account contract constructor. If ``None`` is passed,
``[key_pair.public_key]`` will be used as calldata.
:param nonce: Nonce of the transaction.
:param l1_resource_bounds: Max amount and max price per unit of L1 gas (in Fri) used when executing
this transaction.
:param auto_estimate: Use automatic fee estimation, not recommend as it may lead to high costs.
"""
calldata = (
constructor_calldata
if constructor_calldata is not None
else [key_pair.public_key]
)

account = _prepare_account_to_deploy(
address=address,
class_hash=class_hash,
salt=salt,
key_pair=key_pair,
client=client,
chain=chain,
calldata=calldata,
)

deploy_account_tx = await account.sign_deploy_account_v3_transaction(
class_hash=class_hash,
contract_address_salt=salt,
constructor_calldata=calldata,
nonce=nonce,
l1_resource_bounds=l1_resource_bounds,
auto_estimate=auto_estimate,
)

result = await client.deploy_account(deploy_account_tx)

return AccountDeploymentResult(
hash=result.transaction_hash, account=account, _client=account.client
)

def _default_token_address_for_chain(
self, chain_id: Optional[StarknetChainId] = None
) -> str:
Expand All @@ -697,6 +744,39 @@ def _default_token_address_for_chain(
return FEE_CONTRACT_ADDRESS


def _prepare_account_to_deploy(
address: AddressRepresentation,
class_hash: int,
salt: int,
key_pair: KeyPair,
client: Client,
chain: StarknetChainId,
calldata: List[int],
) -> Account:
# pylint: disable=too-many-arguments
address = parse_address(address)

if address != (
computed := compute_address(
salt=salt,
class_hash=class_hash,
constructor_calldata=calldata,
deployer_address=0,
)
):
raise ValueError(
f"Provided address {hex(address)} is different than computed address {hex(computed)} "
f"for the given class_hash and salt."
)

return Account(
address=address,
client=client,
key_pair=key_pair,
chain=chain,
)


def _is_sierra_contract(data: Dict[str, Any]) -> bool:
return "sierra_program" in data

Expand All @@ -721,19 +801,19 @@ def _add_resource_bounds_to_transaction(

def _parse_calls(cairo_version: int, calls: Calls) -> List[int]:
if cairo_version == 1:
parsed_calls = _parse_calls_v2(ensure_iterable(calls))
wrapped_calldata = _execute_payload_serializer_v2.serialize(
parsed_calls = _parse_calls_cairo_v1(ensure_iterable(calls))
wrapped_calldata = _execute_payload_serializer_v1.serialize(
{"calls": parsed_calls}
)
else:
call_descriptions, calldata = _merge_calls(ensure_iterable(calls))
wrapped_calldata = _execute_payload_serializer.serialize(
wrapped_calldata = _execute_payload_serializer_v0.serialize(
{"call_array": call_descriptions, "calldata": calldata}
)
return wrapped_calldata


def _parse_call(call: Call, entire_calldata: List) -> Tuple[Dict, List]:
def _parse_call_cairo_v0(call: Call, entire_calldata: List) -> Tuple[Dict, List]:
_data = {
"to": call.to_addr,
"selector": call.selector,
Expand All @@ -749,13 +829,13 @@ def _merge_calls(calls: Iterable[Call]) -> Tuple[List[Dict], List[int]]:
call_descriptions = []
entire_calldata = []
for call in calls:
data, entire_calldata = _parse_call(call, entire_calldata)
data, entire_calldata = _parse_call_cairo_v0(call, entire_calldata)
call_descriptions.append(data)

return call_descriptions, entire_calldata


def _parse_calls_v2(calls: Iterable[Call]) -> List[Dict]:
def _parse_calls_cairo_v1(calls: Iterable[Call]) -> List[Dict]:
calls_parsed = []
for call in calls:
_data = {
Expand All @@ -769,30 +849,30 @@ def _parse_calls_v2(calls: Iterable[Call]) -> List[Dict]:


_felt_serializer = FeltSerializer()
_call_description = StructSerializer(
_call_description_cairo_v0 = StructSerializer(
OrderedDict(
to=_felt_serializer,
selector=_felt_serializer,
data_offset=_felt_serializer,
data_len=_felt_serializer,
)
)
_call_description_v2 = StructSerializer(
_call_description_cairo_v1 = StructSerializer(
OrderedDict(
to=_felt_serializer,
selector=_felt_serializer,
calldata=ArraySerializer(_felt_serializer),
)
)

_execute_payload_serializer = PayloadSerializer(
_execute_payload_serializer_v0 = PayloadSerializer(
OrderedDict(
call_array=ArraySerializer(_call_description),
call_array=ArraySerializer(_call_description_cairo_v0),
calldata=ArraySerializer(_felt_serializer),
)
)
_execute_payload_serializer_v2 = PayloadSerializer(
_execute_payload_serializer_v1 = PayloadSerializer(
OrderedDict(
calls=ArraySerializer(_call_description_v2),
calls=ArraySerializer(_call_description_cairo_v1),
)
)
2 changes: 1 addition & 1 deletion starknet_py/net/account/base_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ async def sign_deploy_account_v3_transaction(
"""

@abstractmethod
async def execute(
async def execute_v1(
self,
calls: Calls,
*,
Expand Down
Loading

0 comments on commit df9a651

Please sign in to comment.