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

Support SNIP-9 #1530

Merged
merged 54 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
e25729f
Big things:
baitcode Dec 2, 2024
40b01ed
- Added outside execution model, defined hashing
baitcode Dec 2, 2024
9137b40
* lint
baitcode Dec 5, 2024
ac7b2cc
ok. so I am not allowed to change ci configs
baitcode Dec 8, 2024
6a9fdc9
Merge branch 'development' into feat-snip-9
baitcode Dec 10, 2024
dcb044b
fixing tests
baitcode Dec 10, 2024
0487bd6
lint
baitcode Dec 10, 2024
a79ab75
switched back to increase balance
baitcode Dec 10, 2024
aceded7
tiny revert
baitcode Dec 10, 2024
5851f12
comment documentation test to check if that is what affects test acco…
baitcode Dec 10, 2024
e54e510
Revert "comment documentation test to check if that is what affects t…
baitcode Dec 10, 2024
7ea8760
change for another call in documentation. removed account deployment.
baitcode Dec 10, 2024
14531b1
fix
baitcode Dec 10, 2024
37f24df
fix doctest
baitcode Dec 10, 2024
fb9829e
more fixes
baitcode Dec 10, 2024
4f72882
remove balance change calls
baitcode Dec 10, 2024
1881d84
Fixed naming
baitcode Dec 10, 2024
d369165
fix documentation
baitcode Dec 10, 2024
2c2be9f
added secrets
baitcode Dec 10, 2024
f7867c1
trigger build
baitcode Dec 11, 2024
799b399
Merge branch 'development' into feat-snip-9
baitcode Dec 13, 2024
a6b7774
Review comments fixws
baitcode Dec 14, 2024
92d651c
linter
baitcode Dec 14, 2024
585e8df
fixup
baitcode Dec 14, 2024
9b12024
Added comment
baitcode Dec 14, 2024
81da8fe
fix
baitcode Dec 14, 2024
2bb0282
lost empty line
baitcode Dec 15, 2024
812429d
comments fixes
baitcode Dec 15, 2024
6e2b7f4
Update starknet_py/constants.py
baitcode Dec 15, 2024
44faa3b
comment fixupi
baitcode Dec 15, 2024
37b6ce4
fix wordings
baitcode Dec 15, 2024
6cacd8b
fix
baitcode Dec 15, 2024
11069b4
fixes
baitcode Dec 15, 2024
45644f9
more fixes
baitcode Dec 15, 2024
51f0f7f
more fixes
baitcode Dec 15, 2024
a449d8c
rename
baitcode Dec 15, 2024
a4e75c4
Fix
baitcode Dec 15, 2024
e3a3bed
linter
baitcode Dec 15, 2024
0ac1845
Update starknet_py/tests/e2e/docs/guide/test_account_sign_outside_tra…
baitcode Dec 15, 2024
8d98dbd
Update starknet_py/tests/e2e/docs/guide/test_account_sign_outside_tra…
baitcode Dec 15, 2024
dfdc252
Update starknet_py/net/account/base_account.py
baitcode Dec 15, 2024
c97df0f
a bit more review comment fixes
baitcode Dec 16, 2024
86e8f84
revert execute_v1
baitcode Dec 16, 2024
4b0b0a2
remove auto fee estimation
baitcode Dec 16, 2024
ee9a064
Update docs/guide/account_and_client.rst
baitcode Dec 16, 2024
9776344
Update starknet_py/tests/e2e/docs/guide/test_account_sign_outside_tra…
baitcode Dec 16, 2024
13d8fe0
Merge branch 'development' into feat-snip-9
baitcode Dec 16, 2024
ceaa8ca
import fix
baitcode Dec 16, 2024
a9aa417
fix doctest
baitcode Dec 16, 2024
df4079d
Update starknet_py/tests/e2e/account/outside_execution_test.py
baitcode Dec 16, 2024
817d679
Update starknet_py/tests/e2e/account/outside_execution_test.py
baitcode Dec 16, 2024
b931c5c
changelog
baitcode Dec 16, 2024
e5e76e4
Update docs/migration_guide.rst
baitcode Dec 16, 2024
7434a25
fixing fixes
baitcode Dec 16, 2024
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
9 changes: 9 additions & 0 deletions docs/guide/account_and_client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ Account also provides a way of creating signed transaction without sending them.
:language: python
:dedent: 4

