diff --git a/programs/budget/src/budget_program.rs b/programs/budget/src/budget_program.rs index ea28f4c1f4..537ab96cb0 100644 --- a/programs/budget/src/budget_program.rs +++ b/programs/budget/src/budget_program.rs @@ -143,9 +143,10 @@ pub fn process_instruction( #[cfg(test)] mod test { use super::*; - use solana_budget_api::budget_transaction::BudgetTransaction; + use solana_budget_api::budget_instruction::BudgetInstruction; use solana_budget_api::id; use solana_runtime::bank::Bank; + use solana_runtime::bank_client::BankClient; use solana_sdk::genesis_block::GenesisBlock; use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::transaction::{InstructionError, Transaction, TransactionError}; @@ -157,41 +158,41 @@ mod test { (bank, mint_keypair) } - #[test] - fn test_invalid_instruction() { - let (bank, from) = create_bank(10_000); - let blockhash = bank.last_blockhash(); - let contract = Keypair::new(); - let data = (1u8, 2u8, 3u8); - let tx = Transaction::new_signed(&from, &[contract.pubkey()], &id(), &data, blockhash, 0); - assert!(bank.process_transaction(&tx).is_err()); - } - #[test] fn test_unsigned_witness_key() { - let (bank, from) = create_bank(10_000); - let blockhash = bank.last_blockhash(); + let (bank, mint_keypair) = create_bank(10_000); + let alice_client = BankClient::new(&bank, mint_keypair); + let alice_pubkey = alice_client.pubkey(); // Initialize BudgetState - let contract = Keypair::new().pubkey(); - let to = Keypair::new().pubkey(); + let budget_pubkey = Keypair::new().pubkey(); + let bob_pubkey = Keypair::new().pubkey(); let witness = Keypair::new().pubkey(); - let tx = - BudgetTransaction::new_when_signed(&from, &to, &contract, &witness, None, 1, blockhash); - bank.process_transaction(&tx).unwrap(); + let script = BudgetInstruction::new_when_signed_script( + &alice_pubkey, + &bob_pubkey, + &budget_pubkey, + &witness, + None, + 1, + ); + alice_client.process_script(script).unwrap(); // Attack! Part 1: Sign a witness transaction with a random key. - let rando = Keypair::new(); - bank.transfer(1, &from, &rando.pubkey(), blockhash).unwrap(); - let mut tx = BudgetTransaction::new_signature(&rando, &contract, &to, blockhash); + let mallory_client = BankClient::new(&bank, Keypair::new()); + let mallory_pubkey = mallory_client.pubkey(); + alice_client.transfer(1, &mallory_pubkey).unwrap(); + let instruction = + BudgetInstruction::new_apply_signature(&mallory_pubkey, &budget_pubkey, &bob_pubkey); + let mut transaction = Transaction::new(vec![instruction]); // Attack! Part 2: Point the instruction to the expected, but unsigned, key. - tx.account_keys.push(from.pubkey()); - tx.instructions[0].accounts[0] = 3; + transaction.account_keys.push(alice_pubkey); + transaction.instructions[0].accounts[0] = 3; // Ensure the transaction fails because of the unsigned key. assert_eq!( - bank.process_transaction(&tx), + mallory_client.process_transaction(transaction), Err(TransactionError::InstructionError( 0, InstructionError::ProgramError(ProgramError::MissingRequiredSignature) @@ -201,37 +202,44 @@ mod test { #[test] fn test_unsigned_timestamp() { - let (bank, from) = create_bank(10_000); - let blockhash = bank.last_blockhash(); + let (bank, mint_keypair) = create_bank(10_000); + let alice_client = BankClient::new(&bank, mint_keypair); + let alice_pubkey = alice_client.pubkey(); // Initialize BudgetState - let contract = Keypair::new().pubkey(); - let to = Keypair::new().pubkey(); + let budget_pubkey = Keypair::new().pubkey(); + let bob_pubkey = Keypair::new().pubkey(); let dt = Utc::now(); - let tx = BudgetTransaction::new_on_date( - &from, - &to, - &contract, + let script = BudgetInstruction::new_on_date_script( + &alice_pubkey, + &bob_pubkey, + &budget_pubkey, dt, - &from.pubkey(), + &alice_pubkey, None, 1, - blockhash, ); - bank.process_transaction(&tx).unwrap(); + alice_client.process_script(script).unwrap(); // Attack! Part 1: Sign a timestamp transaction with a random key. - let rando = Keypair::new(); - bank.transfer(1, &from, &rando.pubkey(), blockhash).unwrap(); - let mut tx = BudgetTransaction::new_timestamp(&rando, &contract, &to, dt, blockhash); + let mallory_client = BankClient::new(&bank, Keypair::new()); + let mallory_pubkey = mallory_client.pubkey(); + alice_client.transfer(1, &mallory_pubkey).unwrap(); + let instruction = BudgetInstruction::new_apply_timestamp( + &mallory_pubkey, + &budget_pubkey, + &bob_pubkey, + dt, + ); + let mut transaction = Transaction::new(vec![instruction]); // Attack! Part 2: Point the instruction to the expected, but unsigned, key. - tx.account_keys.push(from.pubkey()); - tx.instructions[0].accounts[0] = 3; + transaction.account_keys.push(alice_pubkey); + transaction.instructions[0].accounts[0] = 3; // Ensure the transaction fails because of the unsigned key. assert_eq!( - bank.process_transaction(&tx), + mallory_client.process_transaction(transaction), Err(TransactionError::InstructionError( 0, InstructionError::ProgramError(ProgramError::MissingRequiredSignature) @@ -241,40 +249,39 @@ mod test { #[test] fn test_transfer_on_date() { - let (bank, from) = create_bank(2); - let blockhash = bank.last_blockhash(); - let contract = Keypair::new(); - let to = Keypair::new(); - let rando = Keypair::new(); + let (bank, mint_keypair) = create_bank(2); + let alice_client = BankClient::new(&bank, mint_keypair); + let alice_pubkey = alice_client.pubkey(); + let budget_pubkey = Keypair::new().pubkey(); + let bob_pubkey = Keypair::new().pubkey(); + let mallory_pubkey = Keypair::new().pubkey(); let dt = Utc::now(); - let tx = BudgetTransaction::new_on_date( - &from, - &to.pubkey(), - &contract.pubkey(), + let script = BudgetInstruction::new_on_date_script( + &alice_pubkey, + &bob_pubkey, + &budget_pubkey, dt, - &from.pubkey(), + &alice_pubkey, None, 1, - blockhash, ); - bank.process_transaction(&tx).unwrap(); - assert_eq!(bank.get_balance(&from.pubkey()), 1); - assert_eq!(bank.get_balance(&contract.pubkey()), 1); + alice_client.process_script(script).unwrap(); + assert_eq!(bank.get_balance(&alice_pubkey), 1); + assert_eq!(bank.get_balance(&budget_pubkey), 1); - let contract_account = bank.get_account(&contract.pubkey()).unwrap(); + let contract_account = bank.get_account(&budget_pubkey).unwrap(); let budget_state = BudgetState::deserialize(&contract_account.data).unwrap(); assert!(budget_state.is_pending()); - // Attack! Try to payout to a rando key - let tx = BudgetTransaction::new_timestamp( - &from, - &contract.pubkey(), - &rando.pubkey(), + // Attack! Try to payout to mallory_pubkey + let instruction = BudgetInstruction::new_apply_timestamp( + &alice_pubkey, + &budget_pubkey, + &mallory_pubkey, dt, - blockhash, ); assert_eq!( - bank.process_transaction(&tx).unwrap_err(), + alice_client.process_script(vec![instruction]).unwrap_err(), TransactionError::InstructionError( 0, InstructionError::ProgramError(ProgramError::CustomError( @@ -282,77 +289,71 @@ mod test { )) ) ); - assert_eq!(bank.get_balance(&from.pubkey()), 1); - assert_eq!(bank.get_balance(&contract.pubkey()), 1); - assert_eq!(bank.get_balance(&to.pubkey()), 0); + assert_eq!(bank.get_balance(&alice_pubkey), 1); + assert_eq!(bank.get_balance(&budget_pubkey), 1); + assert_eq!(bank.get_balance(&bob_pubkey), 0); - let contract_account = bank.get_account(&contract.pubkey()).unwrap(); + let contract_account = bank.get_account(&budget_pubkey).unwrap(); let budget_state = BudgetState::deserialize(&contract_account.data).unwrap(); assert!(budget_state.is_pending()); // Now, acknowledge the time in the condition occurred and // that pubkey's funds are now available. - let tx = BudgetTransaction::new_timestamp( - &from, - &contract.pubkey(), - &to.pubkey(), - dt, - blockhash, - ); - bank.process_transaction(&tx).unwrap(); - assert_eq!(bank.get_balance(&from.pubkey()), 1); - assert_eq!(bank.get_balance(&contract.pubkey()), 0); - assert_eq!(bank.get_balance(&to.pubkey()), 1); - assert_eq!(bank.get_account(&contract.pubkey()), None); + let instruction = + BudgetInstruction::new_apply_timestamp(&alice_pubkey, &budget_pubkey, &bob_pubkey, dt); + alice_client.process_script(vec![instruction]).unwrap(); + assert_eq!(bank.get_balance(&alice_pubkey), 1); + assert_eq!(bank.get_balance(&budget_pubkey), 0); + assert_eq!(bank.get_balance(&bob_pubkey), 1); + assert_eq!(bank.get_account(&budget_pubkey), None); } #[test] fn test_cancel_transfer() { - let (bank, from) = create_bank(3); - let blockhash = bank.last_blockhash(); - let contract = Keypair::new(); - let to = Keypair::new(); + let (bank, mint_keypair) = create_bank(3); + let alice_client = BankClient::new(&bank, mint_keypair); + let alice_pubkey = alice_client.pubkey(); + let budget_pubkey = Keypair::new().pubkey(); + let bob_pubkey = Keypair::new().pubkey(); let dt = Utc::now(); - let tx = BudgetTransaction::new_on_date( - &from, - &to.pubkey(), - &contract.pubkey(), + let script = BudgetInstruction::new_on_date_script( + &alice_pubkey, + &bob_pubkey, + &budget_pubkey, dt, - &from.pubkey(), - Some(from.pubkey()), + &alice_pubkey, + Some(alice_pubkey), 1, - blockhash, ); - bank.process_transaction(&tx).unwrap(); - assert_eq!(bank.get_balance(&from.pubkey()), 2); - assert_eq!(bank.get_balance(&contract.pubkey()), 1); + alice_client.process_script(script).unwrap(); + assert_eq!(bank.get_balance(&alice_pubkey), 2); + assert_eq!(bank.get_balance(&budget_pubkey), 1); - let contract_account = bank.get_account(&contract.pubkey()).unwrap(); + let contract_account = bank.get_account(&budget_pubkey).unwrap(); let budget_state = BudgetState::deserialize(&contract_account.data).unwrap(); assert!(budget_state.is_pending()); // Attack! try to put the lamports into the wrong account with cancel - let rando = Keypair::new(); - bank.transfer(1, &from, &rando.pubkey(), blockhash).unwrap(); - assert_eq!(bank.get_balance(&from.pubkey()), 1); + let mallory_client = BankClient::new(&bank, Keypair::new()); + let mallory_pubkey = mallory_client.pubkey(); + alice_client.transfer(1, &mallory_pubkey).unwrap(); + assert_eq!(bank.get_balance(&alice_pubkey), 1); - let tx = - BudgetTransaction::new_signature(&rando, &contract.pubkey(), &to.pubkey(), blockhash); - // unit test hack, the `from account` is passed instead of the `to` account to avoid - // creating more account vectors - bank.process_transaction(&tx).unwrap(); + let instruction = + BudgetInstruction::new_apply_signature(&mallory_pubkey, &budget_pubkey, &bob_pubkey); + mallory_client.process_script(vec![instruction]).unwrap(); // nothing should be changed because apply witness didn't finalize a payment - assert_eq!(bank.get_balance(&from.pubkey()), 1); - assert_eq!(bank.get_balance(&contract.pubkey()), 1); - assert_eq!(bank.get_account(&to.pubkey()), None); + assert_eq!(bank.get_balance(&alice_pubkey), 1); + assert_eq!(bank.get_balance(&budget_pubkey), 1); + assert_eq!(bank.get_account(&bob_pubkey), None); - // Now, cancel the transaction. from gets her funds back - let tx = - BudgetTransaction::new_signature(&from, &contract.pubkey(), &from.pubkey(), blockhash); - bank.process_transaction(&tx).unwrap(); - assert_eq!(bank.get_balance(&from.pubkey()), 2); - assert_eq!(bank.get_account(&contract.pubkey()), None); - assert_eq!(bank.get_account(&to.pubkey()), None); + // Now, cancel the transaction. mint gets her funds back + let instruction = + BudgetInstruction::new_apply_signature(&alice_pubkey, &budget_pubkey, &alice_pubkey); + alice_client.process_script(vec![instruction]).unwrap(); + assert_eq!(bank.get_balance(&alice_pubkey), 2); + assert_eq!(bank.get_account(&budget_pubkey), None); + assert_eq!(bank.get_account(&bob_pubkey), None); } } diff --git a/programs/budget_api/src/budget_instruction.rs b/programs/budget_api/src/budget_instruction.rs index 71be56c6dd..0bcb0eb3a5 100644 --- a/programs/budget_api/src/budget_instruction.rs +++ b/programs/budget_api/src/budget_instruction.rs @@ -1,9 +1,12 @@ -use crate::budget_expr::BudgetExpr; +use crate::budget_expr::{BudgetExpr, Condition}; +use crate::budget_state::BudgetState; use crate::id; +use bincode::serialized_size; use chrono::prelude::{DateTime, Utc}; use serde_derive::{Deserialize, Serialize}; use solana_sdk::pubkey::Pubkey; -use solana_sdk::transaction::Instruction; +use solana_sdk::system_instruction::SystemInstruction; +use solana_sdk::transaction::{Instruction, Script}; /// A smart contract. #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] @@ -37,6 +40,80 @@ impl BudgetInstruction { Instruction::new(id(), &BudgetInstruction::InitializeAccount(expr), keys) } + pub fn new_initialize_account_script( + from: &Pubkey, + contract: &Pubkey, + lamports: u64, + expr: BudgetExpr, + ) -> Script { + let space = serialized_size(&BudgetState::new(expr.clone())).unwrap(); + vec![ + SystemInstruction::new_program_account(&from, contract, lamports, space, &id()), + Self::new_initialize_account(contract, expr), + ] + } + + /// Create a postdated payment script. + pub fn new_on_date_script( + from: &Pubkey, + to: &Pubkey, + contract: &Pubkey, + dt: DateTime, + dt_pubkey: &Pubkey, + cancelable: Option, + lamports: u64, + ) -> Script { + let expr = if let Some(from) = cancelable { + BudgetExpr::Or( + ( + Condition::Timestamp(dt, *dt_pubkey), + Box::new(BudgetExpr::new_payment(lamports, to)), + ), + ( + Condition::Signature(from), + Box::new(BudgetExpr::new_payment(lamports, &from)), + ), + ) + } else { + BudgetExpr::After( + Condition::Timestamp(dt, *dt_pubkey), + Box::new(BudgetExpr::new_payment(lamports, to)), + ) + }; + + Self::new_initialize_account_script(from, contract, lamports, expr) + } + + /// Create a multisig payment script. + pub fn new_when_signed_script( + from: &Pubkey, + to: &Pubkey, + contract: &Pubkey, + witness: &Pubkey, + cancelable: Option, + lamports: u64, + ) -> Script { + let expr = if let Some(from) = cancelable { + BudgetExpr::Or( + ( + Condition::Signature(*witness), + Box::new(BudgetExpr::new_payment(lamports, to)), + ), + ( + Condition::Signature(from), + Box::new(BudgetExpr::new_payment(lamports, &from)), + ), + ) + } else { + BudgetExpr::After( + Condition::Signature(*witness), + Box::new(BudgetExpr::new_payment(lamports, to)), + ) + }; + + Self::new_initialize_account_script(from, contract, lamports, expr) + } + pub fn new_apply_timestamp( from: &Pubkey, contract: &Pubkey, diff --git a/programs/budget_api/src/budget_transaction.rs b/programs/budget_api/src/budget_transaction.rs index e5f422e54a..481a16ac7d 100644 --- a/programs/budget_api/src/budget_transaction.rs +++ b/programs/budget_api/src/budget_transaction.rs @@ -1,6 +1,6 @@ //! The `budget_transaction` module provides functionality for creating Budget transactions. -use crate::budget_expr::{BudgetExpr, Condition}; +use crate::budget_expr::BudgetExpr; use crate::budget_instruction::BudgetInstruction; use crate::budget_state::BudgetState; use crate::id; @@ -10,7 +10,7 @@ use solana_sdk::hash::Hash; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::system_instruction::SystemInstruction; -use solana_sdk::transaction::Transaction; +use solana_sdk::transaction::{Script, Transaction}; pub struct BudgetTransaction {} @@ -36,6 +36,18 @@ impl BudgetTransaction { tx } + fn new_signed( + from_keypair: &Keypair, + script: Script, + recent_blockhash: Hash, + fee: u64, + ) -> Transaction { + let mut tx = Transaction::new(script); + tx.fee = fee; + tx.sign(&[from_keypair], recent_blockhash); + tx + } + /// Create and sign a new Transaction. Used for unit-testing. pub fn new_payment( from_keypair: &Keypair, @@ -96,24 +108,16 @@ impl BudgetTransaction { lamports: u64, recent_blockhash: Hash, ) -> Transaction { - let expr = if let Some(from) = cancelable { - BudgetExpr::Or( - ( - Condition::Timestamp(dt, *dt_pubkey), - Box::new(BudgetExpr::new_payment(lamports, to)), - ), - ( - Condition::Signature(from), - Box::new(BudgetExpr::new_payment(lamports, &from)), - ), - ) - } else { - BudgetExpr::After( - Condition::Timestamp(dt, *dt_pubkey), - Box::new(BudgetExpr::new_payment(lamports, to)), - ) - }; - Self::new(from_keypair, contract, expr, lamports, recent_blockhash, 0) + let script = BudgetInstruction::new_on_date_script( + &from_keypair.pubkey(), + to, + contract, + dt, + dt_pubkey, + cancelable, + lamports, + ); + Self::new_signed(from_keypair, script, recent_blockhash, 0) } /// Create and sign a multisig Transaction. @@ -126,24 +130,15 @@ impl BudgetTransaction { lamports: u64, recent_blockhash: Hash, ) -> Transaction { - let expr = if let Some(from) = cancelable { - BudgetExpr::Or( - ( - Condition::Signature(*witness), - Box::new(BudgetExpr::new_payment(lamports, to)), - ), - ( - Condition::Signature(from), - Box::new(BudgetExpr::new_payment(lamports, &from)), - ), - ) - } else { - BudgetExpr::After( - Condition::Signature(*witness), - Box::new(BudgetExpr::new_payment(lamports, to)), - ) - }; - Self::new(from_keypair, contract, expr, lamports, recent_blockhash, 0) + let script = BudgetInstruction::new_when_signed_script( + &from_keypair.pubkey(), + to, + contract, + witness, + cancelable, + lamports, + ); + Self::new_signed(from_keypair, script, recent_blockhash, 0) } pub fn system_instruction(tx: &Transaction, index: usize) -> Option { diff --git a/runtime/src/bank_client.rs b/runtime/src/bank_client.rs index 7c5615e610..07e6a943de 100644 --- a/runtime/src/bank_client.rs +++ b/runtime/src/bank_client.rs @@ -1,6 +1,8 @@ use crate::bank::Bank; -use solana_sdk::signature::Keypair; -use solana_sdk::transaction::{Transaction, TransactionError}; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::{Keypair, KeypairUtil}; +use solana_sdk::system_instruction::SystemInstruction; +use solana_sdk::transaction::{Script, Transaction, TransactionError}; pub struct BankClient<'a> { bank: &'a Bank, @@ -12,8 +14,23 @@ impl<'a> BankClient<'a> { Self { bank, keypair } } - pub fn process_transaction(&self, tx: &mut Transaction) -> Result<(), TransactionError> { + pub fn pubkey(&self) -> Pubkey { + self.keypair.pubkey() + } + + pub fn process_transaction(&self, mut tx: Transaction) -> Result<(), TransactionError> { tx.sign(&[&self.keypair], self.bank.last_blockhash()); - self.bank.process_transaction(tx) + self.bank.process_transaction(&mut tx) + } + + /// Create and process a transaction. + pub fn process_script(&self, script: Script) -> Result<(), TransactionError> { + self.process_transaction(Transaction::new(script)) + } + + /// Transfer lamports to pubkey + pub fn transfer(&self, lamports: u64, pubkey: &Pubkey) -> Result<(), TransactionError> { + let move_instruction = SystemInstruction::new_move(&self.pubkey(), pubkey, lamports); + self.process_script(vec![move_instruction]) } } diff --git a/runtime/tests/system.rs b/runtime/tests/system.rs index 5716638063..f815ae78e9 100644 --- a/runtime/tests/system.rs +++ b/runtime/tests/system.rs @@ -5,39 +5,36 @@ use solana_sdk::native_program::ProgramError; use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::system_instruction::SystemInstruction; use solana_sdk::system_program; -use solana_sdk::transaction::{Instruction, InstructionError, Transaction, TransactionError}; +use solana_sdk::transaction::{Instruction, InstructionError, TransactionError}; #[test] fn test_system_unsigned_transaction() { - let (genesis_block, from_keypair) = GenesisBlock::new(100); + let (genesis_block, mint_keypair) = GenesisBlock::new(100); let bank = Bank::new(&genesis_block); - let from_pubkey = from_keypair.pubkey(); - let alice_client = BankClient::new(&bank, from_keypair); - let to_keypair = Keypair::new(); - let to_pubkey = to_keypair.pubkey(); - let mallory_client = BankClient::new(&bank, to_keypair); + let alice_client = BankClient::new(&bank, mint_keypair); + let alice_pubkey = alice_client.pubkey(); + + let mallory_client = BankClient::new(&bank, Keypair::new()); + let mallory_pubkey = mallory_client.pubkey(); // Fund to account to bypass AccountNotFound error - let ix = SystemInstruction::new_move(&from_pubkey, &to_pubkey, 50); - let mut tx = Transaction::new(vec![ix]); - alice_client.process_transaction(&mut tx).unwrap(); + alice_client.transfer(50, &mallory_pubkey).unwrap(); // Erroneously sign transaction with recipient account key // No signature case is tested by bank `test_zero_signatures()` - let ix = Instruction::new( + let malicious_script = vec![Instruction::new( system_program::id(), &SystemInstruction::Move { lamports: 10 }, - vec![(from_pubkey, false), (to_pubkey, true)], - ); - let mut tx = Transaction::new(vec![ix]); + vec![(alice_pubkey, false), (mallory_pubkey, true)], + )]; assert_eq!( - mallory_client.process_transaction(&mut tx), + mallory_client.process_script(malicious_script), Err(TransactionError::InstructionError( 0, InstructionError::ProgramError(ProgramError::MissingRequiredSignature) )) ); - assert_eq!(bank.get_balance(&from_pubkey), 50); - assert_eq!(bank.get_balance(&to_pubkey), 50); + assert_eq!(bank.get_balance(&alice_pubkey), 50); + assert_eq!(bank.get_balance(&mallory_pubkey), 50); } diff --git a/sdk/src/transaction.rs b/sdk/src/transaction.rs index 905a24d855..1ac7087851 100644 --- a/sdk/src/transaction.rs +++ b/sdk/src/transaction.rs @@ -71,7 +71,10 @@ impl GenericInstruction { } pub type Instruction = GenericInstruction; +pub type Script = Vec; + pub type CompiledInstruction = GenericInstruction; +pub type CompiledScript = Vec; impl CompiledInstruction { pub fn serialize_with(mut writer: &mut Cursor<&mut [u8]>, ix: &Self) -> Result<(), Error> { @@ -163,12 +166,12 @@ pub struct Transaction { pub program_ids: Vec, /// Programs that will be executed in sequence and committed in one atomic transaction if all /// succeed. - pub instructions: Vec, + pub instructions: CompiledScript, } impl Transaction { - pub fn new(instructions: Vec) -> Self { - TransactionCompiler::new(instructions).compile() + pub fn new(script: Script) -> Self { + TransactionCompiler::new(script).compile() } pub fn new_with_blockhash_and_fee( @@ -224,7 +227,7 @@ impl Transaction { recent_blockhash: Hash, fee: u64, program_ids: Vec, - instructions: Vec, + instructions: CompiledScript, ) -> Self { let mut account_keys: Vec<_> = from_keypairs .iter() @@ -466,7 +469,7 @@ impl<'a> serde::de::Visitor<'a> for TransactionVisitor { let program_ids: Vec = deserialize_vec_with(&mut rd, Transaction::deserialize_pubkey) .map_err(Error::custom)?; - let instructions: Vec = + let instructions: CompiledScript = deserialize_vec_with(&mut rd, CompiledInstruction::deserialize_from) .map_err(Error::custom)?; Ok(Transaction {