Skip to content

Commit

Permalink
Jankle: parse bubblegum via instructions (without logs) (solana-labs#128
Browse files Browse the repository at this point in the history
)
  • Loading branch information
ngundotra authored Jul 5, 2022
1 parent 0e69717 commit 9a9048d
Show file tree
Hide file tree
Showing 27 changed files with 2,197 additions and 614 deletions.
2 changes: 1 addition & 1 deletion contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"@metaplex-foundation/mpl-token-metadata": "2.2.0",
"@project-serum/anchor": "^0.21.0",
"@solana/spl-token": "^0.1.8",
"@solana/web3.js": "^1.37.0",
"@solana/web3.js": "^1.47.1",
"collections": "^5.1.13",
"cors": "^2.8.5",
"express": "^4.18.1",
Expand Down
71 changes: 35 additions & 36 deletions contracts/programs/bubblegum/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,15 @@
use {
crate::error::BubblegumError,
crate::state::metaplex_anchor::MplTokenMetadata,
crate::state::{
NONCE_SIZE,
VOUCHER_PREFIX,
VOUCHER_SIZE,
ASSET_PREFIX,
leaf_schema::{LeafSchema, Version},
metaplex_anchor::{MasterEdition, TokenMetadata},
Nonce, Voucher,
metaplex_adapter::{MetadataArgs, TokenProgramVersion},
NewNFTEvent,
NFTDecompressionEvent,
},
gummyroll::{
program::Gummyroll,
Node,
state::CandyWrapper,
utils::wrap_event,
metaplex_anchor::{MasterEdition, TokenMetadata},
NFTDecompressionEvent, NewNFTEvent, Nonce, Voucher, ASSET_PREFIX, NONCE_SIZE,
VOUCHER_PREFIX, VOUCHER_SIZE,
},
crate::error::BubblegumError,
crate::utils::{append_leaf,
replace_leaf,
get_asset_id,
cmp_bytes,
cmp_pubkeys,
assert_pubkey_equal,
crate::utils::{
append_leaf, assert_pubkey_equal, cmp_bytes, cmp_pubkeys, get_asset_id, replace_leaf,
},
anchor_lang::{
prelude::*,
Expand All @@ -36,13 +21,13 @@ use {
system_instruction,
},
},
gummyroll::{program::Gummyroll, state::CandyWrapper, utils::wrap_event, Node},
spl_token::state::Mint as SplMint,
};


pub mod error;
pub mod state;
pub mod utils;
pub mod error;

declare_id!("BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY");

Expand Down Expand Up @@ -236,9 +221,9 @@ pub struct DecompressV1<'info> {
#[account(
mut,
seeds = [
ASSET_PREFIX.as_ref(),
voucher.merkle_slab.as_ref(),
voucher.leaf_schema.nonce().to_le_bytes().as_ref()
ASSET_PREFIX.as_ref(),
voucher.merkle_slab.as_ref(),
voucher.leaf_schema.nonce().to_le_bytes().as_ref(),
],
bump
)]
Expand Down Expand Up @@ -395,7 +380,7 @@ pub mod bubblegum {
let new_nft = NewNFTEvent {
version: Version::V1,
metadata: message,
nonce: nonce.count
nonce: nonce.count,
};
emit!(new_nft);
wrap_event(new_nft.try_to_vec()?, &ctx.accounts.candy_wrapper)?;
Expand Down Expand Up @@ -483,8 +468,15 @@ pub mod bubblegum {
data_hash,
creator_hash,
);
let new_leaf =
LeafSchema::new_v0(asset_id, owner, new_delegate, nonce, data_hash, creator_hash);
let new_leaf = LeafSchema::new_v0(
asset_id,
owner,
new_delegate,
nonce,
data_hash,
creator_hash,
);
wrap_event(new_leaf.try_to_vec()?, &ctx.accounts.candy_wrapper)?;
emit!(new_leaf.to_event());
replace_leaf(
&merkle_slab.key(),
Expand Down Expand Up @@ -524,6 +516,7 @@ pub mod bubblegum {
);
emit!(previous_leaf.to_event());
let new_leaf = Node::default();
wrap_event(new_leaf.try_to_vec()?, &ctx.accounts.candy_wrapper)?;
replace_leaf(
&merkle_slab.key(),
*ctx.bumps.get("authority").unwrap(),
Expand Down Expand Up @@ -555,6 +548,7 @@ pub mod bubblegum {
LeafSchema::new_v0(asset_id, owner, delegate, nonce, data_hash, creator_hash);
emit!(previous_leaf.to_event());
let new_leaf = Node::default();
wrap_event(new_leaf.try_to_vec()?, &ctx.accounts.candy_wrapper)?;
replace_leaf(
&merkle_slab.key(),
*ctx.bumps.get("authority").unwrap(),
Expand All @@ -581,14 +575,19 @@ pub mod bubblegum {
) -> Result<()> {
let voucher = &ctx.accounts.voucher;
match ctx.accounts.voucher.leaf_schema {
LeafSchema::V1 { owner, .. } =>
assert_pubkey_equal(&ctx.accounts.owner.key(),
&owner,
Some(BubblegumError::AssetOwnerMismatch.into()),
),
LeafSchema::V1 { owner, .. } => assert_pubkey_equal(
&ctx.accounts.owner.key(),
&owner,
Some(BubblegumError::AssetOwnerMismatch.into()),
),
}?;
let merkle_slab = ctx.accounts.merkle_slab.to_account_info();
emit!(voucher.leaf_schema.to_event());
wrap_event(
voucher.leaf_schema.try_to_vec()?,
&ctx.accounts.candy_wrapper,
)?;

replace_leaf(
&merkle_slab.key(),
*ctx.bumps.get("authority").unwrap(),
Expand All @@ -598,7 +597,7 @@ pub mod bubblegum {
&ctx.accounts.candy_wrapper.to_account_info(),
ctx.remaining_accounts,
root,
[0; 32],
[0; 32],
voucher.leaf_schema.to_node(),
voucher.index,
)
Expand Down Expand Up @@ -627,7 +626,7 @@ pub mod bubblegum {
nonce: nonce,
})
}
_ => Err(BubblegumError::UnsupportedSchemaVersion)
_ => Err(BubblegumError::UnsupportedSchemaVersion),
}?;
let voucher = &ctx.accounts.voucher;
match metadata.token_program_version {
Expand Down
48 changes: 32 additions & 16 deletions contracts/programs/gumball-machine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ use bubblegum::program::Bubblegum;

use bubblegum::state::metaplex_adapter::MetadataArgs;
use bytemuck::cast_slice_mut;
use gummyroll::{
program::Gummyroll, state::CandyWrapper,
};
use gummyroll::{program::Gummyroll, state::CandyWrapper};
use spl_token::native_mint;
pub mod state;
pub mod utils;

use crate::state::{GumballMachineHeader, EncodeMethod, ZeroCopy};
use crate::state::{EncodeMethod, GumballMachineHeader, ZeroCopy};
use crate::utils::get_metadata_args;

declare_id!("GBALLoMcmimUutWvtNdFFGH5oguS7ghUUV6toQPppuTW");

const COMPUTE_BUDGET_ADDRESS: &str = "ComputeBudget111111111111111111111111111111";

#[derive(Accounts)]
pub struct InitGumballMachine<'info> {
/// CHECK: Validation occurs in instruction
Expand Down Expand Up @@ -158,16 +158,30 @@ pub struct Destroy<'info> {
fn assert_valid_single_instruction_transaction<'info>(
instruction_sysvar_account: &AccountInfo<'info>,
) -> Result<()> {
// There should only be one instruction in this transaction (the current call to dispense_...)
// There should only be one non compute-budget instruction
// in this transaction (i.e. the current call to dispense_...)
let instruction_sysvar = instruction_sysvar_account.try_borrow_data()?;
let mut fixed_data = [0u8; 2];
fixed_data.copy_from_slice(&instruction_sysvar[0..2]);
let num_instructions = u16::from_le_bytes(fixed_data);
assert_eq!(num_instructions, 1);

if num_instructions > 2 {
assert!(false, "Suspicious transaction, failing")
} else if num_instructions == 2 {
let compute_budget_instruction =
load_instruction_at_checked(0, instruction_sysvar_account)?;

let compute_budget_id: Pubkey =
Pubkey::new(bs58::decode(&COMPUTE_BUDGET_ADDRESS).into_vec().unwrap()[..32].as_ref());

assert_eq!(compute_budget_instruction.program_id, compute_budget_id);
let current_instruction = load_instruction_at_checked(1, instruction_sysvar_account)?;
assert_eq!(current_instruction.program_id, id());
} else if num_instructions == 1 {
let only_instruction = load_instruction_at_checked(0, instruction_sysvar_account)?;
assert_eq!(only_instruction.program_id, id());
}
// We should not be executing dispense... from a CPI
let only_instruction = load_instruction_at_checked(0, instruction_sysvar_account)?;
assert_eq!(only_instruction.program_id, id());

return Ok(());
}

Expand Down Expand Up @@ -213,7 +227,7 @@ fn fisher_yates_shuffle_and_fetch_nft_metadata<'info>(
gumball_header.creator_address,
nft_index,
config_line,
EncodeMethod::from(gumball_header.config_line_encode_method)
EncodeMethod::from(gumball_header.config_line_encode_method),
);
return Ok(message);
}
Expand All @@ -222,7 +236,7 @@ fn fisher_yates_shuffle_and_fetch_nft_metadata<'info>(
// For efficiency, this returns the GumballMachineHeader because it's required to validate
// payment parameters. But the main purpose of this function is to determine which config
// line to mint to the user, and CPI to bubblegum to actually execute the mint
// Also returns the number of nfts successfully minted, so that the purchaser is charged
// Also returns the number of nfts successfully minted, so that the purchaser is charged
// appropriately
fn find_and_mint_compressed_nfts<'info>(
gumball_machine: &AccountInfo<'info>,
Expand Down Expand Up @@ -261,9 +275,11 @@ fn find_and_mint_compressed_nfts<'info>(

let indices = cast_slice_mut::<u8, u32>(indices_data);
let num_nfts_to_mint: u64 = (num_items).max(1).min(gumball_header.remaining);
assert!(num_nfts_to_mint > 0, "There are no remaining NFTs to dispense!");
for _ in 0..num_nfts_to_mint
{
assert!(
num_nfts_to_mint > 0,
"There are no remaining NFTs to dispense!"
);
for _ in 0..num_nfts_to_mint {
let message = fisher_yates_shuffle_and_fetch_nft_metadata(
recent_blockhashes,
gumball_header,
Expand Down Expand Up @@ -335,7 +351,7 @@ pub mod gumball_machine {
retain_authority: retain_authority.into(),
config_line_encode_method: match encode_method {
Some(e) => e.to_u8(),
None => EncodeMethod::UTF8.to_u8()
None => EncodeMethod::UTF8.to_u8(),
},
_padding: [0; 3],
price,
Expand Down Expand Up @@ -471,7 +487,7 @@ pub mod gumball_machine {
}
match encode_method {
Some(e) => gumball_machine.config_line_encode_method = e.to_u8(),
None => {}
None => {}
}
match seller_fee_basis_points {
Some(s) => gumball_machine.seller_fee_basis_points = s,
Expand Down
12 changes: 12 additions & 0 deletions contracts/sdk/bubblegum/src/convenience.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ export async function getVoucherPDA(connection: Connection, tree: PublicKey, lea
return voucher;
}

export async function getLeafAssetId(tree: PublicKey, leafIndex: BN): Promise<PublicKey> {
let [assetId] = await PublicKey.findProgramAddress(
[
Buffer.from("asset", "utf8"),
tree.toBuffer(),
leafIndex.toBuffer("le", 8),
],
PROGRAM_ID
);
return assetId
}

export async function getCreateTreeIxs(
connection: Connection,
maxDepth: number,
Expand Down
2 changes: 1 addition & 1 deletion contracts/sdk/bubblegum/src/generated/accounts/Nonce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export type NonceArgs = {
count: beet.bignum
}

const nonceDiscriminator = [143, 197, 147, 95, 106, 165, 50, 43]
export const nonceDiscriminator = [143, 197, 147, 95, 106, 165, 50, 43]
/**
* Holds the data for the {@link Nonce} Account and provides de/serialization
* functionality for that data
Expand Down
7 changes: 4 additions & 3 deletions contracts/sdk/bubblegum/src/generated/accounts/Voucher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export type VoucherArgs = {
merkleSlab: web3.PublicKey
}

const voucherDiscriminator = [191, 204, 149, 234, 213, 165, 13, 65]
export const voucherDiscriminator = [191, 204, 149, 234, 213, 165, 13, 65]
/**
* Holds the data for the {@link Voucher} Account and provides de/serialization
* functionality for that data
Expand All @@ -34,7 +34,7 @@ export class Voucher implements VoucherArgs {
readonly leafSchema: LeafSchema,
readonly index: number,
readonly merkleSlab: web3.PublicKey
) {}
) { }

/**
* Creates a {@link Voucher} instance from the provided args.
Expand Down Expand Up @@ -128,7 +128,7 @@ export class Voucher implements VoucherArgs {
*/
pretty() {
return {
leafSchema: 'LeafSchema.' + LeafSchema[this.leafSchema],
leafSchema: this.leafSchema.__kind,
index: this.index,
merkleSlab: this.merkleSlab.toBase58(),
}
Expand All @@ -147,6 +147,7 @@ export const voucherBeet = new beet.BeetStruct<
>(
[
['accountDiscriminator', beet.uniformFixedSizeArray(beet.u8, 8)],
// @ts-ignore
['leafSchema', leafSchemaBeet],
['index', beet.u32],
['merkleSlab', beetSolana.publicKey],
Expand Down
Loading

0 comments on commit 9a9048d

Please sign in to comment.