Creating "Outside transaction" and executing it. `SNIP-9 <https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-9.md>`_
---------------------------------------------------------------------------------------------------------------------------

Account also provides a way of creating a call and signing to allow for another account (caller) to execute it later on original account behalf. Account does not need to funded with tokens for transaction to execute as caller will pay the execution fee.
baitcode marked this conversation as resolved.
Show resolved Hide resolved

.. codesnippet:: ../../starknet_py/tests/e2e/docs/guide/test_account_sign_outside_transaction.py
:language: python
:dedent: 4

Multicall
---------

Expand Down
10 changes: 10 additions & 0 deletions starknet_py/constants.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from enum import IntEnum
from pathlib import Path

# Address came from starkware-libs/starknet-addresses repository: https://github.com/starkware-libs/starknet-addresses
Expand Down Expand Up @@ -45,3 +46,12 @@
PUBLIC_KEY_RESPONSE_LENGTH = 65
SIGNATURE_RESPONSE_LENGTH = 65
VERSION_RESPONSE_LENGTH = 3

# Result of `encode_shortstring("ANY_CALLER")`
ANY_CALLER = 0x414E595F43414C4C4552


# OUTSIDE EXECUTION INTERFACE_VERSION with ID
class OutsideExecutionInterfaceID(IntEnum):
V1 = 0x68CFD18B92D1907B8BA3CC324900277F5A3622099431EA85DD8089255E4181
V2 = 0x1D1144BB2138366FF28D8E9AB57456B1D332AC42196230C3A602003C89872
121 changes: 121 additions & 0 deletions starknet_py/hash/outside_execution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
from starknet_py.constants import OutsideExecutionInterfaceID
from starknet_py.net.client_models import OutsideExecution
from starknet_py.net.schemas.common import Revision
from starknet_py.utils.typed_data import TypedData

OUTSIDE_EXECUTION_INTERFACE_ID_TO_TYPED_DATA_REVISION = {
OutsideExecutionInterfaceID.V1: Revision.V0,
OutsideExecutionInterfaceID.V2: Revision.V1,
}


# TODO(#1537): Implement as method of OutsideExecution
def outside_execution_to_typed_data(
franciszekjob marked this conversation as resolved.
Show resolved Hide resolved
outside_execution: OutsideExecution,
outside_execution_version: OutsideExecutionInterfaceID,
chain_id: int,
) -> TypedData:
"""
SNIP-12 Typed Data for OutsideExecution implementation. For revision V0 and V1.
"""

revision = OUTSIDE_EXECUTION_INTERFACE_ID_TO_TYPED_DATA_REVISION[
outside_execution_version
]

if revision == Revision.V0:
return TypedData.from_dict(
{
"types": {
"StarkNetDomain": [
{"name": "name", "type": "felt"},
{"name": "version", "type": "felt"},
{"name": "chainId", "type": "felt"},
],
"OutsideExecution": [
{"name": "caller", "type": "felt"},
{"name": "nonce", "type": "felt"},
{"name": "execute_after", "type": "felt"},
{"name": "execute_before", "type": "felt"},
{"name": "calls_len", "type": "felt"},
{"name": "calls", "type": "OutsideCall*"},
],
"OutsideCall": [
{"name": "to", "type": "felt"},
{"name": "selector", "type": "felt"},
{"name": "calldata_len", "type": "felt"},
{"name": "calldata", "type": "felt*"},
],
},
"primaryType": "OutsideExecution",
"domain": {
"name": "Account.execute_from_outside",
"version": "1",
"chainId": str(chain_id),
"revision": Revision.V0,
},
"message": {
"caller": outside_execution.caller,
"nonce": outside_execution.nonce,
"execute_after": outside_execution.execute_after,
"execute_before": outside_execution.execute_before,
"calls_len": len(outside_execution.calls),
"calls": [
{
"to": call.to_addr,
"selector": call.selector,
"calldata_len": len(call.calldata),
"calldata": call.calldata,
}
for call in outside_execution.calls
],
},
}
)

