use {
crate::magic::{
base_action::{base_action_instruction, validator_fees_vault_pda},
delegation_action::decrypt_post_delegation_instructions,
effects::{EphemeralAccountEffect, MagicTransactionEffects, ScheduledBaseAction},
magic_program::MagicProgramEntrypoint,
},
dlp_api::{
args::PostDelegationActions, consts::DELEGATION_PROGRAM_ID, state::DelegationRecord,
},
ephemeral_rollups_sdk::{
access_control::structs::EphemeralPermission,
consts::{
ASSOCIATED_TOKEN_PROGRAM_ID, EPHEMERAL_VAULT_ID, ESPL_TOKEN_PROGRAM_ID,
MAGIC_CONTEXT_ID, MAGIC_PROGRAM_ID, PERMISSION_PROGRAM_ID, TOKEN_PROGRAM_ID,
},
ephemeral_accounts,
},
litesvm::{
error::LiteSVMError,
types::{FailedTransactionMetadata, SimulatedTransactionInfo, TransactionResult},
LiteSVM,
},
magicblock_account::{
Account as ForkAccount, AccountBuilder, AccountMode, AccountSharedData, ReadableAccount,
WritableAccount,
},
solana_account::Account as StockAccount,
solana_address::Address,
solana_clock::Epoch,
solana_fee_structure::FeeStructure,
solana_hash::Hash,
solana_keypair::Keypair,
solana_message::{Message, VersionedMessage},
solana_program_runtime::solana_sbpf::program::BuiltinFunctionDefinition,
solana_rent::Rent,
solana_sdk_ids::{bpf_loader_upgradeable, system_program},
solana_signature::Signature,
solana_signer::Signer,
solana_sysvar::Sysvar,
solana_sysvar_id::SysvarId,
solana_transaction::{versioned::VersionedTransaction, InstructionError, Transaction},
solana_transaction_error::TransactionError,
std::{
collections::{HashMap, HashSet},
ops::{Deref, DerefMut},
path::Path,
},
wincode::Serialize,
};
mod base_action;
mod delegation_action;
mod effects;
mod magic_program;
const SPL_TOKEN_ACCOUNT_LEN: usize = 165;
const SPL_TOKEN_ACCOUNT_STATE_INITIALIZED: u8 = 1;
const DEFAULT_LAMPORTS_PER_SIGNATURE: u64 = 5_000;
pub const DEFAULT_VALIDATOR_IDENTITY: &str =
"9Vo7TbA5YfC5a33JhAi9Fb41usA6JwecHNRw3f9MzzHAM8hFnXTzL5DcEHwsAFjuUZ8vNQcJ4XziRFpMc3gTgBQ";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TransactionTarget {
Base,
Ephemeral,
}
pub struct MagicSVM {
base: LiteSVM,
ephemeral: LiteSVM,
validator_keypair: Keypair,
delegated_accounts: HashSet<Address>,
authorized_user: Option<Address>,
}
impl Default for MagicSVM {
fn default() -> Self {
Self::new()
}
}
impl Deref for MagicSVM {
type Target = LiteSVM;
fn deref(&self) -> &Self::Target {
&self.base
}
}
impl DerefMut for MagicSVM {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.base
}
}
impl MagicSVM {
pub fn new() -> Self {
Self::new_with_validator_identity(Keypair::from_base58_string(DEFAULT_VALIDATOR_IDENTITY))
}
pub fn new_with_validator_identity(validator_keypair: Keypair) -> Self {
let mut base = LiteSVM::new();
base.add_program_with_loader(
DELEGATION_PROGRAM_ID,
include_bytes!("../../elfs/dlp.so"),
bpf_loader_upgradeable::id(),
)
.unwrap();
base.add_program_with_loader(
ESPL_TOKEN_PROGRAM_ID,
include_bytes!("../../elfs/espl.so"),
bpf_loader_upgradeable::id(),
)
.unwrap();
base.add_program_with_loader(
PERMISSION_PROGRAM_ID,
include_bytes!("../../elfs/permission.so"),
bpf_loader_upgradeable::id(),
)
.unwrap();
let validator_identity = validator_keypair.pubkey();
let validator_fees_vault = validator_fees_vault_pda(validator_identity);
let vault = AccountBuilder::from(ForkAccount {
lamports: Rent::default().minimum_balance(8),
data: vec![0; 8],
owner: DELEGATION_PROGRAM_ID,
executable: false,
rent_epoch: Epoch::default(),
})
.build();
base.accounts
.add_account(validator_fees_vault, vault)
.unwrap();
base.airdrop(&validator_identity, 1_000_000_000).unwrap();
let mut ephemeral =
LiteSVM::new()
.with_default_programs()
.with_fee_structure(FeeStructure {
lamports_per_signature: 0,
lamports_per_write_lock: 0,
compute_fee_bins: vec![],
});
ephemeral.add_builtin(MAGIC_PROGRAM_ID, MagicProgramEntrypoint::register);
ephemeral
.add_program_with_loader(
ESPL_TOKEN_PROGRAM_ID,
include_bytes!("../../elfs/espl.so"),
bpf_loader_upgradeable::id(),
)
.unwrap();
ephemeral
.add_program_with_loader(
PERMISSION_PROGRAM_ID,
include_bytes!("../../elfs/permission.so"),
bpf_loader_upgradeable::id(),
)
.unwrap();
let magic_context = AccountBuilder::from(ForkAccount {
lamports: u64::MAX / 2,
owner: MAGIC_PROGRAM_ID,
..Default::default()
})
.mode(AccountMode::Delegated)
.build();
ephemeral
.accounts
.add_account(MAGIC_CONTEXT_ID, magic_context)
.unwrap();
let shared_vault = AccountBuilder::from(ForkAccount {
lamports: Rent::default().minimum_balance(0),
data: vec![],
owner: MAGIC_PROGRAM_ID,
executable: false,
rent_epoch: Epoch::default(),
})
.mode(AccountMode::Delegated)
.build();
ephemeral
.accounts
.add_account(EPHEMERAL_VAULT_ID, shared_vault)
.unwrap();
Self {
base,
ephemeral,
validator_keypair,
delegated_accounts: HashSet::new(),
authorized_user: None,
}
}
pub fn validator_keypair(&self) -> &Keypair {
&self.validator_keypair
}
pub fn validator_identity(&self) -> Address {
self.validator_keypair.pubkey()
}
pub fn base(&self) -> &LiteSVM {
&self.base
}
pub fn base_mut(&mut self) -> &mut LiteSVM {
&mut self.base
}
pub fn ephemeral(&self) -> &LiteSVM {
&self.ephemeral
}
pub fn ephemeral_mut(&mut self) -> &mut LiteSVM {
&mut self.ephemeral
}
pub fn set_authorized_user(&mut self, user: Option<Address>) {
self.authorized_user = user;
}
pub fn authorized_user(&self) -> Option<Address> {
self.authorized_user
}
pub fn set_sysvar<T>(&mut self, sysvar: &T)
where
T: Sysvar + SysvarId + Serialize<Src = T>,
{
self.base.set_sysvar(sysvar);
self.ephemeral.set_sysvar(sysvar);
}
pub fn warp_to_slot(&mut self, slot: u64) {
self.base.warp_to_slot(slot);
self.ephemeral.warp_to_slot(slot);
}
pub fn get_account(&self, pubkey: &Address) -> Option<StockAccount> {
self.get_account_for(TransactionTarget::Base, pubkey)
}
pub fn get_account_for(
&self,
target: TransactionTarget,
pubkey: &Address,
) -> Option<StockAccount> {
let fork = match target {
TransactionTarget::Base => self.base.get_account(pubkey),
TransactionTarget::Ephemeral => {
if !self.viewer_can_read_ephemeral(pubkey) {
return None;
}
self.ephemeral
.get_account(pubkey)
.or_else(|| self.base.get_account(pubkey))
}
};
fork.map(fork_account_to_stock)
}
fn viewer_can_read_ephemeral(&self, pubkey: &Address) -> bool {
let (permission_pda, _) = EphemeralPermission::find_pda(pubkey);
let Some(permission_account) = self
.ephemeral
.get_account(&permission_pda)
.or_else(|| self.base.get_account(&permission_pda))
else {
return true;
};
if permission_account.owner != PERMISSION_PROGRAM_ID {
return true;
}
let Ok(permission) = EphemeralPermission::from_bytes(&permission_account.data) else {
return true;
};
if !permission.private {
return true;
}
self.authorized_user.is_some_and(|user| {
permission
.members
.iter()
.any(|member| member.pubkey.as_ref() == user.as_ref())
})
}
pub fn get_shared_account_for(
&self,
target: TransactionTarget,
pubkey: &Address,
) -> Option<AccountSharedData> {
match target {
TransactionTarget::Base => self.base.accounts.get_account(pubkey),
TransactionTarget::Ephemeral => self
.ephemeral
.accounts
.get_account(pubkey)
.or_else(|| self.base.accounts.get_account(pubkey)),
}
}
pub fn set_account(
&mut self,
pubkey: Address,
account: StockAccount,
) -> Result<(), LiteSVMError> {
self.base
.set_account(pubkey, stock_account_to_fork(account))
}
pub fn get_balance(&self, pubkey: &Address) -> Option<u64> {
self.base.get_balance(pubkey)
}
pub fn latest_blockhash(&self) -> Hash {
self.base.latest_blockhash()
}
pub fn latest_blockhash_for(&self, target: TransactionTarget) -> Hash {
match target {
TransactionTarget::Base => self.base.latest_blockhash(),
TransactionTarget::Ephemeral => self.ephemeral.latest_blockhash(),
}
}
pub fn get_transaction_for(
&self,
target: TransactionTarget,
signature: &Signature,
) -> Option<&TransactionResult> {
match target {
TransactionTarget::Base => self.base.get_transaction(signature),
TransactionTarget::Ephemeral => self.ephemeral.get_transaction(signature),
}
}
pub fn expire_blockhash_for(&mut self, target: TransactionTarget) {
match target {
TransactionTarget::Base => self.base.expire_blockhash(),
TransactionTarget::Ephemeral => self.ephemeral.expire_blockhash(),
}
}
pub fn airdrop(&mut self, pubkey: &Address, lamports: u64) -> TransactionResult {
self.base.airdrop(pubkey, lamports)
}
pub fn add_program(
&mut self,
program_id: Address,
program_bytes: &[u8],
) -> Result<(), LiteSVMError> {
self.base.add_program(program_id, program_bytes)?;
self.ephemeral.add_program(program_id, program_bytes)
}
pub fn add_program_from_file(
&mut self,
program_id: Address,
path: impl AsRef<Path>,
) -> Result<(), LiteSVMError> {
let program_bytes = std::fs::read(path).map_err(LiteSVMError::InvalidPath)?;
self.add_program(program_id, &program_bytes)
}
pub fn add_program_with_loader(
&mut self,
program_id: Address,
program_bytes: &[u8],
loader_id: Address,
) -> Result<(), LiteSVMError> {
self.base
.add_program_with_loader(program_id, program_bytes, loader_id)?;
self.ephemeral
.add_program_with_loader(program_id, program_bytes, loader_id)
}
pub fn send_transaction(&mut self, tx: impl Into<VersionedTransaction>) -> TransactionResult {
self.send_transaction_to(TransactionTarget::Base, tx)
}
pub fn send_transaction_to(
&mut self,
target: TransactionTarget,
tx: impl Into<VersionedTransaction>,
) -> TransactionResult {
let vtx = tx.into();
match target {
TransactionTarget::Base => {
let message = vtx.message.clone();
let fee_payer = message
.static_account_keys()
.first()
.copied()
.unwrap_or_default();
let writable_accounts = message
.static_account_keys()
.iter()
.enumerate()
.filter_map(|(index, key)| {
(index != 0
&& message.is_maybe_writable_with_reserved_addresses(
index,
None::<&HashSet<Address>>,
))
.then_some(*key)
})
.collect::<Vec<_>>();
let result = self.base.send_transaction(vtx);
if let Ok(meta) = result {
let effects =
MagicTransactionEffects::from_message_and_metadata(&message, &meta);
if let Err(err) = effects.apply_base(self, fee_payer) {
return Err(FailedTransactionMetadata { err, meta });
}
self.apply_base_account_state(&writable_accounts);
Ok(meta)
} else {
result
}
}
TransactionTarget::Ephemeral => {
if let Err(err) = self.check_ephemeral_writable_accounts(&vtx.message) {
return Err(FailedTransactionMetadata {
err,
meta: Default::default(),
});
}
self.sync_ephemeral_fee_payer(&vtx.message);
self.sync_ephemeral_readonly_accounts_from_base(&vtx.message);
let message = vtx.message.clone();
let fee_payer = message
.static_account_keys()
.first()
.copied()
.unwrap_or_default();
let pre_accounts = self.ephemeral_accounts_snapshot(&message);
let pre_base = self.base.clone();
let pre_ephemeral = self.ephemeral.clone();
let seeded_creates = self.seed_ephemeral_create_placeholders(&message);
let prefund = self.prefund_ephemeral_fee(&message);
let result = self.ephemeral.send_transaction(vtx);
let fee = match &result {
Ok(meta) => meta.fee,
Err(failed) => failed.meta.fee,
};
self.settle_ephemeral_fee(&message, prefund, fee);
if let Ok(meta) = &result {
let effects = MagicTransactionEffects::from_ephemeral_message_and_metadata(
&message, meta,
);
if let Err(err) =
self.reject_seeded_non_magic_creates(&seeded_creates, &effects)
{
self.base = pre_base;
self.ephemeral = pre_ephemeral;
return Err(FailedTransactionMetadata {
err,
meta: meta.clone(),
});
}
if let Err(err) = effects.apply_ephemeral_account(self, &pre_accounts) {
self.base = pre_base;
self.ephemeral = pre_ephemeral;
return Err(FailedTransactionMetadata {
err,
meta: meta.clone(),
});
}
if let Err(err) = effects.apply_base(self, fee_payer) {
self.base = pre_base;
self.ephemeral = pre_ephemeral;
return Err(FailedTransactionMetadata {
err,
meta: meta.clone(),
});
}
if let Err(err) = self.sync_projected_atas_to_eatas(&message) {
self.base = pre_base;
self.ephemeral = pre_ephemeral;
return Err(FailedTransactionMetadata {
err,
meta: meta.clone(),
});
}
self.unseed_ephemeral_create_placeholders(&seeded_creates);
} else {
self.unseed_ephemeral_create_placeholders(&seeded_creates);
}
result
}
}
}
pub fn simulate_transaction_to(
&mut self,
target: TransactionTarget,
tx: impl Into<VersionedTransaction>,
) -> std::result::Result<SimulatedTransactionInfo, FailedTransactionMetadata> {
let vtx = tx.into();
match target {
TransactionTarget::Base => self.base.simulate_transaction(vtx),
TransactionTarget::Ephemeral => {
if let Err(err) = self.check_ephemeral_writable_accounts(&vtx.message) {
return Err(FailedTransactionMetadata {
err,
meta: Default::default(),
});
}
self.sync_ephemeral_fee_payer(&vtx.message);
self.sync_ephemeral_readonly_accounts_from_base(&vtx.message);
self.ephemeral.simulate_transaction(vtx)
}
}
}
pub fn delegate_account(&mut self, delegated_account: Address) -> Result<(), TransactionError> {
let Some(mut base_account) = self.base.accounts.get_account(&delegated_account) else {
return Err(TransactionError::AccountNotFound);
};
let original_owner = {
let record_address = dlp_api::pda::delegation_record_pda_from_delegated_account(
&delegated_account.to_bytes().into(),
);
if let Some(account) = self.base.get_account(&record_address.to_bytes().into()) {
DelegationRecord::try_from_bytes_with_discriminator(&account.data)
.map(|rec| rec.owner.to_bytes().into())
.ok()
} else {
None
}
.unwrap_or(*base_account.owner())
};
base_account.set_owner(DELEGATION_PROGRAM_ID);
self.base
.accounts
.add_account(delegated_account, base_account.clone())
.map_err(|_| TransactionError::InvalidAccountIndex)?;
base_account = with_mode(base_account, AccountMode::Delegated);
base_account.set_owner(original_owner);
self.ephemeral
.accounts
.add_account(delegated_account, base_account.clone())
.map_err(|_| TransactionError::InvalidAccountIndex)?;
self.delegated_accounts.insert(delegated_account);
self.project_eata_to_ata(delegated_account, &base_account, original_owner)?;
Ok(())
}
fn project_eata_to_ata(
&mut self,
delegated_account: Address,
eata_account: &AccountSharedData,
original_owner: Address,
) -> Result<(), TransactionError> {
let Some((owner, mint, amount)) = parse_eata_data(eata_account.data()) else {
return Ok(());
};
let expected_eata =
Address::find_program_address(&[owner.as_ref(), mint.as_ref()], &ESPL_TOKEN_PROGRAM_ID)
.0;
if delegated_account != expected_eata || original_owner != ESPL_TOKEN_PROGRAM_ID {
return Ok(());
}
let ata = Address::find_program_address(
&[owner.as_ref(), TOKEN_PROGRAM_ID.as_ref(), mint.as_ref()],
&ASSOCIATED_TOKEN_PROGRAM_ID,
)
.0;
let mut ata_data = vec![0; SPL_TOKEN_ACCOUNT_LEN];
ata_data[0..32].copy_from_slice(mint.as_ref());
ata_data[32..64].copy_from_slice(owner.as_ref());
ata_data[64..72].copy_from_slice(&amount.to_le_bytes());
ata_data[108] = SPL_TOKEN_ACCOUNT_STATE_INITIALIZED;
let ata_account = AccountBuilder::from(ForkAccount {
lamports: Rent::default().minimum_balance(SPL_TOKEN_ACCOUNT_LEN),
data: ata_data,
owner: TOKEN_PROGRAM_ID,
executable: false,
rent_epoch: Epoch::default(),
})
.mode(AccountMode::Delegated)
.build();
self.ephemeral
.accounts
.add_account(ata, ata_account)
.map_err(|_| TransactionError::InvalidAccountIndex)?;
self.delegated_accounts.insert(ata);
Ok(())
}
fn sync_projected_atas_to_eatas(
&mut self,
message: &VersionedMessage,
) -> Result<(), TransactionError> {
for (index, ata) in message.static_account_keys().iter().enumerate() {
if !message.is_maybe_writable_with_reserved_addresses(index, None::<&HashSet<Address>>)
{
continue;
}
let Some(ata_account) = self.ephemeral.accounts.get_account(ata) else {
continue;
};
if *ata_account.owner() != TOKEN_PROGRAM_ID {
continue;
}
let Some((owner, mint, amount)) = parse_token_account_data(ata_account.data()) else {
continue;
};
let expected_ata = Address::find_program_address(
&[owner.as_ref(), TOKEN_PROGRAM_ID.as_ref(), mint.as_ref()],
&ASSOCIATED_TOKEN_PROGRAM_ID,
)
.0;
if *ata != expected_ata {
continue;
}
let eata = Address::find_program_address(
&[owner.as_ref(), mint.as_ref()],
&ESPL_TOKEN_PROGRAM_ID,
)
.0;
if !self.delegated_accounts.contains(&eata) {
continue;
}
let Some(mut eata_account) = self.ephemeral.accounts.get_account(&eata) else {
continue;
};
if parse_eata_data(eata_account.data()).is_none() {
continue;
}
eata_account.data_as_mut_slice()[64..72].copy_from_slice(&amount.to_le_bytes());
self.ephemeral
.accounts
.add_account(eata, eata_account)
.map_err(|_| TransactionError::InvalidAccountIndex)?;
}
Ok(())
}
pub fn commit_account(&mut self, delegated_account: Address) {
if let Some(eata) = self.eata_for_projected_ata(&delegated_account) {
self.commit_account(eata);
return;
}
let Some(ephemeral_account) = self.ephemeral.accounts.get_account(&delegated_account)
else {
return;
};
let Some(mut base_account) = self.base.accounts.get_account(&delegated_account) else {
return;
};
base_account.set_lamports(ephemeral_account.lamports());
base_account.set_data_from_slice(ephemeral_account.data());
let _ = self
.base
.accounts
.add_account(delegated_account, base_account);
}
pub fn undelegate_account(&mut self, delegated_account: Address) {
if let Some(eata) = self.eata_for_projected_ata(&delegated_account) {
self.undelegate_delegated_account(eata);
self.clear_projected_ata(delegated_account);
return;
}
let projected_ata = self.projected_ata_for_eata(&delegated_account);
self.undelegate_delegated_account(delegated_account);
if let Some(ata) = projected_ata {
self.clear_projected_ata(ata);
}
}
fn undelegate_delegated_account(&mut self, delegated_account: Address) {
self.delegated_accounts.remove(&delegated_account);
if let Some(ephemeral_account) = self.ephemeral.accounts.get_account(&delegated_account) {
let base_account = with_mode(ephemeral_account, AccountMode::ReadOnly);
let _ = self
.base
.accounts
.add_account(delegated_account, base_account);
} else if let Some(base_account) = self.base.accounts.get_account(&delegated_account) {
let base_account = with_mode(base_account, AccountMode::ReadOnly);
let _ = self
.base
.accounts
.add_account(delegated_account, base_account);
}
let _ = self
.ephemeral
.set_account(delegated_account, ForkAccount::default());
}
fn clear_projected_ata(&mut self, ata: Address) {
self.delegated_accounts.remove(&ata);
let _ = self.ephemeral.set_account(ata, ForkAccount::default());
}
fn eata_for_projected_ata(&self, ata: &Address) -> Option<Address> {
let ata_account = self.ephemeral.accounts.get_account(ata)?;
if *ata_account.owner() != TOKEN_PROGRAM_ID {
return None;
}
let (owner, mint, _) = parse_token_account_data(ata_account.data())?;
let expected_ata = Address::find_program_address(
&[owner.as_ref(), TOKEN_PROGRAM_ID.as_ref(), mint.as_ref()],
&ASSOCIATED_TOKEN_PROGRAM_ID,
)
.0;
if *ata != expected_ata {
return None;
}
let eata =
Address::find_program_address(&[owner.as_ref(), mint.as_ref()], &ESPL_TOKEN_PROGRAM_ID)
.0;
self.delegated_accounts.contains(&eata).then_some(eata)
}
fn projected_ata_for_eata(&self, eata: &Address) -> Option<Address> {
let eata_account = self
.ephemeral
.accounts
.get_account(eata)
.or_else(|| self.base.accounts.get_account(eata))?;
let (owner, mint, _) = parse_eata_data(eata_account.data())?;
let expected_eata =
Address::find_program_address(&[owner.as_ref(), mint.as_ref()], &ESPL_TOKEN_PROGRAM_ID)
.0;
if *eata != expected_eata {
return None;
}
Some(
Address::find_program_address(
&[owner.as_ref(), TOKEN_PROGRAM_ID.as_ref(), mint.as_ref()],
&ASSOCIATED_TOKEN_PROGRAM_ID,
)
.0,
)
}
fn check_ephemeral_writable_accounts(
&self,
message: &VersionedMessage,
) -> Result<(), TransactionError> {
for (index, key) in message.static_account_keys().iter().enumerate() {
if message.is_maybe_writable_with_reserved_addresses(index, None::<&HashSet<Address>>)
&& !self.is_ephemeral_writable_exception(message, index, key)
&& !self.delegated_accounts.contains(key)
{
return Err(TransactionError::InvalidWritableAccount);
}
}
Ok(())
}
fn seed_ephemeral_create_placeholders(&mut self, message: &VersionedMessage) -> Vec<Address> {
if !message.static_account_keys().contains(&MAGIC_PROGRAM_ID)
|| !message.static_account_keys().contains(&EPHEMERAL_VAULT_ID)
{
return Vec::new();
}
let mut seeded = Vec::new();
for (index, key) in message.static_account_keys().iter().enumerate() {
if index == 0 || *key == MAGIC_CONTEXT_ID || *key == EPHEMERAL_VAULT_ID {
continue;
}
if !message.is_maybe_writable_with_reserved_addresses(index, None::<&HashSet<Address>>)
{
continue;
}
if self.ephemeral.accounts.get_account(key).is_some() {
continue;
}
let placeholder = with_mode(
AccountSharedData::new(0, 0, &system_program::id()),
AccountMode::Ephemeral,
);
self.ephemeral
.accounts
.add_account_no_checks(*key, placeholder);
seeded.push(*key);
}
seeded
}
fn unseed_ephemeral_create_placeholders(&mut self, seeded: &[Address]) {
for key in seeded {
if self.is_ephemeral_create_placeholder(key) {
let _ = self
.ephemeral
.accounts
.add_account(*key, AccountSharedData::new(0, 0, &system_program::id()));
}
}
}
fn reject_seeded_non_magic_creates(
&self,
seeded: &[Address],
effects: &MagicTransactionEffects,
) -> Result<(), TransactionError> {
let magic_creates: HashSet<Address> = effects
.ephemeral_accounts
.iter()
.filter_map(|effect| match effect {
EphemeralAccountEffect::Create { account, .. } => Some(*account),
_ => None,
})
.collect();
for key in seeded {
if magic_creates.contains(key) || self.is_ephemeral_create_placeholder(key) {
continue;
}
return Err(TransactionError::InvalidWritableAccount);
}
Ok(())
}
fn is_ephemeral_create_placeholder(&self, key: &Address) -> bool {
self.ephemeral
.accounts
.get_account(key)
.is_some_and(|account| {
account.lamports() == 0
&& account.owner() == &system_program::id()
&& account.data().is_empty()
})
}
fn is_ephemeral_writable_exception(
&self,
message: &VersionedMessage,
account_index: usize,
key: &Address,
) -> bool {
account_index == 0
|| *key == MAGIC_CONTEXT_ID
|| *key == EPHEMERAL_VAULT_ID
|| match self.ephemeral.accounts.get_account(key) {
Some(account) => {
account.is(AccountMode::Ephemeral)
|| (account.lamports() == 0
&& account.owner() == &solana_sdk_ids::system_program::ID)
}
None => {
message.static_account_keys().contains(&MAGIC_PROGRAM_ID)
&& message.static_account_keys().contains(&EPHEMERAL_VAULT_ID)
}
}
}
fn prefund_ephemeral_fee(&mut self, message: &VersionedMessage) -> u64 {
let prefund = u64::from(message.header().num_required_signatures)
.saturating_mul(DEFAULT_LAMPORTS_PER_SIGNATURE);
if prefund == 0 {
return 0;
}
let Some(fee_payer) = message.static_account_keys().first().copied() else {
return 0;
};
let mut account = self
.ephemeral
.accounts
.get_account(&fee_payer)
.unwrap_or_else(|| {
let mut account = AccountSharedData::default();
account.set_owner(system_program::id());
account
});
if account.checked_add_lamports(prefund).is_err() {
return 0;
}
if self
.ephemeral
.accounts
.add_account(fee_payer, account)
.is_err()
{
return 0;
}
prefund
}
fn settle_ephemeral_fee(&mut self, message: &VersionedMessage, prefund: u64, fee: u64) {
if prefund == 0 && fee == 0 {
return;
}
let Some(fee_payer) = message.static_account_keys().first().copied() else {
return;
};
let Some(mut account) = self.ephemeral.accounts.get_account(&fee_payer) else {
return;
};
let adjusted = account
.lamports()
.saturating_add(fee)
.saturating_sub(prefund);
account.set_lamports(adjusted);
let _ = self.ephemeral.accounts.add_account(fee_payer, account);
}
fn sync_ephemeral_fee_payer(&mut self, message: &VersionedMessage) {
let Some(fee_payer) = message.static_account_keys().first() else {
return;
};
if self.ephemeral.accounts.get_account(fee_payer).is_some() {
return;
}
if let Some(account) = self.base.accounts.get_account(fee_payer) {
let _ = self.ephemeral.accounts.add_account(*fee_payer, account);
}
}
fn sync_ephemeral_readonly_accounts_from_base(&mut self, message: &VersionedMessage) {
for (index, key) in message.static_account_keys().iter().enumerate() {
if index == 0
|| message
.is_maybe_writable_with_reserved_addresses(index, None::<&HashSet<Address>>)
|| self.delegated_accounts.contains(key)
{
continue;
}
let Some(account) = self.base.accounts.get_account(key) else {
continue;
};
let _ = self.ephemeral.accounts.add_account(*key, account);
}
}
fn ephemeral_accounts_snapshot(
&self,
message: &VersionedMessage,
) -> HashMap<Address, AccountSharedData> {
message
.static_account_keys()
.iter()
.filter_map(|key| {
self.ephemeral
.accounts
.get_account(key)
.map(|account| (*key, account))
})
.collect()
}
fn create_ephemeral_account(
&mut self,
sponsor: Address,
account: Address,
owner: Address,
data_len: u32,
) -> Result<(), TransactionError> {
let rent = ephemeral_accounts::rent(data_len);
self.transfer_ephemeral_rent(sponsor, EPHEMERAL_VAULT_ID, rent)?;
let ephemeral_account = with_mode(
self.ephemeral
.accounts
.get_account(&account)
.unwrap_or_else(|| AccountSharedData::new(0, data_len as usize, &owner)),
AccountMode::Ephemeral,
);
self.ephemeral
.accounts
.add_account_no_checks(account, ephemeral_account);
Ok(())
}
fn resize_ephemeral_account(
&mut self,
sponsor: Address,
account: Address,
new_data_len: u32,
pre_accounts: &HashMap<Address, AccountSharedData>,
) -> Result<(), TransactionError> {
let mut ephemeral_account = self
.ephemeral
.accounts
.get_account(&account)
.or_else(|| pre_accounts.get(&account).cloned())
.ok_or(TransactionError::AccountNotFound)?;
if !ephemeral_account.is(AccountMode::Ephemeral) {
return Err(TransactionError::InstructionError(
0,
InstructionError::InvalidAccountData,
));
}
let old_data_len = pre_accounts
.get(&account)
.map(|account| account.data().len())
.unwrap_or_else(|| ephemeral_account.data().len())
.try_into()
.map_err(|_| {
TransactionError::InstructionError(0, InstructionError::ArithmeticOverflow)
})?;
let old_rent = ephemeral_accounts::rent(old_data_len);
let new_rent = ephemeral_accounts::rent(new_data_len);
if new_rent >= old_rent {
self.transfer_ephemeral_rent(sponsor, EPHEMERAL_VAULT_ID, new_rent - old_rent)?;
} else {
self.transfer_ephemeral_rent(EPHEMERAL_VAULT_ID, sponsor, old_rent - new_rent)?;
}
ephemeral_account.resize(new_data_len as usize, 0);
self.ephemeral
.accounts
.add_account_no_checks(account, ephemeral_account);
Ok(())
}
fn close_ephemeral_account(
&mut self,
sponsor: Address,
account: Address,
pre_accounts: &HashMap<Address, AccountSharedData>,
) -> Result<(), TransactionError> {
let ephemeral_account = self
.ephemeral
.accounts
.get_account(&account)
.or_else(|| pre_accounts.get(&account).cloned())
.ok_or(TransactionError::AccountNotFound)?;
if !ephemeral_account.is(AccountMode::Ephemeral) {
return Err(TransactionError::InstructionError(
0,
InstructionError::InvalidAccountData,
));
}
let data_len = ephemeral_account.data().len().try_into().map_err(|_| {
TransactionError::InstructionError(0, InstructionError::ArithmeticOverflow)
})?;
self.transfer_ephemeral_rent(
EPHEMERAL_VAULT_ID,
sponsor,
ephemeral_accounts::rent(data_len),
)?;
let mut closed = AccountSharedData::default();
closed.set_owner(system_program::id());
self.ephemeral
.accounts
.add_account(account, closed)
.map_err(|_| TransactionError::InvalidAccountIndex)
}
fn transfer_ephemeral_rent(
&mut self,
from: Address,
to: Address,
amount: u64,
) -> Result<(), TransactionError> {
let mut from_account = self
.ephemeral
.accounts
.get_account(&from)
.ok_or(TransactionError::AccountNotFound)?;
let mut to_account = self
.ephemeral
.accounts
.get_account(&to)
.ok_or(TransactionError::AccountNotFound)?;
from_account.checked_sub_lamports(amount).map_err(|_| {
TransactionError::InstructionError(0, InstructionError::InsufficientFunds)
})?;
to_account.checked_add_lamports(amount).map_err(|_| {
TransactionError::InstructionError(0, InstructionError::ArithmeticOverflow)
})?;
self.ephemeral
.accounts
.add_account(from, from_account)
.map_err(|_| TransactionError::InvalidAccountIndex)?;
self.ephemeral
.accounts
.add_account(to, to_account)
.map_err(|_| TransactionError::InvalidAccountIndex)
}
fn run_post_delegation_actions(
&mut self,
actions: Option<PostDelegationActions>,
fee_payer: Address,
) -> Result<(), TransactionError> {
let Some(actions) = actions else {
return Ok(());
};
let instructions = decrypt_post_delegation_instructions(
actions,
&self.validator_identity().to_bytes(),
&self.validator_keypair.to_bytes(),
)?;
let payer = instructions
.iter()
.flat_map(|instruction| instruction.accounts.iter())
.find(|account| account.is_signer)
.map(|account| account.pubkey)
.unwrap_or(fee_payer);
let message = Message::new_with_blockhash(
&instructions,
Some(&payer),
&self.ephemeral.latest_blockhash(),
);
let tx = Transaction::new_unsigned(message);
let sigverify = self.ephemeral.get_sigverify();
self.ephemeral.set_sigverify(false);
let result = self.send_transaction_to(TransactionTarget::Ephemeral, tx);
self.ephemeral.set_sigverify(sigverify);
result.map(|_| ()).map_err(|err| err.err)
}
fn run_post_commit_actions(
&mut self,
actions: &[ScheduledBaseAction],
) -> Result<(), TransactionError> {
if actions.is_empty() {
return Ok(());
}
for scheduled in actions {
let instruction = base_action_instruction(
&scheduled.action,
scheduled.escrow_authority,
self.validator_identity(),
);
self.send_transaction_to(
TransactionTarget::Base,
Transaction::new_signed_with_payer(
&[instruction],
Some(&self.validator_identity()),
&[&self.validator_keypair],
self.base.latest_blockhash(),
),
)
.map_err(|err| err.err)?;
}
Ok(())
}
fn apply_base_account_state(&mut self, writable_accounts: &[Address]) {
for account in writable_accounts {
if self.has_delegation_metadata_for(account) {
if !self.delegated_accounts.contains(account) {
let _ = self.delegate_account(*account);
}
} else if self.delegated_accounts.contains(account)
&& self
.base
.accounts
.get_account(account)
.is_some_and(|account| *account.owner() != DELEGATION_PROGRAM_ID)
&& !self.is_projected_ata_for_delegated_eata(account)
{
self.undelegate_account(*account);
}
}
}
fn is_projected_ata_for_delegated_eata(&self, ata: &Address) -> bool {
self.eata_for_projected_ata(ata).is_some()
}
fn has_delegation_metadata_for(&self, delegated_account: &Address) -> bool {
let metadata = Address::find_program_address(
&[b"delegation-metadata", delegated_account.as_ref()],
&DELEGATION_PROGRAM_ID,
)
.0;
self.base
.accounts
.get_account(&metadata)
.is_some_and(|account| *account.owner() == DELEGATION_PROGRAM_ID)
}
}
fn with_mode(account: AccountSharedData, mode: AccountMode) -> AccountSharedData {
AccountBuilder::from(account).mode(mode).build()
}
fn fork_account_to_stock(account: ForkAccount) -> StockAccount {
StockAccount {
lamports: account.lamports,
data: account.data,
owner: account.owner,
executable: account.executable,
rent_epoch: account.rent_epoch,
}
}
fn stock_account_to_fork(account: StockAccount) -> ForkAccount {
ForkAccount {
lamports: account.lamports,
data: account.data,
owner: account.owner,
executable: account.executable,
rent_epoch: account.rent_epoch,
}
}
fn parse_eata_data(data: &[u8]) -> Option<(Address, Address, u64)> {
let owner = Address::new_from_array(data.get(0..32)?.try_into().ok()?);
let mint = Address::new_from_array(data.get(32..64)?.try_into().ok()?);
let amount = u64::from_le_bytes(data.get(64..72)?.try_into().ok()?);
Some((owner, mint, amount))
}
fn parse_token_account_data(data: &[u8]) -> Option<(Address, Address, u64)> {
let mint = Address::new_from_array(data.get(0..32)?.try_into().ok()?);
let owner = Address::new_from_array(data.get(32..64)?.try_into().ok()?);
let amount = u64::from_le_bytes(data.get(64..72)?.try_into().ok()?);
Some((owner, mint, amount))
}