Skip to content

Commit

Permalink
interop types
Browse files Browse the repository at this point in the history
  • Loading branch information
refcell committed Jan 2, 2025
1 parent c7c417f commit ea4e96d
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 0 deletions.
1 change: 1 addition & 0 deletions crates/protocol/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ op-alloy-consensus.workspace = true

# Alloy
alloy-primitives = { workspace = true, features = ["map"] }
alloy-sol-types.workspace = true
alloy-rlp.workspace = true
alloy-eips.workspace = true
alloy-consensus.workspace = true
Expand Down
3 changes: 3 additions & 0 deletions crates/protocol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

extern crate alloc;

mod messages;
pub use messages::{MessageIdentifier, MessagePayload, SafetyLevel};

mod batch;
pub use batch::{
Batch, BatchDecodingError, BatchEncodingError, BatchReader, BatchTransaction, BatchType,
Expand Down
129 changes: 129 additions & 0 deletions crates/protocol/src/messages/events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//! Message event primitives for OP stack interoperability.
//!
//! <https://specs.optimism.io/interop/messaging.html#messaging>
//! <https://github.com/ethereum-optimism/optimism/blob/34d5f66ade24bd1f3ce4ce7c0a6cfc1a6540eca1/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol>
use alloc::{vec, vec::Vec};
use alloy_primitives::{keccak256, Address, Bytes, Log, U256};
use alloy_sol_types::{sol, SolType};

sol! {
/// @notice The struct for a pointer to a message payload in a remote (or local) chain.
#[derive(Default, Debug, PartialEq, Eq)]
struct MessageIdentifierAbi {
address origin;
uint256 blockNumber;
uint256 logIndex;
uint256 timestamp;
uint256 chainId;
}

/// @notice Emitted when a cross chain message is being executed.
/// @param msgHash Hash of message payload being executed.
/// @param id Encoded Identifier of the message.
#[derive(Default, Debug, PartialEq, Eq)]
event ExecutingMessage(bytes32 indexed msgHash, MessageIdentifierAbi id);

/// @notice Executes a cross chain message on the destination chain.
/// @param _id Identifier of the message.
/// @param _target Target address to call.
/// @param _message Message payload to call target with.
function executeMessage(
MessageIdentifierAbi calldata _id,
address _target,
bytes calldata _message
) external;
}

/// A [MessagePayload] is the raw payload of an initiating message.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MessagePayload(Bytes);

impl From<Log> for MessagePayload {
fn from(log: Log) -> Self {
let mut data = vec![0u8; log.topics().len() * 32 + log.data.data.len()];
for (i, topic) in log.topics().iter().enumerate() {
data[i * 32..(i + 1) * 32].copy_from_slice(topic.as_ref());
}
data[(log.topics().len() * 32)..].copy_from_slice(log.data.data.as_ref());
data.into()
}
}

impl From<Vec<u8>> for MessagePayload {
fn from(data: Vec<u8>) -> Self {
Self(Bytes::from(data))
}
}

impl From<Bytes> for MessagePayload {
fn from(bytes: Bytes) -> Self {
Self(bytes)
}
}

impl From<MessagePayload> for Bytes {
fn from(payload: MessagePayload) -> Self {
payload.0
}
}

impl AsRef<[u8]> for MessagePayload {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}

/// A [MessageIdentifier] uniquely represents a log that is emitted from a chain within
/// the broader dependency set. It is included in the calldata of a transaction sent to the
/// CrossL2Inbox contract.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MessageIdentifier {
/// The account that sent the message.
pub origin: Address,
/// The block number that the message was sent in.
pub block_number: u64,
/// The log index of the message in the block (global).
pub log_index: u64,
/// The timestamp of the message.
pub timestamp: u64,
/// The chain ID of the chain that the message was sent on.
pub chain_id: u64,
}

impl MessageIdentifier {
/// Decode a [MessageIdentifier] from ABI-encoded data.
pub fn abi_decode(data: &[u8], validate: bool) -> Result<Self, alloy_sol_types::Error> {
MessageIdentifierAbi::abi_decode(data, validate).map(|abi| abi.into())
}
}

impl From<MessageIdentifierAbi> for MessageIdentifier {
fn from(abi: MessageIdentifierAbi) -> Self {
Self {
origin: abi.origin,
block_number: abi.blockNumber.to(),
log_index: abi.logIndex.to(),
timestamp: abi.timestamp.to(),
chain_id: abi.chainId.to(),
}
}
}

impl From<MessageIdentifier> for MessageIdentifierAbi {
fn from(id: MessageIdentifier) -> Self {
Self {
origin: id.origin,
blockNumber: U256::from(id.block_number),
logIndex: U256::from(id.log_index),
timestamp: U256::from(id.timestamp),
chainId: U256::from(id.chain_id),
}
}
}

impl From<executeMessageCall> for ExecutingMessage {
fn from(call: executeMessageCall) -> Self {
Self { id: call._id, msgHash: keccak256(call._message.as_ref()) }
}
}
7 changes: 7 additions & 0 deletions crates/protocol/src/messages/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//! Message types for OP Stack interoperability.
mod events;
pub use events::{MessageIdentifier, MessagePayload};

mod safety;
pub use safety::SafetyLevel;
42 changes: 42 additions & 0 deletions crates/protocol/src/messages/safety.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//! Message safety level for interoperability.
/// The safety level of a message.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub enum SafetyLevel {
/// The message is finalized.
Finalized,
/// The message is safe.
Safe,
/// The message is safe locally.
LocalSafe,
/// The message is unsafe across chains.
CrossUnsafe,
/// The message is unsafe.
Unsafe,
/// The message is invalid.
Invalid,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_safety_level_serde() {
let level = SafetyLevel::Finalized;
let json = serde_json::to_string(&level).unwrap();
assert_eq!(json, r#""finalized""#);

let level: SafetyLevel = serde_json::from_str(&json).unwrap();
assert_eq!(level, SafetyLevel::Finalized);
}

#[test]
fn test_serde_safety_level_fails() {
let json = r#""failed""#;
let level: Result<SafetyLevel, _> = serde_json::from_str(json);
assert!(level.is_err());
}
}

0 comments on commit ea4e96d

Please sign in to comment.