# revision == Revision.V1
return TypedData.from_dict(
{
"types": {
"StarknetDomain": [
{"name": "name", "type": "shortstring"},
{"name": "version", "type": "shortstring"},
{"name": "chainId", "type": "shortstring"},
{"name": "revision", "type": "shortstring"},
],
"OutsideExecution": [
{"name": "Caller", "type": "ContractAddress"},
{"name": "Nonce", "type": "felt"},
{"name": "Execute After", "type": "u128"},
{"name": "Execute Before", "type": "u128"},
{"name": "Calls", "type": "Call*"},
],
"Call": [
{"name": "To", "type": "ContractAddress"},
{"name": "Selector", "type": "selector"},
{"name": "Calldata", "type": "felt*"},
],
},
"primaryType": "OutsideExecution",
"domain": {
"name": "Account.execute_from_outside",
"version": "2",
"chainId": str(chain_id),
"revision": Revision.V1,
},
"message": {
"Caller": outside_execution.caller,
"Nonce": outside_execution.nonce,
"Execute After": outside_execution.execute_after,
"Execute Before": outside_execution.execute_before,
"Calls": [
{
"To": call.to_addr,
"Selector": call.selector,
"Calldata": call.calldata,
}
for call in outside_execution.calls
],
},
}
)
140 changes: 132 additions & 8 deletions starknet_py/net/account/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,29 @@
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union

