What changed, and why it matters
This commit upgrades the Rust compiler toolchain from one nightly version to a newer one and adjusts code to keep the project building and tests passing. It does not fix a known security bug or change how user funds are protected. Most of the file changes are new unit tests and small formatting fixes needed for the newer compiler.
No security action required. Treat as routine maintenance. Verify the new nightly toolchain and pinned cuprate tag build correctly in CI and on the target device before release.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit bumps rust-toolchain from nightly-2025-07-01 to nightly-2026-05-22 and updates the matching CMake default. It pins the cuprate-cryptonight dependency to a tagged revision (no_std@0.1.0) instead of a floating git branch. The bulk of the diff adds #[cfg(test)] modules across cosmos, ethereum, monero, and solana apps, plus a rustfmt-style reformat in rust/apps/zcash/build.rs. CI coverage thresholds are raised to reflect the new tests. No runtime logic that handles secrets, signatures, transaction parsing, or memory safety is altered outside of test code.
Changed components
Rust toolchain configuration (rust-toolchain, rust/CMakeLists.txt)CI coverage workflows (.github/workflows/rust-*-checks.yml)cuprate-cryptonight dependency pin (rust/apps/monero/Cargo.toml, rust/Cargo.lock)Test suites in rust/apps/cosmos, ethereum, monero, solanarust/apps/zcash/build.rs formattingInspect captured patch +511 / −13
diff --git a/.github/workflows/rust-cosmos-checks.yml b/.github/workflows/rust-cosmos-checks.yml
index aa4fc90..90cd534 100644
--- a/.github/workflows/rust-cosmos-checks.yml
+++ b/.github/workflows/rust-cosmos-checks.yml
@@ -27,4 +27,4 @@ jobs:
uses: taiki-e/install-action@cargo-llvm-cov
- name: Run rust/apps/cosmos
- run: cd rust/apps/cosmos && cargo +$RUST_TOOLCHAIN llvm-cov --fail-under-regions 65 --fail-under-functions 57 --fail-under-lines 74 --ignore-filename-regex 'keystore/*|utils/*'
+ run: cd rust/apps/cosmos && cargo +$RUST_TOOLCHAIN llvm-cov --fail-under-regions 77 --fail-under-functions 62 --fail-under-lines 74 --ignore-filename-regex 'keystore/*|utils/*'
diff --git a/.github/workflows/rust-ethereum-checks.yml b/.github/workflows/rust-ethereum-checks.yml
index 109f8b1..a8d3674 100644
--- a/.github/workflows/rust-ethereum-checks.yml
+++ b/.github/workflows/rust-ethereum-checks.yml
@@ -27,4 +27,4 @@ jobs:
uses: taiki-e/install-action@cargo-llvm-cov
- name: Run rust/apps/ethereum
- run: cd rust/apps/ethereum && cargo +$RUST_TOOLCHAIN llvm-cov --fail-under-regions 52 --fail-under-functions 56 --fail-under-lines 75 --ignore-filename-regex 'keystore/*|utils/*'
+ run: cd rust/apps/ethereum && cargo +$RUST_TOOLCHAIN llvm-cov --fail-under-regions 72 --fail-under-functions 64 --fail-under-lines 76 --ignore-filename-regex 'keystore/*|utils/*'
diff --git a/.github/workflows/rust-monero-checks.yml b/.github/workflows/rust-monero-checks.yml
index 47904c8..5be6e90 100644
--- a/.github/workflows/rust-monero-checks.yml
+++ b/.github/workflows/rust-monero-checks.yml
@@ -27,4 +27,4 @@ jobs:
uses: taiki-e/install-action@cargo-llvm-cov
- name: Run rust/apps/monero
- run: cd rust/apps/monero && cargo +$RUST_TOOLCHAIN llvm-cov --fail-under-regions 36 --fail-under-functions 41 --fail-under-lines 46 --ignore-filename-regex 'keystore/*'
+ run: cd rust/apps/monero && cargo +$RUST_TOOLCHAIN llvm-cov --fail-under-regions 53 --fail-under-functions 50 --fail-under-lines 50 --ignore-filename-regex 'keystore/*'
diff --git a/.github/workflows/rust-solana-checks.yml b/.github/workflows/rust-solana-checks.yml
index 3c83925..88686e5 100644
--- a/.github/workflows/rust-solana-checks.yml
+++ b/.github/workflows/rust-solana-checks.yml
@@ -27,4 +27,4 @@ jobs:
uses: taiki-e/install-action@cargo-llvm-cov
- name: Run rust/apps/solana
- run: cd rust/apps/solana && cargo +$RUST_TOOLCHAIN llvm-cov --fail-under-regions 49 --fail-under-functions 65 --fail-under-lines 68 --ignore-filename-regex 'keystore/*|utils/*'
+ run: cd rust/apps/solana && cargo +$RUST_TOOLCHAIN llvm-cov --fail-under-regions 66 --fail-under-functions 73 --fail-under-lines 68 --ignore-filename-regex 'keystore/*|utils/*'
diff --git a/.github/workflows/rust-utils-checks.yml b/.github/workflows/rust-utils-checks.yml
index 90a8564..cd4aeb9 100644
--- a/.github/workflows/rust-utils-checks.yml
+++ b/.github/workflows/rust-utils-checks.yml
@@ -27,4 +27,4 @@ jobs:
uses: taiki-e/install-action@cargo-llvm-cov
- name: Run rust/apps/utils
- run: cd rust/apps/utils && cargo +$RUST_TOOLCHAIN llvm-cov --fail-under-regions 91 --fail-under-functions 80 --fail-under-lines 84 --ignore-filename-regex 'keystore/*'
+ run: cd rust/apps/utils && cargo +$RUST_TOOLCHAIN llvm-cov --fail-under-regions 98 --fail-under-functions 100 --fail-under-lines 97 --ignore-filename-regex 'keystore/*|keystone.rs|macros.rs'
diff --git a/rust-toolchain b/rust-toolchain
index 32aa866..8705f35 100644
--- a/rust-toolchain
+++ b/rust-toolchain
@@ -1 +1 @@
-nightly-2025-07-01
+nightly-2026-05-22
diff --git a/rust/CMakeLists.txt b/rust/CMakeLists.txt
index 4d35b4c..5d05611 100644
--- a/rust/CMakeLists.txt
+++ b/rust/CMakeLists.txt
@@ -17,7 +17,7 @@ if(EXISTS ${RUST_TOOLCHAIN_FILE})
file(READ ${RUST_TOOLCHAIN_FILE} RUST_TOOLCHAIN)
string(STRIP ${RUST_TOOLCHAIN} RUST_TOOLCHAIN)
else()
- set(RUST_TOOLCHAIN "nightly-2025-07-01")
+ set(RUST_TOOLCHAIN "nightly-2026-05-22")
endif()
if(BUILD_ENVIRONMENT STREQUAL "SIMULATOR")
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index a0eee4b..15347e3 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -1426,7 +1426,7 @@ checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
[[package]]
name = "cuprate-cryptonight"
version = "0.1.0"
-source = "git+https://github.com/KeystoneHQ/cuprate#8ae09c3d83eb1a48050d7f46dc9ed5e0c6feedca"
+source = "git+https://github.com/KeystoneHQ/cuprate?tag=no_std%400.1.0#731832313f581c13a47a90deff16d399b838d1d6"
dependencies = [
"digest 0.10.7",
"groestl",
diff --git a/rust/apps/cosmos/src/proto_wrapper/serde_helper.rs b/rust/apps/cosmos/src/proto_wrapper/serde_helper.rs
index 5401224..d0b808f 100644
--- a/rust/apps/cosmos/src/proto_wrapper/serde_helper.rs
+++ b/rust/apps/cosmos/src/proto_wrapper/serde_helper.rs
@@ -35,3 +35,36 @@ pub mod base64_format {
.map(Into::into)
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use serde::{Deserialize, Serialize};
+
+ #[derive(Debug, PartialEq, Serialize, Deserialize)]
+ struct Base64Value {
+ #[serde(with = "base64_format")]
+ data: Vec<u8>,
+ }
+
+ #[test]
+ fn base64_helpers_roundtrip_binary_data() {
+ let data = vec![0x00, 0x01, 0xFE, 0xFF];
+ let encoded = to_base64(&data);
+
+ assert_eq!(encoded, "AAH+/w==");
+ assert_eq!(from_base64(&encoded).unwrap(), data);
+ }
+
+ #[test]
+ fn serde_base64_format_roundtrip_and_rejects_invalid_input() {
+ let value = Base64Value {
+ data: vec![1, 2, 3, 4],
+ };
+ let json = serde_json::to_string(&value).unwrap();
+
+ assert_eq!(json, r#"{"data":"AQIDBA=="}"#);
+ assert_eq!(serde_json::from_str::<Base64Value>(&json).unwrap(), value);
+ assert!(serde_json::from_str::<Base64Value>(r#"{"data":"%%%"}"#).is_err());
+ }
+}
diff --git a/rust/apps/ethereum/src/eip712/serde_helpers.rs b/rust/apps/ethereum/src/eip712/serde_helpers.rs
index 47d9f4d..a370b16 100644
--- a/rust/apps/ethereum/src/eip712/serde_helpers.rs
+++ b/rust/apps/ethereum/src/eip712/serde_helpers.rs
@@ -281,3 +281,68 @@ where
Ok(num)
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use serde::Deserialize;
+
+ #[derive(Deserialize)]
+ struct NumericValues {
+ #[serde(deserialize_with = "deserialize_stringified_numeric")]
+ numeric: U256,
+ #[serde(deserialize_with = "deserialize_stringified_numeric_opt")]
+ numeric_opt: Option<U256>,
+ #[serde(deserialize_with = "deserialize_stringified_u64")]
+ value_u64: u64,
+ #[serde(deserialize_with = "deserialize_stringified_eth_u64")]
+ eth_u64: U64,
+ #[serde(deserialize_with = "deserialize_number_seq")]
+ sequence: U256,
+ }
+
+ #[test]
+ fn numeric_conversions_support_strings_hex_and_numbers() {
+ assert_eq!(U256::from(Numeric::Num(7)), U256::from(7));
+ assert_eq!("42".parse::<Numeric>().map(U256::from).unwrap(), 42.into());
+ assert_eq!(
+ "0x2a".parse::<Numeric>().map(U256::from).unwrap(),
+ 42.into()
+ );
+ assert!("not-a-number".parse::<Numeric>().is_err());
+
+ let decimal = U256::try_from(StringifiedNumeric::String("123".into())).unwrap();
+ let hex = U256::try_from(StringifiedNumeric::String("0x7b".into())).unwrap();
+ let number = U256::try_from(StringifiedNumeric::Num(7.into())).unwrap();
+ assert_eq!(decimal, U256::from(123));
+ assert_eq!(hex, U256::from(123));
+ assert_eq!(number, U256::from(7));
+
+ let signed = i128::try_from(StringifiedNumeric::String("-9".into())).unwrap();
+ assert_eq!(signed, -9);
+ let bytes: [u8; 32] = StringifiedNumeric::String("255".into()).try_into().unwrap();
+ assert_eq!(bytes[31], 255);
+ let small: U64 = StringifiedNumeric::String("42".into()).try_into().unwrap();
+ assert_eq!(small, U64::from(42));
+ }
+
+ #[test]
+ fn serde_numeric_helpers_cover_supported_representations() {
+ let values: NumericValues = serde_json::from_str(
+ r#"{
+ "numeric":"0x2a",
+ "numeric_opt":"43",
+ "value_u64":44,
+ "eth_u64":"45",
+ "sequence":["0x2e"]
+ }"#,
+ )
+ .unwrap();
+
+ assert_eq!(values.numeric, U256::from(42));
+ assert_eq!(values.numeric_opt, Some(U256::from(43)));
+ assert_eq!(values.value_u64, 44);
+ assert_eq!(values.eth_u64, U64::from(45));
+ assert_eq!(values.sequence, U256::from(46));
+ }
+}
diff --git a/rust/apps/ethereum/src/eip712/types/bytes.rs b/rust/apps/ethereum/src/eip712/types/bytes.rs
index 5d2462d..61df8ed 100644
--- a/rust/apps/ethereum/src/eip712/types/bytes.rs
+++ b/rust/apps/ethereum/src/eip712/types/bytes.rs
@@ -96,3 +96,36 @@ where
.map(Into::into)
.map_err(|e| serde::de::Error::custom(e.to_string()))
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use alloc::vec;
+
+ #[test]
+ fn bytes_parse_debug_and_conversions() {
+ let prefixed: Bytes = "0xdeadbeef".parse().unwrap();
+ let unprefixed: Bytes = "deadbeef".parse().unwrap();
+
+ assert_eq!(prefixed, unprefixed);
+ assert_eq!(format!("{prefixed:?}"), "Bytes(0xdeadbeef)");
+ assert_eq!(Bytes::new(), Bytes::default());
+ assert_eq!(Bytes::from(vec![1, 2]).0.as_ref(), &[1, 2]);
+ assert_eq!(
+ Bytes::from(bytes::Bytes::from_static(&[3, 4])).0.as_ref(),
+ &[3, 4]
+ );
+ assert_eq!(Bytes::from_static(&[5, 6]).0.as_ref(), &[5, 6]);
+ assert!("0xnot-hex".parse::<Bytes>().is_err());
+ }
+
+ #[test]
+ fn bytes_serde_accepts_prefixed_and_unprefixed_hex() {
+ let value = Bytes::from(vec![0x12, 0xAB]);
+
+ assert_eq!(serde_json::to_string(&value).unwrap(), r#""0x12ab""#);
+ assert_eq!(serde_json::from_str::<Bytes>(r#""0x12ab""#).unwrap(), value);
+ assert_eq!(serde_json::from_str::<Bytes>(r#""12ab""#).unwrap(), value);
+ assert!(serde_json::from_str::<Bytes>(r#""xyz""#).is_err());
+ }
+}
diff --git a/rust/apps/monero/Cargo.toml b/rust/apps/monero/Cargo.toml
index 9281903..c968639 100644
--- a/rust/apps/monero/Cargo.toml
+++ b/rust/apps/monero/Cargo.toml
@@ -19,7 +19,7 @@ chacha20 = { version = "0.9.1", default-features = false }
rand_chacha = { workspace = true }
thiserror = { workspace = true }
keystore = { workspace = true, default-features = false }
-cuprate-cryptonight = { git = "https://github.com/KeystoneHQ/cuprate", default-features = false, features = ["no_std"] }
+cuprate-cryptonight = { git = "https://github.com/KeystoneHQ/cuprate", tag = "no_std@0.1.0", default-features = false, features = ["no_std"] }
monero-serai = { git = "https://github.com/KeystoneHQ/serai", default-features = false }
monero-wallet = { git = "https://github.com/KeystoneHQ/serai", default-features = false }
diff --git a/rust/apps/monero/src/extra.rs b/rust/apps/monero/src/extra.rs
index dbccb9c..2d5f5da 100644
--- a/rust/apps/monero/src/extra.rs
+++ b/rust/apps/monero/src/extra.rs
@@ -90,3 +90,57 @@ impl Extra {
res
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use alloc::vec;
+ use curve25519_dalek::scalar::Scalar;
+
+ #[test]
+ fn serialize_all_extra_field_types() {
+ let public_key = EdwardsPoint::mul_base(&Scalar::ONE);
+ let additional_key = EdwardsPoint::mul_base(&Scalar::from(2u64));
+ let merge_mining_data = [0xAB; 32];
+ let extra = Extra(vec![
+ ExtraField::Padding(2),
+ ExtraField::PublicKey(public_key),
+ ExtraField::Nonce(vec![0x10, 0x20]),
+ ExtraField::MergeMining(32, merge_mining_data),
+ ExtraField::PublicKeys(vec![additional_key]),
+ ExtraField::MysteriousMinergate(vec![0xCA, 0xFE]),
+ ]);
+
+ let serialized = extra.serialize();
+ let mut expected = vec![0x00, 0x00, 0x00];
+ expected.push(0x01);
+ expected.extend_from_slice(&public_key.compress().to_bytes());
+ expected.extend_from_slice(&[0x02, 0x02, 0x10, 0x20]);
+ expected.extend_from_slice(&[0x03, 32]);
+ expected.extend_from_slice(&merge_mining_data);
+ expected.extend_from_slice(&[0x04, 0x01]);
+ expected.extend_from_slice(&additional_key.compress().to_bytes());
+ expected.extend_from_slice(&[0xDE, 0xCA, 0xFE]);
+
+ assert_eq!(serialized, expected);
+ }
+
+ #[test]
+ fn new_and_push_nonce_preserve_field_order() {
+ let public_key = EdwardsPoint::mul_base(&Scalar::ONE);
+ let additional_key = EdwardsPoint::mul_base(&Scalar::from(2u64));
+
+ let mut without_additional = Extra::new(public_key, vec![]);
+ without_additional.push_nonce(vec![7]);
+ assert!(matches!(
+ without_additional.0.as_slice(),
+ [ExtraField::PublicKey(_), ExtraField::Nonce(nonce)] if nonce == &[7]
+ ));
+
+ let with_additional = Extra::new(public_key, vec![additional_key]);
+ assert!(matches!(
+ with_additional.0.as_slice(),
+ [ExtraField::PublicKey(_), ExtraField::PublicKeys(keys)] if keys == &[additional_key]
+ ));
+ }
+}
diff --git a/rust/apps/monero/src/utils/io.rs b/rust/apps/monero/src/utils/io.rs
index 1b5521c..9fc7208 100644
--- a/rust/apps/monero/src/utils/io.rs
+++ b/rust/apps/monero/src/utils/io.rs
@@ -160,3 +160,59 @@ pub fn read_next_u8_32(bytes: &[u8], offset: &mut usize) -> [u8; 32] {
data
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn primitive_readers_advance_the_offset() {
+ let mut bytes = vec![0x7F];
+ bytes.extend_from_slice(&0x1234_5678u32.to_le_bytes());
+ bytes.extend_from_slice(&0x0123_4567_89AB_CDEFu64.to_le_bytes());
+ bytes.push(1);
+ bytes.extend_from_slice(&[9, 8, 7]);
+ bytes.extend_from_slice(&[0xAA; 32]);
+
+ let mut offset = 0;
+ assert_eq!(read_next_u8(&bytes, &mut offset), 0x7F);
+ assert_eq!(read_next_u32(&bytes, &mut offset), 0x1234_5678);
+ assert_eq!(read_next_u64(&bytes, &mut offset), 0x0123_4567_89AB_CDEF);
+ assert!(read_next_bool(&bytes, &mut offset));
+ assert_eq!(read_next_vec_u8(&bytes, &mut offset, 3), vec![9, 8, 7]);
+ assert_eq!(read_next_u8_32(&bytes, &mut offset), [0xAA; 32]);
+ assert_eq!(offset, bytes.len());
+ }
+
+ #[test]
+ fn varinteger_roundtrip_updates_the_offset() {
+ for value in [0, 127, 128, 16_384, u32::MAX as u64, u64::MAX] {
+ let encoded = write_varinteger(value);
+ let mut offset = 0;
+
+ assert_eq!(read_varinteger(&encoded, &mut offset), value);
+ assert_eq!(offset, encoded.len());
+ }
+ }
+
+ #[test]
+ fn destination_entry_roundtrip() {
+ let entry = TxDestinationEntry {
+ original: String::from("48A1-test-address"),
+ amount: 12_345_678_901,
+ addr: AccountPublicAddress {
+ spend_public_key: [0x11; 32],
+ view_public_key: [0x22; 32],
+ },
+ is_subaddress: true,
+ is_integrated: false,
+ };
+
+ let encoded = write_tx_destination_entry(&entry);
+ let mut offset = 0;
+ let decoded = read_next_tx_destination_entry(&encoded, &mut offset);
+
+ assert_eq!(decoded, entry);
+ assert_eq!(offset, encoded.len());
+ }
+}
diff --git a/rust/apps/solana/src/errors.rs b/rust/apps/solana/src/errors.rs
index 04b32ec..0aaaee5 100644
--- a/rust/apps/solana/src/errors.rs
+++ b/rust/apps/solana/src/errors.rs
@@ -57,3 +57,38 @@ impl From<serde_json::Error> for SolanaError {
))
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn error_variants_have_actionable_messages() {
+ let errors = [
+ SolanaError::AddressError("bad address".to_string()),
+ SolanaError::KeystoreError("locked".to_string()),
+ SolanaError::UnsupportedProgram("program".to_string()),
+ SolanaError::InvalidData("message".to_string()),
+ SolanaError::ProgramError("invalid instruction".to_string()),
+ SolanaError::AccountNotFound("source".to_string()),
+ SolanaError::ParseTxError("invalid transaction".to_string()),
+ ];
+
+ for error in errors {
+ assert!(!error.to_string().is_empty());
+ }
+ }
+
+ #[test]
+ fn external_errors_keep_their_context() {
+ let hex_error = hex::decode("xyz").unwrap_err();
+ assert!(SolanaError::from(hex_error)
+ .to_string()
+ .contains("hex operation failed"));
+
+ let json_error = serde_json::from_str::<serde_json::Value>("{").unwrap_err();
+ assert!(SolanaError::from(json_error)
+ .to_string()
+ .contains("serde json operation failed"));
+ }
+}
diff --git a/rust/apps/solana/src/instruction.rs b/rust/apps/solana/src/instruction.rs
index b62385d..34093aa 100644
--- a/rust/apps/solana/src/instruction.rs
+++ b/rust/apps/solana/src/instruction.rs
@@ -143,3 +143,46 @@ impl Instruction {
.map_err(|e| ProgramError(e.to_string()))
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const PROGRAM_IDS: [&str; 8] = [
+ "11111111111111111111111111111111",
+ "Vote111111111111111111111111111111111111111",
+ "Stake11111111111111111111111111111111111111",
+ "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
+ "SwapsVeCiPHMUAtzQWZw7RjsKjgCjhwU55QGu4U1Szw",
+ "LendZqTs7gn5CTSJU1jWKhKuVpjJGom45nnwPb2AMTi",
+ "SQDS4ep65T869zMMBKyuUq6aD6EgTu8psMjkvj52pCf",
+ "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
+ ];
+
+ #[test]
+ fn recognizes_supported_program_ids_and_rejects_unknown_programs() {
+ for program_id in PROGRAM_IDS {
+ assert!(SupportedProgram::from_program_id(program_id.to_string()).is_ok());
+ }
+
+ let error = SupportedProgram::from_program_id("unknown".to_string()).unwrap_err();
+ assert_eq!(error.to_string(), "Program `unknown` is not supported yet");
+ }
+
+ #[test]
+ fn read_and_parse_reject_malformed_instruction_data() {
+ assert!(matches!(
+ Instruction::read(&mut vec![]),
+ Err(SolanaError::InvalidData(_))
+ ));
+
+ for program_id in &PROGRAM_IDS[..6] {
+ let instruction = Instruction {
+ program_index: 0,
+ account_indexes: vec![],
+ data: vec![],
+ };
+ assert!(instruction.parse(program_id, vec![]).is_err());
+ }
+ }
+}
diff --git a/rust/apps/solana/src/parser/detail.rs b/rust/apps/solana/src/parser/detail.rs
index 8b33ea3..7b3212c 100644
--- a/rust/apps/solana/src/parser/detail.rs
+++ b/rust/apps/solana/src/parser/detail.rs
@@ -1228,3 +1228,136 @@ pub struct SolanaDetail {
#[serde(flatten)]
pub kind: ProgramDetail,
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn serializes_representative_program_details() {
+ let details = vec![
+ ProgramDetail::SystemTransfer(ProgramDetailSystemTransfer::default()),
+ ProgramDetail::SystemTransferWithSeed(ProgramDetailSystemTransferWithSeed::default()),
+ ProgramDetail::SystemCreateAccount(ProgramDetailSystemCreateAccount::default()),
+ ProgramDetail::SystemAssign(ProgramDetailSystemAssign::default()),
+ ProgramDetail::SystemAssignWithSeed(ProgramDetailSystemAssignWithSeed::default()),
+ ProgramDetail::SystemCreateAccountWithSeed(
+ ProgramDetailSystemCreateAccountWithSeed::default(),
+ ),
+ ProgramDetail::SystemAdvanceNonceAccount(
+ ProgramDetailSystemAdvanceNonceAccount::default(),
+ ),
+ ProgramDetail::SystemWithdrawNonceAccount(
+ ProgramDetailSystemWithdrawNonceAccount::default(),
+ ),
+ ProgramDetail::SystemInitializeNonceAccount(
+ ProgramDetailSystemInitializeNonceAccount::default(),
+ ),
+ ProgramDetail::SystemAuthorizeNonceAccount(
+ ProgramDetailSystemAuthorizeNonceAccount::default(),
+ ),
+ ProgramDetail::SystemAllocate(ProgramDetailSystemAllocate::default()),
+ ProgramDetail::SystemAllocateWithSeed(ProgramDetailSystemAllocateWithSeed::default()),
+ ProgramDetail::SystemUpgradeNonceAccount(
+ ProgramDetailSystemUpgradeNonceAccount::default(),
+ ),
+ ProgramDetail::VoteInitializeAccount(ProgramDetailVoteInitAccount::default()),
+ ProgramDetail::VoteAuthorize(ProgramDetailVoteAuthorize::default()),
+ ProgramDetail::VoteVote(ProgramDetailVoteVote::default()),
+ ProgramDetail::VoteWithdraw(ProgramDetailVoteWithdraw::default()),
+ ProgramDetail::VoteUpdateValidatorIdentity(
+ ProgramDetailVoteUpdateValidatorIdentity::default(),
+ ),
+ ProgramDetail::VoteUpdateCommission(ProgramDetailVoteUpdateCommission::default()),
+ ProgramDetail::VoteVoteSwitch(ProgramDetailVoteVoteSwitch::default()),
+ ProgramDetail::VoteAuthorizeChecked(ProgramDetailVoteAuthorizeChecked::default()),
+ ProgramDetail::VoteAuthorizeWithSeed(ProgramDetailVoteAuthorizeWithSeed::default()),
+ ProgramDetail::VoteAuthorizeCheckedWithSeed(
+ ProgramDetailVoteAuthorizeCheckedWithSeed::default(),
+ ),
+ ProgramDetail::TokenInitializeMint(ProgramDetailTokenInitializeMint::default()),
+ ProgramDetail::TokenInitializeAccount(ProgramDetailTokenInitializeAccount::default()),
+ ProgramDetail::TokenInitializeMultisig(ProgramDetailTokenInitializeMultisig::default()),
+ ProgramDetail::TokenTransfer(ProgramDetailTokenTransfer::default()),
+ ProgramDetail::TokenApprove(ProgramDetailTokenApprove::default()),
+ ProgramDetail::TokenRevoke(ProgramDetailTokenRevoke::default()),
+ ProgramDetail::TokenSetAuthority(ProgramDetailTokenSetAuthority::default()),
+ ProgramDetail::TokenMintTo(ProgramDetailTokenMintTo::default()),
+ ProgramDetail::TokenBurn(ProgramDetailTokenBurn::default()),
+ ProgramDetail::TokenCloseAccount(ProgramDetailTokenCloseAccount::default()),
+ ProgramDetail::TokenFreezeAccount(ProgramDetailTokenFreezeAccount::default()),
+ ProgramDetail::TokenThawAccount(ProgramDetailTokenThawAccount::default()),
+ ProgramDetail::TokenTransferChecked(ProgramDetailTokenTransferChecked::default()),
+ ProgramDetail::TokenApproveChecked(ProgramDetailTokenApproveChecked::default()),
+ ProgramDetail::TokenMintToChecked(ProgramDetailTokenMintToChecked::default()),
+ ProgramDetail::TokenBurnChecked(ProgramDetailTokenBurnChecked::default()),
+ ProgramDetail::TokenInitializeAccount2(ProgramDetailTokenInitializeAccount2::default()),
+ ProgramDetail::TokenInitializeAccount3(ProgramDetailTokenInitializeAccount3::default()),
+ ProgramDetail::TokenInitializeMultisig2(
+ ProgramDetailTokenInitializeMultisig2::default(),
+ ),
+ ProgramDetail::TokenInitializeMint2(ProgramDetailTokenInitializeMint2::default()),
+ ProgramDetail::TokenSyncNative(ProgramDetailTokenSyncNative::default()),
+ ProgramDetail::StakeDelegateStake(ProgramDetailStakeDelegateStake::default()),
+ ProgramDetail::StakeWithdraw(ProgramDetailStakeWithdraw::default()),
+ ProgramDetail::StakeInitialize(ProgramDetailStakeInitialize::default()),
+ ProgramDetail::StakeAuthorize(ProgramDetailStakeAuthorize::default()),
+ ProgramDetail::StakeSplit(ProgramDetailStakeSplit::default()),
+ ProgramDetail::StakeDeactivate(ProgramDetailStakeDeactivate::default()),
+ ProgramDetail::StakeSetLockup(ProgramDetailStakeSetLockup::default()),
+ ProgramDetail::StakeMerge(ProgramDetailStakeMerge::default()),
+ ProgramDetail::StakeAuthorizeWithSeed(ProgramDetailStakeAuthorizeWithSeed::default()),
+ ProgramDetail::StakeInitializeChecked(ProgramDetailStakeInitializeChecked::default()),
+ ProgramDetail::StakeAuthorizeChecked(ProgramDetailStakeAuthorizeChecked::default()),
+ ProgramDetail::StakeAuthorizeCheckedWithSeed(
+ ProgramDetailStakeAuthorizeCheckedWithSeed::default(),
+ ),
+ ProgramDetail::StakeSetLockupChecked(ProgramDetailStakeSetLockupChecked::default()),
+ ProgramDetail::StakeGetMinimumDelegation(
+ ProgramDetailStakeGetMinimumDelegation::default(),
+ ),
+ ProgramDetail::StakeDeactivateDelinquent(
+ ProgramDetailStakeDeactivateDelinquent::default(),
+ ),
+ ProgramDetail::Unknown(ProgramDetailUnknown::default()),
+ ProgramDetail::GeneralUnknown(ProgramDetailGeneralUnknown::default()),
+ ProgramDetail::RawUnknown(ProgramDetailRawUnknown::default()),
+ ProgramDetail::Instruction(ProgramDetailInstruction::default()),
+ ];
+
+ for kind in details {
+ let detail = SolanaDetail {
+ common: CommonDetail {
+ program: "test-program".to_string(),
+ method: String::new(),
+ },
+ kind,
+ };
+ let json = serde_json::to_string(&detail).unwrap();
+ assert!(json.contains(r#""program":"test-program""#));
+ assert!(!json.contains(r#""method""#));
+ }
+ }
+
+ #[test]
+ fn unknown_detail_conversions_keep_public_information() {
+ let detail = ProgramDetailUnknown {
+ program_index: 7,
+ account_indexes: vec![1, 2],
+ data: "data".to_string(),
+ reason: "unsupported".to_string(),
+ accounts: "accounts".to_string(),
+ program_account: "program".to_string(),
+ };
+
+ let general = ProgramDetailGeneralUnknown::from_unknown_detail(&detail);
+ let raw = ProgramDetailRawUnknown::from_unknown_detail(&detail);
+
+ assert_eq!(general.reason, "unsupported");
+ assert_eq!(raw.program_index, 7);
+ assert_eq!(raw.account_indexes, vec![1, 2]);
+ assert_eq!(raw.data, "data");
+ assert_eq!(raw.accounts, "accounts");
+ assert_eq!(raw.program_account, "program");
+ }
+}
diff --git a/rust/apps/solana/src/parser/overview.rs b/rust/apps/solana/src/parser/overview.rs
index 4b05fa1..374bee7 100644
--- a/rust/apps/solana/src/parser/overview.rs
+++ b/rust/apps/solana/src/parser/overview.rs
@@ -112,3 +112,15 @@ pub enum SolanaOverview {
SplTokenTransfer(ProgramOverviewSplTokenTransfer),
JupiterV6SwapOverview(JupiterV6SwapOverview),
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn unknown_overview_uses_user_facing_description() {
+ let overview = ProgramOverviewUnknown::default();
+
+ assert_eq!(overview.description, "This transaction can not be decoded");
+ }
+}
diff --git a/rust/apps/solana/src/parser/structs.rs b/rust/apps/solana/src/parser/structs.rs
index 8b5838c..4d4002e 100644
--- a/rust/apps/solana/src/parser/structs.rs
+++ b/rust/apps/solana/src/parser/structs.rs
@@ -35,3 +35,25 @@ impl ToString for SolanaTxDisplayType {
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn display_types_have_stable_labels() {
+ let cases = [
+ (SolanaTxDisplayType::Transfer, "Transfer"),
+ (SolanaTxDisplayType::TokenTransfer, "TokenTransfer"),
+ (SolanaTxDisplayType::Vote, "Vote"),
+ (SolanaTxDisplayType::General, "General"),
+ (SolanaTxDisplayType::Unknown, "Unknown"),
+ (SolanaTxDisplayType::SquadsV4, "SquadsV4"),
+ (SolanaTxDisplayType::JupiterV6, "JupiterV6"),
+ ];
+
+ for (display_type, expected) in cases {
+ assert_eq!(display_type.to_string(), expected);
+ }
+ }
+}
diff --git a/rust/apps/zcash/build.rs b/rust/apps/zcash/build.rs
index ff0e0b3..e8ca0b2 100644
--- a/rust/apps/zcash/build.rs
+++ b/rust/apps/zcash/build.rs
@@ -23,12 +23,24 @@ fn main() {
let line = line.trim();
if let Some(val) = line.strip_prefix("#define SOFTWARE_VERSION_MAJOR ") {
if !val.starts_with('(') {
- major = Some(val.trim().parse().expect("SOFTWARE_VERSION_MAJOR is not a valid u8"));
+ major = Some(
+ val.trim()
+ .parse()
+ .expect("SOFTWARE_VERSION_MAJOR is not a valid u8"),
+ );
}
} else if let Some(val) = line.strip_prefix("#define SOFTWARE_VERSION_MINOR ") {
- minor = Some(val.trim().parse().expect("SOFTWARE_VERSION_MINOR is not a valid u8"));
+ minor = Some(
+ val.trim()
+ .parse()
+ .expect("SOFTWARE_VERSION_MINOR is not a valid u8"),
+ );
} else if let Some(val) = line.strip_prefix("#define SOFTWARE_VERSION_BUILD ") {
- build = Some(val.trim().parse().expect("SOFTWARE_VERSION_BUILD is not a valid u8"));
+ build = Some(
+ val.trim()
+ .parse()
+ .expect("SOFTWARE_VERSION_BUILD is not a valid u8"),
+ );
}
}
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.