use {
dlp_api::{
args::{
DelegateArgs, DelegateWithActionsArgs, EncryptedBuffer, MaybeEncryptedAccountMeta,
MaybeEncryptedInstruction, MaybeEncryptedIxData, MaybeEncryptedPubkey,
PostDelegationActions,
},
compact::{AccountMeta as CompactAccountMeta, ClearText},
consts::DELEGATION_PROGRAM_ID,
discriminator::DlpDiscriminator,
encryption,
pda::{DELEGATE_BUFFER_TAG, DELEGATION_METADATA_TAG, DELEGATION_RECORD_TAG},
},
ephemeral_rollups_sdk::consts::{
EPHEMERAL_VAULT_ID, ESPL_TOKEN_PROGRAM_ID, MAGIC_CONTEXT_ID, MAGIC_PROGRAM_ID,
},
magicblock_account::{AccountMode, ReadableAccount},
magicblock_magic_program_api::{
args::{
ActionArgs, BaseActionArgs, CommitAndUndelegateArgs, CommitTypeArgs,
MagicBaseIntentArgs, MagicIntentBundleArgs, ShortAccountMeta, UndelegateTypeArgs,
},
instruction::MagicBlockInstruction,
},
magicsvm::{MagicSVM, TransactionTarget, DEFAULT_VALIDATOR_IDENTITY},
solana_account::Account,
solana_clock::Clock,
solana_instruction::{account_meta::AccountMeta, error::InstructionError, Instruction},
solana_keypair::Keypair,
solana_message::Message,
solana_native_token::LAMPORTS_PER_SOL,
solana_program_runtime::{
declare_process_instruction, invoke_context::InvokeContext,
solana_sbpf::program::BuiltinFunctionDefinition,
},
solana_sdk_ids::{bpf_loader_upgradeable, system_program},
solana_signature::Signature,
solana_signer::Signer,
solana_system_interface::instruction::{allocate, transfer},
solana_transaction::Transaction,
solana_transaction_error::TransactionError,
};
const EPHEMERAL_CALLER_PROGRAM_ID: solana_address::Address =
solana_address::Address::new_from_array([7; 32]);
const NESTED_EPHEMERAL_CALLER_PROGRAM_ID: solana_address::Address =
solana_address::Address::new_from_array([8; 32]);
const READONLY_AUTH_PROGRAM_ID: solana_address::Address =
solana_address::Address::new_from_array([9; 32]);
const POST_COMMIT_ACTION_PROGRAM_ID: solana_address::Address =
solana_address::Address::new_from_array([10; 32]);
const POST_COMMIT_SCHEDULER_PROGRAM_ID: solana_address::Address =
solana_address::Address::new_from_array([11; 32]);
const PERMISSIONLESS_INCREMENT_PROGRAM_ID: solana_address::Address =
solana_address::Address::new_from_array([12; 32]);
const DELEGATE_WITH_ACTIONS_CALLER_PROGRAM_ID: solana_address::Address =
solana_address::Address::new_from_array([13; 32]);
const SAME_TX_CREATE_DISCRIMINATOR: [u8; 8] = [0xab, 0x2e, 0xb4, 0x3a, 0xf5, 0xdd, 0x67, 0xae];
const SAME_TX_CREATE_TRANSFER_LAMPORTS: u64 = 42;
declare_process_instruction!(EphemeralCallerEntrypoint, 1_000, |invoke_context| {
process_ephemeral_caller(invoke_context)
});
declare_process_instruction!(NestedEphemeralCallerEntrypoint, 1_000, |invoke_context| {
process_nested_ephemeral_caller(invoke_context)
});
declare_process_instruction!(ReadonlyAuthEntrypoint, 1_000, |invoke_context| {
let instruction_context = invoke_context
.transaction_context
.get_current_instruction_context()?;
let auth_byte = {
let auth = instruction_context.try_borrow_instruction_account(1)?;
*auth
.get_data()
.first()
.ok_or(InstructionError::InvalidAccountData)?
};
if auth_byte != 42 {
return Err(InstructionError::InvalidAccountData);
}
let mut dest = instruction_context.try_borrow_instruction_account(0)?;
dest.get_data_mut()?[0] = auth_byte;
Ok(())
});
declare_process_instruction!(PostCommitActionEntrypoint, 1_000, |invoke_context| {
let instruction_context = invoke_context
.transaction_context
.get_current_instruction_context()?;
if instruction_context.get_stack_height() <= 1 {
return Err(InstructionError::InvalidInstructionData);
}
if instruction_context.get_number_of_instruction_accounts() != 3 {
return Err(InstructionError::MissingAccount);
}
if !instruction_context.is_instruction_account_signer(2)? {
return Err(InstructionError::MissingRequiredSignature);
}
let mut dest = instruction_context.try_borrow_instruction_account(0)?;
dest.get_data_mut()?[0] = 7;
Ok(())
});
declare_process_instruction!(PostCommitSchedulerEntrypoint, 1_000, |invoke_context| {
let instruction_context = invoke_context
.transaction_context
.get_current_instruction_context()?;
invoke_context.native_invoke_signed(
Instruction {
program_id: MAGIC_PROGRAM_ID,
accounts: vec![
AccountMeta::new(
*instruction_context.get_key_of_instruction_account(0)?,
true,
),
AccountMeta::new(
*instruction_context.get_key_of_instruction_account(1)?,
false,
),
AccountMeta::new_readonly(
*instruction_context.get_key_of_instruction_account(2)?,
false,
),
],
data: instruction_context.get_instruction_data().to_vec(),
},
&[],
)
});
declare_process_instruction!(PermissionlessIncrementEntrypoint, 1_000, |invoke_context| {
let instruction_context = invoke_context
.transaction_context
.get_current_instruction_context()?;
let mut account = instruction_context.try_borrow_instruction_account(0)?;
let next = account
.get_data()
.first()
.copied()
.ok_or(InstructionError::InvalidAccountData)?
.wrapping_add(1);
account.get_data_mut()?[0] = next;
Ok(())
});
declare_process_instruction!(
DelegateWithActionsCallerEntrypoint,
1_000,
|invoke_context| {
let instruction_context = invoke_context
.transaction_context
.get_current_instruction_context()?;
let n = instruction_context.get_number_of_instruction_accounts();
if n < 2 {
return Err(InstructionError::MissingAccount);
}
let mut accounts = Vec::with_capacity(usize::from(n) - 1);
for index in 0..n - 1 {
let pubkey = *instruction_context.get_key_of_instruction_account(index)?;
let is_signer = instruction_context.is_instruction_account_signer(index)?;
accounts.push(
if instruction_context.is_instruction_account_writable(index)? {
AccountMeta::new(pubkey, is_signer)
} else {
AccountMeta::new_readonly(pubkey, is_signer)
},
);
}
invoke_context.native_invoke_signed(
Instruction {
program_id: DELEGATION_PROGRAM_ID,
accounts,
data: instruction_context.get_instruction_data().to_vec(),
},
&[],
)
}
);
fn process_ephemeral_caller(invoke_context: &mut InvokeContext) -> Result<(), InstructionError> {
let instruction_context = invoke_context
.transaction_context
.get_current_instruction_context()?;
let data = instruction_context.get_instruction_data();
if data.first() == Some(&3) {
let mut account = instruction_context.try_borrow_instruction_account(1)?;
let next = account.get_data()[0].wrapping_add(1);
account.get_data_mut()?[0] = next;
return Ok(());
}
let magic_instruction = match data.first().copied() {
Some(0) | Some(4) | Some(5) => {
MagicBlockInstruction::CreateEphemeralAccount { data_len: 16 }
}
Some(1) | Some(6) => MagicBlockInstruction::ResizeEphemeralAccount { new_data_len: 24 },
Some(2) => MagicBlockInstruction::CloseEphemeralAccount,
_ => return Err(InstructionError::InvalidInstructionData),
};
let write_after_create = data.first() == Some(&4) || data.first() == Some(&5);
let write_after_resize = data.first() == Some(&6);
let drain_credited_lamports = data.first() == Some(&5);
let ephemeral_is_signer = data.first() == Some(&0) || write_after_create;
invoke_context.native_invoke_signed(
Instruction::new_with_bincode(
MAGIC_PROGRAM_ID,
&magic_instruction,
vec![
AccountMeta::new(
*instruction_context.get_key_of_instruction_account(0)?,
true,
),
AccountMeta::new(
*instruction_context.get_key_of_instruction_account(1)?,
ephemeral_is_signer,
),
AccountMeta::new(EPHEMERAL_VAULT_ID, false),
],
),
&[],
)?;
if !write_after_create {
if !write_after_resize {
return Ok(());
}
let instruction_context = invoke_context
.transaction_context
.get_current_instruction_context()?;
let mut account = instruction_context.try_borrow_instruction_account(1)?;
let data = account.get_data_mut()?;
if data.len() < 16 + SAME_TX_CREATE_DISCRIMINATOR.len() {
return Err(InstructionError::InvalidAccountData);
}
data[16..16 + SAME_TX_CREATE_DISCRIMINATOR.len()]
.copy_from_slice(&SAME_TX_CREATE_DISCRIMINATOR);
return Ok(());
}
let instruction_context = invoke_context
.transaction_context
.get_current_instruction_context()?;
let mut account = instruction_context.try_borrow_instruction_account(1)?;
let data = account.get_data_mut()?;
if data.len() < SAME_TX_CREATE_DISCRIMINATOR.len() {
return Err(InstructionError::InvalidAccountData);
}
data[..SAME_TX_CREATE_DISCRIMINATOR.len()].copy_from_slice(&SAME_TX_CREATE_DISCRIMINATOR);
drop(account);
if !drain_credited_lamports {
return Ok(());
}
{
let mut ephemeral = instruction_context.try_borrow_instruction_account(1)?;
ephemeral.checked_add_lamports(SAME_TX_CREATE_TRANSFER_LAMPORTS)?;
}
{
let mut ephemeral = instruction_context.try_borrow_instruction_account(1)?;
ephemeral.checked_sub_lamports(SAME_TX_CREATE_TRANSFER_LAMPORTS)?;
}
Ok(())
}
fn process_nested_ephemeral_caller(
invoke_context: &mut InvokeContext,
) -> Result<(), InstructionError> {
let instruction_context = invoke_context
.transaction_context
.get_current_instruction_context()?;
let ephemeral_is_signer = instruction_context.get_instruction_data().first() == Some(&0);
invoke_context.native_invoke_signed(
Instruction {
program_id: EPHEMERAL_CALLER_PROGRAM_ID,
accounts: vec![
AccountMeta::new(
*instruction_context.get_key_of_instruction_account(0)?,
true,
),
AccountMeta::new(
*instruction_context.get_key_of_instruction_account(1)?,
ephemeral_is_signer,
),
AccountMeta::new(EPHEMERAL_VAULT_ID, false),
AccountMeta::new_readonly(MAGIC_PROGRAM_ID, false),
],
data: instruction_context.get_instruction_data().to_vec(),
},
&[],
)
}
fn ephemeral_rent(data_len: u32) -> u64 {
ephemeral_rollups_sdk::ephemeral_accounts::rent(data_len)
}
fn schedule_commit_tx(
payer: &Keypair,
delegated_account: &Keypair,
instruction_data: Vec<u8>,
delegated_account_is_writable: bool,
blockhash: solana_hash::Hash,
) -> Transaction {
let delegated_meta = if delegated_account_is_writable {
AccountMeta::new(delegated_account.pubkey(), false)
} else {
AccountMeta::new_readonly(delegated_account.pubkey(), false)
};
Transaction::new(
&[payer],
Message::new(
&[Instruction {
program_id: MAGIC_PROGRAM_ID,
accounts: vec![
AccountMeta::new(payer.pubkey(), true),
AccountMeta::new(MAGIC_CONTEXT_ID, false),
delegated_meta,
],
data: instruction_data,
}],
Some(&payer.pubkey()),
),
blockhash,
)
}
fn delegate_with_actions_ix(
payer: solana_address::Address,
delegated_account: solana_address::Address,
owner: solana_address::Address,
actions: PostDelegationActions,
extra_accounts: Vec<AccountMeta>,
) -> Instruction {
let delegate_buffer = solana_address::Address::find_program_address(
&[DELEGATE_BUFFER_TAG, delegated_account.as_ref()],
&owner,
)
.0;
let delegation_record = solana_address::Address::find_program_address(
&[DELEGATION_RECORD_TAG, delegated_account.as_ref()],
&DELEGATION_PROGRAM_ID,
)
.0;
let delegation_metadata = solana_address::Address::find_program_address(
&[DELEGATION_METADATA_TAG, delegated_account.as_ref()],
&DELEGATION_PROGRAM_ID,
)
.0;
let args = DelegateWithActionsArgs {
delegate: DelegateArgs::default(),
actions,
};
let mut data = DlpDiscriminator::DelegateWithActions.to_vec();
data.extend_from_slice(&borsh::to_vec(&args).unwrap());
let mut accounts = vec![
AccountMeta::new(payer, true),
AccountMeta::new(delegated_account, true),
AccountMeta::new_readonly(owner, false),
AccountMeta::new(delegate_buffer, false),
AccountMeta::new(delegation_record, false),
AccountMeta::new(delegation_metadata, false),
AccountMeta::new_readonly(system_program::id(), false),
];
accounts.extend(extra_accounts);
Instruction {
program_id: DELEGATION_PROGRAM_ID,
accounts,
data,
}
}
fn delegate_with_actions_tx(
payer: &Keypair,
delegated_account: &Keypair,
actions: PostDelegationActions,
action_accounts: Vec<AccountMeta>,
blockhash: solana_hash::Hash,
) -> Transaction {
Transaction::new(
&[payer, delegated_account],
Message::new(
&[delegate_with_actions_ix(
payer.pubkey(),
delegated_account.pubkey(),
system_program::id(),
actions,
action_accounts,
)],
Some(&payer.pubkey()),
),
blockhash,
)
}
fn permissionless_increment_actions(
delegated_account: solana_address::Address,
) -> PostDelegationActions {
vec![Instruction {
program_id: PERMISSIONLESS_INCREMENT_PROGRAM_ID,
accounts: vec![AccountMeta::new(delegated_account, false)],
data: vec![],
}]
.cleartext()
}
fn add_permissionless_increment_program(svm: &mut MagicSVM) {
svm.add_builtin(
PERMISSIONLESS_INCREMENT_PROGRAM_ID,
PermissionlessIncrementEntrypoint::register,
);
svm.ephemeral_mut().add_builtin(
PERMISSIONLESS_INCREMENT_PROGRAM_ID,
PermissionlessIncrementEntrypoint::register,
);
}
fn set_increment_owned_account(svm: &mut MagicSVM, delegated_account: solana_address::Address) {
svm.set_account(
delegated_account,
Account {
lamports: LAMPORTS_PER_SOL,
data: vec![0],
owner: DELEGATION_PROGRAM_ID,
..Default::default()
},
)
.unwrap();
}
fn encrypted_noop_post_delegation_actions(
validator: solana_address::Address,
delegated_account: solana_address::Address,
) -> PostDelegationActions {
let noop_data = bincode::serialize(&MagicBlockInstruction::Noop(0)).unwrap();
PostDelegationActions {
inserted_signers: 0,
inserted_non_signers: 0,
signers: vec![delegated_account.to_bytes()],
non_signers: vec![MaybeEncryptedPubkey::Encrypted(EncryptedBuffer::new(
encryption::encrypt_ed25519_recipient(
&MAGIC_PROGRAM_ID.to_bytes(),
&validator.to_bytes(),
)
.unwrap(),
))],
instructions: vec![MaybeEncryptedInstruction {
program_id: 1,
accounts: vec![MaybeEncryptedAccountMeta::ClearText(
CompactAccountMeta::try_new(0, true, false).unwrap(),
)],
data: MaybeEncryptedIxData {
prefix: Vec::new(),
suffix: EncryptedBuffer::new(
encryption::encrypt_ed25519_recipient(&noop_data, &validator.to_bytes())
.unwrap(),
),
},
}],
}
}
fn set_delegation_ready_account(svm: &mut MagicSVM, delegated_account: solana_address::Address) {
svm.set_account(
delegated_account,
Account {
lamports: LAMPORTS_PER_SOL,
owner: DELEGATION_PROGRAM_ID,
..Default::default()
},
)
.unwrap();
}
#[test_log::test]
fn magic_svm_loads_delegation_program_by_default() {
let svm = MagicSVM::new();
let delegation_program = svm.get_account(&DELEGATION_PROGRAM_ID).unwrap();
assert!(delegation_program.executable);
assert_eq!(delegation_program.owner, bpf_loader_upgradeable::id());
}
#[test_log::test]
fn magic_svm_loads_magic_program_only_on_ephemeral() {
let svm = MagicSVM::new();
assert!(svm
.get_account_for(TransactionTarget::Base, &MAGIC_PROGRAM_ID)
.is_none());
let magic_program = svm
.get_account_for(TransactionTarget::Ephemeral, &MAGIC_PROGRAM_ID)
.unwrap();
assert!(magic_program.executable);
}
#[test_log::test]
fn magic_svm_loads_espl_on_both_ledgers() {
let svm = MagicSVM::new();
let base = svm
.get_account_for(TransactionTarget::Base, &ESPL_TOKEN_PROGRAM_ID)
.unwrap();
assert!(base.executable);
let ephemeral = svm.ephemeral().get_account(&ESPL_TOKEN_PROGRAM_ID).unwrap();
assert!(ephemeral.executable);
}
#[test_log::test]
fn set_sysvar_updates_ephemeral_clock() {
let mut svm = MagicSVM::new();
let mut clock: Clock = svm.get_sysvar();
clock.unix_timestamp = 1_700_000_000;
svm.set_sysvar(&clock);
assert_eq!(svm.get_sysvar::<Clock>().unix_timestamp, 1_700_000_000);
assert_eq!(
svm.ephemeral().get_sysvar::<Clock>().unix_timestamp,
1_700_000_000
);
}
#[test_log::test]
fn warp_to_slot_updates_ephemeral_clock() {
let mut svm = MagicSVM::new();
svm.warp_to_slot(42);
assert_eq!(svm.get_sysvar::<Clock>().slot, 42);
assert_eq!(svm.ephemeral().get_sysvar::<Clock>().slot, 42);
}
#[test_log::test]
fn ephemeral_account_lookup_falls_back_to_base_when_missing_locally() {
let base_only = Keypair::new();
let mut svm = MagicSVM::new();
let base_account = Account {
lamports: 42,
data: vec![1, 2, 3],
owner: system_program::id(),
..Default::default()
};
svm.set_account(base_only.pubkey(), base_account.clone())
.unwrap();
let fallback_account = svm
.get_account_for(TransactionTarget::Ephemeral, &base_only.pubkey())
.unwrap();
assert_eq!(fallback_account.lamports, base_account.lamports);
assert_eq!(fallback_account.data, base_account.data);
assert_eq!(fallback_account.owner, base_account.owner);
let shared_account = svm
.get_shared_account_for(TransactionTarget::Ephemeral, &base_only.pubkey())
.unwrap();
assert_eq!(shared_account.lamports(), base_account.lamports);
assert_eq!(shared_account.data(), base_account.data.as_slice());
assert_eq!(shared_account.owner(), &base_account.owner);
let ephemeral_account = magicblock_account::Account {
lamports: 7,
data: vec![9],
owner: MAGIC_PROGRAM_ID,
..Default::default()
};
svm.ephemeral_mut()
.set_account(base_only.pubkey(), ephemeral_account.clone())
.unwrap();
let shadowed_account = svm
.get_account_for(TransactionTarget::Ephemeral, &base_only.pubkey())
.unwrap();
assert_eq!(shadowed_account.lamports, ephemeral_account.lamports);
assert_eq!(shadowed_account.data, ephemeral_account.data);
assert_eq!(shadowed_account.owner, ephemeral_account.owner);
}
#[test_log::test]
fn ephemeral_magic_program_accepts_noop_and_rejects_invalid_data() {
let payer = Keypair::new();
let mut svm = MagicSVM::new();
svm.airdrop(&payer.pubkey(), LAMPORTS_PER_SOL).unwrap();
let noop = Transaction::new(
&[&payer],
Message::new(
&[Instruction::new_with_bincode(
MAGIC_PROGRAM_ID,
&MagicBlockInstruction::Noop(0),
vec![],
)],
Some(&payer.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
svm.send_transaction_to(TransactionTarget::Ephemeral, noop)
.unwrap();
svm.expire_blockhash_for(TransactionTarget::Ephemeral);
let invalid = Transaction::new(
&[&payer],
Message::new(
&[Instruction {
program_id: MAGIC_PROGRAM_ID,
accounts: vec![],
data: vec![0xff],
}],
Some(&payer.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
let err = svm
.send_transaction_to(TransactionTarget::Ephemeral, invalid)
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::InvalidInstructionData)
);
}
#[test_log::test]
fn ephemeral_transactions_read_undelegated_readonly_accounts_from_base() {
let payer = Keypair::new();
let dest = Keypair::new();
let auth = Keypair::new();
let mut svm = MagicSVM::new();
svm.ephemeral_mut()
.add_builtin(READONLY_AUTH_PROGRAM_ID, ReadonlyAuthEntrypoint::register);
svm.airdrop(&payer.pubkey(), LAMPORTS_PER_SOL).unwrap();
svm.set_account(
dest.pubkey(),
Account {
lamports: LAMPORTS_PER_SOL,
data: vec![0],
owner: READONLY_AUTH_PROGRAM_ID,
..Default::default()
},
)
.unwrap();
svm.set_account(
auth.pubkey(),
Account {
lamports: LAMPORTS_PER_SOL,
data: vec![42],
owner: system_program::id(),
..Default::default()
},
)
.unwrap();
svm.delegate_account(dest.pubkey()).unwrap();
let tx = Transaction::new(
&[&payer],
Message::new(
&[Instruction {
program_id: READONLY_AUTH_PROGRAM_ID,
accounts: vec![
AccountMeta::new(dest.pubkey(), false),
AccountMeta::new_readonly(auth.pubkey(), false),
],
data: vec![],
}],
Some(&payer.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
svm.send_transaction_to(TransactionTarget::Ephemeral, tx)
.unwrap();
assert_eq!(
svm.get_account_for(TransactionTarget::Ephemeral, &dest.pubkey())
.unwrap()
.data,
vec![42]
);
}
#[test_log::test]
fn magic_svm_handles_magic_program_ephemeral_accounts() {
let sponsor = Keypair::new();
let ephemeral = Keypair::new();
let mut svm = MagicSVM::new();
svm.ephemeral_mut().add_builtin(
EPHEMERAL_CALLER_PROGRAM_ID,
EphemeralCallerEntrypoint::register,
);
svm.set_account(
sponsor.pubkey(),
Account {
lamports: LAMPORTS_PER_SOL,
owner: system_program::id(),
..Default::default()
},
)
.unwrap();
svm.delegate_account(sponsor.pubkey()).unwrap();
let initial_vault_lamports = svm
.get_account_for(TransactionTarget::Ephemeral, &EPHEMERAL_VAULT_ID)
.unwrap()
.lamports;
let create_tx = Transaction::new(
&[&sponsor, &ephemeral],
Message::new(
&[Instruction {
program_id: EPHEMERAL_CALLER_PROGRAM_ID,
accounts: vec![
AccountMeta::new(sponsor.pubkey(), true),
AccountMeta::new(ephemeral.pubkey(), true),
AccountMeta::new(EPHEMERAL_VAULT_ID, false),
AccountMeta::new_readonly(MAGIC_PROGRAM_ID, false),
],
data: vec![0],
}],
Some(&sponsor.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
svm.send_transaction_to(TransactionTarget::Ephemeral, create_tx)
.unwrap();
let account = svm
.get_shared_account_for(TransactionTarget::Ephemeral, &ephemeral.pubkey())
.unwrap();
assert!(account.is(AccountMode::Ephemeral));
assert_eq!(account.owner(), &EPHEMERAL_CALLER_PROGRAM_ID);
assert_eq!(account.data().len(), 16);
assert_eq!(
svm.get_account_for(TransactionTarget::Ephemeral, &EPHEMERAL_VAULT_ID)
.unwrap()
.lamports,
initial_vault_lamports + ephemeral_rent(16)
);
svm.expire_blockhash_for(TransactionTarget::Ephemeral);
let resize_tx = Transaction::new(
&[&sponsor],
Message::new(
&[Instruction {
program_id: EPHEMERAL_CALLER_PROGRAM_ID,
accounts: vec![
AccountMeta::new(sponsor.pubkey(), true),
AccountMeta::new(ephemeral.pubkey(), false),
AccountMeta::new(EPHEMERAL_VAULT_ID, false),
AccountMeta::new_readonly(MAGIC_PROGRAM_ID, false),
],
data: vec![1],
}],
Some(&sponsor.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
svm.send_transaction_to(TransactionTarget::Ephemeral, resize_tx)
.unwrap();
let account = svm
.get_shared_account_for(TransactionTarget::Ephemeral, &ephemeral.pubkey())
.unwrap();
assert!(account.is(AccountMode::Ephemeral));
assert_eq!(account.data().len(), 24);
svm.expire_blockhash_for(TransactionTarget::Ephemeral);
let close_tx = Transaction::new(
&[&sponsor],
Message::new(
&[Instruction {
program_id: EPHEMERAL_CALLER_PROGRAM_ID,
accounts: vec![
AccountMeta::new(sponsor.pubkey(), true),
AccountMeta::new(ephemeral.pubkey(), false),
AccountMeta::new(EPHEMERAL_VAULT_ID, false),
AccountMeta::new_readonly(MAGIC_PROGRAM_ID, false),
],
data: vec![2],
}],
Some(&sponsor.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
svm.send_transaction_to(TransactionTarget::Ephemeral, close_tx)
.unwrap();
assert!(svm
.get_shared_account_for(TransactionTarget::Ephemeral, &ephemeral.pubkey())
.is_none());
assert_eq!(
svm.get_account_for(TransactionTarget::Ephemeral, &EPHEMERAL_VAULT_ID)
.unwrap()
.lamports,
initial_vault_lamports
);
}
#[test_log::test]
fn ephemeral_create_preserves_program_writes_in_the_same_transaction() {
let sponsor = Keypair::new();
let ephemeral = Keypair::new();
let mut svm = MagicSVM::new();
svm.ephemeral_mut().add_builtin(
EPHEMERAL_CALLER_PROGRAM_ID,
EphemeralCallerEntrypoint::register,
);
svm.set_account(
sponsor.pubkey(),
Account {
lamports: LAMPORTS_PER_SOL,
owner: system_program::id(),
..Default::default()
},
)
.unwrap();
svm.delegate_account(sponsor.pubkey()).unwrap();
let create_tx = Transaction::new(
&[&sponsor, &ephemeral],
Message::new(
&[Instruction {
program_id: EPHEMERAL_CALLER_PROGRAM_ID,
accounts: vec![
AccountMeta::new(sponsor.pubkey(), true),
AccountMeta::new(ephemeral.pubkey(), true),
AccountMeta::new(EPHEMERAL_VAULT_ID, false),
AccountMeta::new_readonly(MAGIC_PROGRAM_ID, false),
],
data: vec![4],
}],
Some(&sponsor.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
svm.send_transaction_to(TransactionTarget::Ephemeral, create_tx)
.unwrap();
let account = svm
.get_shared_account_for(TransactionTarget::Ephemeral, &ephemeral.pubkey())
.unwrap();
assert!(account.is(AccountMode::Ephemeral));
assert_eq!(account.owner(), &EPHEMERAL_CALLER_PROGRAM_ID);
assert_eq!(
&account.data()[..SAME_TX_CREATE_DISCRIMINATOR.len()],
&SAME_TX_CREATE_DISCRIMINATOR
);
assert_eq!(account.lamports(), 0);
}
#[test_log::test]
fn ephemeral_resize_makes_new_bytes_writable_in_the_same_transaction() {
let sponsor = Keypair::new();
let ephemeral = Keypair::new();
let mut svm = MagicSVM::new();
svm.ephemeral_mut().add_builtin(
EPHEMERAL_CALLER_PROGRAM_ID,
EphemeralCallerEntrypoint::register,
);
svm.set_account(
sponsor.pubkey(),
Account {
lamports: LAMPORTS_PER_SOL,
owner: system_program::id(),
..Default::default()
},
)
.unwrap();
svm.delegate_account(sponsor.pubkey()).unwrap();
let create_tx = Transaction::new(
&[&sponsor, &ephemeral],
Message::new(
&[Instruction {
program_id: EPHEMERAL_CALLER_PROGRAM_ID,
accounts: vec![
AccountMeta::new(sponsor.pubkey(), true),
AccountMeta::new(ephemeral.pubkey(), true),
AccountMeta::new(EPHEMERAL_VAULT_ID, false),
AccountMeta::new_readonly(MAGIC_PROGRAM_ID, false),
],
data: vec![0],
}],
Some(&sponsor.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
svm.send_transaction_to(TransactionTarget::Ephemeral, create_tx)
.unwrap();
svm.expire_blockhash_for(TransactionTarget::Ephemeral);
let resize_tx = Transaction::new(
&[&sponsor],
Message::new(
&[Instruction {
program_id: EPHEMERAL_CALLER_PROGRAM_ID,
accounts: vec![
AccountMeta::new(sponsor.pubkey(), true),
AccountMeta::new(ephemeral.pubkey(), false),
AccountMeta::new(EPHEMERAL_VAULT_ID, false),
AccountMeta::new_readonly(MAGIC_PROGRAM_ID, false),
],
data: vec![6],
}],
Some(&sponsor.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
svm.send_transaction_to(TransactionTarget::Ephemeral, resize_tx)
.unwrap();
let account = svm
.get_shared_account_for(TransactionTarget::Ephemeral, &ephemeral.pubkey())
.unwrap();
assert!(account.is(AccountMode::Ephemeral));
assert_eq!(account.data().len(), 24);
assert_eq!(&account.data()[16..24], &SAME_TX_CREATE_DISCRIMINATOR);
}
#[test_log::test]
fn ephemeral_create_preserves_same_transaction_lamport_transfers() {
let sponsor = Keypair::new();
let ephemeral = Keypair::new();
let mut svm = MagicSVM::new();
svm.ephemeral_mut().add_builtin(
EPHEMERAL_CALLER_PROGRAM_ID,
EphemeralCallerEntrypoint::register,
);
svm.set_account(
sponsor.pubkey(),
Account {
lamports: LAMPORTS_PER_SOL,
owner: system_program::id(),
..Default::default()
},
)
.unwrap();
svm.delegate_account(sponsor.pubkey()).unwrap();
let create_tx = Transaction::new(
&[&sponsor, &ephemeral],
Message::new(
&[Instruction {
program_id: EPHEMERAL_CALLER_PROGRAM_ID,
accounts: vec![
AccountMeta::new(sponsor.pubkey(), true),
AccountMeta::new(ephemeral.pubkey(), true),
AccountMeta::new(EPHEMERAL_VAULT_ID, false),
AccountMeta::new_readonly(MAGIC_PROGRAM_ID, false),
],
data: vec![5],
}],
Some(&sponsor.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
svm.send_transaction_to(TransactionTarget::Ephemeral, create_tx)
.unwrap();
let account = svm
.get_shared_account_for(TransactionTarget::Ephemeral, &ephemeral.pubkey())
.unwrap();
assert!(account.is(AccountMode::Ephemeral));
assert_eq!(
&account.data()[..SAME_TX_CREATE_DISCRIMINATOR.len()],
&SAME_TX_CREATE_DISCRIMINATOR
);
assert_eq!(account.lamports(), 0);
}
#[test_log::test]
fn nested_cpi_create_records_immediate_caller_as_ephemeral_owner() {
let sponsor = Keypair::new();
let ephemeral = Keypair::new();
let mut svm = MagicSVM::new();
svm.ephemeral_mut().add_builtin(
EPHEMERAL_CALLER_PROGRAM_ID,
EphemeralCallerEntrypoint::register,
);
svm.ephemeral_mut().add_builtin(
NESTED_EPHEMERAL_CALLER_PROGRAM_ID,
NestedEphemeralCallerEntrypoint::register,
);
svm.set_account(
sponsor.pubkey(),
Account {
lamports: LAMPORTS_PER_SOL,
owner: system_program::id(),
..Default::default()
},
)
.unwrap();
svm.delegate_account(sponsor.pubkey()).unwrap();
let create_tx = Transaction::new(
&[&sponsor, &ephemeral],
Message::new(
&[Instruction {
program_id: NESTED_EPHEMERAL_CALLER_PROGRAM_ID,
accounts: vec![
AccountMeta::new(sponsor.pubkey(), true),
AccountMeta::new(ephemeral.pubkey(), true),
AccountMeta::new(EPHEMERAL_VAULT_ID, false),
AccountMeta::new_readonly(EPHEMERAL_CALLER_PROGRAM_ID, false),
AccountMeta::new_readonly(MAGIC_PROGRAM_ID, false),
],
data: vec![0],
}],
Some(&sponsor.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
svm.send_transaction_to(TransactionTarget::Ephemeral, create_tx)
.unwrap();
let account = svm
.get_shared_account_for(TransactionTarget::Ephemeral, &ephemeral.pubkey())
.unwrap();
assert!(account.is(AccountMode::Ephemeral));
assert_eq!(account.owner(), &EPHEMERAL_CALLER_PROGRAM_ID);
svm.expire_blockhash_for(TransactionTarget::Ephemeral);
let resize_tx = Transaction::new(
&[&sponsor],
Message::new(
&[Instruction {
program_id: NESTED_EPHEMERAL_CALLER_PROGRAM_ID,
accounts: vec![
AccountMeta::new(sponsor.pubkey(), true),
AccountMeta::new(ephemeral.pubkey(), false),
AccountMeta::new(EPHEMERAL_VAULT_ID, false),
AccountMeta::new_readonly(EPHEMERAL_CALLER_PROGRAM_ID, false),
AccountMeta::new_readonly(MAGIC_PROGRAM_ID, false),
],
data: vec![1],
}],
Some(&sponsor.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
svm.send_transaction_to(TransactionTarget::Ephemeral, resize_tx)
.unwrap();
let account = svm
.get_shared_account_for(TransactionTarget::Ephemeral, &ephemeral.pubkey())
.unwrap();
assert_eq!(account.data().len(), 24);
}
#[test_log::test]
fn ephemeral_account_effect_failure_rolls_back_transaction_writes() {
let sponsor = Keypair::new();
let ephemeral = Keypair::new();
let mut svm = MagicSVM::new();
svm.ephemeral_mut().add_builtin(
EPHEMERAL_CALLER_PROGRAM_ID,
EphemeralCallerEntrypoint::register,
);
svm.set_account(
sponsor.pubkey(),
Account {
lamports: ephemeral_rent(16) - 1,
owner: system_program::id(),
..Default::default()
},
)
.unwrap();
svm.delegate_account(sponsor.pubkey()).unwrap();
let initial_vault_lamports = svm
.get_account_for(TransactionTarget::Ephemeral, &EPHEMERAL_VAULT_ID)
.unwrap()
.lamports;
let create_tx = Transaction::new(
&[&sponsor, &ephemeral],
Message::new(
&[Instruction {
program_id: EPHEMERAL_CALLER_PROGRAM_ID,
accounts: vec![
AccountMeta::new(sponsor.pubkey(), true),
AccountMeta::new(ephemeral.pubkey(), true),
AccountMeta::new(EPHEMERAL_VAULT_ID, false),
AccountMeta::new_readonly(MAGIC_PROGRAM_ID, false),
],
data: vec![0],
}],
Some(&sponsor.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
let err = svm
.send_transaction_to(TransactionTarget::Ephemeral, create_tx)
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::InsufficientFunds)
);
assert!(svm
.get_shared_account_for(TransactionTarget::Ephemeral, &ephemeral.pubkey())
.is_none());
assert_eq!(
svm.get_account_for(TransactionTarget::Ephemeral, &EPHEMERAL_VAULT_ID)
.unwrap()
.lamports,
initial_vault_lamports
);
}
#[test_log::test]
fn magic_program_rejects_direct_ephemeral_account_create() {
let sponsor = Keypair::new();
let ephemeral = Keypair::new();
let mut svm = MagicSVM::new();
svm.set_account(
sponsor.pubkey(),
Account {
lamports: LAMPORTS_PER_SOL,
owner: system_program::id(),
..Default::default()
},
)
.unwrap();
svm.delegate_account(sponsor.pubkey()).unwrap();
let tx = Transaction::new(
&[&sponsor, &ephemeral],
Message::new(
&[Instruction::new_with_bincode(
MAGIC_PROGRAM_ID,
&MagicBlockInstruction::CreateEphemeralAccount { data_len: 16 },
vec![
AccountMeta::new(sponsor.pubkey(), true),
AccountMeta::new(ephemeral.pubkey(), true),
AccountMeta::new(EPHEMERAL_VAULT_ID, false),
],
)],
Some(&sponsor.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
let err = svm
.send_transaction_to(TransactionTarget::Ephemeral, tx)
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::IncorrectProgramId)
);
}
#[test_log::test]
fn ephemeral_transactions_cannot_create_regular_accounts() {
let sponsor = Keypair::new();
let regular = Keypair::new();
let mut svm = MagicSVM::new();
svm.set_account(
sponsor.pubkey(),
Account {
lamports: LAMPORTS_PER_SOL,
owner: system_program::id(),
..Default::default()
},
)
.unwrap();
svm.delegate_account(sponsor.pubkey()).unwrap();
let tx = Transaction::new(
&[&sponsor, ®ular],
Message::new(
&[
allocate(®ular.pubkey(), 8),
Instruction::new_with_bincode(
MAGIC_PROGRAM_ID,
&MagicBlockInstruction::Noop(0),
vec![AccountMeta::new_readonly(EPHEMERAL_VAULT_ID, false)],
),
],
Some(&sponsor.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
let err = svm
.send_transaction_to(TransactionTarget::Ephemeral, tx)
.unwrap_err()
.err;
assert_eq!(err, TransactionError::InvalidWritableAccount);
assert!(svm
.get_shared_account_for(TransactionTarget::Ephemeral, ®ular.pubkey())
.is_none());
}
#[test_log::test]
fn magic_svm_defaults_to_magicblock_validator_identity() {
let svm = MagicSVM::new();
assert_eq!(
svm.validator_identity(),
Keypair::from_base58_string(DEFAULT_VALIDATOR_IDENTITY).pubkey()
);
}
#[test_log::test]
fn magic_svm_can_be_initialized_with_a_validator_identity() {
let validator = Keypair::new();
let svm = MagicSVM::new_with_validator_identity(validator.insecure_clone());
assert_eq!(svm.validator_identity(), validator.pubkey());
}
#[test_log::test]
fn target_specific_helpers_use_the_selected_ledger() {
let payer = Keypair::new();
let delegated = Keypair::new();
let mut svm = MagicSVM::new();
let base_airdrop = svm.airdrop(&payer.pubkey(), LAMPORTS_PER_SOL).unwrap();
assert!(svm
.get_transaction_for(TransactionTarget::Base, &base_airdrop.signature)
.is_some());
assert!(svm
.get_transaction_for(TransactionTarget::Ephemeral, &base_airdrop.signature)
.is_none());
svm.airdrop(&delegated.pubkey(), LAMPORTS_PER_SOL).unwrap();
svm.delegate_account(delegated.pubkey()).unwrap();
let base_blockhash = svm.latest_blockhash_for(TransactionTarget::Base);
let ephemeral_blockhash = svm.latest_blockhash_for(TransactionTarget::Ephemeral);
svm.expire_blockhash_for(TransactionTarget::Base);
assert_ne!(
svm.latest_blockhash_for(TransactionTarget::Base),
base_blockhash
);
assert_eq!(
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
ephemeral_blockhash
);
let allowed = Transaction::new(
&[&payer, &delegated],
Message::new(&[allocate(&delegated.pubkey(), 8)], Some(&payer.pubkey())),
ephemeral_blockhash,
);
let ephemeral_result = svm
.send_transaction_to(TransactionTarget::Ephemeral, allowed)
.unwrap();
assert!(svm
.get_transaction_for(TransactionTarget::Ephemeral, &ephemeral_result.signature)
.is_some());
assert!(svm
.get_transaction_for(TransactionTarget::Base, &ephemeral_result.signature)
.is_none());
assert_eq!(
svm.get_account_for(TransactionTarget::Base, &delegated.pubkey())
.unwrap()
.data
.len(),
0
);
assert_eq!(
svm.get_account_for(TransactionTarget::Ephemeral, &delegated.pubkey())
.unwrap()
.data
.len(),
8
);
}
#[test_log::test]
fn delegated_accounts_are_mirrored_to_ephemeral_ledger() {
let delegated = Keypair::new();
let mut svm = MagicSVM::new();
svm.airdrop(&delegated.pubkey(), LAMPORTS_PER_SOL).unwrap();
svm.delegate_account(delegated.pubkey()).unwrap();
let base_account = svm.get_account(&delegated.pubkey()).unwrap();
assert_eq!(base_account.owner, DELEGATION_PROGRAM_ID);
let ephemeral_account = svm
.get_shared_account_for(TransactionTarget::Ephemeral, &delegated.pubkey())
.unwrap();
assert!(ephemeral_account.is(AccountMode::Delegated));
assert!(!ephemeral_account.is(AccountMode::Ephemeral));
}
#[test_log::test]
fn account_flags_persist_without_reapply_ephemeral_flags() {
let payer = Keypair::new();
let delegated = Keypair::new();
let sponsor = Keypair::new();
let ephemeral = Keypair::new();
let mut svm = MagicSVM::new();
svm.ephemeral_mut().add_builtin(
EPHEMERAL_CALLER_PROGRAM_ID,
EphemeralCallerEntrypoint::register,
);
svm.airdrop(&payer.pubkey(), LAMPORTS_PER_SOL).unwrap();
svm.airdrop(&delegated.pubkey(), LAMPORTS_PER_SOL).unwrap();
svm.set_account(
sponsor.pubkey(),
Account {
lamports: LAMPORTS_PER_SOL,
owner: system_program::id(),
..Default::default()
},
)
.unwrap();
svm.delegate_account(delegated.pubkey()).unwrap();
svm.delegate_account(sponsor.pubkey()).unwrap();
svm.send_transaction_to(
TransactionTarget::Ephemeral,
Transaction::new(
&[&payer, &delegated],
Message::new(&[allocate(&delegated.pubkey(), 8)], Some(&payer.pubkey())),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
),
)
.unwrap();
let delegated_account = svm
.get_shared_account_for(TransactionTarget::Ephemeral, &delegated.pubkey())
.unwrap();
assert!(delegated_account.is(AccountMode::Delegated));
assert!(!delegated_account.is(AccountMode::Ephemeral));
assert_eq!(delegated_account.data().len(), 8);
svm.expire_blockhash_for(TransactionTarget::Ephemeral);
svm.send_transaction_to(
TransactionTarget::Ephemeral,
Transaction::new(
&[&payer],
Message::new(
&[transfer(&payer.pubkey(), &delegated.pubkey(), 1)],
Some(&payer.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
),
)
.unwrap();
let delegated_account = svm
.get_shared_account_for(TransactionTarget::Ephemeral, &delegated.pubkey())
.unwrap();
assert!(delegated_account.is(AccountMode::Delegated));
let create_tx = Transaction::new(
&[&sponsor, &ephemeral],
Message::new(
&[Instruction {
program_id: EPHEMERAL_CALLER_PROGRAM_ID,
accounts: vec![
AccountMeta::new(sponsor.pubkey(), true),
AccountMeta::new(ephemeral.pubkey(), true),
AccountMeta::new(EPHEMERAL_VAULT_ID, false),
AccountMeta::new_readonly(MAGIC_PROGRAM_ID, false),
],
data: vec![0],
}],
Some(&sponsor.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
svm.send_transaction_to(TransactionTarget::Ephemeral, create_tx)
.unwrap();
let ephemeral_account = svm
.get_shared_account_for(TransactionTarget::Ephemeral, &ephemeral.pubkey())
.unwrap();
assert!(ephemeral_account.is(AccountMode::Ephemeral));
assert!(!ephemeral_account.is(AccountMode::Delegated));
svm.expire_blockhash_for(TransactionTarget::Ephemeral);
svm.send_transaction_to(
TransactionTarget::Ephemeral,
Transaction::new(
&[&sponsor],
Message::new(
&[Instruction {
program_id: EPHEMERAL_CALLER_PROGRAM_ID,
accounts: vec![
AccountMeta::new(sponsor.pubkey(), true),
AccountMeta::new(ephemeral.pubkey(), false),
],
data: vec![3],
}],
Some(&sponsor.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
),
)
.unwrap();
let ephemeral_account = svm
.get_shared_account_for(TransactionTarget::Ephemeral, &ephemeral.pubkey())
.unwrap();
assert!(ephemeral_account.is(AccountMode::Ephemeral));
assert_eq!(ephemeral_account.data()[0], 1);
svm.expire_blockhash_for(TransactionTarget::Ephemeral);
svm.send_transaction_to(
TransactionTarget::Ephemeral,
Transaction::new(
&[&sponsor],
Message::new(
&[Instruction {
program_id: EPHEMERAL_CALLER_PROGRAM_ID,
accounts: vec![
AccountMeta::new(sponsor.pubkey(), true),
AccountMeta::new(ephemeral.pubkey(), false),
],
data: vec![3],
}],
Some(&sponsor.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
),
)
.unwrap();
let ephemeral_account = svm
.get_shared_account_for(TransactionTarget::Ephemeral, &ephemeral.pubkey())
.unwrap();
assert!(ephemeral_account.is(AccountMode::Ephemeral));
assert_eq!(ephemeral_account.data()[0], 2);
svm.expire_blockhash_for(TransactionTarget::Ephemeral);
let resize_tx = Transaction::new(
&[&sponsor],
Message::new(
&[Instruction {
program_id: EPHEMERAL_CALLER_PROGRAM_ID,
accounts: vec![
AccountMeta::new(sponsor.pubkey(), true),
AccountMeta::new(ephemeral.pubkey(), false),
AccountMeta::new(EPHEMERAL_VAULT_ID, false),
AccountMeta::new_readonly(MAGIC_PROGRAM_ID, false),
],
data: vec![1],
}],
Some(&sponsor.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
svm.send_transaction_to(TransactionTarget::Ephemeral, resize_tx)
.unwrap();
let ephemeral_account = svm
.get_shared_account_for(TransactionTarget::Ephemeral, &ephemeral.pubkey())
.unwrap();
assert!(ephemeral_account.is(AccountMode::Ephemeral));
assert_eq!(ephemeral_account.data().len(), 24);
}
#[test_log::test]
fn delegate_with_actions_runs_cleartext_actions_on_ephemeral() {
let payer = Keypair::new();
let delegated = Keypair::new();
let mut svm = MagicSVM::new();
svm.airdrop(&payer.pubkey(), LAMPORTS_PER_SOL).unwrap();
set_delegation_ready_account(&mut svm, delegated.pubkey());
let actions = vec![Instruction::new_with_bincode(
MAGIC_PROGRAM_ID,
&MagicBlockInstruction::Noop(0),
vec![AccountMeta::new_readonly(delegated.pubkey(), true)],
)]
.cleartext();
let tx = delegate_with_actions_tx(
&payer,
&delegated,
actions,
vec![
AccountMeta::new(delegated.pubkey(), true),
AccountMeta::new_readonly(MAGIC_PROGRAM_ID, false),
],
svm.latest_blockhash(),
);
svm.send_transaction_to(TransactionTarget::Base, tx)
.unwrap();
assert!(svm
.get_transaction_for(TransactionTarget::Ephemeral, &Signature::default())
.is_some());
}
#[test_log::test]
fn delegate_with_actions_decrypts_encrypted_actions_with_validator_keypair() {
let payer = Keypair::new();
let delegated = Keypair::new();
let validator = Keypair::new();
let mut svm = MagicSVM::new_with_validator_identity(validator.insecure_clone());
svm.airdrop(&payer.pubkey(), LAMPORTS_PER_SOL).unwrap();
set_delegation_ready_account(&mut svm, delegated.pubkey());
let actions = encrypted_noop_post_delegation_actions(validator.pubkey(), delegated.pubkey());
let tx = delegate_with_actions_tx(
&payer,
&delegated,
actions,
vec![AccountMeta::new(delegated.pubkey(), true)],
svm.latest_blockhash(),
);
svm.send_transaction_to(TransactionTarget::Base, tx)
.unwrap();
assert!(svm
.get_transaction_for(TransactionTarget::Ephemeral, &Signature::default())
.is_some());
}
#[test_log::test]
fn delegate_with_actions_errors_when_encrypted_actions_cannot_be_decrypted() {
let payer = Keypair::new();
let delegated = Keypair::new();
let wrong_validator = Keypair::new();
let mut svm = MagicSVM::new();
svm.airdrop(&payer.pubkey(), LAMPORTS_PER_SOL).unwrap();
set_delegation_ready_account(&mut svm, delegated.pubkey());
let actions =
encrypted_noop_post_delegation_actions(wrong_validator.pubkey(), delegated.pubkey());
let tx = delegate_with_actions_tx(
&payer,
&delegated,
actions,
vec![AccountMeta::new(delegated.pubkey(), true)],
svm.latest_blockhash(),
);
let err = svm
.send_transaction_to(TransactionTarget::Base, tx)
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::InvalidInstructionData)
);
}
#[test_log::test]
fn delegate_with_actions_runs_permissionless_increment_on_ephemeral() {
let payer = Keypair::new();
let delegated = Keypair::new();
let mut svm = MagicSVM::new();
add_permissionless_increment_program(&mut svm);
svm.airdrop(&payer.pubkey(), LAMPORTS_PER_SOL).unwrap();
set_increment_owned_account(&mut svm, delegated.pubkey());
let tx = Transaction::new(
&[&payer, &delegated],
Message::new(
&[delegate_with_actions_ix(
payer.pubkey(),
delegated.pubkey(),
PERMISSIONLESS_INCREMENT_PROGRAM_ID,
permissionless_increment_actions(delegated.pubkey()),
vec![],
)],
Some(&payer.pubkey()),
),
svm.latest_blockhash(),
);
svm.send_transaction_to(TransactionTarget::Base, tx)
.unwrap();
let ephemeral_account = svm
.get_account_for(TransactionTarget::Ephemeral, &delegated.pubkey())
.unwrap();
assert_eq!(ephemeral_account.owner, PERMISSIONLESS_INCREMENT_PROGRAM_ID);
assert_eq!(ephemeral_account.data[0], 1);
}
#[test_log::test]
fn cpi_delegate_with_actions_runs_permissionless_increment_on_ephemeral() {
let payer = Keypair::new();
let delegated = Keypair::new();
let mut svm = MagicSVM::new();
add_permissionless_increment_program(&mut svm);
svm.add_builtin(
DELEGATE_WITH_ACTIONS_CALLER_PROGRAM_ID,
DelegateWithActionsCallerEntrypoint::register,
);
svm.airdrop(&payer.pubkey(), LAMPORTS_PER_SOL).unwrap();
set_increment_owned_account(&mut svm, delegated.pubkey());
let dlp_ix = delegate_with_actions_ix(
payer.pubkey(),
delegated.pubkey(),
PERMISSIONLESS_INCREMENT_PROGRAM_ID,
permissionless_increment_actions(delegated.pubkey()),
vec![],
);
let mut accounts = dlp_ix.accounts;
accounts.push(AccountMeta::new_readonly(DELEGATION_PROGRAM_ID, false));
let tx = Transaction::new(
&[&payer, &delegated],
Message::new(
&[Instruction {
program_id: DELEGATE_WITH_ACTIONS_CALLER_PROGRAM_ID,
accounts,
data: dlp_ix.data,
}],
Some(&payer.pubkey()),
),
svm.latest_blockhash(),
);
svm.send_transaction_to(TransactionTarget::Base, tx)
.unwrap();
let ephemeral_account = svm
.get_account_for(TransactionTarget::Ephemeral, &delegated.pubkey())
.unwrap();
assert_eq!(ephemeral_account.owner, PERMISSIONLESS_INCREMENT_PROGRAM_ID);
assert_eq!(ephemeral_account.data[0], 1);
}
#[test_log::test]
fn ephemeral_transactions_can_only_write_delegated_accounts() {
let payer = Keypair::new();
let delegated = Keypair::new();
let non_delegated = Keypair::new();
let mut svm = MagicSVM::new();
svm.airdrop(&payer.pubkey(), LAMPORTS_PER_SOL).unwrap();
svm.airdrop(&delegated.pubkey(), LAMPORTS_PER_SOL).unwrap();
svm.airdrop(&non_delegated.pubkey(), LAMPORTS_PER_SOL)
.unwrap();
svm.delegate_account(delegated.pubkey()).unwrap();
let allowed = Transaction::new(
&[&payer, &delegated],
Message::new(&[allocate(&delegated.pubkey(), 8)], Some(&payer.pubkey())),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
assert!(svm
.send_transaction_to(TransactionTarget::Ephemeral, allowed)
.is_ok());
let rejected = Transaction::new(
&[&payer, &non_delegated],
Message::new(
&[allocate(&non_delegated.pubkey(), 8)],
Some(&payer.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
let err = svm
.send_transaction_to(TransactionTarget::Ephemeral, rejected)
.unwrap_err()
.err;
assert_eq!(err, TransactionError::InvalidWritableAccount);
}
#[test_log::test]
fn commit_finalize_copies_ephemeral_state_back_to_base() {
let delegated = Keypair::new();
let mut svm = MagicSVM::new();
svm.airdrop(&delegated.pubkey(), LAMPORTS_PER_SOL).unwrap();
svm.delegate_account(delegated.pubkey()).unwrap();
svm.send_transaction_to(
TransactionTarget::Ephemeral,
Transaction::new(
&[&delegated],
Message::new(
&[allocate(&delegated.pubkey(), 8)],
Some(&delegated.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
),
)
.unwrap();
svm.commit_account(delegated.pubkey());
let base_account = svm.get_account(&delegated.pubkey()).unwrap();
assert_eq!(base_account.data.len(), 8);
assert_eq!(base_account.owner, DELEGATION_PROGRAM_ID);
}
#[test_log::test]
fn ephemeral_schedule_commit_variants_copy_state_to_base() {
for instruction_data in [
bincode::serialize(&MagicBlockInstruction::ScheduleCommit).unwrap(),
bincode::serialize(&MagicBlockInstruction::ScheduleCommitFinalize {
request_undelegation: false,
})
.unwrap(),
bincode::serialize(&MagicBlockInstruction::ScheduleBaseIntent(
MagicBaseIntentArgs::Commit(CommitTypeArgs::Standalone(vec![2])),
))
.unwrap(),
bincode::serialize(&MagicBlockInstruction::ScheduleBaseIntent(
MagicBaseIntentArgs::CommitFinalize(CommitTypeArgs::Standalone(vec![2])),
))
.unwrap(),
bincode::serialize(&MagicBlockInstruction::ScheduleIntentBundle(
MagicIntentBundleArgs::from(MagicBaseIntentArgs::Commit(CommitTypeArgs::Standalone(
vec![2],
))),
))
.unwrap(),
bincode::serialize(&MagicBlockInstruction::ScheduleIntentBundle(
MagicIntentBundleArgs::from(MagicBaseIntentArgs::CommitFinalize(
CommitTypeArgs::Standalone(vec![2]),
)),
))
.unwrap(),
] {
let payer = Keypair::new();
let delegated = Keypair::new();
let mut svm = MagicSVM::new();
svm.airdrop(&payer.pubkey(), LAMPORTS_PER_SOL).unwrap();
svm.airdrop(&delegated.pubkey(), LAMPORTS_PER_SOL).unwrap();
svm.delegate_account(delegated.pubkey()).unwrap();
svm.send_transaction_to(
TransactionTarget::Ephemeral,
Transaction::new_signed_with_payer(
&[allocate(&delegated.pubkey(), 8)],
Some(&delegated.pubkey()),
&[&delegated],
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
),
)
.unwrap();
assert_eq!(
svm.get_account_for(TransactionTarget::Ephemeral, &delegated.pubkey())
.unwrap()
.data
.len(),
8
);
assert_eq!(
svm.get_account_for(TransactionTarget::Base, &delegated.pubkey())
.unwrap()
.data
.len(),
0
);
svm.send_transaction_to(
TransactionTarget::Ephemeral,
schedule_commit_tx(
&payer,
&delegated,
instruction_data,
false,
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
),
)
.unwrap();
let base_account = svm.get_account(&delegated.pubkey()).unwrap();
assert_eq!(base_account.data.len(), 8);
assert_eq!(base_account.owner, DELEGATION_PROGRAM_ID);
}
}
#[test_log::test]
fn post_commit_base_actions_run_on_base() {
let payer = Keypair::new();
let delegated = Keypair::new();
let marker = Keypair::new();
let mut svm = MagicSVM::new();
svm.add_builtin(
POST_COMMIT_ACTION_PROGRAM_ID,
PostCommitActionEntrypoint::register,
);
svm.ephemeral_mut().add_builtin(
POST_COMMIT_SCHEDULER_PROGRAM_ID,
PostCommitSchedulerEntrypoint::register,
);
svm.airdrop(&payer.pubkey(), LAMPORTS_PER_SOL).unwrap();
svm.airdrop(&delegated.pubkey(), LAMPORTS_PER_SOL).unwrap();
svm.set_account(
marker.pubkey(),
Account {
lamports: LAMPORTS_PER_SOL,
data: vec![0],
owner: POST_COMMIT_ACTION_PROGRAM_ID,
..Default::default()
},
)
.unwrap();
svm.delegate_account(delegated.pubkey()).unwrap();
let escrow =
dlp_api::pda::ephemeral_balance_pda_from_payer(&payer.pubkey().to_bytes().into(), 255);
svm.airdrop(&escrow.to_bytes().into(), LAMPORTS_PER_SOL)
.unwrap();
let action = BaseActionArgs {
args: ActionArgs::new(vec![]),
compute_units: 200_000,
escrow_authority: 0,
destination_program: POST_COMMIT_ACTION_PROGRAM_ID.to_bytes().into(),
accounts: vec![ShortAccountMeta {
pubkey: marker.pubkey().to_bytes().into(),
is_writable: true,
}],
};
let instruction_data = bincode::serialize(&MagicBlockInstruction::ScheduleIntentBundle(
MagicIntentBundleArgs {
commit: Some(CommitTypeArgs::WithBaseActions {
committed_accounts: vec![2],
base_actions: vec![action],
}),
..Default::default()
},
))
.unwrap();
svm.send_transaction_to(
TransactionTarget::Ephemeral,
Transaction::new(
&[&payer],
Message::new(
&[Instruction {
program_id: POST_COMMIT_SCHEDULER_PROGRAM_ID,
accounts: vec![
AccountMeta::new(payer.pubkey(), true),
AccountMeta::new(MAGIC_CONTEXT_ID, false),
AccountMeta::new_readonly(delegated.pubkey(), false),
AccountMeta::new_readonly(MAGIC_PROGRAM_ID, false),
],
data: instruction_data,
}],
Some(&payer.pubkey()),
),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
),
)
.unwrap();
assert_eq!(
svm.get_account(&marker.pubkey()).unwrap().data[0],
7,
"post-commit base action must run on the base ledger"
);
let base_account = svm.get_account(&delegated.pubkey()).unwrap();
assert_eq!(base_account.owner, DELEGATION_PROGRAM_ID);
}
#[test_log::test]
fn ephemeral_schedule_commit_variants_can_undelegate() {
for instruction_data in [
bincode::serialize(&MagicBlockInstruction::ScheduleCommitAndUndelegate).unwrap(),
bincode::serialize(&MagicBlockInstruction::ScheduleCommitFinalize {
request_undelegation: true,
})
.unwrap(),
bincode::serialize(&MagicBlockInstruction::ScheduleBaseIntent(
MagicBaseIntentArgs::CommitAndUndelegate(CommitAndUndelegateArgs {
commit_type: CommitTypeArgs::Standalone(vec![2]),
undelegate_type: UndelegateTypeArgs::Standalone,
}),
))
.unwrap(),
bincode::serialize(&MagicBlockInstruction::ScheduleBaseIntent(
MagicBaseIntentArgs::CommitFinalizeAndUndelegate(CommitAndUndelegateArgs {
commit_type: CommitTypeArgs::Standalone(vec![2]),
undelegate_type: UndelegateTypeArgs::Standalone,
}),
))
.unwrap(),
bincode::serialize(&MagicBlockInstruction::ScheduleIntentBundle(
MagicIntentBundleArgs::from(MagicBaseIntentArgs::CommitAndUndelegate(
CommitAndUndelegateArgs {
commit_type: CommitTypeArgs::Standalone(vec![2]),
undelegate_type: UndelegateTypeArgs::Standalone,
},
)),
))
.unwrap(),
bincode::serialize(&MagicBlockInstruction::ScheduleIntentBundle(
MagicIntentBundleArgs::from(MagicBaseIntentArgs::CommitFinalizeAndUndelegate(
CommitAndUndelegateArgs {
commit_type: CommitTypeArgs::Standalone(vec![2]),
undelegate_type: UndelegateTypeArgs::Standalone,
},
)),
))
.unwrap(),
] {
let payer = Keypair::new();
let delegated = Keypair::new();
let mut svm = MagicSVM::new();
svm.airdrop(&payer.pubkey(), LAMPORTS_PER_SOL).unwrap();
svm.airdrop(&delegated.pubkey(), LAMPORTS_PER_SOL).unwrap();
svm.delegate_account(delegated.pubkey()).unwrap();
svm.send_transaction_to(
TransactionTarget::Ephemeral,
Transaction::new_signed_with_payer(
&[allocate(&delegated.pubkey(), 8)],
Some(&delegated.pubkey()),
&[&delegated],
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
),
)
.unwrap();
assert_eq!(
svm.get_account_for(TransactionTarget::Ephemeral, &delegated.pubkey())
.unwrap()
.data
.len(),
8
);
assert_eq!(
svm.get_account_for(TransactionTarget::Base, &delegated.pubkey())
.unwrap()
.data
.len(),
0
);
svm.send_transaction_to(
TransactionTarget::Ephemeral,
schedule_commit_tx(
&payer,
&delegated,
instruction_data,
true,
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
),
)
.unwrap();
let base_account = svm.get_account(&delegated.pubkey()).unwrap();
assert_eq!(base_account.data.len(), 8);
assert_eq!(base_account.owner, system_program::id());
svm.expire_blockhash_for(TransactionTarget::Ephemeral);
let rejected = Transaction::new(
&[&payer, &delegated],
Message::new(&[allocate(&delegated.pubkey(), 16)], Some(&payer.pubkey())),
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
let err = svm
.send_transaction_to(TransactionTarget::Ephemeral, rejected)
.unwrap_err()
.err;
assert_eq!(err, TransactionError::InvalidWritableAccount);
}
}
#[test_log::test]
fn ephemeral_magic_processors_reject_invalid_schedule_commit_accounts() {
let payer = Keypair::new();
let schedule_payer = Keypair::new();
let delegated = Keypair::new();
let wrong_context = Keypair::new();
let mut svm = MagicSVM::new();
svm.airdrop(&payer.pubkey(), LAMPORTS_PER_SOL).unwrap();
svm.airdrop(&schedule_payer.pubkey(), LAMPORTS_PER_SOL)
.unwrap();
svm.airdrop(&delegated.pubkey(), LAMPORTS_PER_SOL).unwrap();
svm.delegate_account(delegated.pubkey()).unwrap();
let wrong_context_tx = Transaction::new_signed_with_payer(
&[Instruction::new_with_bincode(
MAGIC_PROGRAM_ID,
&MagicBlockInstruction::ScheduleCommit,
vec![
AccountMeta::new(payer.pubkey(), true),
AccountMeta::new_readonly(wrong_context.pubkey(), false),
AccountMeta::new_readonly(delegated.pubkey(), false),
],
)],
Some(&payer.pubkey()),
&[&payer],
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
let err = svm
.send_transaction_to(TransactionTarget::Ephemeral, wrong_context_tx)
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::MissingAccount)
);
svm.expire_blockhash_for(TransactionTarget::Ephemeral);
let missing_signer_tx = Transaction::new_signed_with_payer(
&[Instruction::new_with_bincode(
MAGIC_PROGRAM_ID,
&MagicBlockInstruction::ScheduleCommit,
vec![
AccountMeta::new_readonly(schedule_payer.pubkey(), false),
AccountMeta::new(MAGIC_CONTEXT_ID, false),
AccountMeta::new_readonly(delegated.pubkey(), false),
],
)],
Some(&payer.pubkey()),
&[&payer],
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
let err = svm
.send_transaction_to(TransactionTarget::Ephemeral, missing_signer_tx)
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
);
svm.expire_blockhash_for(TransactionTarget::Ephemeral);
let no_accounts_tx = Transaction::new_signed_with_payer(
&[Instruction::new_with_bincode(
MAGIC_PROGRAM_ID,
&MagicBlockInstruction::ScheduleCommit,
vec![
AccountMeta::new(payer.pubkey(), true),
AccountMeta::new(MAGIC_CONTEXT_ID, false),
],
)],
Some(&payer.pubkey()),
&[&payer],
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
let err = svm
.send_transaction_to(TransactionTarget::Ephemeral, no_accounts_tx)
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::MissingAccount)
);
svm.expire_blockhash_for(TransactionTarget::Ephemeral);
let readonly_undelegate_tx = schedule_commit_tx(
&payer,
&delegated,
bincode::serialize(&MagicBlockInstruction::ScheduleCommitAndUndelegate).unwrap(),
false,
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
let err = svm
.send_transaction_to(TransactionTarget::Ephemeral, readonly_undelegate_tx)
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified)
);
}
#[test_log::test]
fn ephemeral_transactions_dont_pay_fees() {
let payer = Keypair::new();
let mut svm = MagicSVM::new();
svm.airdrop(&payer.pubkey(), LAMPORTS_PER_SOL).unwrap();
let tx = Transaction::new_signed_with_payer(
&[Instruction::new_with_bincode(
MAGIC_PROGRAM_ID,
&MagicBlockInstruction::Noop(0),
vec![],
)],
Some(&payer.pubkey()),
&[&payer],
svm.latest_blockhash_for(TransactionTarget::Ephemeral),
);
let result = svm
.send_transaction_to(TransactionTarget::Ephemeral, tx)
.unwrap();
assert_eq!(result.fee, 0);
}