Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new type MsgToL2 with parsing and hashing #495

Merged
merged 1 commit into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}