Skip to content

Commit

Permalink
Add execute_from_outside (#1246)
Browse files Browse the repository at this point in the history
# What

Add the SNIP9 entrypoint

Todo

- [x] decode the tx only once at the beginning
- [x] update `accounts.library.validate` to get the signature in
argument and not from `tx_info`
- [x] block `__validate__` and `__execute__` usage
```
    with_attr error_message("EOA: declare not supported") {
        assert 1 = 0;
    }
    return ();
```
- [x] remove recursion in Account.validate and inline Internals.validate
- [x] remove useless storage `Account_cairo1_helpers_class_hash` add get
class from `Kakarot.get_cairo1_helpers_class_hash()`
- [x] add in `Account.validate` the logic from Interpreter.execute (see
[here](https://github.com/ClementWalter/kakarot/blob/af697701bd545e1d60991464d15a7880e7ddc687/src/kakarot/interpreter.cairo#L878
))
- [x] fix unit tests
- [x] update all python utils to have the relayer send the txs
- [x] update [ef-tests](https://github.com/kkrt-labs/ef-tests) / remove
Account_cairo1_helpers_class_hash storage / ensure nonce is not
increased anymore (see
[here](#956 (comment)))


Snippet to send a tx with relayer in python

```python
#%% Imports
import os
import random
os.environ["STARKNET_NETWORK"] = "katana"

from eth_account import Account as EvmAccount

from kakarot_scripts.utils.kakarot import *
from kakarot_scripts.utils.starknet import *
from kakarot_scripts.constants import RPC_CLIENT, NETWORK, DEFAULT_GAS_PRICE
from tests.utils.helpers import pack_calldata, rlp_encode_signed_data
from tests.utils.uint256 import int_to_uint256

#%% Get accounts
relayer = await get_starknet_account()
eoa = await get_eoa()

# %% Send tx
current_timestamp = (await RPC_CLIENT.get_block("latest")).timestamp
nonce = await eoa.get_nonce()
outside_execution = {
    "caller": int.from_bytes(b"ANY_CALLER", "big"),
    "nonce": nonce,
    "execute_after": current_timestamp - 60 * 60,
    "execute_before": current_timestamp + 60 * 60,
}
data_len = 130_000
random.seed(data_len)
typed_transaction = TypedTransaction.from_dict({
    "type": 0x2,
    "chainId": NETWORK["chain_id"],
    "nonce": nonce,
    "gas": 2_000_000,
    "maxPriorityFeePerGas": 1,
    "maxFeePerGas": DEFAULT_GAS_PRICE,
    "to": None,
    "value": 0,
    "data": os.urandom(data_len),
}).as_dict()
evm_tx = EvmAccount.sign_transaction(
    typed_transaction,
    hex(eoa.signer.private_key),
)
encoded_unsigned_tx = rlp_encode_signed_data(typed_transaction)
packed_encoded_unsigned_tx = pack_calldata(bytes(encoded_unsigned_tx))

response = await get_contract("account_contract", address=eoa.address, provider=relayer).functions["execute_from_outside"].invoke_v1(
    outside_execution=outside_execution,
    call_array=[{
        "to": 0xDEAD,
        "selector": 0xDEAD,
        "data_offset": 0,
        "data_len": len(packed_encoded_unsigned_tx),
    }],
    calldata=list(packed_encoded_unsigned_tx),
    signature=[
        *int_to_uint256(evm_tx.r),
        *int_to_uint256(evm_tx.s),
        evm_tx.v,
    ],
    max_fee=int(1e18)
)
```

Resolves: #1240

<!-- Reviewable:start -->
- - -
This change is [<img src="https://reviewable.io/review_button.svg"
height="34" align="absmiddle"
alt="Reviewable"/>](https://reviewable.io/reviews/kkrt-labs/kakarot/1246)
<!-- Reviewable:end -->

---------

Co-authored-by: Oba <[email protected]>
  • Loading branch information
ClementWalter and obatirou authored Jul 17, 2024
1 parent 7d3e00e commit 47c5ba1
Show file tree
Hide file tree
Showing 29 changed files with 799 additions and 851 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ jobs:
- name: Checkout ef-tests
uses: actions/checkout@v4
with:
repository: kkrt-labs/ef-tests
ref: v0.2.2
repository: obatirou/ef-tests
ref: oba/execute-from-outside-ef-tests
- name: Checkout local skip file
uses: actions/checkout@v4
with:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/trunk-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ jobs:
- name: Checkout
uses: actions/checkout@v3

- name: Set up Python 3.10
- name: Set up Python 3.10.14
uses: actions/setup-python@v4
with:
python-version: 3.10.14
cache: pip
- run: pip install sympy==1.11.1 cairo-lang==0.13.1
- run: pip install cairo-lang==0.13.1 sympy==1.11.1

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
Expand Down
2 changes: 1 addition & 1 deletion deployments/kakarot-staging/declarations.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
"replace_class": "0xa187318c5e79b010cf45975f589f0a8d441fadde5b1e7ccad46501568437b5",
"Counter": "0x2abf5b9916d3c6ae6000ab239bf5aba8b40d9a1750ffc54b6d281ac83137382",
"MockPragmaOracle": "0x675f00328ff84f127d71b179b3f3a3a06ce8432054770cddd5729c8d62866da"
}
}
2 changes: 1 addition & 1 deletion deployments/kakarot-staging/deployments.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@
"tx": "0x27f878dbcff30f3d0a031bd5af2af6feb269f5e41df545b92f2ef875bcd385f",
"artifact": "build/ssj/contracts_MockPragmaOracle"
}
}
}
4 changes: 2 additions & 2 deletions kakarot_scripts/ef_tests/fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

