Skip to content

Commit

Permalink
Add L1 to L2 message mock endpoint (0xSpaceShard#365)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikiw committed Dec 15, 2022
1 parent d7a07be commit dfd1ccb
Show file tree
Hide file tree
Showing 7 changed files with 360 additions and 10 deletions.
53 changes: 53 additions & 0 deletions page/docs/guide/postman.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,59 @@ constructor(MockStarknetMessaging mockStarknetMessaging_) public {
}
```

### Postman - l1 to l2 mock endpoint

Sending mock transactions from L1 to L2 without the need for running L1. Deployed L2 contract address `l2_contract_address` and `entry_point_selector` must be valid otherwise new block will not be created.

Normally `nonce` is calculated by l1 StarknetContract and it's used in L1 and L2. In this case, we need to provide it manually.


```
POST /postman/send_message_to_l2
```

Request:
```
{
"l2_contract_address":"0x00285ddb7e5c777b310d806b9b2a0f7c7ba0a41f12b420219209d97a3b7f25b2",
"entry_point_selector":"0xC73F681176FC7B3F9693986FD7B14581E8D540519E27400E88B8713932BE01",
"l1_contract_address":"0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
"payload":[
"0x1",
"0x2"
],
"nonce":"0x0"
}
```

Response:
```
{'transaction_hash': 1283711137402474683298514877824575801113282594306182191973277342794215646143}
```

### Postman - l2 to l1 mock endpoint

Sending mock transactions from L2 to L1.
Deployed L2 contract address `l2_contract_address` and `l1_contract_address` must be valid.

```
POST /postman/consume_message_from_l2
```

Request:
```
{
"l2_contract_address": "0x00285ddb7e5c777b310d806b9b2a0f7c7ba0a41f12b420219209d97a3b7f25b2",
"l1_contract_address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
"payload": ["0x0", "0x1", "0x3e8"],
}
```

Response:
```
{'message_hash': '0xae14f241131b524ac8d043d9cb4934253ac5c5589afef19f0d761816a9c7e26d'}
```

## Dumping

To preserve your Devnet instance for future use, there are several options:
Expand Down
18 changes: 11 additions & 7 deletions starknet_devnet/blueprints/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@

from starknet_devnet.fee_token import FeeToken
from starknet_devnet.state import state
from starknet_devnet.util import StarknetDevnetException, check_valid_dump_path
from starknet_devnet.util import (
StarknetDevnetException,
check_valid_dump_path,
custom_int,
)

base = Blueprint("base", __name__)

Expand Down Expand Up @@ -44,20 +48,20 @@ def extract_positive(request_json, prop_name: str):
return value


def extract_hex_string(request_json, prop_name: str) -> int:
"""Parse value from hex string to int"""
def hex_converter(request_json, prop_name: str, convert=custom_int) -> int:
"""Parse value from hex string to int, or values from hex strings to ints"""
value = request_json.get(prop_name)
if value is None:
raise StarknetDevnetException(
code=StarkErrorCode.MALFORMED_REQUEST,
status_code=400,
message=f"{prop_name} value must be provided.",
message=f"{prop_name} value or values must be provided.",
)

try:
return int(value, 16)
return convert(value)
except (ValueError, TypeError) as error:
message = f"{prop_name} value must be a hex string."
message = f"{prop_name} value or values must be a hex string."
raise StarknetDevnetException(
code=StarkErrorCode.MALFORMED_REQUEST,
status_code=400,
Expand Down Expand Up @@ -172,7 +176,7 @@ async def mint():
"""Mint token and transfer to the provided address"""
request_json = request.json or {}

address = extract_hex_string(request_json, "address")
address = hex_converter(request_json, "address")
amount = extract_positive(request_json, "amount")
is_lite = request_json.get("lite", False)

Expand Down
48 changes: 47 additions & 1 deletion starknet_devnet/blueprints/postman.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
import json

from flask import Blueprint, jsonify, request
from starkware.starknet.business_logic.transaction.objects import InternalL1Handler
from starkware.starknet.definitions.error_codes import StarknetErrorCode
from starkware.starkware_utils.error_handling import StarkErrorCode

from starknet_devnet.blueprints.base import hex_converter
from starknet_devnet.state import state
from starknet_devnet.util import StarknetDevnetException
from starknet_devnet.util import StarknetDevnetException, to_int_array

postman = Blueprint("postman", __name__, url_prefix="/postman")

Expand Down Expand Up @@ -55,3 +58,46 @@ async def flush():

result_dict = await state.starknet_wrapper.postman_flush()
return jsonify(result_dict)


@postman.route("/send_message_to_l2", methods=["POST"])
async def send_message_to_l2():
"""L1 to L2 message mock endpoint"""
request_json = request.json or {}

# Generate transactions
transaction = InternalL1Handler.create(
contract_address=hex_converter(request_json, "l2_contract_address"),
entry_point_selector=hex_converter(request_json, "entry_point_selector"),
calldata=[
hex_converter(request_json, "l1_contract_address"),
*hex_converter(request_json, "payload", to_int_array),
],
nonce=hex_converter(request_json, "nonce"),
chain_id=state.starknet_wrapper.get_state().general_config.chain_id.value,
)

result = await state.starknet_wrapper.mock_message_to_l2(transaction)
return jsonify({"transaction_hash": result})


@postman.route("/consume_message_from_l2", methods=["POST"])
async def consume_message_from_l2():
"""L2 to L1 message mock endpoint"""
request_json = request.json or {}

from_address = hex_converter(request_json, "l2_contract_address")
to_address = hex_converter(request_json, "l1_contract_address")
payload = hex_converter(request_json, "payload", to_int_array)

try:
result = await state.starknet_wrapper.consume_message_from_l2(
from_address, to_address, payload
)
return jsonify({"message_hash": result})
except AssertionError as err:
raise StarknetDevnetException(
code=StarknetErrorCode.L1_TO_L2_MESSAGE_ZEROED_COUNTER,
message="Message is fully consumed or does not exist.",
status_code=500,
) from err
33 changes: 33 additions & 0 deletions starknet_devnet/starknet_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
DeployAccount,
InvokeFunction,
)
from starkware.starknet.services.api.messages import StarknetMessageToL1
from starkware.starknet.testing.objects import FunctionInvocation
from starkware.starknet.testing.starknet import Starknet
from starkware.starknet.third_party.open_zeppelin.starknet_contracts import (
Expand Down Expand Up @@ -547,6 +548,38 @@ async def load_messaging_contract_in_l1(
self.starknet, network_url, contract_address, network_id
)

async def consume_message_from_l2(
self, from_address: int, to_address: int, payload: List[int]
) -> str:
"""
Mocks the L1 contract function consumeMessageFromL2.
"""
state = self.get_state()

starknet_message = StarknetMessageToL1(
from_address=from_address,
to_address=to_address,
payload=payload,
)
message_hash = starknet_message.get_hash()
state.consume_message_hash(message_hash=message_hash)
return message_hash

async def mock_message_to_l2(self, transaction: InternalL1Handler) -> dict:
"""Handles L1 to L2 message mock endpoint"""

state = self.get_state()

# Execute transactions inside StarknetWrapper
async with self.__get_transaction_handler() as tx_handler:
tx_handler.internal_tx = transaction
tx_handler.execution_info = await state.execute_tx(tx_handler.internal_tx)
tx_handler.internal_calls = (
tx_handler.execution_info.call_info.internal_calls
)

return transaction.hash_value

async def postman_flush(self) -> dict:
"""Handles all pending L1 <> L2 messages and sends them to the other layer."""

Expand Down
5 changes: 5 additions & 0 deletions starknet_devnet/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ def fixed_length_hex(arg: int) -> str:
return f"0x{arg:064x}"


def to_int_array(values: List[str]) -> List[int]:
"""Convert to List of ints"""
return [int(numeric, 16) for numeric in values]


@dataclass
class Uint256:
"""Abstraction of Uint256 type"""
Expand Down
4 changes: 2 additions & 2 deletions test/test_fee_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,15 @@ def test_wrong_mint_address_format():
resp = mint_client({"amount": 10, "address": "invalid_address"})

assert resp.status_code == 400
assert resp.json["message"] == "address value must be a hex string."
assert resp.json["message"] == "address value or values must be a hex string."


def test_missing_mint_address():
"""Assert failure if mint address missing"""
resp = mint_client({"amount": 10})

assert resp.status_code == 400
assert resp.json["message"] == "address value must be provided."
assert resp.json["message"] == "address value or values must be provided."


@pytest.mark.fee_token
Expand Down
Loading

0 comments on commit dfd1ccb

Please sign in to comment.