forked from solana-labs/solana
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Extract execute_instruction() to seed new runtime module
Fixes solana-labs#1528
- Loading branch information
Showing
4 changed files
with
142 additions
and
143 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
use budget_program; | ||
use native_loader; | ||
use program::ProgramError; | ||
use solana_sdk::account::{create_keyed_accounts, Account, KeyedAccount}; | ||
use solana_sdk::pubkey::Pubkey; | ||
use storage_program; | ||
use system_program; | ||
use transaction::Transaction; | ||
use vote_program; | ||
|
||
pub fn is_legacy_program(program_id: &Pubkey) -> bool { | ||
system_program::check_id(program_id) | ||
|| budget_program::check_id(program_id) | ||
|| storage_program::check_id(program_id) | ||
|| vote_program::check_id(program_id) | ||
} | ||
|
||
/// Process an instruction | ||
/// This method calls the instruction's program entry pont method | ||
fn process_instruction( | ||
tx: &Transaction, | ||
instruction_index: usize, | ||
executable_accounts: &mut [(Pubkey, Account)], | ||
program_accounts: &mut [&mut Account], | ||
tick_height: u64, | ||
) -> Result<(), ProgramError> { | ||
let program_id = tx.program_id(instruction_index); | ||
|
||
// Call the contract method | ||
// It's up to the contract to implement its own rules on moving funds | ||
if is_legacy_program(&program_id) { | ||
if system_program::check_id(&program_id) { | ||
system_program::process(&tx, instruction_index, program_accounts)?; | ||
} else if budget_program::check_id(&program_id) { | ||
budget_program::process(&tx, instruction_index, program_accounts)?; | ||
} else if storage_program::check_id(&program_id) { | ||
storage_program::process(&tx, instruction_index, program_accounts)?; | ||
} else if vote_program::check_id(&program_id) { | ||
vote_program::process(&tx, instruction_index, program_accounts)?; | ||
} else { | ||
unreachable!(); | ||
}; | ||
} else { | ||
let mut keyed_accounts = create_keyed_accounts(executable_accounts); | ||
let mut keyed_accounts2: Vec<_> = tx.instructions[instruction_index] | ||
.accounts | ||
.iter() | ||
.map(|&index| &tx.account_keys[index as usize]) | ||
.zip(program_accounts.iter_mut()) | ||
.map(|(key, account)| KeyedAccount { key, account }) | ||
.collect(); | ||
keyed_accounts.append(&mut keyed_accounts2); | ||
|
||
if !native_loader::process_instruction( | ||
&program_id, | ||
&mut keyed_accounts, | ||
&tx.instructions[instruction_index].userdata, | ||
tick_height, | ||
) { | ||
return Err(ProgramError::RuntimeError); | ||
} | ||
} | ||
Ok(()) | ||
} | ||
|
||
fn verify_instruction( | ||
program_id: &Pubkey, | ||
pre_program_id: &Pubkey, | ||
pre_tokens: u64, | ||
account: &Account, | ||
) -> Result<(), ProgramError> { | ||
// Verify the transaction | ||
|
||
// Make sure that program_id is still the same or this was just assigned by the system call contract | ||
if *pre_program_id != account.owner && !system_program::check_id(&program_id) { | ||
return Err(ProgramError::ModifiedProgramId); | ||
} | ||
// For accounts unassigned to the contract, the individual balance of each accounts cannot decrease. | ||
if *program_id != account.owner && pre_tokens > account.tokens { | ||
return Err(ProgramError::ExternalAccountTokenSpend); | ||
} | ||
Ok(()) | ||
} | ||
|
||
/// Execute an instruction | ||
/// This method calls the instruction's program entry pont method and verifies that the result of | ||
/// the call does not violate the bank's accounting rules. | ||
/// The accounts are committed back to the bank only if this function returns Ok(_). | ||
pub fn execute_instruction( | ||
tx: &Transaction, | ||
instruction_index: usize, | ||
executable_accounts: &mut [(Pubkey, Account)], | ||
program_accounts: &mut [&mut Account], | ||
tick_height: u64, | ||
) -> Result<(), ProgramError> { | ||
let program_id = tx.program_id(instruction_index); | ||
// TODO: the runtime should be checking read/write access to memory | ||
// we are trusting the hard coded contracts not to clobber or allocate | ||
let pre_total: u64 = program_accounts.iter().map(|a| a.tokens).sum(); | ||
let pre_data: Vec<_> = program_accounts | ||
.iter_mut() | ||
.map(|a| (a.owner, a.tokens)) | ||
.collect(); | ||
|
||
process_instruction( | ||
tx, | ||
instruction_index, | ||
executable_accounts, | ||
program_accounts, | ||
tick_height, | ||
)?; | ||
|
||
// Verify the instruction | ||
for ((pre_program_id, pre_tokens), post_account) in pre_data.iter().zip(program_accounts.iter()) | ||
{ | ||
verify_instruction(&program_id, pre_program_id, *pre_tokens, post_account)?; | ||
} | ||
// The total sum of all the tokens in all the accounts cannot change. | ||
let post_total: u64 = program_accounts.iter().map(|a| a.tokens).sum(); | ||
if pre_total != post_total { | ||
return Err(ProgramError::UnbalancedInstruction); | ||
} | ||
Ok(()) | ||
} |