import requests

EF_TESTS_TAG = "v13.2"
EF_TESTS_TAG = "v13.3-kkrt-1"
EF_TESTS_URL = (
f"https://github.com/ethereum/tests/archive/refs/tags/{EF_TESTS_TAG}.tar.gz"
f"https://github.com/kkrt-labs/tests/archive/refs/tags/{EF_TESTS_TAG}.tar.gz"
)
EF_TESTS_DIR = Path("tests") / "ef_tests" / "test_data" / EF_TESTS_TAG
EF_TESTS_PARSED_DIR = Path("tests") / "ef_tests" / "test_data" / "parsed"
Expand Down
74 changes: 42 additions & 32 deletions kakarot_scripts/utils/kakarot.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
from hexbytes import HexBytes
from starknet_py.net.account.account import Account
from starknet_py.net.client_errors import ClientError
from starknet_py.net.client_models import Call
from starknet_py.net.models.transaction import InvokeV1
from starknet_py.net.signer.stark_curve_signer import KeyPair
from starkware.starknet.public.abi import starknet_keccak
from web3 import Web3
Expand All @@ -44,6 +42,7 @@
from kakarot_scripts.utils.starknet import get_balance
from kakarot_scripts.utils.starknet import get_contract as _get_starknet_contract
from kakarot_scripts.utils.starknet import get_deployments as _get_starknet_deployments
from kakarot_scripts.utils.starknet import get_starknet_account
from kakarot_scripts.utils.starknet import invoke as _invoke_starknet
from kakarot_scripts.utils.starknet import wait_for_transaction
from tests.utils.constants import TRANSACTION_GAS_LIMIT
Expand Down Expand Up @@ -411,7 +410,13 @@ async def eth_send_transaction(
evm_account.signer.public_key.to_checksum_address()
)
else:
nonce = await evm_account.get_nonce()
nonce = (
await (
_get_starknet_contract("account_contract", address=evm_account.address)
.functions["get_nonce"]
.call()
)
).nonce

