Add and update tests (#9566)

This commit is contained in:
Jack May
2020-04-18 17:04:13 -07:00
committed by GitHub
parent 657fbfbefa
commit 58887c591b
3 changed files with 409 additions and 239 deletions

View File

@ -71,13 +71,13 @@ pub fn register_helpers<'a>(
#[macro_export] #[macro_export]
macro_rules! translate { macro_rules! translate {
($vm_addr:expr, $len:expr, $regions:expr) => { ($vm_addr:expr, $len:expr, $regions:expr) => {
translate_addr( translate_addr::<BPFError>(
$vm_addr as u64, $vm_addr as u64,
$len as usize, $len as usize,
file!(), file!(),
line!() as usize - ELF_INSN_DUMP_OFFSET + 1, line!() as usize - ELF_INSN_DUMP_OFFSET + 1,
$regions, $regions,
)? )
}; };
} }
@ -85,40 +85,51 @@ macro_rules! translate {
macro_rules! translate_type_mut { macro_rules! translate_type_mut {
($t:ty, $vm_addr:expr, $regions:expr) => { ($t:ty, $vm_addr:expr, $regions:expr) => {
unsafe { unsafe {
&mut *(translate_addr( match translate_addr::<BPFError>(
$vm_addr as u64, $vm_addr as u64,
size_of::<$t>(), size_of::<$t>(),
file!(), file!(),
line!() as usize - ELF_INSN_DUMP_OFFSET + 1, line!() as usize - ELF_INSN_DUMP_OFFSET + 1,
$regions, $regions,
)? as *mut $t) ) {
Ok(value) => Ok(&mut *(value as *mut $t)),
Err(e) => Err(e),
}
} }
}; };
} }
#[macro_export] #[macro_export]
macro_rules! translate_type { macro_rules! translate_type {
($t:ty, $vm_addr:expr, $regions:expr) => { ($t:ty, $vm_addr:expr, $regions:expr) => {
&*translate_type_mut!($t, $vm_addr, $regions) match translate_type_mut!($t, $vm_addr, $regions) {
Ok(value) => Ok(&*value),
Err(e) => Err(e),
}
}; };
} }
#[macro_export] #[macro_export]
macro_rules! translate_slice_mut { macro_rules! translate_slice_mut {
($t:ty, $vm_addr:expr, $len: expr, $regions:expr) => {{ ($t:ty, $vm_addr:expr, $len: expr, $regions:expr) => {
let host_addr = translate_addr( match translate_addr::<BPFError>(
$vm_addr as u64, $vm_addr as u64,
$len as usize * size_of::<$t>(), $len as usize * size_of::<$t>(),
file!(), file!(),
line!() as usize - ELF_INSN_DUMP_OFFSET + 1, line!() as usize - ELF_INSN_DUMP_OFFSET + 1,
$regions, $regions,
)? as *mut $t; ) {
unsafe { from_raw_parts_mut(host_addr, $len as usize) } Ok(value) => Ok(unsafe { from_raw_parts_mut(value as *mut $t, $len as usize) }),
}}; Err(e) => Err(e),
}
};
} }
#[macro_export] #[macro_export]
macro_rules! translate_slice { macro_rules! translate_slice {
($t:ty, $vm_addr:expr, $len: expr, $regions:expr) => { ($t:ty, $vm_addr:expr, $len: expr, $regions:expr) => {
&*translate_slice_mut!($t, $vm_addr, $len, $regions) match translate_slice_mut!($t, $vm_addr, $len, $regions) {
Ok(value) => Ok(&*value),
Err(e) => Err(e),
}
}; };
} }
@ -130,7 +141,7 @@ fn translate_string_and_do(
regions: &[MemoryRegion], regions: &[MemoryRegion],
work: &dyn Fn(&str) -> Result<u64, EbpfError<BPFError>>, work: &dyn Fn(&str) -> Result<u64, EbpfError<BPFError>>,
) -> Result<u64, EbpfError<BPFError>> { ) -> Result<u64, EbpfError<BPFError>> {
let buf = translate_slice!(u8, addr, len, regions); let buf = translate_slice!(u8, addr, len, regions)?;
let i = match buf.iter().position(|byte| *byte == 0) { let i = match buf.iter().position(|byte| *byte == 0) {
Some(i) => i, Some(i) => i,
None => len as usize, None => len as usize,
@ -243,3 +254,232 @@ impl HelperObject<BPFError> for HelperSolAllocFree {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use solana_sdk::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
};
#[test]
fn test_translate() {
const START: u64 = 100;
const LENGTH: u64 = 1000;
let data = vec![0u8; LENGTH as usize];
let addr = data.as_ptr() as u64;
let regions = vec![MemoryRegion::new_from_slice(&data, START)];
let cases = vec![
(true, START, 0, addr),
(true, START, 1, addr),
(true, START, LENGTH, addr),
(true, START + 1, LENGTH - 1, addr + 1),
(false, START + 1, LENGTH, 0),
(true, START + LENGTH - 1, 1, addr + LENGTH - 1),
(true, START + LENGTH, 0, addr + LENGTH),
(false, START + LENGTH, 1, 0),
(false, START, LENGTH + 1, 0),
(false, 0, 0, 0),
(false, 0, 1, 0),
(false, START - 1, 0, 0),
(false, START - 1, 1, 0),
(true, START + LENGTH / 2, LENGTH / 2, addr + LENGTH / 2),
];
for (ok, start, length, value) in cases {
match ok {
true => assert_eq!(translate!(start, length, &regions).unwrap(), value),
false => assert!(translate!(start, length, &regions).is_err()),
}
}
}
#[test]
fn test_translate_type() {
// Pubkey
let pubkey = Pubkey::new_rand();
let addr = &pubkey as *const _ as u64;
let regions = vec![MemoryRegion {
addr_host: addr,
addr_vm: 100,
len: std::mem::size_of::<Pubkey>() as u64,
}];
let translated_pubkey = translate_type!(Pubkey, 100, &regions).unwrap();
assert_eq!(pubkey, *translated_pubkey);
// Instruction
let instruction = Instruction::new(
Pubkey::new_rand(),
&"foobar",
vec![AccountMeta::new(Pubkey::new_rand(), false)],
);
let addr = &instruction as *const _ as u64;
let regions = vec![MemoryRegion {
addr_host: addr,
addr_vm: 100,
len: std::mem::size_of::<Instruction>() as u64,
}];
let translated_instruction = translate_type!(Instruction, 100, &regions).unwrap();
assert_eq!(instruction, *translated_instruction);
}
#[test]
fn test_translate_slice() {
// u8
let mut data = vec![1u8, 2, 3, 4, 5];
let addr = data.as_ptr() as *const _ as u64;
let regions = vec![MemoryRegion {
addr_host: addr,
addr_vm: 100,
len: data.len() as u64,
}];
let translated_data = translate_slice!(u8, 100, data.len(), &regions).unwrap();
assert_eq!(data, translated_data);
data[0] = 10;
assert_eq!(data, translated_data);
// Pubkeys
let mut data = vec![Pubkey::new_rand(); 5];
let addr = data.as_ptr() as *const _ as u64;
let regions = vec![MemoryRegion {
addr_host: addr,
addr_vm: 100,
len: (data.len() * std::mem::size_of::<Pubkey>()) as u64,
}];
let translated_data = translate_slice!(Pubkey, 100, data.len(), &regions).unwrap();
assert_eq!(data, translated_data);
data[0] = Pubkey::new_rand(); // Both should point to same place
assert_eq!(data, translated_data);
}
#[test]
fn test_translate_string_and_do() {
let string = "Gaggablaghblagh!";
let addr = string.as_ptr() as *const _ as u64;
let regions = vec![MemoryRegion {
addr_host: addr,
addr_vm: 100,
len: string.len() as u64,
}];
assert_eq!(
42,
translate_string_and_do(100, string.len() as u64, &regions, &|string: &str| {
assert_eq!(string, "Gaggablaghblagh!");
Ok(42)
})
.unwrap()
);
}
#[test]
#[should_panic(expected = "UserError(HelperError(Abort))")]
fn test_helper_abort() {
let ro_region = MemoryRegion::default();
let rw_region = MemoryRegion::default();
helper_abort(0, 0, 0, 0, 0, &[ro_region], &[rw_region]).unwrap();
}
#[test]
#[should_panic(expected = "UserError(HelperError(Panic(\"Gaggablaghblagh!\", 42, 84)))")]
fn test_helper_sol_panic() {
let string = "Gaggablaghblagh!";
let addr = string.as_ptr() as *const _ as u64;
let ro_region = MemoryRegion {
addr_host: addr,
addr_vm: 100,
len: string.len() as u64,
};
let rw_region = MemoryRegion::default();
helper_sol_panic(
100,
string.len() as u64,
42,
84,
0,
&[ro_region],
&[rw_region],
)
.unwrap();
}
#[test]
fn test_helper_sol_log() {
let string = "Gaggablaghblagh!";
let addr = string.as_ptr() as *const _ as u64;
let ro_regions = &[MemoryRegion {
addr_host: addr,
addr_vm: 100,
len: string.len() as u64,
}];
let rw_regions = &[MemoryRegion::default()];
solana_logger::setup_with_default("solana=info");
helper_sol_log(100, string.len() as u64, 0, 0, 0, ro_regions, rw_regions).unwrap();
solana_logger::setup_with_default("solana=info");
helper_sol_log(
100,
string.len() as u64 * 2,
0,
0,
0,
ro_regions,
rw_regions,
)
.unwrap_err();
}
#[test]
fn test_helper_sol_log_u64() {
solana_logger::setup_with_default("solana=info");
let ro_regions = &[MemoryRegion::default()];
let rw_regions = &[MemoryRegion::default()];
helper_sol_log_u64(1, 2, 3, 4, 5, ro_regions, rw_regions).unwrap();
}
#[test]
fn test_helper_sol_alloc_free() {
// large alloc
{
let heap = vec![0_u8; 100];
let ro_regions = &[MemoryRegion::default()];
let rw_regions = &[MemoryRegion::new_from_slice(&heap, MM_HEAP_START)];
let mut helper = HelperSolAllocFree {
allocator: BPFAllocator::new(heap, MM_HEAP_START),
};
assert_ne!(
helper
.call(100, 0, 0, 0, 0, ro_regions, rw_regions)
.unwrap(),
0
);
assert_eq!(
helper
.call(100, 0, 0, 0, 0, ro_regions, rw_regions)
.unwrap(),
0
);
}
// many small allocs
{
let heap = vec![0_u8; 100];
let ro_regions = &[MemoryRegion::default()];
let rw_regions = &[MemoryRegion::new_from_slice(&heap, MM_HEAP_START)];
let mut helper = HelperSolAllocFree {
allocator: BPFAllocator::new(heap, MM_HEAP_START),
};
for _ in 0..100 {
assert_ne!(
helper.call(1, 0, 0, 0, 0, ro_regions, rw_regions).unwrap(),
0
);
}
assert_eq!(
helper
.call(100, 0, 0, 0, 0, ro_regions, rw_regions)
.unwrap(),
0
);
}
}
}

View File

@ -13,7 +13,7 @@ fn bench_verify_account_changes_data(bencher: &mut Bencher) {
let owner = Pubkey::new_rand(); let owner = Pubkey::new_rand();
let non_owner = Pubkey::new_rand(); let non_owner = Pubkey::new_rand();
let pre = PreAccount::new(&Account::new(0, BUFSIZE, &owner), true, &owner); let pre = PreAccount::new(&Account::new(0, BUFSIZE, &owner), &owner, true);
let post = Account::new(0, BUFSIZE, &owner); let post = Account::new(0, BUFSIZE, &owner);
assert_eq!(pre.verify(&owner, &RentCollector::default(), &post), Ok(())); assert_eq!(pre.verify(&owner, &RentCollector::default(), &post), Ok(()));
@ -25,7 +25,7 @@ fn bench_verify_account_changes_data(bencher: &mut Bencher) {
let summary = bencher.bench(|_bencher| {}).unwrap(); let summary = bencher.bench(|_bencher| {}).unwrap();
info!("data no change by owner: {} ns/iter", summary.median); info!("data no change by owner: {} ns/iter", summary.median);
let pre = PreAccount::new(&Account::new(0, BUFSIZE, &owner), true, &non_owner); let pre = PreAccount::new(&Account::new(0, BUFSIZE, &owner), &non_owner, true);
match pre.data { match pre.data {
Some(ref data) => bencher.iter(|| *data == post.data), Some(ref data) => bencher.iter(|| *data == post.data),
None => panic!("No data!"), None => panic!("No data!"),

View File

@ -23,11 +23,11 @@ pub struct PreAccount {
pub data_len: usize, pub data_len: usize,
pub data: Option<Vec<u8>>, pub data: Option<Vec<u8>>,
pub owner: Pubkey, pub owner: Pubkey,
pub executable: bool, pub is_executable: bool,
pub rent_epoch: Epoch, pub rent_epoch: Epoch,
} }
impl PreAccount { impl PreAccount {
pub fn new(account: &Account, is_writable: bool, program_id: &Pubkey) -> Self { pub fn new(account: &Account, program_id: &Pubkey, is_writable: bool) -> Self {
Self { Self {
is_writable, is_writable,
lamports: account.lamports, lamports: account.lamports,
@ -43,7 +43,7 @@ impl PreAccount {
None None
}, },
owner: account.owner, owner: account.owner,
executable: account.executable, is_executable: account.executable,
rent_epoch: account.rent_epoch, rent_epoch: account.rent_epoch,
} }
} }
@ -91,7 +91,7 @@ impl PreAccount {
if !self.is_writable { if !self.is_writable {
return Err(InstructionError::ReadonlyLamportChange); return Err(InstructionError::ReadonlyLamportChange);
} }
if self.executable { if self.is_executable {
return Err(InstructionError::ExecutableLamportChange); return Err(InstructionError::ExecutableLamportChange);
} }
} }
@ -105,11 +105,16 @@ impl PreAccount {
return Err(InstructionError::AccountDataSizeChanged); return Err(InstructionError::AccountDataSizeChanged);
} }
if Self::should_verify_data(&self.owner, program_id, self.is_writable, self.executable) { if Self::should_verify_data(
&self.owner,
program_id,
self.is_writable,
self.is_executable,
) {
match &self.data { match &self.data {
Some(data) if *data == post.data => (), Some(data) if *data == post.data => (),
_ => { _ => {
if self.executable { if self.is_executable {
return Err(InstructionError::ExecutableDataModified); return Err(InstructionError::ExecutableDataModified);
} else if self.is_writable { } else if self.is_writable {
return Err(InstructionError::ExternalAccountDataModified); return Err(InstructionError::ExternalAccountDataModified);
@ -121,7 +126,7 @@ impl PreAccount {
} }
// executable is one-way (false->true) and only the account owner may set it. // executable is one-way (false->true) and only the account owner may set it.
if self.executable != post.executable { if self.is_executable != post.executable {
if !rent_collector if !rent_collector
.rent .rent
.is_exempt(post.lamports, post.data.len()) .is_exempt(post.lamports, post.data.len())
@ -129,7 +134,7 @@ impl PreAccount {
return Err(InstructionError::ExecutableAccountNotRentExempt); return Err(InstructionError::ExecutableAccountNotRentExempt);
} }
if !self.is_writable // line coverage used to get branch coverage if !self.is_writable // line coverage used to get branch coverage
|| self.executable // line coverage used to get branch coverage || self.is_executable // line coverage used to get branch coverage
|| *program_id != self.owner || *program_id != self.owner
{ {
return Err(InstructionError::ExecutableModified); return Err(InstructionError::ExecutableModified);
@ -250,7 +255,7 @@ impl MessageProcessor {
let mut work = |_unique_index: usize, account_index: usize| { let mut work = |_unique_index: usize, account_index: usize| {
let is_writable = message.is_writable(account_index); let is_writable = message.is_writable(account_index);
let account = accounts[account_index].borrow(); let account = accounts[account_index].borrow();
pre_accounts.push(PreAccount::new(&account, is_writable, program_id)); pre_accounts.push(PreAccount::new(&account, program_id, is_writable));
Ok(()) Ok(())
}; };
let _ = instruction.visit_each_account(&mut work); let _ = instruction.visit_each_account(&mut work);
@ -420,90 +425,119 @@ mod tests {
); );
} }
#[test] struct Change {
fn test_verify_account_changes_owner() { // key: Pubkey,
fn change_owner( program_id: Pubkey,
ix: &Pubkey, rent_collector: RentCollector,
pre: &Pubkey, pre: PreAccount,
post: &Pubkey, post: Account,
is_writable: bool, }
) -> Result<(), InstructionError> { impl Change {
PreAccount::new(&Account::new(0, 0, pre), is_writable, ix).verify( pub fn new(owner: &Pubkey, program_id: &Pubkey) -> Self {
ix, Self {
&RentCollector::default(), // key: Pubkey::new_rand(),
&Account::new(0, 0, post), program_id: *program_id,
) rent_collector: RentCollector::default(),
pre: PreAccount::new(
&Account {
owner: *owner,
lamports: std::u64::MAX,
data: vec![],
..Account::default()
},
&Pubkey::new_rand(),
true,
),
post: Account {
owner: *owner,
lamports: std::u64::MAX,
..Account::default()
},
}
}
pub fn read_only(mut self) -> Self {
self.pre.is_writable = false;
self
}
pub fn executable(mut self, pre: bool, post: bool) -> Self {
self.pre.is_executable = pre;
self.post.executable = post;
self
}
pub fn lamports(mut self, pre: u64, post: u64) -> Self {
self.pre.lamports = pre;
self.post.lamports = post;
self
}
pub fn owner(mut self, post: &Pubkey) -> Self {
self.post.owner = *post;
self
}
pub fn data(mut self, pre: Vec<u8>, post: Vec<u8>) -> Self {
self.pre.data_len = pre.len();
self.pre.data = Some(pre);
self.post.data = post;
self
}
pub fn rent_epoch(mut self, pre: u64, post: u64) -> Self {
self.pre.rent_epoch = pre;
self.post.rent_epoch = post;
self
}
pub fn verify(&self) -> Result<(), InstructionError> {
self.pre
.verify(&self.program_id, &self.rent_collector, &self.post)
}
} }
#[test]
fn test_verify_account_changes_owner() {
let system_program_id = system_program::id(); let system_program_id = system_program::id();
let alice_program_id = Pubkey::new_rand(); let alice_program_id = Pubkey::new_rand();
let mallory_program_id = Pubkey::new_rand(); let mallory_program_id = Pubkey::new_rand();
assert_eq!( assert_eq!(
change_owner( Change::new(&system_program_id, &system_program_id)
&system_program_id, .owner(&alice_program_id)
&system_program_id, .verify(),
&alice_program_id,
true
),
Ok(()), Ok(()),
"system program should be able to change the account owner" "system program should be able to change the account owner"
); );
assert_eq!( assert_eq!(
change_owner( Change::new(&system_program_id, &system_program_id)
&system_program_id, .owner(&alice_program_id)
&system_program_id, .read_only()
&alice_program_id, .verify(),
false
),
Err(InstructionError::ModifiedProgramId), Err(InstructionError::ModifiedProgramId),
"system program should not be able to change the account owner of a read-only account" "system program should not be able to change the account owner of a read-only account"
); );
assert_eq!( assert_eq!(
change_owner( Change::new(&mallory_program_id, &system_program_id)
&system_program_id, .owner(&alice_program_id)
&mallory_program_id, .verify(),
&alice_program_id,
true
),
Err(InstructionError::ModifiedProgramId), Err(InstructionError::ModifiedProgramId),
"system program should not be able to change the account owner of a non-system account" "system program should not be able to change the account owner of a non-system account"
); );
assert_eq!( assert_eq!(
change_owner( Change::new(&mallory_program_id, &mallory_program_id)
&mallory_program_id, .owner(&alice_program_id)
&mallory_program_id, .verify(),
&alice_program_id,
true
),
Ok(()), Ok(()),
"mallory should be able to change the account owner, if she leaves clear data" "mallory should be able to change the account owner, if she leaves clear data"
); );
assert_eq!( assert_eq!(
PreAccount::new( Change::new(&mallory_program_id, &mallory_program_id)
&Account::new_data(0, &[42], &mallory_program_id).unwrap(), .owner(&alice_program_id)
true, .data(vec![42], vec![0])
&mallory_program_id, .verify(),
)
.verify(
&mallory_program_id,
&RentCollector::default(),
&Account::new_data(0, &[0], &alice_program_id).unwrap(),
),
Ok(()), Ok(()),
"mallory should be able to change the account owner, if she leaves clear data" "mallory should be able to change the account owner, if she leaves clear data"
); );
assert_eq!( assert_eq!(
PreAccount::new( Change::new(&mallory_program_id, &mallory_program_id)
&Account::new_data(0, &[42], &mallory_program_id).unwrap(), .owner(&alice_program_id)
true, .data(vec![42], vec![42])
&mallory_program_id, .verify(),
)
.verify(
&mallory_program_id,
&RentCollector::default(),
&Account::new_data(0, &[42], &alice_program_id).unwrap(),
),
Err(InstructionError::ModifiedProgramId), Err(InstructionError::ModifiedProgramId),
"mallory should not be able to inject data into the alice program" "mallory should not be able to inject data into the alice program"
); );
@ -515,58 +549,6 @@ mod tests {
let mallory_program_id = Pubkey::new_rand(); let mallory_program_id = Pubkey::new_rand();
let system_program_id = system_program::id(); let system_program_id = system_program::id();
struct Change {
pre: PreAccount,
post: Account,
program_id: Pubkey,
}
impl Change {
pub fn new(owner: &Pubkey, program_id: &Pubkey) -> Self {
Self {
pre: PreAccount::new(
&Account {
owner: *owner,
lamports: std::u64::MAX,
data: vec![],
..Account::default()
},
true,
&Pubkey::new_rand(), // Force some data, ignored if not needed
),
post: Account {
owner: *owner,
lamports: std::u64::MAX,
..Account::default()
},
program_id: *program_id,
}
}
pub fn read_only(mut self) -> Self {
self.pre.is_writable = false;
self
}
pub fn executable(mut self, pre: bool, post: bool) -> Self {
self.pre.executable = pre;
self.post.executable = post;
self
}
pub fn lamports(mut self, pre: u64, post: u64) -> Self {
self.pre.lamports = pre;
self.post.lamports = post;
self
}
pub fn data(mut self, pre: Vec<u8>, post: Vec<u8>) -> Self {
self.pre.data_len = pre.len();
self.pre.data = Some(pre);
self.post.data = post;
self
}
pub fn verify(self) -> Result<(), InstructionError> {
self.pre
.verify(&self.program_id, &RentCollector::default(), &self.post)
}
}
assert_eq!( assert_eq!(
Change::new(&owner, &system_program_id) Change::new(&owner, &system_program_id)
.executable(false, true) .executable(false, true)
@ -670,31 +652,19 @@ mod tests {
#[test] #[test]
fn test_verify_account_changes_data_len() { fn test_verify_account_changes_data_len() {
assert_eq!(
PreAccount::new(
&Account::new_data(0, &[0], &system_program::id()).unwrap(),
true,
&system_program::id()
)
.verify(
&system_program::id(),
&RentCollector::default(),
&Account::new_data(0, &[0, 0], &system_program::id()).unwrap()
),
Ok(()),
"system program should be able to change the data len"
);
let alice_program_id = Pubkey::new_rand(); let alice_program_id = Pubkey::new_rand();
assert_eq!( assert_eq!(
PreAccount::new( Change::new(&system_program::id(), &system_program::id())
&Account::new_data(0, &[0], &alice_program_id).unwrap(), .data(vec![0], vec![0, 0])
true, .verify(),
&system_program::id(), Ok(()),
).verify( "system program should be able to change the data len"
&system_program::id(), &RentCollector::default(), );
&Account::new_data(0, &[0, 0], &alice_program_id).unwrap(), assert_eq!(
), Change::new(&alice_program_id, &system_program::id())
.data(vec![0], vec![0,0])
.verify(),
Err(InstructionError::AccountDataSizeChanged), Err(InstructionError::AccountDataSizeChanged),
"system program should not be able to change the data length of accounts it does not own" "system program should not be able to change the data length of accounts it does not own"
); );
@ -703,32 +673,27 @@ mod tests {
#[test] #[test]
fn test_verify_account_changes_data() { fn test_verify_account_changes_data() {
let alice_program_id = Pubkey::new_rand(); let alice_program_id = Pubkey::new_rand();
let change_data =
|program_id: &Pubkey, is_writable: bool| -> Result<(), InstructionError> {
let pre = PreAccount::new(
&Account::new_data(0, &[0], &alice_program_id).unwrap(),
is_writable,
&program_id,
);
let post = Account::new_data(0, &[42], &alice_program_id).unwrap();
pre.verify(&program_id, &RentCollector::default(), &post)
};
let mallory_program_id = Pubkey::new_rand(); let mallory_program_id = Pubkey::new_rand();
assert_eq!( assert_eq!(
change_data(&alice_program_id, true), Change::new(&alice_program_id, &alice_program_id)
.data(vec![0], vec![42])
.verify(),
Ok(()), Ok(()),
"alice program should be able to change the data" "alice program should be able to change the data"
); );
assert_eq!( assert_eq!(
change_data(&mallory_program_id, true), Change::new(&mallory_program_id, &alice_program_id)
.data(vec![0], vec![42])
.verify(),
Err(InstructionError::ExternalAccountDataModified), Err(InstructionError::ExternalAccountDataModified),
"non-owner mallory should not be able to change the account data" "non-owner mallory should not be able to change the account data"
); );
assert_eq!( assert_eq!(
change_data(&alice_program_id, false), Change::new(&alice_program_id, &alice_program_id)
.data(vec![0], vec![42])
.read_only()
.verify(),
Err(InstructionError::ReadonlyDataModified), Err(InstructionError::ReadonlyDataModified),
"alice isn't allowed to touch a CO account" "alice isn't allowed to touch a CO account"
); );
@ -737,23 +702,16 @@ mod tests {
#[test] #[test]
fn test_verify_account_changes_rent_epoch() { fn test_verify_account_changes_rent_epoch() {
let alice_program_id = Pubkey::new_rand(); let alice_program_id = Pubkey::new_rand();
let rent_collector = RentCollector::default();
let pre = PreAccount::new(
&Account::new(0, 0, &alice_program_id),
false,
&system_program::id(),
);
let mut post = Account::new(0, 0, &alice_program_id);
assert_eq!( assert_eq!(
pre.verify(&system_program::id(), &rent_collector, &post), Change::new(&alice_program_id, &system_program::id()).verify(),
Ok(()), Ok(()),
"nothing changed!" "nothing changed!"
); );
post.rent_epoch += 1;
assert_eq!( assert_eq!(
pre.verify(&system_program::id(), &rent_collector, &post), Change::new(&alice_program_id, &system_program::id())
.rent_epoch(0, 1)
.verify(),
Err(InstructionError::RentEpochModified), Err(InstructionError::RentEpochModified),
"no one touches rent_epoch" "no one touches rent_epoch"
); );
@ -763,16 +721,14 @@ mod tests {
fn test_verify_account_changes_deduct_lamports_and_reassign_account() { fn test_verify_account_changes_deduct_lamports_and_reassign_account() {
let alice_program_id = Pubkey::new_rand(); let alice_program_id = Pubkey::new_rand();
let bob_program_id = Pubkey::new_rand(); let bob_program_id = Pubkey::new_rand();
let pre = PreAccount::new(
&Account::new_data(42, &[42], &alice_program_id).unwrap(),
true,
&alice_program_id,
);
let post = Account::new_data(1, &[0], &bob_program_id).unwrap();
// positive test of this capability // positive test of this capability
assert_eq!( assert_eq!(
pre.verify(&alice_program_id, &RentCollector::default(), &post), Change::new(&alice_program_id, &alice_program_id)
.owner(&bob_program_id)
.lamports(42, 1)
.data(vec![42], vec![0])
.verify(),
Ok(()), Ok(()),
"alice should be able to deduct lamports and give the account to bob if the data is zeroed", "alice should be able to deduct lamports and give the account to bob if the data is zeroed",
); );
@ -781,52 +737,36 @@ mod tests {
#[test] #[test]
fn test_verify_account_changes_lamports() { fn test_verify_account_changes_lamports() {
let alice_program_id = Pubkey::new_rand(); let alice_program_id = Pubkey::new_rand();
let rent_collector = RentCollector::default();
let pre = PreAccount::new(
&Account::new(42, 0, &alice_program_id),
false,
&system_program::id(),
);
let post = Account::new(0, 0, &alice_program_id);
assert_eq!( assert_eq!(
pre.verify(&system_program::id(), &rent_collector, &post), Change::new(&alice_program_id, &system_program::id())
.lamports(42, 0)
.read_only()
.verify(),
Err(InstructionError::ExternalAccountLamportSpend), Err(InstructionError::ExternalAccountLamportSpend),
"debit should fail, even if system program" "debit should fail, even if system program"
); );
let pre = PreAccount::new(
&Account::new(42, 0, &alice_program_id),
false,
&alice_program_id,
);
assert_eq!( assert_eq!(
pre.verify(&alice_program_id, &rent_collector, &post), Change::new(&alice_program_id, &alice_program_id)
.lamports(42, 0)
.read_only()
.verify(),
Err(InstructionError::ReadonlyLamportChange), Err(InstructionError::ReadonlyLamportChange),
"debit should fail, even if owning program" "debit should fail, even if owning program"
); );
let pre = PreAccount::new(
&Account::new(42, 0, &alice_program_id),
true,
&system_program::id(),
);
let post = Account::new(0, 0, &system_program::id());
assert_eq!( assert_eq!(
pre.verify(&system_program::id(), &rent_collector, &post), Change::new(&alice_program_id, &system_program::id())
.lamports(42, 0)
.owner(&system_program::id())
.verify(),
Err(InstructionError::ModifiedProgramId), Err(InstructionError::ModifiedProgramId),
"system program can't debit the account unless it was the pre.owner" "system program can't debit the account unless it was the pre.owner"
); );
let pre = PreAccount::new(
&Account::new(42, 0, &system_program::id()),
true,
&system_program::id(),
);
let post = Account::new(0, 0, &alice_program_id);
assert_eq!( assert_eq!(
pre.verify(&system_program::id(), &rent_collector, &post), Change::new(&system_program::id(), &system_program::id())
.lamports(42, 0)
.owner(&alice_program_id)
.verify(),
Ok(()), Ok(()),
"system can spend (and change owner)" "system can spend (and change owner)"
); );
@ -834,36 +774,26 @@ mod tests {
#[test] #[test]
fn test_verify_account_changes_data_size_changed() { fn test_verify_account_changes_data_size_changed() {
let rent_collector = RentCollector::default();
let alice_program_id = Pubkey::new_rand(); let alice_program_id = Pubkey::new_rand();
let pre = PreAccount::new(
&Account::new_data(42, &[0], &alice_program_id).unwrap(),
true,
&system_program::id(),
);
let post = Account::new_data(42, &[0, 0], &alice_program_id).unwrap();
assert_eq!( assert_eq!(
pre.verify(&system_program::id(), &rent_collector, &post), Change::new(&alice_program_id, &system_program::id())
.data(vec![0], vec![0, 0])
.verify(),
Err(InstructionError::AccountDataSizeChanged), Err(InstructionError::AccountDataSizeChanged),
"system program should not be able to change another program's account data size" "system program should not be able to change another program's account data size"
); );
let pre = PreAccount::new(
&Account::new_data(42, &[0], &alice_program_id).unwrap(),
true,
&alice_program_id,
);
assert_eq!( assert_eq!(
pre.verify(&alice_program_id, &rent_collector, &post), Change::new(&alice_program_id, &alice_program_id)
.data(vec![0], vec![0, 0])
.verify(),
Err(InstructionError::AccountDataSizeChanged), Err(InstructionError::AccountDataSizeChanged),
"non-system programs cannot change their data size" "non-system programs cannot change their data size"
); );
let pre = PreAccount::new(
&Account::new_data(42, &[0], &system_program::id()).unwrap(),
true,
&system_program::id(),
);
assert_eq!( assert_eq!(
pre.verify(&system_program::id(), &rent_collector, &post), Change::new(&system_program::id(), &system_program::id())
.data(vec![0], vec![0, 0])
.verify(),
Ok(()), Ok(()),
"system program should be able to change acount data size" "system program should be able to change acount data size"
); );