Skip to content

Commit 97a2e45

Browse files
committed
feat(wasm-solana): add Marinade staking activate pattern detection
Add Pattern 3 to combine_instructions() that detects Marinade staking: - CreateAccount + StakeInitialize (without Delegate) → StakingActivate(MARINADE) - The validator field stores the authorized staker address for Marinade - Added unit test for Marinade staking activate transaction parsing Also disabled wasm-opt in Cargo.toml to fix build issues with bulk memory operations.
1 parent fde732e commit 97a2e45

2 files changed

Lines changed: 62 additions & 1 deletion

File tree

packages/wasm-solana/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ name = "wasm-solana"
33
version = "0.1.0"
44
edition = "2021"
55

6+
[package.metadata.wasm-pack.profile.release]
7+
wasm-opt = false
8+
69
[lib]
710
crate-type = ["cdylib", "rlib"]
811

packages/wasm-solana/src/parser.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ fn combine_instructions(instructions: Vec<ParsedInstruction>) -> Vec<ParsedInstr
179179
}
180180
}
181181

182-
// Pattern 2: CreateAccount + StakeInitialize + StakingDelegate → StakingActivate
182+
// Pattern 2: CreateAccount + StakeInitialize + StakingDelegate → StakingActivate (NATIVE)
183+
// Must check this 3-instruction pattern BEFORE the 2-instruction Marinade pattern
183184
if i + 2 < instructions.len() {
184185
if let (
185186
ParsedInstruction::StakeInitialize(ref stake_init),
@@ -204,6 +205,38 @@ fn combine_instructions(instructions: Vec<ParsedInstruction>) -> Vec<ParsedInstr
204205
}
205206
}
206207
}
208+
209+
// Pattern 3: CreateAccount + StakeInitialize (without Delegate) → StakingActivate (MARINADE)
210+
// Marinade staking creates a stake account but doesn't delegate to a validator
211+
// The "validator" field stores the authorized staker address instead
212+
if i + 1 < instructions.len() {
213+
if let ParsedInstruction::StakeInitialize(ref stake_init) = instructions[i + 1] {
214+
// Check if CreateAccount target matches StakeInitialize staking address
215+
// and owner is Stake Program
216+
// Also make sure the next instruction (if any) is NOT a StakingDelegate
217+
let is_not_followed_by_delegate = i + 2 >= instructions.len()
218+
|| !matches!(
219+
instructions[i + 2],
220+
ParsedInstruction::StakingDelegate(_)
221+
);
222+
223+
if create.new_address == stake_init.staking_address
224+
&& create.owner == STAKE_PROGRAM_ID
225+
&& is_not_followed_by_delegate
226+
{
227+
result.push(ParsedInstruction::StakingActivate(StakingActivateParams {
228+
from_address: create.from_address.clone(),
229+
staking_address: stake_init.staking_address.clone(),
230+
amount: create.amount.clone(),
231+
// For Marinade, the validator field stores the authorized staker
232+
validator: stake_init.staker.clone(),
233+
staking_type: "MARINADE".to_string(),
234+
}));
235+
i += 2; // Skip both instructions
236+
continue;
237+
}
238+
}
239+
}
207240
}
208241

209242
// No pattern matched, keep the instruction as-is
@@ -277,4 +310,29 @@ mod tests {
277310
assert!(json.contains("instructionsData"));
278311
assert!(json.contains("Transfer"));
279312
}
313+
314+
// Marinade staking activate transaction (CreateAccount + StakeInitialize without Delegate)
315+
const MARINADE_STAKING_ACTIVATE: &str = "AuRFS0r7hJ+/+WuDQbbwdjSgxfnKOWi94EnWEha9uaBPt8VZOXiOoSiSoES34VkyBNLlLqlfK0fP3d5eJR+srQvN04gqzpOZPTVzqiomyMXqwQ6FYoQg5nEkdiDVny8SsyhRnAeDMzexkKD+3rwSGP0E+XN/2crTL6PZRnip42YFAgADBUXlebz5JTz2i0ff8fs6OlwsIbrFsjwJrhKm4FVr8ItBYnsvugEnYfm5Gbz5TLtMncgFHZ8JMpkxTTlJIzJovekAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAah2BeRN1QqmDQ3vf4qerJVf1NcinhyK2ikncAAAAAABqfVFxksXFEhjMlMPUrxf1ja7gibof1E49vZigAAAADjMtr5L6vs6LY/96RABeX9/Zr6FYdWthxalfkEs7jQgQICAgABNAAAAADgkwQAAAAAAMgAAAAAAAAABqHYF5E3VCqYNDe9/ip6slV/U1yKeHIraKSdwAAAAAADAgEEdAAAAACx+Xl4mhxH0TxI2HovJxcQ63+TJglRFzFikL1sKdr12UXlebz5JTz2i0ff8fs6OlwsIbrFsjwJrhKm4FVr8ItBAAAAAAAAAAAAAAAAAAAAAEXlebz5JTz2i0ff8fs6OlwsIbrFsjwJrhKm4FVr8ItB";
316+
317+
#[test]
318+
fn test_parse_marinade_staking_activate() {
319+
let bytes = BASE64_STANDARD.decode(MARINADE_STAKING_ACTIVATE).unwrap();
320+
let parsed = parse_transaction(&bytes).unwrap();
321+
322+
println!("Parsed instructions: {:?}", parsed.instructions_data);
323+
println!("Parsed JSON: {}", serde_json::to_string_pretty(&parsed).unwrap());
324+
325+
// Should combine CreateAccount + StakeInitialize into StakingActivate(MARINADE)
326+
assert_eq!(parsed.instructions_data.len(), 1, "Expected 1 combined instruction");
327+
328+
match &parsed.instructions_data[0] {
329+
ParsedInstruction::StakingActivate(params) => {
330+
assert_eq!(params.staking_type, "MARINADE");
331+
assert_eq!(params.from_address, "5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe");
332+
assert_eq!(params.staking_address, "7dRuGFbU2y2kijP6o1LYNzVyz4yf13MooqoionCzv5Za");
333+
assert_eq!(params.amount, "300000");
334+
}
335+
other => panic!("Expected StakingActivate(MARINADE) instruction, got {:?}", other),
336+
}
337+
}
280338
}

0 commit comments

Comments
 (0)