feat(solana): add Drift Protocol visualizer preset#239
feat(solana): add Drift Protocol visualizer preset#239prasanna-anchorage wants to merge 2 commits into
Conversation
Add IDL-based visualizer for Drift Protocol v2 (dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH) with 249 instructions. Uses LazyLock to cache parsed IDL. Includes unit tests for IDL loading, discriminator validation, and error handling. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new Solana preset visualizer that decodes Drift Protocol v2 instructions via an embedded Anchor IDL, producing structured preview fields (with a raw-data fallback) and caching the parsed IDL for performance.
Changes:
- Registered a new
driftpreset module in the Solana presets registry. - Implemented
DriftVisualizerwith IDL-backed instruction parsing + account labeling + raw-data fallback. - Added a Drift integration config to route Drift program instructions to the new visualizer.
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/chain_parsers/visualsign-solana/src/presets/mod.rs |
Exposes the new drift preset module so it can be instantiated by the build-time visualizer registry. |
src/chain_parsers/visualsign-solana/src/presets/drift/mod.rs |
Implements Drift instruction visualization using an embedded IDL with LazyLock caching and adds unit tests. |
src/chain_parsers/visualsign-solana/src/presets/drift/config.rs |
Adds the config mapping for the Drift program ID so the visualizer can handle Drift instructions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| let (title, condensed_fields, expanded_fields) = match parsed { | ||
| Ok(parsed) => build_parsed_fields(&parsed, &instruction.program_id.to_string()), | ||
| Err(_) => build_fallback_fields(&instruction.program_id.to_string()), |
There was a problem hiding this comment.
visualize_tx_commands discards the concrete parsing error (Err(_)) and always falls back to an "Unknown instruction type" display. This makes failures like short data, IDL load failures, or decode errors indistinguishable from an actually-unknown discriminator. Consider capturing the error and surfacing it (e.g., in a Status field and/or via tracing::warn!) so users and logs reflect the real failure reason.
| Err(_) => build_fallback_fields(&instruction.program_id.to_string()), | |
| Err(err) => { | |
| tracing::warn!( | |
| program_id = %instruction.program_id, | |
| error = %err, | |
| "Failed to parse Drift instruction" | |
| ); | |
| let status_text = format!("Failed to parse Drift instruction: {err}"); | |
| let (title, mut condensed_fields, mut expanded_fields) = | |
| build_fallback_fields(&instruction.program_id.to_string()); | |
| condensed_fields.push(create_text_field("Status", &status_text)); | |
| expanded_fields.push(create_text_field("Status", &status_text)); | |
| (title, condensed_fields, expanded_fields) | |
| } |
| struct DriftParsedInstruction { | ||
| parsed: SolanaParsedInstructionData, | ||
| named_accounts: HashMap<String, String>, | ||
| } |
There was a problem hiding this comment.
DriftParsedInstruction stores named_accounts separately even though SolanaParsedInstructionData already supports named accounts (see the existing population pattern in presets/unknown_program/mod.rs). Keeping a parallel map increases maintenance overhead and risks divergence between the parsed payload and displayed account labels. Consider populating parsed.named_accounts directly and using it when building fields, removing the extra wrapper struct if possible.
|
|
||
| named_accounts | ||
| } | ||
|
|
There was a problem hiding this comment.
The expanded view relies on build_named_accounts to label accounts, but current tests only cover IDL loading/discriminators and generic error cases. Add a focused unit test that exercises build_named_accounts (or the end-to-end visualize path) for at least one known discriminator to ensure account names are mapped correctly and remain stable as the embedded IDL changes.
| #[cfg(test)] | |
| mod tests { | |
| use super::*; | |
| #[test] | |
| fn build_named_accounts_maps_initialize_user_accounts_by_name() { | |
| let idl = get_drift_idl().expect("Drift IDL should be available for tests"); | |
| let instruction = idl | |
| .instructions | |
| .iter() | |
| .find(|inst| inst.name == "initializeUser") | |
| .expect("expected initializeUser instruction in embedded Drift IDL"); | |
| let discriminator = instruction | |
| .discriminator | |
| .clone() | |
| .expect("initializeUser should have a discriminator"); | |
| let expected_account_names = [ | |
| "user", | |
| "userStats", | |
| "state", | |
| "authority", | |
| "payer", | |
| "rent", | |
| "systemProgram", | |
| ]; | |
| let actual_account_names: Vec<&str> = instruction | |
| .accounts | |
| .iter() | |
| .take(expected_account_names.len()) | |
| .map(|account| account.name.as_str()) | |
| .collect(); | |
| assert_eq!( | |
| actual_account_names, | |
| expected_account_names, | |
| "initializeUser accounts changed in the embedded Drift IDL; update this test if the change is intentional" | |
| ); | |
| let accounts: Vec<solana_sdk::instruction::AccountMeta> = expected_account_names | |
| .iter() | |
| .enumerate() | |
| .map(|(index, _)| { | |
| let pubkey = solana_sdk::pubkey::Pubkey::new_from_array([index as u8 + 1; 32]); | |
| solana_sdk::instruction::AccountMeta::new(pubkey, false) | |
| }) | |
| .collect(); | |
| let named_accounts = build_named_accounts(&discriminator, idl, &accounts); | |
| assert_eq!(named_accounts.len(), expected_account_names.len()); | |
| for (index, account_name) in expected_account_names.iter().enumerate() { | |
| let expected_pubkey = | |
| solana_sdk::pubkey::Pubkey::new_from_array([index as u8 + 1; 32]).to_string(); | |
| assert_eq!( | |
| named_accounts.get(*account_name), | |
| Some(&expected_pubkey), | |
| "account name {account_name} should map to the corresponding account meta pubkey" | |
| ); | |
| } | |
| } | |
| } |
When an IDL instruction has both an account and an argument with the same name (e.g. "admin" in Drift's updateAdmin), label them as "Current admin" (account) and "New admin" (argument) in the expanded view to avoid confusion. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH)LazyLockto cache parsed 438KB IDL (parsed once, not on every call)Test plan
cargo clippy -p visualsign-solana --all-targets -- -D warningscargo test -p visualsign-solana(all 108 tests pass)make -C src test(full workspace)🤖 Generated with Claude Code