payload = {
"type": 0x2,
Expand Down Expand Up @@ -447,7 +452,6 @@ async def eth_send_transaction(
evm_tx.s,
evm_tx.v,
packed_encoded_unsigned_tx,
evm_account,
max_fee,
)

Expand All @@ -458,38 +462,44 @@ async def send_starknet_transaction(
signature_s: int,
signature_v: int,
packed_encoded_unsigned_tx: List[int],
caller_eoa: Optional[Account] = None,
max_fee: Optional[int] = None,
):
prepared_invoke = await evm_account._prepare_invoke(
calls=[
Call(
to_addr=0xDEAD, # unused in current EOA implementation
selector=0xDEAD, # unused in current EOA implementation
calldata=packed_encoded_unsigned_tx,
)
],
max_fee=int(5e17) if max_fee is None else max_fee,
)
# We need to reconstruct the prepared_invoke with the new signature
# And Invoke.signature is Frozen
prepared_invoke = InvokeV1(
version=prepared_invoke.version,
max_fee=prepared_invoke.max_fee,
signature=[
*int_to_uint256(signature_r),
*int_to_uint256(signature_s),
signature_v,
],
nonce=prepared_invoke.nonce,
sender_address=prepared_invoke.sender_address,
calldata=prepared_invoke.calldata,
relayer = await get_starknet_account()
current_timestamp = (await RPC_CLIENT.get_block("latest")).timestamp
outside_execution = {
"caller": int.from_bytes(b"ANY_CALLER", "big"),
"nonce": 0, # not used in Kakarot
"execute_after": current_timestamp - 60 * 60,
"execute_before": current_timestamp + 60 * 60,
}
max_fee = int(5e17) if max_fee in [None, 0] else max_fee
response = (
await _get_starknet_contract(
"account_contract", address=evm_account.address, provider=relayer
)
.functions["execute_from_outside"]
.invoke_v1(
outside_execution=outside_execution,
call_array=[
{
"to": 0xDEAD,
"selector": 0xDEAD,
"data_offset": 0,
"data_len": len(packed_encoded_unsigned_tx),
}
],
calldata=list(packed_encoded_unsigned_tx),
signature=[
*int_to_uint256(signature_r),
*int_to_uint256(signature_s),
signature_v,
],
max_fee=max_fee,
)
)

response = await evm_account.client.send_transaction(prepared_invoke)

await wait_for_transaction(tx_hash=response.transaction_hash)
receipt = await RPC_CLIENT.get_transaction_receipt(response.transaction_hash)
await wait_for_transaction(tx_hash=response.hash)
receipt = await RPC_CLIENT.get_transaction_receipt(response.hash)
transaction_events = [
event
for event in receipt.events
Expand Down
10 changes: 5 additions & 5 deletions kakarot_scripts/utils/l1.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ def l1_contract_exists(address: HexBytes) -> bool:
return False


async def deploy_on_l1(
def deploy_on_l1(
contract_app: str, contract_name: str, *args, **kwargs
) -> Web3Contract:
logger.info(f"⏳ Deploying {contract_name}")
caller_eoa = kwargs.pop("caller_eoa", None)
contract = get_l1_contract(contract_app, contract_name)
value = kwargs.pop("value", 0)
receipt, response, success, gas_used = await send_l1_transaction(
receipt, response, success, gas_used = send_l1_transaction(
to=0,
gas=int(TRANSACTION_GAS_LIMIT),
data=contract.constructor(*args, **kwargs).data_in_transaction,
Expand Down Expand Up @@ -117,7 +117,7 @@ def get_l1_contract(
return contract


async def send_l1_transaction(
def send_l1_transaction(
to: Union[int, str],
data: Union[str, bytes],
gas: int = 21_000,
Expand Down Expand Up @@ -161,7 +161,7 @@ async def send_l1_transaction(
def _wrap_web3(fun: str, caller_eoa_: Optional[EvmAccount] = None):
"""Wrap a contract function call with the WEB3 provider."""

async def _wrapper(self, *args, **kwargs):
def _wrapper(self, *args, **kwargs):
abi = self.get_function_by_name(fun).abi
gas_price = kwargs.pop("gas_price", DEFAULT_GAS_PRICE)
gas_limit = kwargs.pop("gas_limit", TRANSACTION_GAS_LIMIT)
Expand Down Expand Up @@ -196,7 +196,7 @@ async def _wrapper(self, *args, **kwargs):
return normalized[0] if len(normalized) == 1 else normalized

logger.info(f"⏳ Executing {fun} at address {self.address}")
receipt, response, success, gas_used = await send_l1_transaction(
receipt, response, success, gas_used = send_l1_transaction(
to=self.address,
value=value,
gas=gas_limit,
Expand Down
8 changes: 7 additions & 1 deletion src/backend/starknet.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ from starkware.cairo.common.cairo_builtins import HashBuiltin
from starkware.cairo.common.dict_access import DictAccess
from starkware.cairo.common.uint256 import Uint256
from starkware.cairo.common.math import unsigned_div_rem
from starkware.cairo.common.math_cmp import is_le
from starkware.cairo.common.memset import memset
from starkware.starknet.common.syscalls import (
emit_event,
Expand Down Expand Up @@ -241,8 +242,13 @@ namespace Internals {
}

let event: model.Event = [events];
// See 300 max data_len for events
// https://github.com/starkware-libs/blockifier/blob/9bfb3d4c8bf1b68a0c744d1249b32747c75a4d87/crates/blockifier/resources/versioned_constants.json
// The whole data_len should be less than 300
tempvar data_len = is_le(event.data_len, 300) * (event.data_len - 300) + 300;

emit_event(
keys_len=event.topics_len, keys=event.topics, data_len=event.data_len, data=event.data
keys_len=event.topics_len, keys=event.topics, data_len=data_len, data=event.data
);

_emit_events(events_len - 1, events + model.Event.SIZE);
Expand Down
Loading

0 comments on commit 47c5ba1

Please sign in to comment.