Skip to content
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
22 changes: 22 additions & 0 deletions src/chain_parsers/visualsign-solana/clippy.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Determinism enforcement for visualsign-solana.
#
# The rendered SignablePayload JSON is consumed downstream for hashing and
# wallet display, so iteration order over any map that ends up in the output
# (e.g. `named_accounts` in each preset's `mod.rs`) MUST be stable across
# runs. HashMap/HashSet's randomized hasher silently breaks this.
#
# Use BTreeMap/BTreeSet instead. The performance delta is negligible at the
# sizes we use these maps (typically <100 entries) and the determinism
# guarantee is worth the trade.
#
# This rule is intentionally crate-wide rather than scoped to `presets/` —
# even maps that look "lookup-only" (e.g. config.rs routing tables, the IDL
# registry's internal maps) can leak iteration order via debug formatting,
# error messages, or future code paths that walk them. Forcing BTreeMap
# everywhere keeps the determinism story simple and review-friendly: no
# `#[allow(clippy::disallowed_types)]` escape hatches in the codebase.

disallowed-types = [
{ path = "std::collections::HashMap", reason = "use BTreeMap — iteration order affects rendered SignablePayload output and breaks deterministic hashing. See clippy.toml comment." },
{ path = "std::collections::HashSet", reason = "use BTreeSet for the same reason as HashMap." },
]
4 changes: 2 additions & 2 deletions src/chain_parsers/visualsign-solana/src/core/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::HashMap;
use std::collections::BTreeMap;

use ::visualsign::AnnotatedPayloadField;
use ::visualsign::errors::VisualSignError;
Expand Down Expand Up @@ -88,7 +88,7 @@ impl<'a> VisualizerContext<'a> {
}

pub struct SolanaIntegrationConfigData {
pub programs: HashMap<&'static str, HashMap<&'static str, Vec<&'static str>>>,
pub programs: BTreeMap<&'static str, BTreeMap<&'static str, Vec<&'static str>>>,
}
Comment on lines 90 to 92
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the get_token_lookup_table reply: visualsign-solana is workspace-internal (path-dep only, no external consumers), so the SolanaIntegrationConfigData.programs field type change is contained. No version bump or compatibility shim needed.

