Skip to content
Open
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
141 changes: 141 additions & 0 deletions clients/rust/tests/execute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
//! Integration tests for the ExecuteV1 instruction.
#![cfg(feature = "test-sbf")]

pub mod setup;
use mpl_core::instructions::ExecuteV1Builder;
use setup::*;

use solana_program::{
instruction::AccountMeta, pubkey::Pubkey, system_instruction, system_program,
};
use solana_program_test::tokio;
use solana_sdk::{signature::Keypair, signer::Signer, transaction::Transaction};

const EXECUTE_PREFIX: &str = "mpl-core-execute";
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Consider using a shared constant for PDA prefix.

The constant EXECUTE_PREFIX uses the same value as FREEZE_EXECUTE_PREFIX in the freeze_execute.rs file. Consider extracting this to a shared module to maintain consistency and avoid duplication.

You could create a shared constants module:

// In a shared module (e.g., setup/constants.rs)
+pub const EXECUTE_PREFIX: &str = "mpl-core-execute";

// Then import it in both files
+use setup::constants::EXECUTE_PREFIX;
-const EXECUTE_PREFIX: &str = "mpl-core-execute";

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In clients/rust/tests/execute.rs at line 14, the constant EXECUTE_PREFIX
duplicates the value of FREEZE_EXECUTE_PREFIX from freeze_execute.rs. To fix
this, create a shared constants module accessible by both files, move the common
prefix constant there, and update both files to import and use the shared
constant instead of defining their own.


#[tokio::test]
async fn test_execute() {
// ----------------------------------
// 0. Test setup
// ----------------------------------
let mut context = program_test().start_with_context().await;

// Fund payer so we can deposit SOL to the asset later.
let payer_key = context.payer.pubkey();
airdrop(&mut context, &payer_key, 2_000_000_000)
.await
.unwrap();

// ----------------------------------
// 1. Mint an asset
// ----------------------------------
let asset = Keypair::new();
create_asset(
&mut context,
CreateAssetHelperArgs {
owner: None,
payer: None,
asset: &asset,
data_state: None,
name: None,
uri: None,
authority: None,
update_authority: None,
collection: None,
plugins: vec![],
external_plugin_adapters: vec![],
},
)
.await
.unwrap();

assert_asset(
&mut context,
AssertAssetHelperArgs {
asset: asset.pubkey(),
owner: payer_key,
update_authority: None,
name: None,
uri: None,
plugins: vec![],
external_plugin_adapters: vec![],
},
)
.await;

// ----------------------------------
// 2. Deposit backing SOL into the asset account (simulate 0.5 SOL backing)
// ----------------------------------
let (asset_signer, _) = Pubkey::find_program_address(
&[EXECUTE_PREFIX.as_bytes(), asset.pubkey().as_ref()],
&mpl_core::ID,
);

let backing_amount: u64 = 500_000_000; // 0.5 SOL
let transfer_ix = system_instruction::transfer(&payer_key, &asset_signer, backing_amount);
let tx = Transaction::new_signed_with_payer(
&[transfer_ix],
Some(&payer_key),
&[&context.payer],
context.last_blockhash,
);
context.banks_client.process_transaction(tx).await.unwrap();

// Capture lamports held by the asset account after deposit.
let lamports_in_asset = context
.banks_client
.get_balance(asset_signer)
.await
.expect("get_balance failed");

// Verify that the deposit was successful.
assert_eq!(lamports_in_asset, backing_amount);

// ----------------------------------
// 3. Execute → Transfer funds from asset_signer to payer.
// ----------------------------------
let execute_transfer_ix =
system_instruction::transfer(&asset_signer, &payer_key, lamports_in_asset);

let execute_ix = ExecuteV1Builder::new()
.asset(asset.pubkey())
.collection(None)
.asset_signer(asset_signer)
.payer(payer_key)
.authority(Some(payer_key))
.system_program(system_program::ID)
.program_id(system_program::ID) // use system program as harmless target
.instruction_data(execute_transfer_ix.data)
.add_remaining_accounts(&[
AccountMeta {
pubkey: asset_signer,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: payer_key,
is_signer: false,
is_writable: true,
},
])
.instruction();

let tx = Transaction::new_signed_with_payer(
&[execute_ix],
Some(&payer_key),
&[&context.payer],
context.last_blockhash,
);

context.banks_client.process_transaction(tx).await.unwrap();

// Verify that the transfer was successful.
let lamports_in_asset_after_execute = context
.banks_client
.get_balance(asset_signer)
.await
.expect("get_balance failed");

// Verify that the transfer was successful.
assert_eq!(lamports_in_asset_after_execute, 0);
}
55 changes: 39 additions & 16 deletions clients/rust/tests/freeze_execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ use mpl_core::{
};
use setup::*;