from starknet_py.common import create_compiled_contract, create_sierra_compiled_contract
from starknet_py.constants import FEE_CONTRACT_ADDRESS, QUERY_VERSION_BASE
from starknet_py.constants import (
ANY_CALLER,
FEE_CONTRACT_ADDRESS,
QUERY_VERSION_BASE,
OutsideExecutionInterfaceID,
)
from starknet_py.hash.address import compute_address
from starknet_py.hash.outside_execution import outside_execution_to_typed_data
from starknet_py.hash.selector import get_selector_from_name
from starknet_py.hash.utils import verify_message_signature
from starknet_py.net.account.account_deployment_result import AccountDeploymentResult
from starknet_py.net.account.base_account import BaseAccount
from starknet_py.net.account.base_account import (
BaseAccount,
OutsideExecutionSupportBaseMixin,
)
from starknet_py.net.client import Client
from starknet_py.net.client_models import (
Call,
Calls,
EstimatedFee,
Hash,
OutsideExecution,
OutsideExecutionTimeBounds,
ResourceBounds,
ResourceBoundsMapping,
SentTransactionResponse,
Expand All @@ -39,21 +50,21 @@
from starknet_py.net.models.typed_data import TypedDataDict
from starknet_py.net.signer import BaseSigner
from starknet_py.net.signer.stark_curve_signer import KeyPair, StarkCurveSigner
from starknet_py.serialization.data_serializers.array_serializer import ArraySerializer
from starknet_py.serialization.data_serializers.felt_serializer import FeltSerializer
from starknet_py.serialization.data_serializers.payload_serializer import (
from starknet_py.serialization.data_serializers import (
ArraySerializer,
FeltSerializer,
PayloadSerializer,
)
from starknet_py.serialization.data_serializers.struct_serializer import (
StructSerializer,
UintSerializer,
)
from starknet_py.utils.iterable import ensure_iterable
from starknet_py.utils.sync import add_sync_methods
from starknet_py.utils.typed_data import TypedData


# pylint: disable=too-many-public-methods,disable=too-many-lines
@add_sync_methods
class Account(BaseAccount):
class Account(BaseAccount, OutsideExecutionSupportBaseMixin):
"""
Default Account implementation.
"""
Expand Down Expand Up @@ -290,6 +301,55 @@ async def get_nonce(
self.address, block_hash=block_hash, block_number=block_number
)

async def _check_outside_execution_nonce(
self,
nonce: int,
*,
block_hash: Optional[Union[Hash, Tag]] = None,
block_number: Optional[Union[int, Tag]] = None,
) -> bool:
(is_valid,) = await self._client.call_contract(
call=Call(
to_addr=self.address,
selector=get_selector_from_name("is_valid_outside_execution_nonce"),
calldata=[nonce],
),
block_hash=block_hash,
block_number=block_number,
)
return bool(is_valid)

async def get_outside_execution_nonce(self, retry_count=10) -> int:
while retry_count > 0:
random_stark_address = KeyPair.generate().public_key
if await self._check_outside_execution_nonce(random_stark_address):
return random_stark_address
retry_count -= 1
raise RuntimeError("Failed to generate a valid nonce")
franciszekjob marked this conversation as resolved.
Show resolved Hide resolved

async def _get_outside_execution_version(
self,
) -> Union[OutsideExecutionInterfaceID, None]:
for version in [
OutsideExecutionInterfaceID.V1,
OutsideExecutionInterfaceID.V2,
]:
if await self.supports_interface(version):
return version
return None

async def supports_interface(
self, interface_id: OutsideExecutionInterfaceID
) -> bool:
(does_support,) = await self._client.call_contract(
Call(
to_addr=self.address,
selector=get_selector_from_name("supports_interface"),
calldata=[interface_id],
)
)
return bool(does_support)

async def get_balance(
self,
token_address: Optional[AddressRepresentation] = None,
Expand Down Expand Up @@ -344,6 +404,56 @@ async def sign_invoke_v1(
signature = self.signer.sign_transaction(execute_tx)
return _add_signature_to_transaction(execute_tx, signature)

async def sign_outside_execution_call(
franciszekjob marked this conversation as resolved.
Show resolved Hide resolved
self,
calls: Calls,
execution_time_bounds: OutsideExecutionTimeBounds,
*,
caller: AddressRepresentation = ANY_CALLER,
nonce: Optional[int] = None,
interface_version: Optional[OutsideExecutionInterfaceID] = None,
) -> Call:
if interface_version is None:
interface_version = await self._get_outside_execution_version()

if interface_version is None:
raise RuntimeError(
"Can't initiate call, outside execution is not supported."
)

if nonce is None:
nonce = await self.get_outside_execution_nonce()

outside_execution = OutsideExecution(
caller=parse_address(caller),
nonce=nonce,
execute_after=execution_time_bounds.execute_after_timestamp,
execute_before=execution_time_bounds.execute_before_timestamp,
calls=list(ensure_iterable(calls)),
)
chain_id = await self._get_chain_id()
signature = self.signer.sign_message(
outside_execution_to_typed_data(
outside_execution, interface_version, chain_id
),
self.address,
)
selector_for_version = {
OutsideExecutionInterfaceID.V1: "execute_from_outside",
OutsideExecutionInterfaceID.V2: "execute_from_outside_v2",
}

return Call(
to_addr=self.address,
selector=get_selector_from_name(selector_for_version[interface_version]),
calldata=_outside_transaction_serialiser.serialize(
{
"outside_execution": outside_execution.to_abi_dict(),
"signature": signature,
}
),
)

async def sign_invoke_v3(
self,
calls: Calls,
Expand Down Expand Up @@ -889,3 +999,17 @@ def _parse_calls_cairo_v1(calls: Iterable[Call]) -> List[Dict]:
calls=ArraySerializer(_call_description_cairo_v1),
)
)
_outside_transaction_serialiser = StructSerializer(
OrderedDict(
outside_execution=StructSerializer(
OrderedDict(
caller=FeltSerializer(),
nonce=FeltSerializer(),
execute_after=UintSerializer(bits=64),
execute_before=UintSerializer(bits=64),
calls=ArraySerializer(_call_description_cairo_v1),
)
),
signature=ArraySerializer(FeltSerializer()),
)
)
Loading
Loading