Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Account.deploy_account_v3 static method #1265

Merged
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(
THenry14 marked this conversation as resolved.
Show resolved Hide resolved
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
170 changes: 125 additions & 45 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
If using Client for GOERLI, SEPOLIA or MAINNET, this method will verify if the address balance
ddoktorski marked this conversation as resolved.
Show resolved Hide resolved
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:
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I am not sure how to implement a similar check for the V3 transaction now. I'm guessing eventually we'd need to pull the balance from the STRK contract and an exchange rate to WEI?

Let me know if I am missing something here. Generally I think it is not crucial, but definitely nice to have.

Copy link
Member

Choose a reason for hiding this comment

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

Good question. I suppose it could depend on fee DA mode as well, but I'm not 100% sure. Let's open an issue to investigate this, we might need to ask SW as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

This is not crucial since tx will be rejected with a descriptive enough error message anyway

I'm guessing eventually we'd need to pull the balance from the STRK contract and an exchange rate to WEI?

It should probably be done the same way, except we'll have to pass STRK token address to get_balance. I don't really understand why'd you want to convert to WEI though?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're absolutely right, there is no need for conversion. I thought that L1_GAS in the resource bounds is specified in WEI, even though V3 transaction costs are paid in FRI, which is not the case. Apologies for the confusion.

We can indeed check it the same way as for V1, but with the STRK contract. On the other hand, as @DelevoXDG said, it is not crucial, since it will throw an error anyway. @THenry14 Wdyt?

Copy link
Member

Choose a reason for hiding this comment

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

Agreed, let's update #1266 with the findings and do necessary changes under that issue at some later date

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 Wei) 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
Loading