Skip to content

Commit 7f4c71a

Browse files
committed
- Added outside execution model, defined hashing
- Extended base account interface - Added all utilities to deal with SNIP-9 nonce and generating OutsideExecution call - Bugfix type data generation - Cleaned up as much as I could to keep changes minimal - Added docs - Added test for positive scenario - Added one test for wrong caller scenario
1 parent 114b612 commit 7f4c71a

File tree

17 files changed

+554
-275
lines changed

17 files changed

+554
-275
lines changed

.pylintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,7 @@ max-locals=15
561561
max-parents=7
562562

563563
# Maximum number of public methods for a class (see R0904).
564-
max-public-methods=20
564+
max-public-methods=23
565565

566566
# Maximum number of return / yield for function / method body.
567567
max-returns=6

docs/guide/account_and_client.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ Account also provides a way of creating signed transaction without sending them.
4646
:language: python
4747
:dedent: 4
4848

49+
Creating "Outside transaction" and executing it. `SNIP-9 <https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-9.md>`_
50+
--------------------------------------------
51+
52+
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. This will also allow caller to execute calls encoded in that transaction for free (signer will pay the fee).
53+
54+
.. codesnippet:: ../../starknet_py/tests/e2e/docs/guide/test_account_sign_outside_transaction.py
55+
:language: python
56+
:dedent: 4
57+
4958
Multicall
5059
---------
5160

starknet_py/constants.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from enum import IntEnum
12
from pathlib import Path
23

34
# Address came from starkware-libs/starknet-addresses repository: https://github.com/starkware-libs/starknet-addresses
@@ -47,4 +48,9 @@
4748
VERSION_RESPONSE_LENGTH = 3
4849

4950
# SNIP-9 ANY_CALLER
50-
ANY_CALLER = 0x414e595f43414c4c4552
51+
ANY_CALLER = 0x414e595f43414c4c4552
52+
53+
# SNIP-9 INTERFACE_VERSION with ID
54+
class SNIP9InterfaceVersion(IntEnum):
55+
V1 = 0x68cfd18b92d1907b8ba3cc324900277f5a3622099431ea85dd8089255e4181
56+
V2 = 0x1d1144bb2138366ff28d8e9ab57456b1d332ac42196230c3a602003c89872
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
from starknet_py.net.client_models import OutsideExecution
2+
3+
from starknet_py.constants import SNIP9InterfaceVersion
4+
5+
from starknet_py.net.schemas.common import Revision
6+
from starknet_py.utils import typed_data as td
7+
8+
SNIP9_INTERFACE_ID_TO_SNIP12_REVISION = {
9+
SNIP9InterfaceVersion.V1: Revision.V0,
10+
SNIP9InterfaceVersion.V2: Revision.V1,
11+
}
12+
13+
def outside_execution_to_typed_data(
14+
outside_execution: OutsideExecution,
15+
snip9_version: SNIP9InterfaceVersion,
16+
chain_id: int
17+
) -> td.TypedData:
18+
"""
19+
SNIP-12 Typed Data for OutsideExecution implementation. For revision V0 and V1.
20+
"""
21+
22+
revision = SNIP9_INTERFACE_ID_TO_SNIP12_REVISION[snip9_version]
23+
24+
if revision == Revision.V0:
25+
return td.TypedData.from_dict({
26+
'types': {
27+
'StarkNetDomain': [
28+
{'name': 'name', 'type': 'felt'},
29+
{'name': 'version', 'type': 'felt'},
30+
{'name': 'chainId', 'type': 'felt'},
31+
],
32+
'OutsideExecution': [
33+
{'name': 'caller', 'type': 'felt' },
34+
{'name': 'nonce', 'type': 'felt' },
35+
{'name': 'execute_after', 'type': 'felt' },
36+
{'name': 'execute_before', 'type': 'felt' },
37+
{'name': 'calls_len', 'type': 'felt' },
38+
{'name': 'calls', 'type': 'OutsideCall*' },
39+
],
40+
'OutsideCall': [
41+
{ 'name': 'to', 'type': 'felt' },
42+
{ 'name': 'selector', 'type': 'felt' },
43+
{ 'name': 'calldata_len', 'type': 'felt' },
44+
{ 'name': 'calldata', 'type': 'felt*' },
45+
],
46+
},
47+
'primaryType': 'OutsideExecution',
48+
'domain': {
49+
'name': 'Account.execute_from_outside',
50+
'version': '1',
51+
'chainId': str(chain_id),
52+
'revision': Revision.V0,
53+
},
54+
'message': {
55+
'caller': outside_execution.caller,
56+
'nonce': outside_execution.nonce,
57+
'execute_after': outside_execution.execute_after,
58+
'execute_before': outside_execution.execute_before,
59+
'calls_len': len(outside_execution.calls),
60+
'calls': [
61+
{
62+
'to': call.to_addr,
63+
'selector': call.selector,
64+
'calldata_len': len(call.calldata),
65+
'calldata': call.calldata,
66+
} for call in outside_execution.calls
67+
],
68+
},
69+
})
70+
71+
# revision == Revision.V1
72+
return td.TypedData.from_dict({
73+
'types': {
74+
'StarknetDomain': [
75+
{'name': 'name', 'type': 'shortstring'},
76+
{'name': 'version', 'type': 'shortstring'},
77+
{'name': 'chainId', 'type': 'shortstring'},
78+
{'name': 'revision', 'type': 'shortstring'},
79+
],
80+
'OutsideExecution': [
81+
{'name': 'Caller', 'type': 'ContractAddress' },
82+
{'name': 'Nonce', 'type': 'felt' },
83+
{'name': 'Execute After', 'type': 'u128' },
84+
{'name': 'Execute Before', 'type': 'u128' },
85+
{'name': 'Calls', 'type': 'Call*' },
86+
],
87+
'Call': [
88+
{ 'name': 'To', 'type': 'ContractAddress' },
89+
{ 'name': 'Selector', 'type': 'selector' },
90+
{ 'name': 'Calldata', 'type': 'felt*' },
91+
],
92+
},
93+
'primaryType': 'OutsideExecution',
94+
'domain': {
95+
'name': 'Account.execute_from_outside',
96+
'version': '2',
97+
'chainId': str(chain_id),
98+
'revision': Revision.V1,
99+
},
100+
'message': {
101+
'Caller': outside_execution.caller,
102+
'Nonce': outside_execution.nonce,
103+
'Execute After': outside_execution.execute_after,
104+
'Execute Before': outside_execution.execute_before,
105+
'Calls': [
106+
{
107+
'To': call.to_addr,
108+
'Selector': call.selector,
109+
'Calldata': call.calldata,
110+
} for call in outside_execution.calls
111+
],
112+
},
113+
})

0 commit comments

Comments
 (0)