Skip to content
This repository has been archived by the owner on Jan 9, 2025. It is now read-only.

Commit

Permalink
refactor: use CairoLib for messages
Browse files Browse the repository at this point in the history
  • Loading branch information
enitrat committed Jun 12, 2024
1 parent a9b40e7 commit fb0ab7b
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 30 deletions.
17 changes: 17 additions & 0 deletions solidity_contracts/src/CairoPrecompiles/CairoLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity >=0.7.0 <0.9.0;
library CairoLib {
/// @dev The Cairo precompile contract's address.
address constant CAIRO_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000075001;
address constant CAIRO_MESSAGING_ADDRESS = 0x0000000000000000000000000000000000075002;

/// @notice Performs a low-level call to a Cairo contract deployed on the Starknet appchain.
/// @dev Used with intent to modify the state of the Cairo contract.
Expand Down Expand Up @@ -156,4 +157,20 @@ library CairoLib {
return libraryCall(classHash, functionSelector, data);
}

/// @notice Performs a low-level call to send a message from the Kakarot to the Ethereum network.
/// @param toAddress The address of the Ethereum contract to send the message to.
/// @param data The data to send to the Ethereum contract.
function sendMessageToL1(
address toAddress,
uint248[] memory data
) internal {
bytes memory callData = abi.encode(
toAddress,
data
);

(bool success, ) = CAIRO_MESSAGING_ADDRESS.call(callData);
require(success, "CairoLib: send_message_to_l1 failed");
}

}
6 changes: 3 additions & 3 deletions solidity_contracts/src/L1L2Messaging/MessageConsumerTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ contract MessageConsumerTest {

//
IStarknetMessaging private _starknetMessaging;
uint256 private _cairoMessaging;
uint256 private _kakarotAddress;

/**
@notice Constructor.
Expand All @@ -27,7 +27,7 @@ contract MessageConsumerTest {
*/
constructor(address snMessaging, uint256 cairoMessaging) {
_starknetMessaging = IStarknetMessaging(snMessaging);
_cairoMessaging = cairoMessaging;
_kakarotAddress = _kakarotAddress;
}

/**
Expand Down Expand Up @@ -93,7 +93,7 @@ contract MessageConsumerTest {
external
{
// Will revert if the message is not consumable.
bytes32 msghash = _starknetMessaging.consumeMessageFromL2(_cairoMessaging, payload);
bytes32 msghash = _starknetMessaging.consumeMessageFromL2(_kakarotAddress, payload);

// The previous call returns the message hash (bytes32)
// that can be used if necessary.
Expand Down
12 changes: 8 additions & 4 deletions solidity_contracts/src/L1L2Messaging/MessageSenderL2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ contract MessageSenderL2 {
// @dev Uses the Cairo Precompiles mechanism to invoke a Cairo contract that uses the Starknet
// messaging system.
function sendMessageToL1(address to, uint128 value) external {
uint256[] memory data = new uint256[](2);
data[0] = uint256(uint160(to));
data[1] = uint256(value);
messagingContract.callContract(SEND_MESSAGE_VALUE, data);
// uint256[] memory data = new uint256[](2);
// data[0] = uint256(uint160(to));
// data[1] = uint256(value);
// messagingContract.callContract(SEND_MESSAGE_VALUE, data);

uint248[] memory data = new uint248[](1);
data[0] = uint248(value);
CairoLib.sendMessageToL1(to, data);
}
}
45 changes: 44 additions & 1 deletion src/kakarot/precompiles/kakarot_precompiles.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin
from starkware.cairo.common.math_cmp import is_le
from starkware.cairo.common.alloc import alloc
from starkware.cairo.common.bool import TRUE, FALSE
from starkware.starknet.common.syscalls import call_contract, library_call
from starkware.starknet.common.messages import send_message_to_l1

from kakarot.errors import Errors
from kakarot.storages import Kakarot_authorized_cairo_precompiles_callers
Expand All @@ -14,6 +16,8 @@ const LIBRARY_CALL_SOLIDITY_SELECTOR = 0x5a9af197;

const CAIRO_PRECOMPILE_GAS = 10000;

const CAIRO_MESSAGE_GAS = 5000;

namespace KakarotPrecompiles {
func is_caller_whitelisted{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
caller_address: felt
Expand Down Expand Up @@ -42,7 +46,7 @@ namespace KakarotPrecompiles {
}

// Input is formatted as:
// [selectore: bytes4][starknet_address: bytes32][starknet_selector:bytes32][data_offset: bytes32][data_len: bytes32][data: bytes[]]
// [selector: bytes4][starknet_address: bytes32][starknet_selector:bytes32][data_offset: bytes32][data_len: bytes32][data: bytes[]]

// Load selector from first 4 bytes of input.
let selector = Helpers.bytes4_to_felt(input);
Expand Down Expand Up @@ -89,4 +93,43 @@ namespace KakarotPrecompiles {
let (revert_reason_len, revert_reason) = Errors.invalidCairoSelector();
return (revert_reason_len, revert_reason, CAIRO_PRECOMPILE_GAS, 1);
}

func cairo_message{
syscall_ptr: felt*,
pedersen_ptr: HashBuiltin*,
range_check_ptr,
bitwise_ptr: BitwiseBuiltin*,
}(evm_address: felt, input_len: felt, input: felt*) -> (
output_len: felt, output: felt*, gas_used: felt, reverted: felt
) {
alloc_locals;

// Input must be at least 3*32 bytes long.
let is_input_invalid = is_le(input_len, 95);
if (is_input_invalid != 0) {
let (revert_reason_len, revert_reason) = Errors.outOfBoundsRead();
return (revert_reason_len, revert_reason, CAIRO_MESSAGE_GAS, TRUE);
}

// Input is formatted as:
// [to_address: address][data_offset: uint256][data_len: uint256][data: uint248[]]

// Load target EVM address
let target_address = Helpers.bytes32_to_felt(input);

let data_offset_ptr = input + 32;
let data_offset = Helpers.bytes32_to_felt(data_offset_ptr);
let data_len_ptr = input + data_offset;

// Load input data by packing all
// If the input data is larger than the size of a felt, it will wrap around the felt size.
let data_words_len = Helpers.bytes32_to_felt(data_len_ptr);
let data_bytes_len = data_words_len * 32;
let data_ptr = data_len_ptr + 32;
let (data_len, data) = Helpers.load_256_bits_array(data_bytes_len, data_ptr);

send_message_to_l1(target_address, data_words_len, data);
let (output) = alloc();
return (0, output, CAIRO_PRECOMPILE_GAS, FALSE);
}
}
39 changes: 27 additions & 12 deletions src/kakarot/precompiles/precompiles.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const FIRST_ROLLUP_PRECOMPILE_ADDRESS = 0x100;
const LAST_ROLLUP_PRECOMPILE_ADDRESS = 0x100;
const EXEC_PRECOMPILE_SELECTOR = 0x01e3e7ac032066525c37d0791c3c0f5fbb1c17f1cb6fe00afc206faa3fbd18e1;
const FIRST_KAKAROT_PRECOMPILE_ADDRESS = 0x75001;
const LAST_KAKAROT_PRECOMPILE_ADDRESS = 0x75001;
const LAST_KAKAROT_PRECOMPILE_ADDRESS = 0x75002;

// @title Precompile related functions.
namespace Precompiles {
Expand Down Expand Up @@ -85,20 +85,11 @@ namespace Precompiles {
jmp rollup_precompile if is_rollup_precompile_ != 0;

let is_kakarot_precompile_ = is_kakarot_precompile(evm_address);
let is_whitelisted = KakarotPrecompiles.is_caller_whitelisted(caller_address);
tempvar is_kakarot_and_whitelisted = is_kakarot_precompile_ * is_whitelisted;
tempvar syscall_ptr = syscall_ptr;
tempvar pedersen_ptr = pedersen_ptr;
tempvar range_check_ptr = range_check_ptr;
jmp kakarot_precompile if is_kakarot_and_whitelisted != 0;

// Prepare arguments if none of the above conditions are met
[ap] = syscall_ptr, ap++;
[ap] = pedersen_ptr, ap++;
[ap] = range_check_ptr, ap++;
[ap] = bitwise_ptr, ap++;
call unauthorized_precompile;
ret;
jmp pre_kakarot_precompile if is_kakarot_precompile_ != 0;
jmp unauthorized_call;

eth_precompile:
tempvar index = evm_address;
Expand All @@ -110,6 +101,19 @@ namespace Precompiles {
);
jmp call_precompile;

pre_kakarot_precompile:
let is_cairo_contract_call = Helpers.is_zero(
FIRST_KAKAROT_PRECOMPILE_ADDRESS - evm_address
);
let is_whitelisted = KakarotPrecompiles.is_caller_whitelisted(caller_address);
tempvar is_authorized = is_cairo_contract_call * is_whitelisted;
tempvar syscall_ptr = syscall_ptr;
tempvar pedersen_ptr = pedersen_ptr;
tempvar range_check_ptr = range_check_ptr;
jmp kakarot_precompile if is_authorized != 0;
jmp unauthorized_call if is_cairo_contract_call != 0;
jmp kakarot_precompile;

kakarot_precompile:
let rollup_precompiles_count = LAST_ROLLUP_PRECOMPILE_ADDRESS -
FIRST_ROLLUP_PRECOMPILE_ADDRESS + 1;
Expand All @@ -118,6 +122,15 @@ namespace Precompiles {
);
jmp call_precompile;

unauthorized_call:
// Prepare arguments if none of the above conditions are met
[ap] = syscall_ptr, ap++;
[ap] = pedersen_ptr, ap++;
[ap] = range_check_ptr, ap++;
[ap] = bitwise_ptr, ap++;
call unauthorized_precompile;
ret;

call_precompile:
// Compute the corresponding offset in the jump table:
// count 1 for "next line" and 3 steps per index: call, precompile, ret
Expand Down Expand Up @@ -165,6 +178,8 @@ namespace Precompiles {
// the amount of rollup precompiles.
call KakarotPrecompiles.cairo_precompile; // offset 0x0c: precompile 0x75001
ret;
call KakarotPrecompiles.cairo_message; // offset 0x0d: precompile 0x75002
ret;
}

// @notice A placeholder for attempts to call a precompile without permissions
Expand Down
18 changes: 9 additions & 9 deletions tests/end_to_end/L1L2Messaging/test_messaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
get_l1_contract,
l1_contract_exists,
)
from kakarot_scripts.utils.starknet import get_deployments, invoke
from kakarot_scripts.utils.starknet import get_deployments


@pytest.fixture(scope="session")
Expand Down Expand Up @@ -40,23 +40,23 @@ async def message_sender_l2(deploy_contract, owner):
caller_eoa=owner.starknet_contract,
)

await invoke(
"kakarot",
"set_authorized_cairo_precompile_caller",
int(message_sender.address, 16),
True,
)
# await invoke(
# "kakarot",
# "set_authorized_cairo_precompile_caller",
# int(message_sender.address, 16),
# True,
# )
return message_sender


@pytest.fixture(scope="session")
async def message_consumer_test(deploy_l1_contract, sn_messaging_local):
cairo_messaging_address = get_deployments()["CairoMessaging"]["address"]
kakarot_address = get_deployments()["kakarot"]["address"]
return await deploy_l1_contract(
"L1L2Messaging",
"MessageConsumerTest",
sn_messaging_local.address,
cairo_messaging_address,
kakarot_address,
)


Expand Down
20 changes: 20 additions & 0 deletions tests/src/kakarot/precompiles/test_precompiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,26 @@ def test__cairo_precompiles(

return

class TestKakarotMessaging:
def test__cairo_message(self, cairo_run):
input_data = bytes.fromhex(
f"{0xc0de:064x}"
+ f"{0x40:064x}" # data_offset
+ f"{0x01:064x}" # data_len: 1 word
+ f"{0x2a:064x}" # data: uint256(42)
)

address = 0x75002
return_data, reverted, gas_used = cairo_run(
"test__precompiles_run",
address=address,
input=input_data,
caller_address=0,
)
SyscallHandler.mock_send_message_to_l1.assert_any_call(
to_address=0xC0DE, payload=[0x2A]
)

class TestIsPrecompile:
@pytest.mark.parametrize(
"address", range(0, LAST_ETHEREUM_PRECOMPILE_ADDRESS + 2)
Expand Down
2 changes: 1 addition & 1 deletion tests/utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
FIRST_ROLLUP_PRECOMPILE_ADDRESS = 0x100
LAST_ROLLUP_PRECOMPILE_ADDRESS = 0x100
FIRST_KAKAROT_PRECOMPILE_ADDRESS = 0x75001
LAST_KAKAROT_PRECOMPILE_ADDRESS = 0x75001
LAST_KAKAROT_PRECOMPILE_ADDRESS = 0x75002

CAIRO_PRECOMPILE_GAS = 10000

Expand Down
19 changes: 19 additions & 0 deletions tests/utils/syscall_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ class SyscallHandler:
mock_storage = mock.MagicMock()
mock_event = mock.MagicMock()
mock_replace_class = mock.MagicMock()
mock_send_message_to_l1 = mock.MagicMock()

# Patch the keccak library call to return the keccak of the input data.
# We need to reconstruct the raw bytes from the Cairo-style keccak calldata.
Expand Down Expand Up @@ -469,6 +470,24 @@ def library_call(self, segments, syscall_ptr):
segments.write_arg(retdata_segment, retdata)
segments.write_arg(syscall_ptr + 5, [len(retdata), retdata_segment])

def send_message_to_l1(self, segments, syscall_ptr):
"""
Record the send_message call in the internal mock object.
Syscall structure is:
struct SendMessageToL1SysCall {
selector: felt,
to_address: felt,
payload_size: felt,
payload_ptr: felt*,
}
"""
to_address = segments.memory[syscall_ptr + 1]
payload_size = segments.memory[syscall_ptr + 2]
payload_ptr = segments.memory[syscall_ptr + 3]
payload = [segments.memory[payload_ptr + i] for i in range(payload_size)]
self.mock_send_message_to_l1(to_address=to_address, payload=payload)

@classmethod
@contextmanager
def patch(cls, target: str, *args, value: Optional[Union[callable, int]] = None):
Expand Down

0 comments on commit fb0ab7b

Please sign in to comment.