2025-11-18 17:52:46 +02:00
|
|
|
use borsh::{BorshDeserialize, BorshSerialize};
|
2026-06-01 17:10:46 -03:00
|
|
|
use lee_core::{
|
2025-08-10 18:51:55 -03:00
|
|
|
account::Nonce,
|
|
|
|
|
program::{InstructionData, ProgramId},
|
|
|
|
|
};
|
2025-08-13 19:21:20 -03:00
|
|
|
use serde::Serialize;
|
2026-04-30 14:21:47 -04:00
|
|
|
use sha2::{Digest as _, Sha256};
|
2025-08-10 14:10:11 -03:00
|
|
|
|
2026-06-01 17:10:46 -03:00
|
|
|
use crate::{AccountId, error::LeeError, program::Program};
|
2025-08-10 14:10:11 -03:00
|
|
|
|
2026-04-30 14:21:47 -04:00
|
|
|
const PREFIX: &[u8; 32] = b"/LEE/v0.3/Message/Public/\x00\x00\x00\x00\x00\x00\x00";
|
|
|
|
|
|
2026-02-27 16:37:28 +11:00
|
|
|
#[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
2025-08-10 14:10:11 -03:00
|
|
|
pub struct Message {
|
2026-01-24 04:05:59 +03:00
|
|
|
pub program_id: ProgramId,
|
|
|
|
|
pub account_ids: Vec<AccountId>,
|
|
|
|
|
pub nonces: Vec<Nonce>,
|
|
|
|
|
pub instruction_data: InstructionData,
|
2025-08-10 14:10:11 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-27 16:37:28 +11:00
|
|
|
impl std::fmt::Debug for Message {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
let program_id_hex = hex::encode(
|
2026-02-27 18:22:12 +11:00
|
|
|
self.program_id
|
|
|
|
|
.iter()
|
|
|
|
|
.flat_map(|n| n.to_le_bytes())
|
|
|
|
|
.collect::<Vec<u8>>(),
|
2026-02-27 16:37:28 +11:00
|
|
|
);
|
|
|
|
|
f.debug_struct("Message")
|
|
|
|
|
.field("program_id", &program_id_hex)
|
|
|
|
|
.field("account_ids", &self.account_ids)
|
|
|
|
|
.field("nonces", &self.nonces)
|
|
|
|
|
.field("instruction_data", &self.instruction_data)
|
|
|
|
|
.finish()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-10 14:10:11 -03:00
|
|
|
impl Message {
|
2025-08-11 12:07:30 -03:00
|
|
|
pub fn try_new<T: Serialize>(
|
2025-08-10 14:10:11 -03:00
|
|
|
program_id: ProgramId,
|
2025-11-24 17:09:30 +03:00
|
|
|
account_ids: Vec<AccountId>,
|
2025-08-10 14:10:11 -03:00
|
|
|
nonces: Vec<Nonce>,
|
2025-08-10 18:59:29 -03:00
|
|
|
instruction: T,
|
2026-06-01 17:10:46 -03:00
|
|
|
) -> Result<Self, LeeError> {
|
2025-08-11 12:07:30 -03:00
|
|
|
let instruction_data = Program::serialize_instruction(instruction)?;
|
2025-12-17 14:21:36 +02:00
|
|
|
|
2025-08-11 12:07:30 -03:00
|
|
|
Ok(Self {
|
2025-08-10 14:10:11 -03:00
|
|
|
program_id,
|
2025-11-24 17:09:30 +03:00
|
|
|
account_ids,
|
2025-08-10 14:10:11 -03:00
|
|
|
nonces,
|
|
|
|
|
instruction_data,
|
2025-08-11 12:07:30 -03:00
|
|
|
})
|
2025-08-10 14:10:11 -03:00
|
|
|
}
|
2025-12-17 11:44:52 +02:00
|
|
|
|
2026-03-03 23:21:08 +03:00
|
|
|
#[must_use]
|
2026-03-09 18:27:56 +03:00
|
|
|
pub const fn new_preserialized(
|
2025-12-17 11:44:52 +02:00
|
|
|
program_id: ProgramId,
|
|
|
|
|
account_ids: Vec<AccountId>,
|
|
|
|
|
nonces: Vec<Nonce>,
|
2025-12-22 04:42:32 +02:00
|
|
|
instruction_data: InstructionData,
|
2025-12-17 11:44:52 +02:00
|
|
|
) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
program_id,
|
|
|
|
|
account_ids,
|
|
|
|
|
nonces,
|
|
|
|
|
instruction_data,
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-30 14:21:47 -04:00
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub fn hash(&self) -> [u8; 32] {
|
|
|
|
|
let mut bytes = Vec::with_capacity(
|
|
|
|
|
PREFIX
|
|
|
|
|
.len()
|
|
|
|
|
.checked_add(self.to_bytes().len())
|
|
|
|
|
.expect("length overflow"),
|
|
|
|
|
);
|
|
|
|
|
bytes.extend_from_slice(PREFIX);
|
|
|
|
|
bytes.extend_from_slice(&self.to_bytes());
|
|
|
|
|
|
|
|
|
|
Sha256::digest(bytes).into()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
2026-06-01 17:10:46 -03:00
|
|
|
use lee_core::account::{AccountId, Nonce};
|
2026-04-30 14:21:47 -04:00
|
|
|
use sha2::{Digest as _, Sha256};
|
|
|
|
|
|
|
|
|
|
use super::{Message, PREFIX};
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn hash_public_pinned() {
|
|
|
|
|
let msg = Message::new_preserialized(
|
|
|
|
|
[1_u32; 8],
|
|
|
|
|
vec![AccountId::new([42_u8; 32])],
|
|
|
|
|
vec![Nonce(5)],
|
|
|
|
|
vec![],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// program_id: [1_u32; 8], each word as LE u32
|
|
|
|
|
let program_id_bytes: &[u8] = &[
|
|
|
|
|
1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
|
|
|
|
|
0, 0, 0,
|
|
|
|
|
];
|
|
|
|
|
// account_ids: AccountId([42_u8; 32])
|
|
|
|
|
let account_ids_bytes: &[u8] = &[42_u8; 32];
|
|
|
|
|
// nonces: u32 len=1, then Nonce(5) as LE u128
|
|
|
|
|
let nonces_bytes: &[u8] = &[1, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
|
|
|
let instruction_data_bytes: &[u8] = &[0_u8; 4];
|
|
|
|
|
|
|
|
|
|
let expected_borsh_vec: Vec<u8> = [
|
|
|
|
|
program_id_bytes,
|
|
|
|
|
&[1_u8, 0, 0, 0], // account_ids len=1
|
|
|
|
|
account_ids_bytes,
|
|
|
|
|
nonces_bytes,
|
|
|
|
|
instruction_data_bytes,
|
|
|
|
|
]
|
|
|
|
|
.concat();
|
|
|
|
|
let expected_borsh: &[u8] = &expected_borsh_vec;
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
borsh::to_vec(&msg).unwrap(),
|
|
|
|
|
expected_borsh,
|
|
|
|
|
"`public_transaction::hash()`: expected borsh order has changed"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let mut preimage = Vec::with_capacity(PREFIX.len() + expected_borsh.len());
|
|
|
|
|
preimage.extend_from_slice(PREFIX);
|
|
|
|
|
preimage.extend_from_slice(expected_borsh);
|
|
|
|
|
let expected_hash: [u8; 32] = Sha256::digest(&preimage).into();
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
msg.hash(),
|
|
|
|
|
expected_hash,
|
|
|
|
|
"`public_transaction::hash()`: serialization has changed"
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-08-12 10:16:04 -03:00
|
|
|
}
|