use solana_program::{pubkey::Pubkey, system_instruction, system_program};
use solana_program::{
instruction::AccountMeta, pubkey::Pubkey, system_instruction, system_program,
};
use solana_program_test::tokio;
use solana_sdk::{signature::Keypair, signer::Signer, transaction::Transaction};

Expand Down Expand Up @@ -75,8 +77,13 @@ async fn test_freeze_execute_backed_nft_flow() {
// ----------------------------------
// 2. Deposit backing SOL into the asset account (simulate 0.5 SOL backing)
// ----------------------------------
let (asset_signer, _) = Pubkey::find_program_address(
&[FREEZE_EXECUTE_PREFIX.as_bytes(), asset.pubkey().as_ref()],
&mpl_core::ID,
);

let backing_amount: u64 = 500_000_000; // 0.5 SOL
let transfer_ix = system_instruction::transfer(&payer_key, &asset.pubkey(), backing_amount);
let transfer_ix = system_instruction::transfer(&payer_key, &asset_signer, backing_amount);
let tx = Transaction::new_signed_with_payer(
&[transfer_ix],
Some(&payer_key),
Expand All @@ -86,21 +93,22 @@ async fn test_freeze_execute_backed_nft_flow() {
context.banks_client.process_transaction(tx).await.unwrap();

// Capture lamports held by the asset account after deposit.
let asset_account_after_deposit = context
let lamports_in_asset = context
.banks_client
.get_account(asset.pubkey())
.get_balance(asset_signer)
.await
.expect("get_account")
.expect("asset account not found");
let lamports_in_asset = asset_account_after_deposit.lamports;
.expect("get_balance failed");

// Verify that the deposit was successful.
assert_eq!(lamports_in_asset, backing_amount);

// ----------------------------------
// 3. Attempt Execute → should fail because plugin is frozen.
// ----------------------------------
let (asset_signer, _) = Pubkey::find_program_address(
&[FREEZE_EXECUTE_PREFIX.as_bytes(), asset.pubkey().as_ref()],
&mpl_core::ID,
);
let execute_transfer_ix =
system_instruction::transfer(&asset_signer, &payer_key, lamports_in_asset);

println!("execute_transfer_ix: {:#?}", execute_transfer_ix);
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Remove debug print statement.

The debug print appears to be left over from development/debugging and should be removed before merging.

-    println!("execute_transfer_ix: {:#?}", execute_transfer_ix);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
println!("execute_transfer_ix: {:#?}", execute_transfer_ix);
🤖 Prompt for AI Agents
In clients/rust/tests/freeze_execute.rs at line 111, remove the debug print
statement that outputs the execute_transfer_ix variable. This println! line was
used for debugging and should be deleted to clean up the code before merging.


let execute_ix = ExecuteV1Builder::new()
.asset(asset.pubkey())
Expand All @@ -110,7 +118,19 @@ async fn test_freeze_execute_backed_nft_flow() {
.authority(Some(payer_key))
.system_program(system_program::ID)
.program_id(system_program::ID) // use system program as harmless target
.instruction_data(Vec::new())
.instruction_data(execute_transfer_ix.data)
.add_remaining_accounts(&[
AccountMeta {
pubkey: asset_signer,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: payer_key,
is_signer: false,
is_writable: true,
},
])
.instruction();

let tx = Transaction::new_signed_with_payer(
Expand Down Expand Up @@ -149,12 +169,15 @@ async fn test_freeze_execute_backed_nft_flow() {
);
context.banks_client.process_transaction(tx).await.unwrap();

// Asset account should be closed or at least drained.
let _asset_account_after_burn = context
// Verify that the transfer was successful.
let lamports_in_asset_after_execute = context
.banks_client
.get_account(asset.pubkey())
.get_balance(asset_signer)
.await
.unwrap();
.expect("get_balance failed");

// Verify that the asset balance has not changed.
assert_eq!(lamports_in_asset_after_execute, lamports_in_asset);

// Verify payer balance increased (should receive refund though exact amount may be reduced by rent/taxes).
let payer_balance_after_burn = context.banks_client.get_balance(payer_key).await.unwrap();
Expand Down