Skip to content

Commit

Permalink
feat: new type MsgToL2 with parsing and hashing (#495)
Browse files Browse the repository at this point in the history
  • Loading branch information
xJonathanLEI authored Nov 3, 2023
1 parent 925eb66 commit e343224
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 0 deletions.
99 changes: 99 additions & 0 deletions starknet-core/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ pub use eth_address::EthAddress;
mod execution_result;
pub use execution_result::ExecutionResult;

mod msg;
pub use msg::MsgToL2;

// TODO: move generated request code to `starknet-providers`
pub mod requests;

Expand Down Expand Up @@ -262,6 +265,36 @@ pub enum ExecuteInvocation {
Reverted(RevertedInvocation),
}

mod errors {
use core::fmt::{Display, Formatter, Result};

#[derive(Debug)]
pub enum ParseMsgToL2Error {
EmptyCalldata,
FromAddressOutOfRange,
}

#[cfg(feature = "std")]
impl std::error::Error for ParseMsgToL2Error {}

impl Display for ParseMsgToL2Error {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self {
Self::EmptyCalldata => {
write!(
f,
"calldata must contain at least 1 element for from_address"
)
}
Self::FromAddressOutOfRange => {
write!(f, "from_address is larger than 20 bytes")
}
}
}
}
}
pub use errors::ParseMsgToL2Error;

impl MaybePendingBlockWithTxHashes {
pub fn transactions(&self) -> &[FieldElement] {
match self {
Expand Down Expand Up @@ -392,6 +425,24 @@ impl PendingTransactionReceipt {
}
}

impl L1HandlerTransaction {
pub fn parse_msg_to_l2(&self) -> Result<MsgToL2, ParseMsgToL2Error> {
if self.calldata.is_empty() {
return Err(ParseMsgToL2Error::EmptyCalldata);
}

Ok(MsgToL2 {
from_address: self.calldata[0]
.try_into()
.map_err(|_| ParseMsgToL2Error::FromAddressOutOfRange)?,
to_address: self.contract_address,
selector: self.entry_point_selector,
payload: self.calldata[1..].to_vec(),
nonce: self.nonce,
})
}
}

impl AsRef<BlockId> for BlockId {
fn as_ref(&self) -> &BlockId {
self
Expand Down Expand Up @@ -434,6 +485,14 @@ impl AsRef<BroadcastedDeployAccountTransaction> for BroadcastedDeployAccountTran
}
}

impl TryFrom<&L1HandlerTransaction> for MsgToL2 {
type Error = ParseMsgToL2Error;

fn try_from(value: &L1HandlerTransaction) -> Result<Self, Self::Error> {
value.parse_msg_to_l2()
}
}

impl TryFrom<i64> for StarknetError {
type Error = ();

Expand Down Expand Up @@ -492,4 +551,44 @@ mod tests {
let parsed_object = serde_json::from_str::<BroadcastedInvokeTransaction>(raw).unwrap();
assert!(parsed_object.is_query);
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
fn test_parse_msg_to_l2() {
let l1_handler_tx = L1HandlerTransaction {
transaction_hash: FieldElement::from_hex_be(
"0x374286ae28f201e61ffbc5b022cc9701208640b405ea34ea9799f97d5d2d23c",
)
.unwrap(),
version: 0,
nonce: 775628,
contract_address: FieldElement::from_hex_be(
"0x73314940630fd6dcda0d772d4c972c4e0a9946bef9dabf4ef84eda8ef542b82",
)
.unwrap(),
entry_point_selector: FieldElement::from_hex_be(
"0x2d757788a8d8d6f21d1cd40bce38a8222d70654214e96ff95d8086e684fbee5",
)
.unwrap(),
calldata: vec![
FieldElement::from_hex_be("0xc3511006c04ef1d78af4c8e0e74ec18a6e64ff9e").unwrap(),
FieldElement::from_hex_be(
"0x689ead7d814e51ed93644bc145f0754839b8dcb340027ce0c30953f38f55d7",
)
.unwrap(),
FieldElement::from_hex_be("0x2c68af0bb140000").unwrap(),
FieldElement::from_hex_be("0x0").unwrap(),
],
};

let msg_to_l2 = l1_handler_tx.parse_msg_to_l2().unwrap();

let expected_hash: [u8; 32] =
hex::decode("c51a543ef9563ad2545342b390b67edfcddf9886aa36846cf70382362fc5fab3")
.unwrap()
.try_into()
.unwrap();

assert_eq!(msg_to_l2.hash(), expected_hash);
}
}
93 changes: 93 additions & 0 deletions starknet-core/src/types/msg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use alloc::vec::Vec;

use sha3::{Digest, Keccak256};
use starknet_ff::FieldElement;

use super::EthAddress;

#[derive(Debug, Clone)]
pub struct MsgToL2 {
pub from_address: EthAddress,
pub to_address: FieldElement,
pub selector: FieldElement,
pub payload: Vec<FieldElement>,
pub nonce: u64,
}

impl MsgToL2 {
/// Calculates the message hash based on the algorithm documented here:
///
/// https://docs.starknet.io/documentation/architecture_and_concepts/L1-L2_Communication/messaging-mechanism/
pub fn hash(&self) -> [u8; 32] {
let mut hasher = Keccak256::new();

// FromAddress
hasher.update([0u8; 12]);
hasher.update(self.from_address.as_bytes());

// ToAddress
hasher.update(self.to_address.to_bytes_be());

// Nonce
hasher.update([0u8; 24]);
hasher.update(self.nonce.to_be_bytes());

// Selector
hasher.update(self.selector.to_bytes_be());

// Payload.length
hasher.update([0u8; 24]);
hasher.update((self.payload.len() as u64).to_be_bytes());

// Payload
for item in self.payload.iter() {
hasher.update(item.to_bytes_be());
}

let hash = hasher.finalize();

// Because we know hash is always 32 bytes
unsafe { *(hash[..].as_ptr() as *const [u8; 32]) }
}
}

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

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
fn test_msg_to_l2_hash() {
// Goerli-1 tx: 0374286ae28f201e61ffbc5b022cc9701208640b405ea34ea9799f97d5d2d23c

let msg = MsgToL2 {
from_address: EthAddress::from_hex("0xc3511006C04EF1d78af4C8E0e74Ec18A6E64Ff9e")
.unwrap(),
to_address: FieldElement::from_hex_be(
"0x73314940630fd6dcda0d772d4c972c4e0a9946bef9dabf4ef84eda8ef542b82",
)
.unwrap(),
selector: FieldElement::from_hex_be(
"0x2d757788a8d8d6f21d1cd40bce38a8222d70654214e96ff95d8086e684fbee5",
)
.unwrap(),
payload: vec![
FieldElement::from_hex_be(
"0x689ead7d814e51ed93644bc145f0754839b8dcb340027ce0c30953f38f55d7",
)
.unwrap(),
FieldElement::from_hex_be("0x2c68af0bb140000").unwrap(),
FieldElement::from_hex_be("0x0").unwrap(),
],
nonce: 775628,
};

let expected_hash: [u8; 32] =
hex::decode("c51a543ef9563ad2545342b390b67edfcddf9886aa36846cf70382362fc5fab3")
.unwrap()
.try_into()
.unwrap();

assert_eq!(msg.hash(), expected_hash);
}
}

0 comments on commit e343224

Please sign in to comment.