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

Improve DevnetClient docs #1415

Merged
merged 8 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
6 changes: 6 additions & 0 deletions docs/devnet_utils.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Devnet Utils
============

.. toctree::

devnet_utils/mocking_interaction_with_l1
65 changes: 65 additions & 0 deletions docs/devnet_utils/mocking_interaction_with_l1.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
Mocking interaction with L1
===========================

Abstract
--------

In order to test interaction with L1 contracts, devnet client provides a way to mock the L1 interaction.
Before taking a look at the examples, please get familiar with the `devnet postman docs <https://0xspaceshard.github.io/starknet-devnet-rs/docs/postman>`_ and messaging mechanism:

- `Writing messaging contracts <https://book.cairo-lang.org/ch16-04-L1-L2-messaging.html>`_
- `Mechanism overview <https://docs.starknet.io/architecture-and-concepts/network-architecture/messaging-mechanism/>`_
- `StarkGate example <https://docs.starknet.io/architecture-and-concepts/network-architecture/messaging-mechanism/>`_

L1 network setup
----------------

First of all you should deploy `messaging contract <https://github.com/0xSpaceShard/starknet-devnet-rs/blob/138120b355c44ae60269167b326d1a267f7af0a8/contracts/l1-l2-messaging/solidity/src/MockStarknetMessaging.sol>`_
on ethereum network or load the existing one.

.. codesnippet:: ../../starknet_py/tests/e2e/docs/devnet_utils/test_l1_integration.py
:language: python
:dedent: 4


L2 -> L1
--------

Deploying L2 interaction contract
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Interaction with L1 is done by sending a message using `send_message_to_l1_syscall` function.
So in order to test it, you need to deploy a contract that has this functionality.
Example contract: `l1_l2.cairo <https://github.com/0xSpaceShard/starknet-devnet-js/blob/5069ec3397f31a408d3df2734ae40d93b42a0f7f/test/data/l1_l2.cairo>`_

.. codesnippet:: ../../starknet_py/tests/e2e/docs/devnet_utils/test_l1_integration.py
:language: python
:dedent: 4
:start-after: docs: messaging-contract-start
:end-before: docs: messaging-contract-end


Consuming message
^^^^^^^^^^^^^^^^^

After deploying the contract, you need to flush the messages to the L1 network.
And then you can consume the message on the L1 network.

.. codesnippet:: ../../starknet_py/tests/e2e/docs/devnet_utils/test_l1_integration.py
:language: python
:dedent: 4
:start-after: docs: flush-1-start
:end-before: docs: flush-1-end

L1 -> L2
--------

Sending mock transactions from L1 to L2 does not require L1 node to be running.

.. codesnippet:: ../../starknet_py/tests/e2e/docs/devnet_utils/test_l1_integration.py
:language: python
:dedent: 4
:start-after: docs: send-l2-start
:end-before: docs: send-l2-end


1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Starknet SDK for Python
account_creation
quickstart
guide
devnet_utils
api
development
migration_guide
18 changes: 18 additions & 0 deletions starknet_py/tests/e2e/client_devnet/fixtures/contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,21 @@ async def deploy_string_contract(
contract_name="MyString",
class_hash=f_string_contract_class_hash,
)


@pytest_asyncio.fixture(scope="package", name="l1_l2_contract_class_hash")
async def declare_l1_l2_contract(account) -> int:
contract = load_contract("l1_l2")
class_hash, _ = await declare_cairo1_contract(
account, contract["sierra"], contract["casm"]
)
return class_hash


@pytest_asyncio.fixture(scope="package", name="l1_l2_contract")
async def deploy_l1_l2_contract(account, l1_l2_contract_class_hash) -> Contract:
return await deploy_v1_contract(
account=account,
contract_name="l1_l2",
class_hash=l1_l2_contract_class_hash,
)
Empty file.
92 changes: 92 additions & 0 deletions starknet_py/tests/e2e/docs/devnet_utils/test_l1_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import pytest

from starknet_py.hash.selector import get_selector_from_name
from starknet_py.net.client_models import ResourceBounds


@pytest.mark.skip(reason="Test require eth node running.")
@pytest.mark.asyncio
async def test_postman_load(devnet_client, l1_l2_contract, account):
# pylint: disable=import-outside-toplevel
# pylint: disable=unused-variable

eth_account_address = 1390849295786071768276380950238675083608645509734

# docs: start
from starknet_py.devnet_utils.devnet_client import DevnetClient

client = DevnetClient(node_url="http://127.0.0.1:5050")

# docs: end
client: DevnetClient = devnet_client
# docs: start
# Deploying the messaging contract on ETH network
# e.g. anvil eth devnet https://github.com/foundry-rs/foundry/tree/master/crates/anvil

kkawula marked this conversation as resolved.
Show resolved Hide resolved
await client.postman_load(network_url="http://127.0.0.1:8545")
# docs: end

# docs: messaging-contract-start
from starknet_py.contract import Contract

# Address of your contract that is emitting messages
contract_address = "0x12345"

# docs: messaging-contract-end
contract_address = l1_l2_contract.address

# docs: messaging-contract-start
contract = await Contract.from_address(address=contract_address, provider=account)

