diff --git a/Cargo.lock b/Cargo.lock index 767a95b..aba5f30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -639,6 +639,7 @@ dependencies = [ "bs58 0.4.0", "flatbuffers", "lazy_static", + "log", "mpl-bubblegum", "mpl-candy-guard", "mpl-candy-machine-core", diff --git a/blockbuster/Cargo.toml b/blockbuster/Cargo.toml index a4f864f..cda936e 100644 --- a/blockbuster/Cargo.toml +++ b/blockbuster/Cargo.toml @@ -25,6 +25,7 @@ borsh = "~0.10.3" thiserror = "1.0.32" solana-sdk = "~1.16.5" anchor-lang = { version = "0.28.0"} +log = "0.4.17" [dev-dependencies] rand = "0.8.5" diff --git a/blockbuster/src/programs/bubblegum/mod.rs b/blockbuster/src/programs/bubblegum/mod.rs index 6137fdc..8be8f0b 100644 --- a/blockbuster/src/programs/bubblegum/mod.rs +++ b/blockbuster/src/programs/bubblegum/mod.rs @@ -1,3 +1,5 @@ +use log::warn; + use crate::{ error::BlockbusterError, instruction::InstructionBundle, @@ -21,6 +23,7 @@ pub use spl_account_compression::events::{ AccountCompressionEvent::{self, ApplicationData, ChangeLog}, ApplicationDataEvent, ChangeLogEvent, ChangeLogEventV1, }; + use spl_noop; #[derive(Eq, PartialEq)] @@ -29,9 +32,8 @@ pub enum Payload { MintV1 { args: MetadataArgs }, Decompress { args: MetadataArgs }, CancelRedeem { root: [u8; 32] }, - VerifyCreator { creator: Pubkey }, - UnverifyCreator { creator: Pubkey }, - SetAndVerifyCollection { collection: Pubkey } + CreatorVerification { creator: Pubkey, verify: bool }, + CollectionVerification { collection: Pubkey, verify: bool }, } //TODO add more of the parsing here to minimize program transformer code pub struct BubblegumInstruction { @@ -93,6 +95,7 @@ impl ProgramParser for BubblegumParser { bundle: &InstructionBundle, ) -> Result, BlockbusterError> { let InstructionBundle { + txn_id, instruction, inner_ix, keys, @@ -116,32 +119,41 @@ impl ProgramParser for BubblegumParser { if let Some(inner_ix_data) = cix.data() { let inner_ix_data = inner_ix_data.iter().collect::>(); if !inner_ix_data.is_empty() { - match AccountCompressionEvent::try_from_slice(&inner_ix_data)? { - ChangeLog(changelog_event) => { - let ChangeLogEvent::V1(changelog_event) = changelog_event; - b_inst.tree_update = Some(changelog_event); - } - ApplicationData(app_data) => { - let ApplicationDataEvent::V1(app_data) = app_data; - let app_data = app_data.application_data; - - let event_type_byte = if !app_data.is_empty() { - &app_data[0..1] - } else { - return Err(BlockbusterError::DeserializationError); - }; - - match BubblegumEventType::try_from_slice(event_type_byte)? { - BubblegumEventType::Uninitialized => { - return Err( - BlockbusterError::MissingBubblegumEventData, - ); - } - BubblegumEventType::LeafSchemaEvent => { - b_inst.leaf_update = - Some(LeafSchemaEvent::try_from_slice(&app_data)?); + match AccountCompressionEvent::try_from_slice(&inner_ix_data) { + Ok(result) => match result { + ChangeLog(changelog_event) => { + let ChangeLogEvent::V1(changelog_event) = changelog_event; + b_inst.tree_update = Some(changelog_event); + } + ApplicationData(app_data) => { + let ApplicationDataEvent::V1(app_data) = app_data; + let app_data = app_data.application_data; + + let event_type_byte = if !app_data.is_empty() { + &app_data[0..1] + } else { + return Err(BlockbusterError::DeserializationError); + }; + + match BubblegumEventType::try_from_slice(event_type_byte)? { + BubblegumEventType::Uninitialized => { + return Err( + BlockbusterError::MissingBubblegumEventData, + ); + } + BubblegumEventType::LeafSchemaEvent => { + b_inst.leaf_update = Some( + LeafSchemaEvent::try_from_slice(&app_data)?, + ); + } } } + }, + Err(e) => { + warn!( + "Error while deserializing txn {:?} with noop data: {:?}", + txn_id, e + ); } } } @@ -178,32 +190,16 @@ impl ProgramParser for BubblegumParser { b_inst.payload = Some(Payload::CancelRedeem { root: slice }); } InstructionName::VerifyCreator => { - let creator = keys - .get(5) - .ok_or(BlockbusterError::InstructionParsingError)? - .0; - b_inst.payload = Some(Payload::VerifyCreator { - creator: Pubkey::new_from_array(creator), - }); + b_inst.payload = Some(build_creator_verification_payload(keys, true)?); } InstructionName::UnverifyCreator => { - let creator = keys - .get(5) - .ok_or(BlockbusterError::InstructionParsingError)? - .0; - b_inst.payload = Some(Payload::UnverifyCreator { - creator: Pubkey::new_from_array(creator), - }); + b_inst.payload = Some(build_creator_verification_payload(keys, false)?); + } + InstructionName::VerifyCollection | InstructionName::SetAndVerifyCollection => { + b_inst.payload = Some(build_collection_verification_payload(keys, true)?); } - // We don't extract any additional info w.r.t. verify and unverify - // collection ops for now. - InstructionName::SetAndVerifyCollection => { - // Deserializing this to get to the second argument encoded in the slice, - // which is the collection address. Is there a (safe) way to get to that - // directly? - let _args: MetadataArgs = MetadataArgs::try_from_slice(ix_data)?; - let collection: Pubkey = Pubkey::try_from_slice(ix_data)?; - b_inst.payload = Some(Payload::SetAndVerifyCollection { collection }); + InstructionName::UnverifyCollection => { + b_inst.payload = Some(build_collection_verification_payload(keys, false)?); } InstructionName::Unknown => {} _ => {} @@ -214,4 +210,34 @@ impl ProgramParser for BubblegumParser { Ok(Box::new(b_inst)) } } - \ No newline at end of file + +// See Bubblegum documentation for offsets and positions: +// https://github.com/metaplex-foundation/mpl-bubblegum/blob/main/programs/bubblegum/README.md#-verify_creator-and-unverify_creator +fn build_creator_verification_payload( + keys: &[plerkle_serialization::Pubkey], + verify: bool, +) -> Result { + let creator = keys + .get(5) + .ok_or(BlockbusterError::InstructionParsingError)? + .0; + Ok(Payload::CreatorVerification { + creator: Pubkey::new_from_array(creator), + verify, + }) +} + +// See Bubblegum for offsets and positions: +// https://github.com/metaplex-foundation/mpl-bubblegum/blob/main/programs/bubblegum/README.md#-verify_collection-unverify_collection-and-set_and_verify_collection +// NOTE: Unverfication does not include collection. This needs to be fixed in the README. +fn build_collection_verification_payload( + keys: &[plerkle_serialization::Pubkey], + verify: bool, +) -> Result { + let collection_raw = keys + .get(8) + .ok_or(BlockbusterError::InstructionParsingError)? + .0; + let collection: Pubkey = Pubkey::try_from_slice(&collection_raw)?; + Ok(Payload::CollectionVerification { collection, verify }) +}