Skip to content

Commit

Permalink
feat: add messaging cancellation
Browse files Browse the repository at this point in the history
  • Loading branch information
glihm committed Feb 7, 2024
1 parent e3e5b2b commit 108064b
Show file tree
Hide file tree
Showing 5 changed files with 384 additions and 22 deletions.
146 changes: 130 additions & 16 deletions src/messaging/component.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@

/// Errors.
mod errors {
const INVALID_NONCE: felt252 = 'INVALID_NONCE';
const INVALID_MESSAGE_TO_CONSUME: felt252 = 'INVALID_MESSAGE_TO_CONSUME';
const NO_MESSAGE_TO_CANCEL: felt252 = 'NO_MESSAGE_TO_CANCEL';
const CANCELLATION_NOT_REQUESTED: felt252 = 'CANCELLATION_NOT_REQUESTED';
const CANCELLATION_NOT_ALLOWED_YET: felt252 = 'CANCELLATION_NOT_ALLOWED_YET';
const CANCEL_ALLOWED_TIME_OVERFLOW: felt252 = 'CANCEL_ALLOWED_TIME_OVERFLOW';
}

/// Messaging component.
Expand All @@ -44,46 +49,81 @@ mod messaging_cpt {
use starknet::ContractAddress;
use super::errors;

type MessageHash = felt252;
type Nonce = felt252;

#[storage]
struct Storage {
/// Cancellation delay in seconds for message from Starknet to Appchain.
cancellation_delay_secs: u64,
/// Ledger of messages from Starknet to Appchain that are being cancelled.
sn_to_appc_cancellations: LegacyMap::<MessageHash, u64>,
/// The nonce for messages sent to the Appchain from Starknet.
sn_to_appc_nonce: felt252,
sn_to_appc_nonce: Nonce,
/// Ledger of messages hashes sent from Starknet to the appchain.
sn_to_appc_messages: LegacyMap::<felt252, felt252>,
/// Ledger of messages hashes registered from the appchain and a refcount
sn_to_appc_messages: LegacyMap::<MessageHash, Nonce>,
/// Ledger of messages hashes registered from the Appchain and a refcount
/// associated to it to control messages consumption.
appc_to_sn_messages: LegacyMap::<felt252, felt252>,
appc_to_sn_messages: LegacyMap::<MessageHash, felt252>,
}

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
MessageSent: MessageSent,
MessageConsumed: MessageConsumed,
MessageCancellationStarted: MessageCancellationStarted,
MessageCanceled: MessageCanceled,
}

#[derive(Drop, starknet::Event)]
struct MessageSent {
#[key]
message_hash: felt252,
message_hash: MessageHash,
#[key]
from: ContractAddress,
#[key]
to: ContractAddress,
selector: felt252,
nonce: felt252,
nonce: Nonce,
payload: Span<felt252>,
}

#[derive(Drop, starknet::Event)]
struct MessageConsumed {
#[key]
message_hash: felt252,
message_hash: MessageHash,
#[key]
from: ContractAddress,
#[key]
to: ContractAddress,
payload: Span<felt252>,
}

#[derive(Drop, starknet::Event)]
struct MessageCancellationStarted {
#[key]
message_hash: MessageHash,
#[key]
from: ContractAddress,
#[key]
to: ContractAddress,
selector: felt252,
payload: Span<felt252>,
nonce: Nonce,
}

#[derive(Drop, starknet::Event)]
struct MessageCanceled {
#[key]
message_hash: MessageHash,
#[key]
from: ContractAddress,
#[key]
to: ContractAddress,
selector: felt252,
payload: Span<felt252>,
nonce: Nonce,
}

#[embeddable_as(MessagingImpl)]
Expand All @@ -95,9 +135,10 @@ mod messaging_cpt {
to_address: ContractAddress,
selector: felt252,
payload: Span<felt252>
) -> (felt252, felt252) {
let nonce = self.sn_to_appc_nonce.read();
self.sn_to_appc_nonce.write(nonce + 1);
) -> (MessageHash, Nonce) {
// Starts by +1 to avoid 0 as a valid nonce.
let nonce = self.sn_to_appc_nonce.read() + 1;
self.sn_to_appc_nonce.write(nonce);

let message_hash = self
.compute_message_hash_sn_to_appc(nonce, to_address, selector, payload);
Expand All @@ -114,8 +155,6 @@ mod messaging_cpt {
}
);