await contract.functions["increase_balance"].invoke_v3(
user=account.address,
amount=100,
l1_resource_bounds=ResourceBounds(
max_amount=50000, max_price_per_unit=int(1e12)
),
)

# docs: messaging-contract-end
assert await contract.functions["get_balance"].call(user=account.address) == (100,)

# docs: messaging-contract-start
# Invoking function that is emitting message
await contract.functions["withdraw"].invoke_v3(
user=account.address,
amount=100,
l1_address=eth_account_address,
l1_resource_bounds=ResourceBounds(
max_amount=50000, max_price_per_unit=int(1e12)
),
)
# docs: messaging-contract-end
assert await contract.functions["get_balance"].call(user=account.address) == (0,)

# docs: flush-1-start
# Sending messages from L2 to L1.
flush_response = await client.postman_flush()

message = flush_response.messages_to_l1[0]

message_hash = await client.consume_message_from_l2(
from_address=message.from_address,
to_address=message.to_address,
payload=message.payload,
)
# docs: flush-1-end

# docs: send-l2-start
await client.send_message_to_l2(
l2_contract_address=contract_address,
entry_point_selector=get_selector_from_name("deposit"),
l1_contract_address="0xa000000000000000000000000000000000000001",
payload=[account.address, 420],
kkawula marked this conversation as resolved.
Show resolved Hide resolved
nonce="0x0",
paid_fee_on_l1="0xfffffffffff",
)

# Sending messages from L1 to L2.
flush_response = await client.postman_flush()
# docs: send-l2-end

assert await contract.functions["get_balance"].call(user=account.address) == (420,)
kkawula marked this conversation as resolved.
Show resolved Hide resolved
110 changes: 110 additions & 0 deletions starknet_py/tests/e2e/mock/contracts_v2/src/l1_l2.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//! L1 L2 messaging demo contract.
//! Rewrite in Cairo 1 of the contract from previous Devnet version:
//! https://github.com/0xSpaceShard/starknet-devnet/blob/e477aa1bbe2348ba92af2a69c32d2eef2579d863/test/contracts/cairo/l1l2.cairo
//!
//! This contract does not use interface to keep the code as simple as possible.
//!

#[starknet::contract]
mod l1_l2 {
const MESSAGE_WITHDRAW: felt252 = 0;

#[storage]
struct Storage {
// Balances (users) -> (amount).
balances: LegacyMap<felt252, felt252>,
}

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
DepositFromL1: DepositFromL1,
}

#[derive(Drop, starknet::Event)]
struct DepositFromL1 {
#[key]
user: felt252,
#[key]
amount: felt252,
}

/// Gets the balance of the `user`.
#[external(v0)]
fn get_balance(self: @ContractState, user: felt252) -> felt252 {
self.balances.read(user)
}

/// Increases the balance of the `user` for the `amount`.
#[external(v0)]
fn increase_balance(ref self: ContractState, user: felt252, amount: felt252) {
let balance = self.balances.read(user);
self.balances.write(user, balance + amount);
}

/// Withdraws the `amount` for the `user` and sends a message to `l1_address` to
/// send the funds.
#[external(v0)]
fn withdraw(ref self: ContractState, user: felt252, amount: felt252, l1_address: felt252) {
assert(amount.is_non_zero(), 'Amount must be positive');

let balance = self.balances.read(user);
assert(balance.is_non_zero(), 'Balance is already 0');

// We need u256 to make comparisons.
let balance_u: u256 = balance.into();
let amount_u: u256 = amount.into();
assert(balance_u >= amount_u, 'Balance will be negative');

let new_balance = balance - amount;

self.balances.write(user, new_balance);

let payload = array![MESSAGE_WITHDRAW, user, amount,];

starknet::send_message_to_l1_syscall(l1_address, payload.span()).unwrap();
}

/// Withdraws the `amount` for the `user` and sends a message to `l1_address` to
/// send the funds.
#[external(v0)]
fn withdraw_from_lib(
ref self: ContractState, user: felt252, amount: felt252, l1_address: felt252, message_sender_class_hash: starknet::ClassHash,
) {
assert(amount.is_non_zero(), 'Amount must be positive');

let balance = self.balances.read(user);
assert(balance.is_non_zero(), 'Balance is already 0');

// We need u256 to make comparisons.
let balance_u: u256 = balance.into();
let amount_u: u256 = amount.into();
assert(balance_u >= amount_u, 'Balance will be negative');

let new_balance = balance - amount;

self.balances.write(user, new_balance);

let calldata = array![user, amount, l1_address];

starknet::SyscallResultTrait::unwrap_syscall(
starknet::library_call_syscall(
message_sender_class_hash,
selector!("send_withdraw_message"),
calldata.span(),
)
);
}

/// Deposits the `amount` for the `user`. Can only be called by the sequencer itself,
/// after having fetched some messages from the L1.
#[l1_handler]
fn deposit(ref self: ContractState, from_address: felt252, user: felt252, amount: felt252) {
// In a real case scenario, here we would assert from_address value

let balance = self.balances.read(user);
self.balances.write(user, balance + amount);

self.emit(DepositFromL1 { user, amount });
}
}
1 change: 1 addition & 0 deletions starknet_py/tests/e2e/mock/contracts_v2/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ mod test_contract;
mod test_enum;
mod test_option;
mod token_bridge;
mod l1_l2;
Loading