What changed, and why it matters
This commit only adds unit tests for existing TON (The Open Network) message parsing code. It does not change any production behavior, fix bugs, or alter how the firmware handles real transactions. The tests verify that Jetton, NFT, comment, and error-conversion routines behave as already implemented.
No security action required. Treat as routine test-only commit during review/audit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff adds #[cfg(test)] modules to four Rust files under rust/apps/ton. It introduces tests for MnemonicError/TransactionError conversions, JettonTransferMessage parsing with/without optional payloads, NFTTransferMessage parsing with/without optional payloads, InternalMessage/Comment parsing, and opcode inference. No production code paths are modified; only test coverage is added.
Changed components
rust/apps/ton/src/errors.rsrust/apps/ton/src/messages/jetton.rsrust/apps/ton/src/messages/mod.rsrust/apps/ton/src/messages/nft.rsInspect captured patch +414 / −0
diff --git a/rust/apps/ton/src/errors.rs b/rust/apps/ton/src/errors.rs
index 50235cc..c0f498f 100644
--- a/rust/apps/ton/src/errors.rs
+++ b/rust/apps/ton/src/errors.rs
@@ -44,3 +44,54 @@ impl From<MnemonicError> for TonError {
TonError::MnemonicError(value.to_string())
}
}
+
+#[cfg(test)]
+mod tests {
+ extern crate std;
+
+ use alloc::string::ToString;
+ use super::{MnemonicError, TonError};
+ use crate::vendor::cell::TonCellError;
+
+ #[test]
+ fn test_mnemonic_error_conversion_preserves_context() {
+ let cases = [
+ (
+ MnemonicError::UnexpectedWordCount(11),
+ "Invalid mnemonic word count (count: 11)",
+ ),
+ (
+ MnemonicError::InvalidWord("foobar".to_string()),
+ "Invalid mnemonic word (word: foobar)",
+ ),
+ (
+ MnemonicError::InvalidFirstByte(0xAB),
+ "Invalid mnemonic with password (first byte: 0xAB)",
+ ),
+ (
+ MnemonicError::InvalidPasswordlessMenmonicFirstByte(0xCD),
+ "Invalid passwordless mnemonic (first byte: 0xCD)",
+ ),
+ ];
+
+ for (mnemonic_err, expected) in cases {
+ let ton_err: TonError = mnemonic_err.into();
+ match ton_err {
+ TonError::MnemonicError(message) => assert_eq!(message, expected),
+ _ => panic!("expected mnemonic error"),
+ }
+ }
+ }
+
+ #[test]
+ fn test_transaction_error_conversion_from_ton_cell_error() {
+ let ton_err: TonError = TonCellError::CellParserError("bad payload".to_string()).into();
+ match ton_err {
+ TonError::TransactionError(TonCellError::CellParserError(message)) => {
+ assert_eq!(message, "bad payload")
+ }
+ _ => panic!("expected transaction error"),
+ }
+ }
+}
+
diff --git a/rust/apps/ton/src/messages/jetton.rs b/rust/apps/ton/src/messages/jetton.rs
index 389eee1..6a27472 100644
--- a/rust/apps/ton/src/messages/jetton.rs
+++ b/rust/apps/ton/src/messages/jetton.rs
@@ -105,3 +105,127 @@ impl ParseCell for JettonTransferMessage {
})
}
}
+
+#[cfg(test)]
+mod tests {
+ extern crate std;
+
+ use alloc::string::ToString;
+
+ use anyhow::Result;
+ use num_bigint::BigUint;
+
+ use super::{JettonMessage, JettonTransferMessage, JETTON_TRANSFER};
+ use crate::messages::traits::ParseCell;
+ use crate::vendor::address::TonAddress;
+ use crate::vendor::cell::{ArcCell, CellBuilder, TonCellError};
+
+ fn test_address(seed: u8) -> TonAddress {
+ TonAddress::new(0, &[seed; 32])
+ }
+
+ fn build_payload_cell(bytes: &[u8]) -> Result<ArcCell, TonCellError> {
+ let mut builder = CellBuilder::new();
+ builder.store_slice(bytes)?;
+ Ok(builder.build()?.to_arc())
+ }
+
+ fn build_comment_cell(text: &str) -> Result<ArcCell, TonCellError> {
+ let mut builder = CellBuilder::new();
+ builder.store_u32(32, 0)?;
+ builder.store_string(text)?;
+ Ok(builder.build()?.to_arc())
+ }
+
+ fn build_jetton_transfer_cell(
+ response_destination: &TonAddress,
+ custom_payload: Option<&ArcCell>,
+ forward_payload: Option<&ArcCell>,
+ forward_ton_amount: u32,
+ ) -> Result<ArcCell, TonCellError> {
+ let mut builder = CellBuilder::new();
+ builder.store_u32(32, JETTON_TRANSFER)?;
+ builder.store_u64(64, 7)?;
+ builder.store_coins(&BigUint::from(42u32))?;
+ builder.store_address(&test_address(1))?;
+ builder.store_address(response_destination)?;
+ builder.store_bit(custom_payload.is_some())?;
+ if let Some(custom_payload) = custom_payload {
+ builder.store_reference(custom_payload)?;
+ }
+ builder.store_coins(&BigUint::from(forward_ton_amount))?;
+ builder.store_bit(forward_payload.is_some())?;
+ if let Some(forward_payload) = forward_payload {
+ builder.store_reference(forward_payload)?;
+ }
+ Ok(builder.build()?.to_arc())
+ }
+
+ #[test]
+ fn test_parse_jetton_transfer_with_payloads() -> Result<()> {
+ let custom_payload = build_payload_cell(&[0xDE, 0xAD])?;
+ let forward_payload = build_comment_cell("note")?;
+ let cell = build_jetton_transfer_cell(
+ &TonAddress::null(),
+ Some(&custom_payload),
+ Some(&forward_payload),
+ 100,
+ )?;
+
+ let parsed = JettonTransferMessage::parse(&cell)?;
+ assert_eq!(parsed.query_id, "7");
+ assert_eq!(parsed.amount, "42");
+ assert_eq!(
+ parsed.destination,
+ test_address(1).to_base64_url_flags(true, false)
+ );
+ assert_eq!(
+ parsed.response_destination,
+ Some(TonAddress::null().to_base64_url_flags(true, false))
+ );
+ assert_eq!(
+ parsed.custom_payload,
+ Some(hex::encode(custom_payload.data.clone()))
+ );
+ assert_eq!(parsed.forward_ton_amount, "100");
+ assert_eq!(
+ parsed.forward_payload,
+ Some(hex::encode(forward_payload.data.clone()))
+ );
+ assert_eq!(parsed.comment, Some("note".to_string()));
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_parse_jetton_transfer_without_optional_fields() -> Result<()> {
+ let response_destination = test_address(2);
+ let cell = build_jetton_transfer_cell(&response_destination, None, None, 0)?;
+
+ let parsed = JettonTransferMessage::parse(&cell)?;
+ assert_eq!(parsed.response_destination, None);
+ assert_eq!(parsed.custom_payload, None);
+ assert_eq!(parsed.forward_ton_amount, "0");
+ assert_eq!(parsed.forward_payload, None);
+ assert_eq!(parsed.comment, None);
+
+ let message = JettonMessage::parse(&cell)?;
+ match message {
+ JettonMessage::JettonTransferMessage(inner) => assert_eq!(inner.query_id, "7"),
+ }
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_parse_jetton_message_invalid_opcode() -> Result<()> {
+ let mut builder = CellBuilder::new();
+ builder.store_u32(32, 0xDEADBEEF)?;
+ let cell = builder.build()?.to_arc();
+
+ let err = JettonMessage::parse(&cell).unwrap_err();
+ assert!(matches!(err, TonCellError::InternalError(message) if message.contains("Invalid Op Code")));
+
+ Ok(())
+ }
+}
diff --git a/rust/apps/ton/src/messages/mod.rs b/rust/apps/ton/src/messages/mod.rs
index 0fcbf8c..e15620f 100644
--- a/rust/apps/ton/src/messages/mod.rs
+++ b/rust/apps/ton/src/messages/mod.rs
@@ -223,3 +223,128 @@ impl ParseCell for Comment {
})
}
}
+
+#[cfg(test)]
+mod tests {
+ extern crate std;
+
+ use anyhow::Result;
+
+ use super::nft::NFT_TRANSFER;
+ use super::traits::ParseCell;
+ use super::{infer_action, Comment, InternalMessage, Operation};
+ use crate::vendor::cell::{ArcCell, CellBuilder, TonCellError};
+ use crate::vendor::message::JETTON_TRANSFER;
+
+ fn build_utf8_cell(payload: &str, child: Option<&ArcCell>) -> Result<ArcCell, TonCellError> {
+ let mut builder = CellBuilder::new();
+ builder.store_string(payload)?;
+ if let Some(child) = child {
+ builder.store_reference(child)?;
+ }
+ Ok(builder.build()?.to_arc())
+ }
+
+ fn build_internal_comment_cell(
+ payload: &str,
+ child: Option<&ArcCell>,
+ ) -> Result<ArcCell, TonCellError> {
+ let mut builder = CellBuilder::new();
+ builder.store_u32(32, 0)?;
+ builder.store_string(payload)?;
+ if let Some(child) = child {
+ builder.store_reference(child)?;
+ }
+ Ok(builder.build()?.to_arc())
+ }
+
+ fn assert_invalid_comment_error(cell: &ArcCell) {
+ let err = Comment::parse(cell).unwrap_err();
+ assert!(
+ matches!(err, TonCellError::CellParserError(ref message) if message == "payload is not a comment")
+ || matches!(err, TonCellError::NonEmptyReader(_))
+ );
+ }
+
+ #[test]
+ fn test_infer_action_known_and_unknown() {
+ assert_eq!(infer_action(JETTON_TRANSFER).as_deref(), Some("Jetton Transfer"));
+ assert_eq!(infer_action(NFT_TRANSFER).as_deref(), Some("NFT Transfer"));
+ assert_eq!(infer_action(0xDEADBEEF), None);
+ }
+
+ #[test]
+ fn test_parse_internal_comment_with_child_chain() -> Result<()> {
+ let tail = build_utf8_cell("world", None)?;
+ let mid = build_utf8_cell(" ", Some(&tail))?;
+ let root = build_internal_comment_cell("hello", Some(&mid))?;
+
+ let parsed = InternalMessage::parse(&root)?;
+ assert_eq!(parsed.op_code, "0");
+ assert_eq!(parsed.action, None);
+
+ match parsed.operation {
+ Operation::Comment(comment) => assert_eq!(comment, "hello world"),
+ _ => panic!("expected comment operation"),
+ }
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_parse_internal_other_message() -> Result<()> {
+ let mut builder = CellBuilder::new();
+ builder.store_u32(32, 0x12345678)?;
+ builder.store_slice(&[0xAA, 0xBB, 0xCC])?;
+ let cell = builder.build()?.to_arc();
+
+ let parsed = InternalMessage::parse(&cell)?;
+ assert_eq!(parsed.op_code, "12345678");
+ assert_eq!(parsed.action, None);
+
+ match parsed.operation {
+ Operation::OtherMessage(other) => assert_eq!(other.payload, "aabbcc"),
+ _ => panic!("expected other message operation"),
+ }
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_parse_comment_success() -> Result<()> {
+ let cell = build_internal_comment_cell("memo", None)?;
+ let comment = Comment::parse(&cell)?;
+ assert_eq!(comment, "memo");
+ Ok(())
+ }
+
+ #[test]
+ fn test_parse_comment_rejects_short_payload() -> Result<()> {
+ let mut builder = CellBuilder::new();
+ builder.store_u8(8, 0xFF)?;
+ let cell = builder.build()?.to_arc();
+ assert_invalid_comment_error(&cell);
+ Ok(())
+ }
+
+ #[test]
+ fn test_parse_comment_rejects_non_zero_opcode() -> Result<()> {
+ let mut builder = CellBuilder::new();
+ builder.store_u32(32, 1)?;
+ let cell = builder.build()?.to_arc();
+ assert_invalid_comment_error(&cell);
+ Ok(())
+ }
+
+ #[test]
+ fn test_parse_comment_rejects_invalid_utf8() -> Result<()> {
+ let mut builder = CellBuilder::new();
+ builder.store_u32(32, 0)?;
+ builder.store_byte(0xFF)?;
+ let cell = builder.build()?.to_arc();
+ assert_invalid_comment_error(&cell);
+ Ok(())
+ }
+}
+
+
diff --git a/rust/apps/ton/src/messages/nft.rs b/rust/apps/ton/src/messages/nft.rs
index 15b6dd8..96bcb1a 100644
--- a/rust/apps/ton/src/messages/nft.rs
+++ b/rust/apps/ton/src/messages/nft.rs
@@ -84,3 +84,117 @@ impl ParseCell for NFTTransferMessage {
})
}
}
+
+#[cfg(test)]
+mod tests {
+ extern crate std;
+
+ use alloc::string::ToString;
+
+ use anyhow::Result;
+ use num_bigint::BigUint;
+
+ use super::{NFTMessage, NFTTransferMessage, NFT_TRANSFER};
+ use crate::messages::traits::ParseCell;
+ use crate::vendor::address::TonAddress;
+ use crate::vendor::cell::{ArcCell, CellBuilder, TonCellError};
+
+ fn test_address(seed: u8) -> TonAddress {
+ TonAddress::new(0, &[seed; 32])
+ }
+
+ fn build_payload_cell(bytes: &[u8]) -> Result<ArcCell, TonCellError> {
+ let mut builder = CellBuilder::new();
+ builder.store_slice(bytes)?;
+ Ok(builder.build()?.to_arc())
+ }
+
+ fn build_comment_cell(text: &str) -> Result<ArcCell, TonCellError> {
+ let mut builder = CellBuilder::new();
+ builder.store_u32(32, 0)?;
+ builder.store_string(text)?;
+ Ok(builder.build()?.to_arc())
+ }
+
+ fn build_nft_transfer_cell(
+ custom_payload: Option<&ArcCell>,
+ forward_payload: Option<&ArcCell>,
+ forward_ton_amount: u32,
+ ) -> Result<ArcCell, TonCellError> {
+ let mut builder = CellBuilder::new();
+ builder.store_u32(32, NFT_TRANSFER)?;
+ builder.store_u64(64, 9)?;
+ builder.store_address(&test_address(1))?;
+ builder.store_address(&test_address(2))?;
+ builder.store_bit(custom_payload.is_some())?;
+ if let Some(custom_payload) = custom_payload {
+ builder.store_reference(custom_payload)?;
+ }
+ builder.store_coins(&BigUint::from(forward_ton_amount))?;
+ builder.store_bit(forward_payload.is_some())?;
+ if let Some(forward_payload) = forward_payload {
+ builder.store_reference(forward_payload)?;
+ }
+ Ok(builder.build()?.to_arc())
+ }
+
+ #[test]
+ fn test_parse_nft_transfer_with_payloads() -> Result<()> {
+ let custom_payload = build_payload_cell(&[0xAB, 0xCD])?;
+ let forward_payload = build_comment_cell("memo")?;
+ let cell = build_nft_transfer_cell(Some(&custom_payload), Some(&forward_payload), 5)?;
+
+ let parsed = NFTTransferMessage::parse(&cell)?;
+ assert_eq!(parsed.query_id, "9");
+ assert_eq!(
+ parsed.new_owner_address,
+ test_address(1).to_base64_url_flags(true, false)
+ );
+ assert_eq!(
+ parsed.response_address,
+ test_address(2).to_base64_url_flags(true, false)
+ );
+ assert_eq!(
+ parsed.custom_payload,
+ Some(hex::encode(custom_payload.data.clone()))
+ );
+ assert_eq!(parsed.forward_ton_amount, "5");
+ assert_eq!(
+ parsed.forward_payload,
+ Some(hex::encode(forward_payload.data.clone()))
+ );
+ assert_eq!(parsed.comment, Some("memo".to_string()));
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_parse_nft_transfer_without_optional_fields() -> Result<()> {
+ let cell = build_nft_transfer_cell(None, None, 0)?;
+
+ let parsed = NFTTransferMessage::parse(&cell)?;
+ assert_eq!(parsed.custom_payload, None);
+ assert_eq!(parsed.forward_ton_amount, "0");
+ assert_eq!(parsed.forward_payload, None);
+ assert_eq!(parsed.comment, None);
+
+ let message = NFTMessage::parse(&cell)?;
+ match message {
+ NFTMessage::NFTTransferMessage(inner) => assert_eq!(inner.query_id, "9"),
+ }
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_parse_nft_message_invalid_opcode() -> Result<()> {
+ let mut builder = CellBuilder::new();
+ builder.store_u32(32, 0x01020304)?;
+ let cell = builder.build()?.to_arc();
+
+ let err = NFTMessage::parse(&cell).unwrap_err();
+ assert!(matches!(err, TonCellError::InternalError(message) if message.contains("Invalid Op Code")));
+
+ Ok(())
+ }
+}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.