// L1 starknet core contract stores the msg.value + 1.
// TODO: is the nonce a good thing to keep here then?
self.sn_to_appc_messages.write(message_hash, nonce);
(message_hash, nonce)
}
Expand All @@ -124,7 +163,7 @@ mod messaging_cpt {
ref self: ComponentState<TContractState>,
from_address: ContractAddress,
payload: Span<felt252>
) -> felt252 {
) -> MessageHash {
let to_address = starknet::get_caller_address();

let message_hash = self
Expand All @@ -142,12 +181,87 @@ mod messaging_cpt {

message_hash
}

fn start_message_cancellation(
ref self: ComponentState<TContractState>,
to_address: ContractAddress,
selector: felt252,
payload: Span<felt252>,
nonce: Nonce,
) -> MessageHash {
assert(nonce.is_non_zero(), errors::INVALID_NONCE);
let from = starknet::get_caller_address();

let message_hash = self
.compute_message_hash_sn_to_appc(nonce, to_address, selector, payload);

assert(
self.sn_to_appc_messages.read(message_hash) == nonce, errors::NO_MESSAGE_TO_CANCEL
);

self.sn_to_appc_cancellations.write(message_hash, starknet::get_block_timestamp());

self
.emit(
MessageCancellationStarted {
message_hash, from, to: to_address, selector, payload, nonce
}
);

return message_hash;
}

fn cancel_message(
ref self: ComponentState<TContractState>,
to_address: ContractAddress,
selector: felt252,
payload: Span<felt252>,
nonce: felt252,
) -> MessageHash {
let from = starknet::get_caller_address();

let message_hash = self
.compute_message_hash_sn_to_appc(nonce, to_address, selector, payload);

assert(
self.sn_to_appc_messages.read(message_hash) == nonce, errors::NO_MESSAGE_TO_CANCEL
);

let request_time = self.sn_to_appc_cancellations.read(message_hash);
assert(request_time.is_non_zero(), errors::CANCELLATION_NOT_REQUESTED);

let cancel_allowed_time = request_time + self.cancellation_delay_secs.read();
assert(cancel_allowed_time >= request_time, errors::CANCEL_ALLOWED_TIME_OVERFLOW);
assert(
starknet::get_block_timestamp() >= cancel_allowed_time,
errors::CANCELLATION_NOT_ALLOWED_YET
);

self
.emit(
MessageCanceled { message_hash, from, to: to_address, selector, payload, nonce }
);

// Once canceled, no more operation can be done on this message.
self.sn_to_appc_messages.write(message_hash, 0);

return message_hash;
}
}

#[generate_trait]
impl InternalImpl<
TContractState, +HasComponent<TContractState>, +Drop<TContractState>
> of InternalTrait<TContractState> {
/// Initializes the messaging component.
///
/// # Arguments
///
/// * `cancellation_delay_secs` - The delay in seconds for message cancellation window.
fn initialize(ref self: ComponentState<TContractState>, cancellation_delay_secs: u64) {
self.cancellation_delay_secs.write(cancellation_delay_secs);
}

/// Computes the hash of a message that is sent from Starknet to the Appchain.
///
/// <https://github.com/starkware-libs/cairo-lang/blob/caba294d82eeeccc3d86a158adb8ba209bf2d8fc/src/starkware/starknet/solidity/StarknetMessaging.sol#L88>
Expand All @@ -165,11 +279,11 @@ mod messaging_cpt {
/// The hash of the message from Starknet to the Appchain.
fn compute_message_hash_sn_to_appc(
ref self: ComponentState<TContractState>,
nonce: felt252,
nonce: Nonce,
to_address: ContractAddress,
selector: felt252,
payload: Span<felt252>
) -> felt252 {
) -> MessageHash {
let mut hash_data = array![nonce, to_address.into(), selector,];

let mut i = 0_usize;
Expand Down Expand Up @@ -202,7 +316,7 @@ mod messaging_cpt {
from_address: ContractAddress,
to_address: ContractAddress,
payload: Span<felt252>
) -> felt252 {
) -> MessageHash {
let mut hash_data: Array<felt252> = array![
from_address.into(), to_address.into(), payload.len().into(),
];
Expand Down
47 changes: 47 additions & 0 deletions src/messaging/interface.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,51 @@ trait IMessaging<T> {
fn consume_message_from_appchain(
ref self: T, from_address: ContractAddress, payload: Span<felt252>
) -> felt252;

/// Starts the cancellation procedure for a message sent from
/// Starknet to the Appchain.
///
/// Once the cancellation delay is over, the message can be cancelled.
///
/// <https://github.com/starkware-libs/cairo-lang/blob/caba294d82eeeccc3d86a158adb8ba209bf2d8fc/src/starkware/starknet/solidity/StarknetMessaging.sol#L147>.
///
/// # Arguments
///
/// * `to_address` - Contract address to send the message to on the Appchain.
/// * `selector` - The `l1_handler` function selector of the contract on the Appchain to execute.
/// * `payload` - The message's payload.
/// * `nonce` - The message's nonce.
///
/// # Returns
///
/// The hash of the message.
fn start_message_cancellation(
ref self: T,
to_address: ContractAddress,
selector: felt252,
payload: Span<felt252>,
nonce: felt252,
) -> felt252;

/// Cancels a message from Starknet to Appchain if the cancellation delays has expired.
///
/// <https://github.com/starkware-libs/cairo-lang/blob/caba294d82eeeccc3d86a158adb8ba209bf2d8fc/src/starkware/starknet/solidity/StarknetMessaging.sol#L161>.
///
/// # Arguments
///
/// * `to_address` - Contract address to send the message to on the Appchain.
/// * `selector` - The `l1_handler` function selector of the contract on the Appchain to execute.
/// * `payload` - The message's payload.
/// * `nonce` - The message's nonce.
///
/// # Returns
///
/// The hash of the message.
fn cancel_message(
ref self: T,
to_address: ContractAddress,
selector: felt252,
payload: Span<felt252>,
nonce: felt252,
) -> felt252;
}
4 changes: 3 additions & 1 deletion src/messaging/mock.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,7 @@ mod messaging_mock {
}

#[constructor]
fn constructor(ref self: ContractState) {}
fn constructor(ref self: ContractState, cancellation_delay_secs: u64) {
self.messaging.initialize(cancellation_delay_secs);
}
}
4 changes: 4 additions & 0 deletions tests/constants.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ fn contract_b() -> ContractAddress {
starknet::contract_address_const::<'CONTRACT_B'>()
}

fn contract_c() -> ContractAddress {
starknet::contract_address_const::<'CONTRACT_C'>()
}

fn owner() -> ContractAddress {
starknet::contract_address_const::<'OWNER'>()
}
Expand Down
Loading

0 comments on commit 108064b

Please sign in to comment.