|
4 | 4 | from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
|
5 | 5 |
|
6 | 6 | from starknet_py.common import create_compiled_contract, create_sierra_compiled_contract
|
7 |
| -from starknet_py.constants import FEE_CONTRACT_ADDRESS, QUERY_VERSION_BASE |
| 7 | +from starknet_py.constants import ( |
| 8 | + ANY_CALLER, |
| 9 | + FEE_CONTRACT_ADDRESS, |
| 10 | + QUERY_VERSION_BASE, |
| 11 | + OutsideExecutionInterfaceID, |
| 12 | +) |
8 | 13 | from starknet_py.hash.address import compute_address
|
| 14 | +from starknet_py.hash.outside_execution import outside_execution_to_typed_data |
9 | 15 | from starknet_py.hash.selector import get_selector_from_name
|
10 | 16 | from starknet_py.hash.utils import verify_message_signature
|
11 | 17 | from starknet_py.net.account.account_deployment_result import AccountDeploymentResult
|
12 |
| -from starknet_py.net.account.base_account import BaseAccount |
| 18 | +from starknet_py.net.account.base_account import ( |
| 19 | + BaseAccount, |
| 20 | + OutsideExecutionSupportBaseMixin, |
| 21 | +) |
13 | 22 | from starknet_py.net.client import Client
|
14 | 23 | from starknet_py.net.client_models import (
|
15 | 24 | Call,
|
16 | 25 | Calls,
|
17 | 26 | EstimatedFee,
|
18 | 27 | Hash,
|
| 28 | + OutsideExecution, |
| 29 | + OutsideExecutionTimeBounds, |
19 | 30 | ResourceBounds,
|
20 | 31 | ResourceBoundsMapping,
|
21 | 32 | SentTransactionResponse,
|
|
40 | 51 | from starknet_py.net.signer import BaseSigner
|
41 | 52 | from starknet_py.net.signer.key_pair import KeyPair
|
42 | 53 | from starknet_py.net.signer.stark_curve_signer import StarkCurveSigner
|
43 |
| -from starknet_py.serialization.data_serializers.array_serializer import ArraySerializer |
44 |
| -from starknet_py.serialization.data_serializers.felt_serializer import FeltSerializer |
45 |
| -from starknet_py.serialization.data_serializers.payload_serializer import ( |
| 54 | +from starknet_py.serialization.data_serializers import ( |
| 55 | + ArraySerializer, |
| 56 | + FeltSerializer, |
46 | 57 | PayloadSerializer,
|
47 |
| -) |
48 |
| -from starknet_py.serialization.data_serializers.struct_serializer import ( |
49 | 58 | StructSerializer,
|
| 59 | + UintSerializer, |
50 | 60 | )
|
51 | 61 | from starknet_py.utils.iterable import ensure_iterable
|
52 | 62 | from starknet_py.utils.sync import add_sync_methods
|
53 | 63 | from starknet_py.utils.typed_data import TypedData
|
54 | 64 |
|
55 | 65 |
|
| 66 | +# pylint: disable=too-many-public-methods,disable=too-many-lines |
56 | 67 | @add_sync_methods
|
57 |
| -class Account(BaseAccount): |
| 68 | +class Account(BaseAccount, OutsideExecutionSupportBaseMixin): |
58 | 69 | """
|
59 | 70 | Default Account implementation.
|
60 | 71 | """
|
@@ -291,6 +302,55 @@ async def get_nonce(
|
291 | 302 | self.address, block_hash=block_hash, block_number=block_number
|
292 | 303 | )
|
293 | 304 |
|
| 305 | + async def _check_outside_execution_nonce( |
| 306 | + self, |
| 307 | + nonce: int, |
| 308 | + *, |
| 309 | + block_hash: Optional[Union[Hash, Tag]] = None, |
| 310 | + block_number: Optional[Union[int, Tag]] = None, |
| 311 | + ) -> bool: |
| 312 | + (is_valid,) = await self._client.call_contract( |
| 313 | + call=Call( |
| 314 | + to_addr=self.address, |
| 315 | + selector=get_selector_from_name("is_valid_outside_execution_nonce"), |
| 316 | + calldata=[nonce], |
| 317 | + ), |
| 318 | + block_hash=block_hash, |
| 319 | + block_number=block_number, |
| 320 | + ) |
| 321 | + return bool(is_valid) |
| 322 | + |
| 323 | + async def get_outside_execution_nonce(self, retry_count=10) -> int: |
| 324 | + while retry_count > 0: |
| 325 | + random_stark_address = KeyPair.generate().public_key |
| 326 | + if await self._check_outside_execution_nonce(random_stark_address): |
| 327 | + return random_stark_address |
| 328 | + retry_count -= 1 |
| 329 | + raise RuntimeError("Failed to generate a valid nonce") |
| 330 | + |
| 331 | + async def _get_outside_execution_version( |
| 332 | + self, |
| 333 | + ) -> Union[OutsideExecutionInterfaceID, None]: |
| 334 | + for version in [ |
| 335 | + OutsideExecutionInterfaceID.V1, |
| 336 | + OutsideExecutionInterfaceID.V2, |
| 337 | + ]: |
| 338 | + if await self.supports_interface(version): |
| 339 | + return version |
| 340 | + return None |
| 341 | + |
| 342 | + async def supports_interface( |
| 343 | + self, interface_id: OutsideExecutionInterfaceID |
| 344 | + ) -> bool: |
| 345 | + (does_support,) = await self._client.call_contract( |
| 346 | + Call( |
| 347 | + to_addr=self.address, |
| 348 | + selector=get_selector_from_name("supports_interface"), |
| 349 | + calldata=[interface_id], |
| 350 | + ) |
| 351 | + ) |
| 352 | + return bool(does_support) |
| 353 | + |
294 | 354 | async def get_balance(
|
295 | 355 | self,
|
296 | 356 | token_address: Optional[AddressRepresentation] = None,
|
@@ -345,6 +405,56 @@ async def sign_invoke_v1(
|
345 | 405 | signature = self.signer.sign_transaction(execute_tx)
|
346 | 406 | return _add_signature_to_transaction(execute_tx, signature)
|
347 | 407 |
|
| 408 | + async def sign_outside_execution_call( |
| 409 | + self, |
| 410 | + calls: Calls, |
| 411 | + execution_time_bounds: OutsideExecutionTimeBounds, |
| 412 | + *, |
| 413 | + caller: AddressRepresentation = ANY_CALLER, |
| 414 | + nonce: Optional[int] = None, |
| 415 | + interface_version: Optional[OutsideExecutionInterfaceID] = None, |
| 416 | + ) -> Call: |
| 417 | + if interface_version is None: |
| 418 | + interface_version = await self._get_outside_execution_version() |
| 419 | + |
| 420 | + if interface_version is None: |
| 421 | + raise RuntimeError( |
| 422 | + "Can't initiate call, outside execution is not supported." |
| 423 | + ) |
| 424 | + |
| 425 | + if nonce is None: |
| 426 | + nonce = await self.get_outside_execution_nonce() |
| 427 | + |
| 428 | + outside_execution = OutsideExecution( |
| 429 | + caller=parse_address(caller), |
| 430 | + nonce=nonce, |
| 431 | + execute_after=execution_time_bounds.execute_after_timestamp, |
| 432 | + execute_before=execution_time_bounds.execute_before_timestamp, |
| 433 | + calls=list(ensure_iterable(calls)), |
| 434 | + ) |
| 435 | + chain_id = await self._get_chain_id() |
| 436 | + signature = self.signer.sign_message( |
| 437 | + outside_execution_to_typed_data( |
| 438 | + outside_execution, interface_version, chain_id |
| 439 | + ), |
| 440 | + self.address, |
| 441 | + ) |
| 442 | + selector_for_version = { |
| 443 | + OutsideExecutionInterfaceID.V1: "execute_from_outside", |
| 444 | + OutsideExecutionInterfaceID.V2: "execute_from_outside_v2", |
| 445 | + } |
| 446 | + |
| 447 | + return Call( |
| 448 | + to_addr=self.address, |
| 449 | + selector=get_selector_from_name(selector_for_version[interface_version]), |
| 450 | + calldata=_outside_transaction_serialiser.serialize( |
| 451 | + { |
| 452 | + "outside_execution": outside_execution.to_abi_dict(), |
| 453 | + "signature": signature, |
| 454 | + } |
| 455 | + ), |
| 456 | + ) |
| 457 | + |
348 | 458 | async def sign_invoke_v3(
|
349 | 459 | self,
|
350 | 460 | calls: Calls,
|
@@ -890,3 +1000,17 @@ def _parse_calls_cairo_v1(calls: Iterable[Call]) -> List[Dict]:
|
890 | 1000 | calls=ArraySerializer(_call_description_cairo_v1),
|
891 | 1001 | )
|
892 | 1002 | )
|
| 1003 | +_outside_transaction_serialiser = StructSerializer( |
| 1004 | + OrderedDict( |
| 1005 | + outside_execution=StructSerializer( |
| 1006 | + OrderedDict( |
| 1007 | + caller=FeltSerializer(), |
| 1008 | + nonce=FeltSerializer(), |
| 1009 | + execute_after=UintSerializer(bits=64), |
| 1010 | + execute_before=UintSerializer(bits=64), |
| 1011 | + calls=ArraySerializer(_call_description_cairo_v1), |
| 1012 | + ) |
| 1013 | + ), |
| 1014 | + signature=ArraySerializer(FeltSerializer()), |
| 1015 | + ) |
| 1016 | +) |
0 commit comments