pub trait SolanaIntegrationConfig {
fn new() -> Self
Expand Down
11 changes: 7 additions & 4 deletions src/chain_parsers/visualsign-solana/src/core/txtypes/v0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub fn decode_v0_transfers(
) -> Result<Vec<AnnotatedPayloadField>, VisualSignError> {
use crate::presets::jupiter_swap::{JUPITER_IDL_JSON, JUPITER_PROGRAM_ID};
use solana_parser::solana::parser::parse_transaction;
use std::collections::HashMap;
use std::collections::BTreeMap;

// Serialize the full versioned transaction
let transaction_bytes = bincode::serialize(versioned_tx).map_err(|e| {
Expand All @@ -29,18 +29,21 @@ pub fn decode_v0_transfers(
// Override the stale Jupiter v6 IDL bundled in solana_parser with our locally
// refreshed copy so that newer instructions (e.g. route_v2) don't cause the
// whole-tx decode to bail with "no matching instruction discriminator".
let mut custom_idls: HashMap<String, (String, bool)> = HashMap::new();
let mut custom_idls: BTreeMap<String, (String, bool)> = BTreeMap::new();
custom_idls.insert(
JUPITER_PROGRAM_ID.to_string(),
(JUPITER_IDL_JSON.to_string(), true),
);

let is_full_transaction = true; // true because we're passing full tx and not message
// Parse using solana-parser which handles V0 transactions and lookup tables
// Parse using solana-parser which handles V0 transactions and lookup tables.
// Boundary conversion: solana_parser's `parse_transaction` API takes a HashMap;
// we collect into the inferred parameter type so our local code keeps working
// with BTreeMap (per crate-wide determinism rule in clippy.toml).
let parsed_transaction = parse_transaction(
hex::encode(transaction_bytes),
is_full_transaction,
Some(custom_idls),
Some(custom_idls.into_iter().collect()),
)
.map_err(|e| {
VisualSignError::ParseError(visualsign::vsptrait::TransactionParseError::DecodeError(
Expand Down
6 changes: 3 additions & 3 deletions src/chain_parsers/visualsign-solana/src/core/visualsign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use solana_sdk::{
message::VersionedMessage,
transaction::{Transaction as SolanaTransaction, VersionedTransaction},
};
use std::collections::HashMap;
use std::collections::BTreeMap;
use visualsign::{
SignablePayload, SignablePayloadField, SignablePayloadFieldCommon,
encodings::SupportedEncodings,
Expand Down Expand Up @@ -89,8 +89,8 @@ impl SolanaTransactionWrapper {

/// Extract IDL mappings from VisualSignOptions metadata
///
/// Returns a HashMap of program_id (base58 string) -> (IDL JSON string, program name)
fn extract_idl_mappings(options: &VisualSignOptions) -> HashMap<String, (String, String)> {
/// Returns a BTreeMap of program_id (base58 string) -> (IDL JSON string, program name)
fn extract_idl_mappings(options: &VisualSignOptions) -> BTreeMap<String, (String, String)> {
options
.metadata
.as_ref()
Expand Down
24 changes: 12 additions & 12 deletions src/chain_parsers/visualsign-solana/src/idl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

use solana_parser::{CustomIdl, CustomIdlConfig, Idl, ProgramType, decode_idl_data};
use solana_sdk::pubkey::Pubkey;
use std::collections::HashMap;
use std::collections::BTreeMap;

/// Registry for managing program IDLs (program_id -> CustomIdlConfig)
///
Expand All @@ -19,20 +19,20 @@ use std::collections::HashMap;
pub struct IdlRegistry {
/// Maps program_id (base58 string) -> CustomIdlConfig
/// These are user-provided IDLs that override built-ins
configs: HashMap<String, CustomIdlConfig>,
configs: BTreeMap<String, CustomIdlConfig>,
/// Maps program_id -> human-readable name (extracted from IDL or provided by user)
names: HashMap<String, String>,
names: BTreeMap<String, String>,
/// Maps program_id -> IDL name from metadata.name in JSON
idl_names: HashMap<String, String>,
idl_names: BTreeMap<String, String>,
}

impl IdlRegistry {
/// Create empty registry (built-in IDLs handled by solana_parser directly)
pub fn new() -> Self {
Self {
configs: HashMap::new(),
names: HashMap::new(),
idl_names: HashMap::new(),
configs: BTreeMap::new(),
names: BTreeMap::new(),
idl_names: BTreeMap::new(),
}
}

Expand All @@ -45,11 +45,11 @@ impl IdlRegistry {
/// * `Ok(IdlRegistry)` with the custom IDLs configured to override built-ins
/// * `Err` if any IDL JSON is invalid
pub fn from_idl_mappings(
idl_mappings: HashMap<String, (String, String)>,
idl_mappings: BTreeMap<String, (String, String)>,
) -> Result<Self, Box<dyn std::error::Error>> {
let mut configs = HashMap::new();
let mut names = HashMap::new();
let mut idl_names = HashMap::new();
let mut configs = BTreeMap::new();
let mut names = BTreeMap::new();
let mut idl_names = BTreeMap::new();

for (program_id, (idl_json, program_name)) in idl_mappings {
// Extract IDL name from JSON metadata
Expand Down Expand Up @@ -82,7 +82,7 @@ impl IdlRegistry {
///
/// Reserved for future integration with solana_parser's batch transaction parsing.
#[allow(dead_code)]
pub fn get_all_configs(&self) -> &HashMap<String, CustomIdlConfig> {
pub fn get_all_configs(&self) -> &BTreeMap<String, CustomIdlConfig> {
&self.configs
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ impl SolanaIntegrationConfig for AssociatedTokenAccountConfig {
fn data(&self) -> &SolanaIntegrationConfigData {
static DATA: std::sync::OnceLock<SolanaIntegrationConfigData> = std::sync::OnceLock::new();
DATA.get_or_init(|| {
let mut programs = std::collections::HashMap::new();
let mut ata_instructions = std::collections::HashMap::new();
let mut programs = std::collections::BTreeMap::new();
let mut ata_instructions = std::collections::BTreeMap::new();
ata_instructions.insert("*", vec!["*"]);
programs.insert(
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ impl SolanaIntegrationConfig for ComputeBudgetConfig {
fn data(&self) -> &SolanaIntegrationConfigData {
static DATA: std::sync::OnceLock<SolanaIntegrationConfigData> = std::sync::OnceLock::new();
DATA.get_or_init(|| {
let mut programs = std::collections::HashMap::new();
let mut compute_budget_instructions = std::collections::HashMap::new();
let mut programs = std::collections::BTreeMap::new();
let mut compute_budget_instructions = std::collections::BTreeMap::new();
compute_budget_instructions.insert("*", vec!["*"]);
programs.insert(
"ComputeBudget111111111111111111111111111111",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::DFLOW_AGGREGATOR_PROGRAM_ID;
use crate::core::{SolanaIntegrationConfig, SolanaIntegrationConfigData};
use std::collections::HashMap;
use std::collections::BTreeMap;

pub struct DflowAggregatorConfig;

Expand All @@ -12,8 +12,8 @@ impl SolanaIntegrationConfig for DflowAggregatorConfig {
fn data(&self) -> &SolanaIntegrationConfigData {
static DATA: std::sync::OnceLock<SolanaIntegrationConfigData> = std::sync::OnceLock::new();
DATA.get_or_init(|| {
let mut programs = HashMap::new();
let mut instructions = HashMap::new();
let mut programs = BTreeMap::new();
let mut instructions = BTreeMap::new();
instructions.insert("*", vec!["*"]);
programs.insert(DFLOW_AGGREGATOR_PROGRAM_ID, instructions);
SolanaIntegrationConfigData { programs }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ mod tests {
parsed: SolanaParsedInstructionData {
instruction_name: "test_ix".to_string(),
discriminator: "00".to_string(),
named_accounts: std::collections::HashMap::new(),
named_accounts: Default::default(),
program_call_args: serde_json::Map::new(),
idl_source: IdlSource::Custom,
idl_hash: String::new(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::JUPITER_BORROW_PROGRAM_ID;
use crate::core::{SolanaIntegrationConfig, SolanaIntegrationConfigData};
use std::collections::HashMap;
use std::collections::BTreeMap;

pub struct JupiterBorrowConfig;

Expand All @@ -12,8 +12,8 @@ impl SolanaIntegrationConfig for JupiterBorrowConfig {
fn data(&self) -> &SolanaIntegrationConfigData {
static DATA: std::sync::OnceLock<SolanaIntegrationConfigData> = std::sync::OnceLock::new();
DATA.get_or_init(|| {
let mut programs = HashMap::new();
let mut instructions = HashMap::new();
let mut programs = BTreeMap::new();
let mut instructions = BTreeMap::new();
instructions.insert("*", vec!["*"]);
programs.insert(JUPITER_BORROW_PROGRAM_ID, instructions);
SolanaIntegrationConfigData { programs }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::JUPITER_EARN_PROGRAM_ID;
use crate::core::{SolanaIntegrationConfig, SolanaIntegrationConfigData};
use std::collections::HashMap;
use std::collections::BTreeMap;

pub struct JupiterEarnConfig;

Expand All @@ -12,8 +12,8 @@ impl SolanaIntegrationConfig for JupiterEarnConfig {
fn data(&self) -> &SolanaIntegrationConfigData {
static DATA: std::sync::OnceLock<SolanaIntegrationConfigData> = std::sync::OnceLock::new();
DATA.get_or_init(|| {
let mut programs = HashMap::new();
let mut instructions = HashMap::new();
let mut programs = BTreeMap::new();
let mut instructions = BTreeMap::new();
instructions.insert("*", vec!["*"]);
programs.insert(JUPITER_EARN_PROGRAM_ID, instructions);
SolanaIntegrationConfigData { programs }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::JUPITER_PERPS_PROGRAM_ID;
use crate::core::{SolanaIntegrationConfig, SolanaIntegrationConfigData};
use std::collections::HashMap;
use std::collections::BTreeMap;

pub struct JupiterPerpsConfig;

Expand All @@ -12,8 +12,8 @@ impl SolanaIntegrationConfig for JupiterPerpsConfig {
fn data(&self) -> &SolanaIntegrationConfigData {
static DATA: std::sync::OnceLock<SolanaIntegrationConfigData> = std::sync::OnceLock::new();
DATA.get_or_init(|| {
let mut programs = HashMap::new();
let mut instructions = HashMap::new();
let mut programs = BTreeMap::new();
let mut instructions = BTreeMap::new();
instructions.insert("*", vec!["*"]);
programs.insert(JUPITER_PERPS_PROGRAM_ID, instructions);
SolanaIntegrationConfigData { programs }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::JUPITER_PROGRAM_ID;
use crate::core::{SolanaIntegrationConfig, SolanaIntegrationConfigData};
use std::collections::HashMap;
use std::collections::BTreeMap;

pub struct JupiterSwapConfig;

Expand All @@ -12,8 +12,8 @@ impl SolanaIntegrationConfig for JupiterSwapConfig {
fn data(&self) -> &SolanaIntegrationConfigData {
static DATA: std::sync::OnceLock<SolanaIntegrationConfigData> = std::sync::OnceLock::new();
DATA.get_or_init(|| {
let mut programs = HashMap::new();
let mut jupiter_instructions = HashMap::new();
let mut programs = BTreeMap::new();
let mut jupiter_instructions = BTreeMap::new();
jupiter_instructions.insert("*", vec!["*"]);
programs.insert(JUPITER_PROGRAM_ID, jupiter_instructions);
SolanaIntegrationConfigData { programs }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::KAMINO_BORROW_PROGRAM_ID;
use crate::core::{SolanaIntegrationConfig, SolanaIntegrationConfigData};
use std::collections::HashMap;
use std::collections::BTreeMap;

pub struct KaminoBorrowConfig;

Expand All @@ -12,8 +12,8 @@ impl SolanaIntegrationConfig for KaminoBorrowConfig {
fn data(&self) -> &SolanaIntegrationConfigData {
static DATA: std::sync::OnceLock<SolanaIntegrationConfigData> = std::sync::OnceLock::new();
DATA.get_or_init(|| {
let mut programs = HashMap::new();
let mut instructions = HashMap::new();
let mut programs = BTreeMap::new();
let mut instructions = BTreeMap::new();
instructions.insert("*", vec!["*"]);
programs.insert(KAMINO_BORROW_PROGRAM_ID, instructions);
SolanaIntegrationConfigData { programs }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::KAMINO_FARMS_PROGRAM_ID;
use crate::core::{SolanaIntegrationConfig, SolanaIntegrationConfigData};
use std::collections::HashMap;
use std::collections::BTreeMap;

pub struct KaminoFarmsConfig;

Expand All @@ -12,8 +12,8 @@ impl SolanaIntegrationConfig for KaminoFarmsConfig {
fn data(&self) -> &SolanaIntegrationConfigData {
static DATA: std::sync::OnceLock<SolanaIntegrationConfigData> = std::sync::OnceLock::new();
DATA.get_or_init(|| {
let mut programs = HashMap::new();
let mut instructions = HashMap::new();
let mut programs = BTreeMap::new();
let mut instructions = BTreeMap::new();
instructions.insert("*", vec!["*"]);
programs.insert(KAMINO_FARMS_PROGRAM_ID, instructions);
SolanaIntegrationConfigData { programs }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::KAMINO_VAULT_PROGRAM_ID;
use crate::core::{SolanaIntegrationConfig, SolanaIntegrationConfigData};
use std::collections::HashMap;
use std::collections::BTreeMap;

pub struct KaminoVaultConfig;

Expand All @@ -12,8 +12,8 @@ impl SolanaIntegrationConfig for KaminoVaultConfig {
fn data(&self) -> &SolanaIntegrationConfigData {
static DATA: std::sync::OnceLock<SolanaIntegrationConfigData> = std::sync::OnceLock::new();
DATA.get_or_init(|| {
let mut programs = HashMap::new();
let mut instructions = HashMap::new();
let mut programs = BTreeMap::new();
let mut instructions = BTreeMap::new();
instructions.insert("*", vec!["*"]);
programs.insert(KAMINO_VAULT_PROGRAM_ID, instructions);
SolanaIntegrationConfigData { programs }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::METADAO_CONDITIONAL_VAULT_PROGRAM_ID;
use crate::core::{SolanaIntegrationConfig, SolanaIntegrationConfigData};
use std::collections::HashMap;
use std::collections::BTreeMap;

pub struct MetadaoConditionalVaultConfig;

Expand All @@ -12,8 +12,8 @@ impl SolanaIntegrationConfig for MetadaoConditionalVaultConfig {
fn data(&self) -> &SolanaIntegrationConfigData {
static DATA: std::sync::OnceLock<SolanaIntegrationConfigData> = std::sync::OnceLock::new();
DATA.get_or_init(|| {
let mut programs = HashMap::new();
let mut instructions = HashMap::new();
let mut programs = BTreeMap::new();
let mut instructions = BTreeMap::new();
instructions.insert("*", vec!["*"]);
programs.insert(METADAO_CONDITIONAL_VAULT_PROGRAM_ID, instructions);
SolanaIntegrationConfigData { programs }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::METADAO_FUTARCHY_PROGRAM_ID;
use crate::core::{SolanaIntegrationConfig, SolanaIntegrationConfigData};
use std::collections::HashMap;
use std::collections::BTreeMap;

pub struct MetadaoFutarchyConfig;

Expand All @@ -12,8 +12,8 @@ impl SolanaIntegrationConfig for MetadaoFutarchyConfig {
fn data(&self) -> &SolanaIntegrationConfigData {
static DATA: std::sync::OnceLock<SolanaIntegrationConfigData> = std::sync::OnceLock::new();
DATA.get_or_init(|| {
let mut programs = HashMap::new();
let mut instructions = HashMap::new();
let mut programs = BTreeMap::new();
let mut instructions = BTreeMap::new();
instructions.insert("*", vec!["*"]);
programs.insert(METADAO_FUTARCHY_PROGRAM_ID, instructions);
SolanaIntegrationConfigData { programs }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::METEORA_DAMM_V2_PROGRAM_ID;
use crate::core::{SolanaIntegrationConfig, SolanaIntegrationConfigData};
use std::collections::HashMap;
use std::collections::BTreeMap;

pub struct MeteoraDammV2Config;

Expand All @@ -12,8 +12,8 @@ impl SolanaIntegrationConfig for MeteoraDammV2Config {
fn data(&self) -> &SolanaIntegrationConfigData {
static DATA: std::sync::OnceLock<SolanaIntegrationConfigData> = std::sync::OnceLock::new();
DATA.get_or_init(|| {
let mut programs = HashMap::new();
let mut instructions = HashMap::new();
let mut programs = BTreeMap::new();
let mut instructions = BTreeMap::new();
instructions.insert("*", vec!["*"]);
programs.insert(METEORA_DAMM_V2_PROGRAM_ID, instructions);
SolanaIntegrationConfigData { programs }
Expand Down
Loading
Loading