Allow the same account to be passed multiple times to a single instruction (#7795)
This commit is contained in:
@ -21,6 +21,14 @@ fn deposit_many(bank: &Bank, pubkeys: &mut Vec<Pubkey>, num: usize) {
|
||||
}
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_has_duplicates(bencher: &mut Bencher) {
|
||||
bencher.iter(|| {
|
||||
let data = test::black_box([1, 2, 3]);
|
||||
assert!(!Accounts::has_duplicates(&data));
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn test_accounts_create(bencher: &mut Bencher) {
|
||||
let (genesis_config, _) = create_genesis_config(10_000);
|
||||
|
@ -7,14 +7,6 @@ use solana_runtime::message_processor::*;
|
||||
use solana_sdk::{account::Account, pubkey::Pubkey};
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn bench_has_duplicates(bencher: &mut Bencher) {
|
||||
bencher.iter(|| {
|
||||
let data = test::black_box([1, 2, 3]);
|
||||
assert!(!has_duplicates(&data));
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_verify_account_changes_data(bencher: &mut Bencher) {
|
||||
solana_logger::setup();
|
||||
|
@ -5,7 +5,6 @@ use crate::accounts_index::AccountsIndex;
|
||||
use crate::append_vec::StoredAccount;
|
||||
use crate::bank::{HashAgeKind, TransactionProcessResult};
|
||||
use crate::blockhash_queue::BlockhashQueue;
|
||||
use crate::message_processor::has_duplicates;
|
||||
use crate::nonce_utils::prepare_if_nonce_account;
|
||||
use crate::rent_collector::RentCollector;
|
||||
use crate::system_instruction_processor::{get_system_account_kind, SystemAccountKind};
|
||||
@ -87,6 +86,19 @@ impl Accounts {
|
||||
.accounts_from_stream(stream, local_paths, append_vecs_path)
|
||||
}
|
||||
|
||||
/// Return true if the slice has any duplicate elements
|
||||
pub fn has_duplicates<T: PartialEq>(xs: &[T]) -> bool {
|
||||
// Note: This is an O(n^2) algorithm, but requires no heap allocations. The benchmark
|
||||
// `bench_has_duplicates` in benches/message_processor.rs shows that this implementation is
|
||||
// ~50 times faster than using HashSet for very short slices.
|
||||
for i in 1..xs.len() {
|
||||
if xs[i..].contains(&xs[i - 1]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn load_tx_accounts(
|
||||
&self,
|
||||
storage: &AccountStorage,
|
||||
@ -103,7 +115,7 @@ impl Accounts {
|
||||
Err(TransactionError::MissingSignatureForFee)
|
||||
} else {
|
||||
// Check for unique account keys
|
||||
if has_duplicates(&message.account_keys) {
|
||||
if Self::has_duplicates(&message.account_keys) {
|
||||
error_counters.account_loaded_twice += 1;
|
||||
return Err(TransactionError::AccountLoadedTwice);
|
||||
}
|
||||
@ -1634,4 +1646,10 @@ mod tests {
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_duplicates() {
|
||||
assert!(!Accounts::has_duplicates(&[1, 2]));
|
||||
assert!(Accounts::has_duplicates(&[1, 2, 1]));
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
//! on behalf of the caller, and a low-level API for when they have
|
||||
//! already been signed and verified.
|
||||
use crate::{
|
||||
accounts::{Accounts, TransactionLoadResult},
|
||||
accounts::{Accounts, TransactionAccounts, TransactionLoadResult, TransactionLoaders},
|
||||
accounts_db::{AccountStorageEntry, AccountsDBSerialize, AppendVecId, ErrorCounters},
|
||||
blockhash_queue::BlockhashQueue,
|
||||
message_processor::{MessageProcessor, ProcessInstruction},
|
||||
@ -51,9 +51,11 @@ use solana_sdk::{
|
||||
use solana_stake_program::stake_state::Delegation;
|
||||
use solana_vote_program::vote_state::VoteState;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
io::{BufReader, Cursor, Error as IOError, Read},
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
sync::atomic::{AtomicBool, AtomicU64, Ordering},
|
||||
sync::{Arc, RwLock, RwLockReadGuard},
|
||||
};
|
||||
@ -64,6 +66,8 @@ pub const MAX_SNAPSHOT_DATA_FILE_SIZE: u64 = 32 * 1024 * 1024 * 1024; // 32 GiB
|
||||
pub const MAX_LEADER_SCHEDULE_STAKES: Epoch = 5;
|
||||
|
||||
type BankStatusCache = StatusCache<Result<()>>;
|
||||
type TransactionAccountRefCells = Vec<Rc<RefCell<Account>>>;
|
||||
type TransactionLoaderRefCells = Vec<Vec<(Pubkey, RefCell<Account>)>>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BankRc {
|
||||
@ -1167,6 +1171,48 @@ impl Bank {
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts Accounts into RefCell<Account>, this involves moving
|
||||
/// ownership by draining the source
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn into_refcells(
|
||||
accounts: &mut TransactionAccounts,
|
||||
loaders: &mut TransactionLoaders,
|
||||
) -> (TransactionAccountRefCells, TransactionLoaderRefCells) {
|
||||
let account_refcells: Vec<_> = accounts
|
||||
.drain(..)
|
||||
.map(|account| Rc::new(RefCell::new(account)))
|
||||
.collect();
|
||||
let loader_refcells: Vec<Vec<_>> = loaders
|
||||
.iter_mut()
|
||||
.map(|v| {
|
||||
v.drain(..)
|
||||
.map(|(pubkey, account)| (pubkey, RefCell::new(account)))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
(account_refcells, loader_refcells)
|
||||
}
|
||||
|
||||
/// Converts back from RefCell<Account> to Account, this involves moving
|
||||
/// ownership by draining the sources
|
||||
fn from_refcells(
|
||||
accounts: &mut TransactionAccounts,
|
||||
loaders: &mut TransactionLoaders,
|
||||
mut account_refcells: TransactionAccountRefCells,
|
||||
loader_refcells: TransactionLoaderRefCells,
|
||||
) {
|
||||
account_refcells.drain(..).for_each(|account_refcell| {
|
||||
accounts.push(Rc::try_unwrap(account_refcell).unwrap().into_inner())
|
||||
});
|
||||
loaders
|
||||
.iter_mut()
|
||||
.zip(loader_refcells)
|
||||
.for_each(|(ls, mut lrcs)| {
|
||||
lrcs.drain(..)
|
||||
.for_each(|(pubkey, lrc)| ls.push((pubkey, lrc.into_inner())))
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn load_and_execute_transactions(
|
||||
&self,
|
||||
@ -1222,9 +1268,18 @@ impl Bank {
|
||||
(Err(e), hash_age_kind) => (Err(e.clone()), hash_age_kind.clone()),
|
||||
(Ok((accounts, loaders, _rents)), hash_age_kind) => {
|
||||
signature_count += u64::from(tx.message().header.num_required_signatures);
|
||||
let process_result =
|
||||
self.message_processor
|
||||
.process_message(tx.message(), loaders, accounts);
|
||||
|
||||
let (mut account_refcells, mut loader_refcells) =
|
||||
Self::into_refcells(accounts, loaders);
|
||||
|
||||
let process_result = self.message_processor.process_message(
|
||||
tx.message(),
|
||||
&mut loader_refcells,
|
||||
&mut account_refcells,
|
||||
);
|
||||
|
||||
Self::from_refcells(accounts, loaders, account_refcells, loader_refcells);
|
||||
|
||||
if let Err(TransactionError::InstructionError(_, _)) = &process_result {
|
||||
error_counters.instruction_error += 1;
|
||||
}
|
||||
@ -2285,8 +2340,8 @@ mod tests {
|
||||
if let Ok(instruction) = bincode::deserialize(data) {
|
||||
match instruction {
|
||||
MockInstruction::Deduction => {
|
||||
keyed_accounts[1].account.lamports += 1;
|
||||
keyed_accounts[2].account.lamports -= 1;
|
||||
keyed_accounts[1].account.borrow_mut().lamports += 1;
|
||||
keyed_accounts[2].account.borrow_mut().lamports -= 1;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -2873,8 +2928,8 @@ mod tests {
|
||||
|
||||
// set up stakes, vote, and storage accounts
|
||||
bank.store_account(&stake.0, &stake.1);
|
||||
bank.store_account(&validator_id, &validator_account);
|
||||
bank.store_account(&archiver_id, &archiver_account);
|
||||
bank.store_account(&validator_id, &validator_account.borrow());
|
||||
bank.store_account(&archiver_id, &archiver_account.borrow());
|
||||
|
||||
// generate some rewards
|
||||
let mut vote_state = VoteState::from(&vote_account).unwrap();
|
||||
@ -5136,4 +5191,56 @@ mod tests {
|
||||
assert_eq!(transaction_balances_set.pre_balances[2], vec![9, 0, 1]);
|
||||
assert_eq!(transaction_balances_set.post_balances[2], vec![8, 0, 1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transaction_with_duplicate_accounts_in_instruction() {
|
||||
let (genesis_config, mint_keypair) = create_genesis_config(500);
|
||||
let mut bank = Bank::new(&genesis_config);
|
||||
|
||||
fn mock_process_instruction(
|
||||
_program_id: &Pubkey,
|
||||
keyed_accounts: &mut [KeyedAccount],
|
||||
data: &[u8],
|
||||
) -> result::Result<(), InstructionError> {
|
||||
let lamports = data[0] as u64;
|
||||
{
|
||||
let mut to_account = keyed_accounts[1].try_account_ref_mut()?;
|
||||
let mut dup_account = keyed_accounts[2].try_account_ref_mut()?;
|
||||
dup_account.lamports -= lamports;
|
||||
to_account.lamports += lamports;
|
||||
}
|
||||
keyed_accounts[0].try_account_ref_mut()?.lamports -= lamports;
|
||||
keyed_accounts[1].try_account_ref_mut()?.lamports += lamports;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let mock_program_id = Pubkey::new(&[2u8; 32]);
|
||||
bank.add_instruction_processor(mock_program_id, mock_process_instruction);
|
||||
|
||||
let from_pubkey = Pubkey::new_rand();
|
||||
let to_pubkey = Pubkey::new_rand();
|
||||
let dup_pubkey = from_pubkey.clone();
|
||||
let from_account = Account::new(100, 1, &mock_program_id);
|
||||
let to_account = Account::new(0, 1, &mock_program_id);
|
||||
bank.store_account(&from_pubkey, &from_account);
|
||||
bank.store_account(&to_pubkey, &to_account);
|
||||
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(from_pubkey, false),
|
||||
AccountMeta::new(to_pubkey, false),
|
||||
AccountMeta::new(dup_pubkey, false),
|
||||
];
|
||||
let instruction = Instruction::new(mock_program_id, &10, account_metas);
|
||||
let tx = Transaction::new_signed_with_payer(
|
||||
vec![instruction],
|
||||
Some(&mint_keypair.pubkey()),
|
||||
&[&mint_keypair],
|
||||
bank.last_blockhash(),
|
||||
);
|
||||
|
||||
let result = bank.process_transaction(&tx);
|
||||
assert_eq!(result, Ok(()));
|
||||
assert_eq!(bank.get_balance(&from_pubkey), 80);
|
||||
assert_eq!(bank.get_balance(&to_pubkey), 20);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,9 @@ use solana_sdk::message::Message;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::system_program;
|
||||
use solana_sdk::transaction::TransactionError;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use std::sync::RwLock;
|
||||
|
||||
#[cfg(unix)]
|
||||
@ -17,44 +19,6 @@ use libloading::os::unix::*;
|
||||
#[cfg(windows)]
|
||||
use libloading::os::windows::*;
|
||||
|
||||
/// Return true if the slice has any duplicate elements
|
||||
pub fn has_duplicates<T: PartialEq>(xs: &[T]) -> bool {
|
||||
// Note: This is an O(n^2) algorithm, but requires no heap allocations. The benchmark
|
||||
// `bench_has_duplicates` in benches/message_processor.rs shows that this implementation is
|
||||
// ~50 times faster than using HashSet for very short slices.
|
||||
for i in 1..xs.len() {
|
||||
if xs[i..].contains(&xs[i - 1]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Get mut references to a subset of elements.
|
||||
fn get_subset_unchecked_mut<'a, T>(
|
||||
xs: &'a mut [T],
|
||||
indexes: &[u8],
|
||||
) -> Result<Vec<&'a mut T>, InstructionError> {
|
||||
// Since the compiler doesn't know the indexes are unique, dereferencing
|
||||
// multiple mut elements is assumed to be unsafe. If, however, all
|
||||
// indexes are unique, it's perfectly safe. The returned elements will share
|
||||
// the liftime of the input slice.
|
||||
|
||||
// Make certain there are no duplicate indexes. If there are, return an error
|
||||
// because we can't return multiple mut references to the same element.
|
||||
if has_duplicates(indexes) {
|
||||
return Err(InstructionError::DuplicateAccountIndex);
|
||||
}
|
||||
|
||||
Ok(indexes
|
||||
.iter()
|
||||
.map(|i| {
|
||||
let ptr = &mut xs[*i as usize] as *mut T;
|
||||
unsafe { &mut *ptr }
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
// The relevant state of an account before an Instruction executes, used
|
||||
// to verify account integrity after the Instruction completes
|
||||
pub struct PreInstructionAccount {
|
||||
@ -204,8 +168,8 @@ impl MessageProcessor {
|
||||
&self,
|
||||
message: &Message,
|
||||
instruction: &CompiledInstruction,
|
||||
executable_accounts: &mut [(Pubkey, Account)],
|
||||
program_accounts: &mut [&mut Account],
|
||||
executable_accounts: &mut [(Pubkey, RefCell<Account>)],
|
||||
program_accounts: &mut [Rc<RefCell<Account>>],
|
||||
) -> Result<(), InstructionError> {
|
||||
let program_id = instruction.program_id(&message.account_keys);
|
||||
let mut keyed_accounts = create_keyed_readonly_accounts(executable_accounts);
|
||||
@ -234,7 +198,7 @@ impl MessageProcessor {
|
||||
keyed_accounts.append(&mut keyed_accounts2);
|
||||
|
||||
assert!(
|
||||
keyed_accounts[0].account.executable,
|
||||
keyed_accounts[0].try_account_ref()?.executable,
|
||||
"loader not executable"
|
||||
);
|
||||
|
||||
@ -257,8 +221,38 @@ impl MessageProcessor {
|
||||
)
|
||||
}
|
||||
|
||||
fn sum_account_lamports(accounts: &mut [&mut Account]) -> u128 {
|
||||
accounts.iter().map(|a| u128::from(a.lamports)).sum()
|
||||
pub fn verify_account_references(
|
||||
executable_accounts: &mut [(Pubkey, RefCell<Account>)],
|
||||
program_accounts: &mut [Rc<RefCell<Account>>],
|
||||
) -> Result<(), InstructionError> {
|
||||
for account in program_accounts.iter() {
|
||||
account
|
||||
.try_borrow_mut()
|
||||
.map_err(|_| InstructionError::AccountBorrowOutstanding)?;
|
||||
}
|
||||
for (_, account) in executable_accounts.iter() {
|
||||
account
|
||||
.try_borrow_mut()
|
||||
.map_err(|_| InstructionError::AccountBorrowOutstanding)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn sum_account_lamports(accounts: &[Rc<RefCell<Account>>]) -> u128 {
|
||||
// Note: This is an O(n^2) algorithm,
|
||||
// but performed on a very small slice and requires no heap allocations
|
||||
accounts
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, a)| {
|
||||
for account in accounts.iter().skip(i + 1) {
|
||||
if Rc::ptr_eq(a, account) {
|
||||
return 0; // don't double count duplicates
|
||||
}
|
||||
}
|
||||
u128::from(a.borrow().lamports)
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
/// Execute an instruction
|
||||
@ -269,8 +263,8 @@ impl MessageProcessor {
|
||||
&self,
|
||||
message: &Message,
|
||||
instruction: &CompiledInstruction,
|
||||
executable_accounts: &mut [(Pubkey, Account)],
|
||||
program_accounts: &mut [&mut Account],
|
||||
executable_accounts: &mut [(Pubkey, RefCell<Account>)],
|
||||
program_accounts: &mut [Rc<RefCell<Account>>],
|
||||
) -> Result<(), InstructionError> {
|
||||
assert_eq!(instruction.accounts.len(), program_accounts.len());
|
||||
let program_id = instruction.program_id(&message.account_keys);
|
||||
@ -280,8 +274,9 @@ impl MessageProcessor {
|
||||
.enumerate()
|
||||
.map(|(i, account)| {
|
||||
let is_writable = message.is_writable(instruction.accounts[i] as usize);
|
||||
let account = account.borrow();
|
||||
PreInstructionAccount::new(
|
||||
account,
|
||||
&account,
|
||||
is_writable,
|
||||
need_account_data_checked(&account.owner, program_id, is_writable),
|
||||
)
|
||||
@ -292,11 +287,16 @@ impl MessageProcessor {
|
||||
|
||||
self.process_instruction(message, instruction, executable_accounts, program_accounts)?;
|
||||
|
||||
// Verify all accounts have zero outstanding refs
|
||||
Self::verify_account_references(executable_accounts, program_accounts)?;
|
||||
// Verify the instruction
|
||||
for (pre_account, post_account) in pre_accounts.iter().zip(program_accounts.iter()) {
|
||||
verify_account_changes(&program_id, pre_account, post_account)?;
|
||||
let post_account = post_account
|
||||
.try_borrow()
|
||||
.map_err(|_| InstructionError::AccountBorrowFailed)?;
|
||||
verify_account_changes(&program_id, pre_account, &post_account)?;
|
||||
}
|
||||
// The total sum of all the lamports in all the accounts cannot change.
|
||||
// Verify total sum of all the lamports did not change
|
||||
let post_total = Self::sum_account_lamports(program_accounts);
|
||||
if pre_total != post_total {
|
||||
return Err(InstructionError::UnbalancedInstruction);
|
||||
@ -310,19 +310,24 @@ impl MessageProcessor {
|
||||
pub fn process_message(
|
||||
&self,
|
||||
message: &Message,
|
||||
loaders: &mut [Vec<(Pubkey, Account)>],
|
||||
accounts: &mut [Account],
|
||||
loaders: &mut [Vec<(Pubkey, RefCell<Account>)>],
|
||||
accounts: &mut [Rc<RefCell<Account>>],
|
||||
) -> Result<(), TransactionError> {
|
||||
for (instruction_index, instruction) in message.instructions.iter().enumerate() {
|
||||
let executable_index = message
|
||||
.program_position(instruction.program_id_index as usize)
|
||||
.ok_or(TransactionError::InvalidAccountIndex)?;
|
||||
let executable_accounts = &mut loaders[executable_index];
|
||||
let mut program_accounts = get_subset_unchecked_mut(accounts, &instruction.accounts)
|
||||
.map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?;
|
||||
// TODO: `get_subset_unchecked_mut` panics on an index out of bounds if an executable
|
||||
|
||||
// TODO: panics on an index out of bounds if an executable
|
||||
// account is also included as a regular account for an instruction, because the
|
||||
// executable account is not passed in as part of the accounts slice
|
||||
let mut program_accounts: Vec<_> = instruction
|
||||
.accounts
|
||||
.iter()
|
||||
.map(|i| accounts[*i as usize].clone())
|
||||
.collect();
|
||||
|
||||
self.execute_instruction(
|
||||
message,
|
||||
instruction,
|
||||
@ -373,37 +378,72 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_duplicates() {
|
||||
assert!(!has_duplicates(&[1, 2]));
|
||||
assert!(has_duplicates(&[1, 2, 1]));
|
||||
fn test_verify_account_references() {
|
||||
let mut executable_accounts = vec![(Pubkey::new_rand(), RefCell::new(Account::default()))];
|
||||
let mut program_accounts = vec![Rc::new(RefCell::new(Account::default()))];
|
||||
|
||||
assert!(MessageProcessor::verify_account_references(
|
||||
&mut executable_accounts,
|
||||
&mut program_accounts,
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
let cloned = program_accounts[0].clone();
|
||||
let _borrowed = cloned.borrow();
|
||||
assert_eq!(
|
||||
MessageProcessor::verify_account_references(
|
||||
&mut executable_accounts,
|
||||
&mut program_accounts,
|
||||
),
|
||||
Err(InstructionError::AccountBorrowOutstanding)
|
||||
);
|
||||
|
||||
// TODO when the `&mut`s go away test outstanding executable_account refs
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_subset_unchecked_mut() {
|
||||
assert_eq!(
|
||||
get_subset_unchecked_mut(&mut [7, 8], &[0]).unwrap(),
|
||||
vec![&mut 7]
|
||||
);
|
||||
assert_eq!(
|
||||
get_subset_unchecked_mut(&mut [7, 8], &[0, 1]).unwrap(),
|
||||
vec![&mut 7, &mut 8]
|
||||
);
|
||||
}
|
||||
fn test_sum_account_lamports() {
|
||||
let owner_pubkey = Pubkey::new_rand();
|
||||
let account1 = Rc::new(RefCell::new(Account::new(1, 1, &owner_pubkey)));
|
||||
let account2 = Rc::new(RefCell::new(Account::new(2, 1, &owner_pubkey)));
|
||||
let account3 = Rc::new(RefCell::new(Account::new(3, 1, &owner_pubkey)));
|
||||
|
||||
#[test]
|
||||
fn test_get_subset_unchecked_mut_duplicate_index() {
|
||||
// This panics, because it assumes duplicate detection is done elsewhere.
|
||||
assert_eq!(0, MessageProcessor::sum_account_lamports(&mut vec![]));
|
||||
assert_eq!(
|
||||
get_subset_unchecked_mut(&mut [7, 8], &[0, 0]).unwrap_err(),
|
||||
InstructionError::DuplicateAccountIndex
|
||||
6,
|
||||
MessageProcessor::sum_account_lamports(&mut vec![
|
||||
account1.clone(),
|
||||
account2.clone(),
|
||||
account3.clone()
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
3,
|
||||
MessageProcessor::sum_account_lamports(&mut vec![
|
||||
account1.clone(),
|
||||
account2.clone(),
|
||||
account1.clone()
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
1,
|
||||
MessageProcessor::sum_account_lamports(&mut vec![
|
||||
account1.clone(),
|
||||
account1.clone(),
|
||||
account1.clone()
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
6,
|
||||
MessageProcessor::sum_account_lamports(&mut vec![
|
||||
account1.clone(),
|
||||
account2.clone(),
|
||||
account3.clone(),
|
||||
account1.clone(),
|
||||
account2.clone(),
|
||||
account3.clone(),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_get_subset_unchecked_mut_out_of_bounds() {
|
||||
// This panics, because it assumes bounds validation is done elsewhere.
|
||||
get_subset_unchecked_mut(&mut [7, 8], &[2]).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -772,13 +812,13 @@ mod tests {
|
||||
match instruction {
|
||||
MockSystemInstruction::Correct => Ok(()),
|
||||
MockSystemInstruction::AttemptCredit { lamports } => {
|
||||
keyed_accounts[0].account.lamports -= lamports;
|
||||
keyed_accounts[1].account.lamports += lamports;
|
||||
keyed_accounts[0].account.borrow_mut().lamports -= lamports;
|
||||
keyed_accounts[1].account.borrow_mut().lamports += lamports;
|
||||
Ok(())
|
||||
}
|
||||
// Change data in a read-only account
|
||||
MockSystemInstruction::AttemptDataChange { data } => {
|
||||
keyed_accounts[1].account.data = vec![data];
|
||||
keyed_accounts[1].account.borrow_mut().data = vec![data];
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -792,14 +832,14 @@ mod tests {
|
||||
message_processor
|
||||
.add_instruction_processor(mock_system_program_id, mock_system_process_instruction);
|
||||
|
||||
let mut accounts: Vec<Account> = Vec::new();
|
||||
let account = Account::new(100, 1, &mock_system_program_id);
|
||||
let mut accounts: Vec<Rc<RefCell<Account>>> = Vec::new();
|
||||
let account = Account::new_ref(100, 1, &mock_system_program_id);
|
||||
accounts.push(account);
|
||||
let account = Account::new(0, 1, &mock_system_program_id);
|
||||
let account = Account::new_ref(0, 1, &mock_system_program_id);
|
||||
accounts.push(account);
|
||||
|
||||
let mut loaders: Vec<Vec<(Pubkey, Account)>> = Vec::new();
|
||||
let account = create_loadable_account("mock_system_program");
|
||||
let mut loaders: Vec<Vec<(Pubkey, RefCell<Account>)>> = Vec::new();
|
||||
let account = RefCell::new(create_loadable_account("mock_system_program"));
|
||||
loaders.push(vec![(mock_system_program_id, account)]);
|
||||
|
||||
let from_pubkey = Pubkey::new_rand();
|
||||
@ -816,8 +856,8 @@ mod tests {
|
||||
|
||||
let result = message_processor.process_message(&message, &mut loaders, &mut accounts);
|
||||
assert_eq!(result, Ok(()));
|
||||
assert_eq!(accounts[0].lamports, 100);
|
||||
assert_eq!(accounts[1].lamports, 0);
|
||||
assert_eq!(accounts[0].borrow().lamports, 100);
|
||||
assert_eq!(accounts[1].borrow().lamports, 0);
|
||||
|
||||
let message = Message::new(vec![Instruction::new(
|
||||
mock_system_program_id,
|
||||
@ -849,4 +889,124 @@ mod tests {
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_message_duplicate_accounts() {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
enum MockSystemInstruction {
|
||||
BorrowFail,
|
||||
MultiBorrowMut,
|
||||
DoWork { lamports: u64, data: u8 },
|
||||
}
|
||||
|
||||
fn mock_system_process_instruction(
|
||||
_program_id: &Pubkey,
|
||||
keyed_accounts: &mut [KeyedAccount],
|
||||
data: &[u8],
|
||||
) -> Result<(), InstructionError> {
|
||||
if let Ok(instruction) = bincode::deserialize(data) {
|
||||
match instruction {
|
||||
MockSystemInstruction::BorrowFail => {
|
||||
let from_account = keyed_accounts[0].try_account_ref_mut()?;
|
||||
let dup_account = keyed_accounts[2].try_account_ref_mut()?;
|
||||
if from_account.lamports != dup_account.lamports {
|
||||
return Err(InstructionError::InvalidArgument);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
MockSystemInstruction::MultiBorrowMut => {
|
||||
let from_lamports = {
|
||||
let from_account = keyed_accounts[0].try_account_ref_mut()?;
|
||||
from_account.lamports
|
||||
};
|
||||
let dup_lamports = {
|
||||
let dup_account = keyed_accounts[2].try_account_ref_mut()?;
|
||||
dup_account.lamports
|
||||
};
|
||||
if from_lamports != dup_lamports {
|
||||
return Err(InstructionError::InvalidArgument);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
MockSystemInstruction::DoWork { lamports, data } => {
|
||||
{
|
||||
let mut to_account = keyed_accounts[1].try_account_ref_mut()?;
|
||||
let mut dup_account = keyed_accounts[2].try_account_ref_mut()?;
|
||||
dup_account.lamports -= lamports;
|
||||
to_account.lamports += lamports;
|
||||
dup_account.data = vec![data];
|
||||
}
|
||||
keyed_accounts[0].try_account_ref_mut()?.lamports -= lamports;
|
||||
keyed_accounts[1].try_account_ref_mut()?.lamports += lamports;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(InstructionError::InvalidInstructionData)
|
||||
}
|
||||
}
|
||||
|
||||
let mock_program_id = Pubkey::new(&[2u8; 32]);
|
||||
let mut message_processor = MessageProcessor::default();
|
||||
message_processor
|
||||
.add_instruction_processor(mock_program_id, mock_system_process_instruction);
|
||||
|
||||
let mut accounts: Vec<Rc<RefCell<Account>>> = Vec::new();
|
||||
let account = Account::new_ref(100, 1, &mock_program_id);
|
||||
accounts.push(account);
|
||||
let account = Account::new_ref(0, 1, &mock_program_id);
|
||||
accounts.push(account);
|
||||
|
||||
let mut loaders: Vec<Vec<(Pubkey, RefCell<Account>)>> = Vec::new();
|
||||
let account = RefCell::new(create_loadable_account("mock_system_program"));
|
||||
loaders.push(vec![(mock_program_id, account)]);
|
||||
|
||||
let from_pubkey = Pubkey::new_rand();
|
||||
let to_pubkey = Pubkey::new_rand();
|
||||
let dup_pubkey = from_pubkey.clone();
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(from_pubkey, true),
|
||||
AccountMeta::new(to_pubkey, false),
|
||||
AccountMeta::new(dup_pubkey, false),
|
||||
];
|
||||
|
||||
// Try to borrow mut the same account
|
||||
let message = Message::new(vec![Instruction::new(
|
||||
mock_program_id,
|
||||
&MockSystemInstruction::BorrowFail,
|
||||
account_metas.clone(),
|
||||
)]);
|
||||
let result = message_processor.process_message(&message, &mut loaders, &mut accounts);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(TransactionError::InstructionError(
|
||||
0,
|
||||
InstructionError::AccountBorrowFailed
|
||||
))
|
||||
);
|
||||
|
||||
// Try to borrow mut the same account in a safe way
|
||||
let message = Message::new(vec![Instruction::new(
|
||||
mock_program_id,
|
||||
&MockSystemInstruction::MultiBorrowMut,
|
||||
account_metas.clone(),
|
||||
)]);
|
||||
let result = message_processor.process_message(&message, &mut loaders, &mut accounts);
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
// Do work on the same account but at different location in keyed_accounts[]
|
||||
let message = Message::new(vec![Instruction::new(
|
||||
mock_program_id,
|
||||
&MockSystemInstruction::DoWork {
|
||||
lamports: 10,
|
||||
data: 42,
|
||||
},
|
||||
account_metas,
|
||||
)]);
|
||||
let result = message_processor.process_message(&message, &mut loaders, &mut accounts);
|
||||
assert_eq!(result, Ok(()));
|
||||
assert_eq!(accounts[0].borrow().lamports, 80);
|
||||
assert_eq!(accounts[1].borrow().lamports, 20);
|
||||
assert_eq!(accounts[0].borrow().data, vec![42]);
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ pub fn invoke_entrypoint(
|
||||
) -> Result<(), InstructionError> {
|
||||
// dispatch it
|
||||
let (names, params) = keyed_accounts.split_at_mut(1);
|
||||
let name_vec = &names[0].account.data;
|
||||
let name_vec = &names[0].try_account_ref()?.data;
|
||||
if let Some(entrypoint) = symbol_cache.read().unwrap().get(name_vec) {
|
||||
unsafe {
|
||||
return entrypoint(program_id, params, instruction_data);
|
||||
|
@ -196,14 +196,20 @@ mod tests {
|
||||
nonce_account
|
||||
.nonce_initialize(&authorized, &recent_blockhashes, &Rent::free())
|
||||
.unwrap();
|
||||
assert!(verify_nonce(&nonce_account.account, &recent_blockhashes[0]));
|
||||
assert!(verify_nonce(
|
||||
&nonce_account.account.borrow(),
|
||||
&recent_blockhashes[0]
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_nonce_bad_acc_state_fail() {
|
||||
with_test_keyed_account(42, true, |nonce_account| {
|
||||
assert!(!verify_nonce(&nonce_account.account, &Hash::default()));
|
||||
assert!(!verify_nonce(
|
||||
&nonce_account.account.borrow(),
|
||||
&Hash::default()
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
@ -221,7 +227,7 @@ mod tests {
|
||||
.nonce_initialize(&authorized, &recent_blockhashes, &Rent::free())
|
||||
.unwrap();
|
||||
assert!(!verify_nonce(
|
||||
&nonce_account.account,
|
||||
&nonce_account.account.borrow(),
|
||||
&recent_blockhashes[1]
|
||||
));
|
||||
});
|
||||
|
@ -91,7 +91,7 @@ pub(crate) mod tests {
|
||||
storage_instruction::{self, StorageAccountType},
|
||||
storage_processor,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::{cell::RefCell, rc::Rc, sync::Arc};
|
||||
|
||||
#[test]
|
||||
fn test_store_and_recover() {
|
||||
@ -149,25 +149,24 @@ pub(crate) mod tests {
|
||||
let ((validator_pubkey, validator_account), (archiver_pubkey, archiver_account)) =
|
||||
create_storage_accounts_with_credits(credits);
|
||||
|
||||
storage_accounts.store(&validator_pubkey, &validator_account);
|
||||
storage_accounts.store(&archiver_pubkey, &archiver_account);
|
||||
storage_accounts.store(&validator_pubkey, &validator_account.borrow());
|
||||
storage_accounts.store(&archiver_pubkey, &archiver_account.borrow());
|
||||
// check that 2x credits worth of points are available
|
||||
assert_eq!(storage_accounts.points(), credits * 2);
|
||||
|
||||
let ((validator_pubkey, validator_account), (archiver_pubkey, mut archiver_account)) =
|
||||
let ((validator_pubkey, validator_account), (archiver_pubkey, archiver_account)) =
|
||||
create_storage_accounts_with_credits(credits);
|
||||
|
||||
storage_accounts.store(&validator_pubkey, &validator_account);
|
||||
storage_accounts.store(&archiver_pubkey, &archiver_account);
|
||||
storage_accounts.store(&validator_pubkey, &validator_account.borrow());
|
||||
storage_accounts.store(&archiver_pubkey, &archiver_account.borrow());
|
||||
// check that 4x credits worth of points are available
|
||||
assert_eq!(storage_accounts.points(), credits * 2 * 2);
|
||||
|
||||
storage_accounts.store(&validator_pubkey, &validator_account);
|
||||
storage_accounts.store(&archiver_pubkey, &archiver_account);
|
||||
storage_accounts.store(&validator_pubkey, &validator_account.borrow());
|
||||
storage_accounts.store(&archiver_pubkey, &archiver_account.borrow());
|
||||
// check that storing again has no effect
|
||||
assert_eq!(storage_accounts.points(), credits * 2 * 2);
|
||||
|
||||
let storage_contract = &mut archiver_account.state().unwrap();
|
||||
let storage_contract = &mut archiver_account.borrow().state().unwrap();
|
||||
if let StorageContract::ArchiverStorage {
|
||||
credits: account_credits,
|
||||
..
|
||||
@ -175,8 +174,11 @@ pub(crate) mod tests {
|
||||
{
|
||||
account_credits.current_epoch += 1;
|
||||
}
|
||||
archiver_account.set_state(storage_contract).unwrap();
|
||||
storage_accounts.store(&archiver_pubkey, &archiver_account);
|
||||
archiver_account
|
||||
.borrow_mut()
|
||||
.set_state(storage_contract)
|
||||
.unwrap();
|
||||
storage_accounts.store(&archiver_pubkey, &archiver_account.borrow());
|
||||
|
||||
// check that incremental store increases credits
|
||||
assert_eq!(storage_accounts.points(), credits * 2 * 2 + 1);
|
||||
@ -188,48 +190,55 @@ pub(crate) mod tests {
|
||||
|
||||
pub fn create_storage_accounts_with_credits(
|
||||
credits: u64,
|
||||
) -> ((Pubkey, Account), (Pubkey, Account)) {
|
||||
) -> (
|
||||
(Pubkey, Rc<RefCell<Account>>),
|
||||
(Pubkey, Rc<RefCell<Account>>),
|
||||
) {
|
||||
let validator_pubkey = Pubkey::new_rand();
|
||||
let archiver_pubkey = Pubkey::new_rand();
|
||||
|
||||
let mut validator_account = Account::new(
|
||||
let validator_account = Account::new_ref(
|
||||
1,
|
||||
STORAGE_ACCOUNT_SPACE as usize,
|
||||
&solana_storage_program::id(),
|
||||
);
|
||||
let mut validator = StorageAccount::new(validator_pubkey, &mut validator_account);
|
||||
validator
|
||||
.initialize_storage(validator_pubkey, StorageAccountType::Validator)
|
||||
.unwrap();
|
||||
let storage_contract = &mut validator_account.state().unwrap();
|
||||
if let StorageContract::ValidatorStorage {
|
||||
credits: account_credits,
|
||||
..
|
||||
} = storage_contract
|
||||
{
|
||||
account_credits.current_epoch = credits;
|
||||
}
|
||||
validator_account.set_state(storage_contract).unwrap();
|
||||
|
||||
let mut archiver_account = Account::new(
|
||||
let archiver_account = Account::new_ref(
|
||||
1,
|
||||
STORAGE_ACCOUNT_SPACE as usize,
|
||||
&solana_storage_program::id(),
|
||||
);
|
||||
let mut archiver = StorageAccount::new(archiver_pubkey, &mut archiver_account);
|
||||
archiver
|
||||
.initialize_storage(archiver_pubkey, StorageAccountType::Archiver)
|
||||
.unwrap();
|
||||
let storage_contract = &mut archiver_account.state().unwrap();
|
||||
if let StorageContract::ArchiverStorage {
|
||||
credits: account_credits,
|
||||
..
|
||||
} = storage_contract
|
||||
{
|
||||
account_credits.current_epoch = credits;
|
||||
}
|
||||
archiver_account.set_state(storage_contract).unwrap();
|
||||
StorageAccount::new(validator_pubkey, &mut validator_account.borrow_mut())
|
||||
.initialize_storage(validator_pubkey, StorageAccountType::Validator)
|
||||
.unwrap();
|
||||
let storage_contract = &mut validator_account.borrow().state().unwrap();
|
||||
if let StorageContract::ValidatorStorage {
|
||||
credits: account_credits,
|
||||
..
|
||||
} = storage_contract
|
||||
{
|
||||
account_credits.current_epoch = credits;
|
||||
}
|
||||
validator_account
|
||||
.borrow_mut()
|
||||
.set_state(storage_contract)
|
||||
.unwrap();
|
||||
|
||||
StorageAccount::new(archiver_pubkey, &mut archiver_account.borrow_mut())
|
||||
.initialize_storage(archiver_pubkey, StorageAccountType::Archiver)
|
||||
.unwrap();
|
||||
let storage_contract = &mut archiver_account.borrow().state().unwrap();
|
||||
if let StorageContract::ArchiverStorage {
|
||||
credits: account_credits,
|
||||
..
|
||||
} = storage_contract
|
||||
{
|
||||
account_credits.current_epoch = credits;
|
||||
}
|
||||
archiver_account
|
||||
.borrow_mut()
|
||||
.set_state(storage_contract)
|
||||
.unwrap();
|
||||
}
|
||||
(
|
||||
(validator_pubkey, validator_account),
|
||||
(archiver_pubkey, archiver_account),
|
||||
|
@ -151,20 +151,20 @@ fn transfer(
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
|
||||
if !from.account.data.is_empty() {
|
||||
if !from.data_is_empty()? {
|
||||
debug!("Transfer: `from` must not carry data");
|
||||
return Err(InstructionError::InvalidArgument);
|
||||
}
|
||||
|
||||
if lamports > from.account.lamports {
|
||||
if lamports > from.lamports()? {
|
||||
debug!(
|
||||
"Transfer: insufficient lamports ({}, need {})",
|
||||
from.account.lamports, lamports
|
||||
from.lamports()?,
|
||||
lamports
|
||||
);
|
||||
return Err(SystemError::ResultWithNegativeLamports.into());
|
||||
}
|
||||
|
||||
from.account.lamports -= lamports;
|
||||
from.try_account_ref_mut()?.lamports -= lamports;
|
||||
to.lamports += lamports;
|
||||
Ok(())
|
||||
}
|
||||
@ -190,10 +190,11 @@ pub fn process_instruction(
|
||||
} => {
|
||||
let from = next_keyed_account(keyed_accounts_iter)?;
|
||||
let to = next_keyed_account(keyed_accounts_iter)?;
|
||||
let mut to_account = to.try_account_ref_mut()?;
|
||||
let to_address = Address::create(to.unsigned_key(), None)?;
|
||||
create_account(
|
||||
from,
|
||||
to.account,
|
||||
&mut to_account,
|
||||
&to_address,
|
||||
lamports,
|
||||
space,
|
||||
@ -210,12 +211,12 @@ pub fn process_instruction(
|
||||
} => {
|
||||
let from = next_keyed_account(keyed_accounts_iter)?;
|
||||
let to = next_keyed_account(keyed_accounts_iter)?;
|
||||
let mut to_account = to.try_account_ref_mut()?;
|
||||
let to_address =
|
||||
Address::create(&to.unsigned_key(), Some((&base, &seed, &program_id)))?;
|
||||
|
||||
create_account(
|
||||
from,
|
||||
to.account,
|
||||
&mut to_account,
|
||||
&to_address,
|
||||
lamports,
|
||||
space,
|
||||
@ -225,13 +226,14 @@ pub fn process_instruction(
|
||||
}
|
||||
SystemInstruction::Assign { program_id } => {
|
||||
let keyed_account = next_keyed_account(keyed_accounts_iter)?;
|
||||
let mut account = keyed_account.try_account_ref_mut()?;
|
||||
let address = Address::create(keyed_account.unsigned_key(), None)?;
|
||||
assign(keyed_account.account, &address, &program_id, &signers)
|
||||
assign(&mut account, &address, &program_id, &signers)
|
||||
}
|
||||
SystemInstruction::Transfer { lamports } => {
|
||||
let from = next_keyed_account(keyed_accounts_iter)?;
|
||||
let to = next_keyed_account(keyed_accounts_iter)?;
|
||||
transfer(from, to.account, lamports)
|
||||
let mut to_account = next_keyed_account(keyed_accounts_iter)?.try_account_ref_mut()?;
|
||||
transfer(from, &mut to_account, lamports)
|
||||
}
|
||||
SystemInstruction::AdvanceNonceAccount => {
|
||||
let me = &mut next_keyed_account(keyed_accounts_iter)?;
|
||||
@ -265,9 +267,9 @@ pub fn process_instruction(
|
||||
}
|
||||
SystemInstruction::Allocate { space } => {
|
||||
let keyed_account = next_keyed_account(keyed_accounts_iter)?;
|
||||
let mut account = keyed_account.try_account_ref_mut()?;
|
||||
let address = Address::create(keyed_account.unsigned_key(), None)?;
|
||||
|
||||
allocate(keyed_account.account, &address, space, &signers)
|
||||
allocate(&mut account, &address, space, &signers)
|
||||
}
|
||||
SystemInstruction::AllocateWithSeed {
|
||||
base,
|
||||
@ -276,17 +278,12 @@ pub fn process_instruction(
|
||||
program_id,
|
||||
} => {
|
||||
let keyed_account = next_keyed_account(keyed_accounts_iter)?;
|
||||
let mut account = keyed_account.try_account_ref_mut()?;
|
||||
let address = Address::create(
|
||||
keyed_account.unsigned_key(),
|
||||
Some((&base, &seed, &program_id)),
|
||||
)?;
|
||||
allocate_and_assign(
|
||||
keyed_account.account,
|
||||
&address,
|
||||
space,
|
||||
&program_id,
|
||||
&signers,
|
||||
)
|
||||
allocate_and_assign(&mut account, &address, space, &program_id, &signers)
|
||||
}
|
||||
SystemInstruction::AssignWithSeed {
|
||||
base,
|
||||
@ -294,12 +291,13 @@ pub fn process_instruction(
|
||||
program_id,
|
||||
} => {
|
||||
let keyed_account = next_keyed_account(keyed_accounts_iter)?;
|
||||
let mut account = keyed_account.try_account_ref_mut()?;
|
||||
let address = Address::create(
|
||||
keyed_account.unsigned_key(),
|
||||
Some((&base, &seed, &program_id)),
|
||||
)?;
|
||||
|
||||
assign(keyed_account.account, &address, &program_id, &signers)
|
||||
assign(&mut account, &address, &program_id, &signers)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -341,6 +339,7 @@ mod tests {
|
||||
system_instruction, system_program, sysvar,
|
||||
transaction::TransactionError,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
|
||||
impl From<Pubkey> for Address {
|
||||
fn from(address: Pubkey) -> Self {
|
||||
@ -351,13 +350,29 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_default_account() -> RefCell<Account> {
|
||||
RefCell::new(Account::default())
|
||||
}
|
||||
fn create_default_recent_blockhashes_account() -> RefCell<Account> {
|
||||
RefCell::new(sysvar::recent_blockhashes::create_account_with_data(
|
||||
1,
|
||||
vec![(0u64, &Hash::default()); 32].into_iter(),
|
||||
))
|
||||
}
|
||||
fn create_default_rent_account() -> RefCell<Account> {
|
||||
RefCell::new(sysvar::recent_blockhashes::create_account_with_data(
|
||||
1,
|
||||
vec![(0u64, &Hash::default()); 32].into_iter(),
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_account() {
|
||||
let new_program_owner = Pubkey::new(&[9; 32]);
|
||||
let from = Pubkey::new_rand();
|
||||
let to = Pubkey::new_rand();
|
||||
let mut from_account = Account::new(100, 0, &system_program::id());
|
||||
let mut to_account = Account::new(0, 0, &Pubkey::default());
|
||||
let mut from_account = Account::new_ref(100, 0, &system_program::id());
|
||||
let mut to_account = Account::new_ref(0, 0, &Pubkey::default());
|
||||
|
||||
assert_eq!(
|
||||
process_instruction(
|
||||
@ -375,10 +390,10 @@ mod tests {
|
||||
),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(from_account.lamports, 50);
|
||||
assert_eq!(to_account.lamports, 50);
|
||||
assert_eq!(to_account.owner, new_program_owner);
|
||||
assert_eq!(to_account.data, [0, 0]);
|
||||
assert_eq!(from_account.borrow().lamports, 50);
|
||||
assert_eq!(to_account.borrow().lamports, 50);
|
||||
assert_eq!(to_account.borrow().owner, new_program_owner);
|
||||
assert_eq!(to_account.borrow().data, [0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -388,8 +403,8 @@ mod tests {
|
||||
let seed = "shiny pepper";
|
||||
let to = create_address_with_seed(&from, seed, &new_program_owner).unwrap();
|
||||
|
||||
let mut from_account = Account::new(100, 0, &system_program::id());
|
||||
let mut to_account = Account::new(0, 0, &Pubkey::default());
|
||||
let mut from_account = Account::new_ref(100, 0, &system_program::id());
|
||||
let mut to_account = Account::new_ref(0, 0, &Pubkey::default());
|
||||
|
||||
assert_eq!(
|
||||
process_instruction(
|
||||
@ -409,10 +424,10 @@ mod tests {
|
||||
),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(from_account.lamports, 50);
|
||||
assert_eq!(to_account.lamports, 50);
|
||||
assert_eq!(to_account.owner, new_program_owner);
|
||||
assert_eq!(to_account.data, [0, 0]);
|
||||
assert_eq!(from_account.borrow().lamports, 50);
|
||||
assert_eq!(to_account.borrow().lamports, 50);
|
||||
assert_eq!(to_account.borrow().owner, new_program_owner);
|
||||
assert_eq!(to_account.borrow().data, [0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -435,13 +450,13 @@ mod tests {
|
||||
let seed = "dull boy";
|
||||
let to = create_address_with_seed(&from, seed, &new_program_owner).unwrap();
|
||||
|
||||
let mut from_account = Account::new(100, 0, &system_program::id());
|
||||
let from_account = Account::new_ref(100, 0, &system_program::id());
|
||||
let mut to_account = Account::new(0, 0, &Pubkey::default());
|
||||
let to_address = Address::create(&to, Some((&from, seed, &new_program_owner))).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
create_account(
|
||||
&mut KeyedAccount::new(&from, false, &mut from_account),
|
||||
&mut KeyedAccount::new(&from, false, &from_account),
|
||||
&mut to_account,
|
||||
&to_address,
|
||||
50,
|
||||
@ -451,7 +466,7 @@ mod tests {
|
||||
),
|
||||
Err(InstructionError::MissingRequiredSignature)
|
||||
);
|
||||
assert_eq!(from_account.lamports, 100);
|
||||
assert_eq!(from_account.borrow().lamports, 100);
|
||||
assert_eq!(to_account, Account::default());
|
||||
}
|
||||
|
||||
@ -460,14 +475,14 @@ mod tests {
|
||||
// create account with zero lamports tranferred
|
||||
let new_program_owner = Pubkey::new(&[9; 32]);
|
||||
let from = Pubkey::new_rand();
|
||||
let mut from_account = Account::new(100, 1, &Pubkey::new_rand()); // not from system account
|
||||
let from_account = Account::new_ref(100, 1, &Pubkey::new_rand()); // not from system account
|
||||
|
||||
let to = Pubkey::new_rand();
|
||||
let mut to_account = Account::new(0, 0, &Pubkey::default());
|
||||
|
||||
assert_eq!(
|
||||
create_account(
|
||||
&mut KeyedAccount::new(&from, false, &mut from_account), // no signer
|
||||
&mut KeyedAccount::new(&from, false, &from_account), // no signer
|
||||
&mut to_account,
|
||||
&to.into(),
|
||||
0,
|
||||
@ -478,7 +493,7 @@ mod tests {
|
||||
Ok(())
|
||||
);
|
||||
|
||||
let from_lamports = from_account.lamports;
|
||||
let from_lamports = from_account.borrow().lamports;
|
||||
let to_lamports = to_account.lamports;
|
||||
let to_owner = to_account.owner;
|
||||
let to_data = to_account.data.clone();
|
||||
@ -493,7 +508,7 @@ mod tests {
|
||||
// Attempt to create account with more lamports than remaining in from_account
|
||||
let new_program_owner = Pubkey::new(&[9; 32]);
|
||||
let from = Pubkey::new_rand();
|
||||
let mut from_account = Account::new(100, 0, &system_program::id());
|
||||
let mut from_account = Account::new_ref(100, 0, &system_program::id());
|
||||
|
||||
let to = Pubkey::new_rand();
|
||||
let mut to_account = Account::new(0, 0, &Pubkey::default());
|
||||
@ -512,7 +527,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_request_more_than_allowed_data_length() {
|
||||
let mut from_account = Account::new(100, 0, &system_program::id());
|
||||
let mut from_account = Account::new_ref(100, 0, &system_program::id());
|
||||
let from = Pubkey::new_rand();
|
||||
let mut to_account = Account::default();
|
||||
let to = Pubkey::new_rand();
|
||||
@ -556,7 +571,7 @@ mod tests {
|
||||
// Attempt to create system account in account already owned by another program
|
||||
let new_program_owner = Pubkey::new(&[9; 32]);
|
||||
let from = Pubkey::new_rand();
|
||||
let mut from_account = Account::new(100, 0, &system_program::id());
|
||||
let from_account = Account::new_ref(100, 0, &system_program::id());
|
||||
|
||||
let original_program_owner = Pubkey::new(&[5; 32]);
|
||||
let owned_key = Pubkey::new_rand();
|
||||
@ -567,7 +582,7 @@ mod tests {
|
||||
let owned_address = owned_key.into();
|
||||
|
||||
let result = create_account(
|
||||
&mut KeyedAccount::new(&from, true, &mut from_account),
|
||||
&mut KeyedAccount::new(&from, true, &from_account),
|
||||
&mut owned_account,
|
||||
&owned_address,
|
||||
50,
|
||||
@ -577,7 +592,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(result, Err(SystemError::AccountAlreadyInUse.into()));
|
||||
|
||||
let from_lamports = from_account.lamports;
|
||||
let from_lamports = from_account.borrow().lamports;
|
||||
assert_eq!(from_lamports, 100);
|
||||
assert_eq!(owned_account, unchanged_account);
|
||||
|
||||
@ -585,7 +600,7 @@ mod tests {
|
||||
let mut owned_account = Account::new(0, 1, &Pubkey::default());
|
||||
let unchanged_account = owned_account.clone();
|
||||
let result = create_account(
|
||||
&mut KeyedAccount::new(&from, true, &mut from_account),
|
||||
&mut KeyedAccount::new(&from, true, &from_account),
|
||||
&mut owned_account,
|
||||
&owned_address,
|
||||
50,
|
||||
@ -594,14 +609,14 @@ mod tests {
|
||||
&signers,
|
||||
);
|
||||
assert_eq!(result, Err(SystemError::AccountAlreadyInUse.into()));
|
||||
let from_lamports = from_account.lamports;
|
||||
let from_lamports = from_account.borrow().lamports;
|
||||
assert_eq!(from_lamports, 100);
|
||||
assert_eq!(owned_account, unchanged_account);
|
||||
|
||||
// Verify that create_account works even if `to` has a non-zero balance
|
||||
let mut owned_account = Account::new(1, 0, &Pubkey::default());
|
||||
let result = create_account(
|
||||
&mut KeyedAccount::new(&from, true, &mut from_account),
|
||||
&mut KeyedAccount::new(&from, true, &from_account),
|
||||
&mut owned_account,
|
||||
&owned_address,
|
||||
50,
|
||||
@ -610,7 +625,7 @@ mod tests {
|
||||
&signers,
|
||||
);
|
||||
assert_eq!(result, Ok(()));
|
||||
assert_eq!(from_account.lamports, from_lamports - 50);
|
||||
assert_eq!(from_account.borrow().lamports, from_lamports - 50);
|
||||
assert_eq!(owned_account.lamports, 1 + 50);
|
||||
}
|
||||
|
||||
@ -619,7 +634,7 @@ mod tests {
|
||||
// Attempt to create an account without signing the transfer
|
||||
let new_program_owner = Pubkey::new(&[9; 32]);
|
||||
let from = Pubkey::new_rand();
|
||||
let mut from_account = Account::new(100, 0, &system_program::id());
|
||||
let mut from_account = Account::new_ref(100, 0, &system_program::id());
|
||||
|
||||
let owned_key = Pubkey::new_rand();
|
||||
let mut owned_account = Account::new(0, 0, &Pubkey::default());
|
||||
@ -669,7 +684,7 @@ mod tests {
|
||||
fn test_create_sysvar_invalid_id() {
|
||||
// Attempt to create system account in account already owned by another program
|
||||
let from = Pubkey::new_rand();
|
||||
let mut from_account = Account::new(100, 0, &system_program::id());
|
||||
let mut from_account = Account::new_ref(100, 0, &system_program::id());
|
||||
|
||||
let to = Pubkey::new_rand();
|
||||
let mut to_account = Account::default();
|
||||
@ -696,7 +711,7 @@ mod tests {
|
||||
// Attempt to create system account in account with populated data
|
||||
let new_program_owner = Pubkey::new(&[9; 32]);
|
||||
let from = Pubkey::new_rand();
|
||||
let mut from_account = Account::new(100, 0, &system_program::id());
|
||||
let mut from_account = Account::new_ref(100, 0, &system_program::id());
|
||||
|
||||
let populated_key = Pubkey::new_rand();
|
||||
let mut populated_account = Account {
|
||||
@ -725,7 +740,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_create_from_account_is_nonce_fail() {
|
||||
let nonce = Pubkey::new_rand();
|
||||
let mut nonce_account = Account::new_data(
|
||||
let mut nonce_account = Account::new_ref_data(
|
||||
42,
|
||||
&nonce_state::NonceState::Initialized(
|
||||
nonce_state::Meta::new(&Pubkey::default()),
|
||||
@ -783,6 +798,7 @@ mod tests {
|
||||
Ok(())
|
||||
);
|
||||
|
||||
let mut account = RefCell::new(account);
|
||||
assert_eq!(
|
||||
process_instruction(
|
||||
&Pubkey::default(),
|
||||
@ -825,7 +841,7 @@ mod tests {
|
||||
assert_eq!(result, Err(InstructionError::NotEnoughAccountKeys));
|
||||
|
||||
let from = Pubkey::new_rand();
|
||||
let mut from_account = Account::new(100, 0, &system_program::id());
|
||||
let mut from_account = Account::new_ref(100, 0, &system_program::id());
|
||||
// Attempt to transfer with no destination
|
||||
let instruction = SystemInstruction::Transfer { lamports: 0 };
|
||||
let data = serialize(&instruction).unwrap();
|
||||
@ -840,51 +856,40 @@ mod tests {
|
||||
#[test]
|
||||
fn test_transfer_lamports() {
|
||||
let from = Pubkey::new_rand();
|
||||
let mut from_account = Account::new(100, 0, &Pubkey::new(&[2; 32])); // account owner should not matter
|
||||
let from_account = Account::new_ref(100, 0, &Pubkey::new(&[2; 32])); // account owner should not matter
|
||||
let mut to_account = Account::new(1, 0, &Pubkey::new(&[3; 32])); // account owner should not matter
|
||||
transfer(
|
||||
&mut KeyedAccount::new(&from, true, &mut from_account),
|
||||
&mut to_account,
|
||||
50,
|
||||
)
|
||||
.unwrap();
|
||||
let from_lamports = from_account.lamports;
|
||||
let mut from_keyed_account = KeyedAccount::new(&from, true, &from_account);
|
||||
transfer(&mut from_keyed_account, &mut to_account, 50).unwrap();
|
||||
let from_lamports = from_keyed_account.account.borrow().lamports;
|
||||
let to_lamports = to_account.lamports;
|
||||
assert_eq!(from_lamports, 50);
|
||||
assert_eq!(to_lamports, 51);
|
||||
|
||||
// Attempt to move more lamports than remaining in from_account
|
||||
let result = transfer(
|
||||
&mut KeyedAccount::new(&from, true, &mut from_account),
|
||||
&mut to_account,
|
||||
100,
|
||||
);
|
||||
let mut from_keyed_account = KeyedAccount::new(&from, true, &from_account);
|
||||
let result = transfer(&mut from_keyed_account, &mut to_account, 100);
|
||||
assert_eq!(result, Err(SystemError::ResultWithNegativeLamports.into()));
|
||||
assert_eq!(from_account.lamports, 50);
|
||||
assert_eq!(from_keyed_account.account.borrow().lamports, 50);
|
||||
assert_eq!(to_account.lamports, 51);
|
||||
|
||||
// test unsigned transfer of zero
|
||||
assert!(transfer(
|
||||
&mut KeyedAccount::new(&from, false, &mut from_account),
|
||||
&mut to_account,
|
||||
0,
|
||||
)
|
||||
.is_ok(),);
|
||||
assert_eq!(from_account.lamports, 50);
|
||||
let mut from_keyed_account = KeyedAccount::new(&from, false, &from_account);
|
||||
assert!(transfer(&mut from_keyed_account, &mut to_account, 0,).is_ok(),);
|
||||
assert_eq!(from_keyed_account.account.borrow().lamports, 50);
|
||||
assert_eq!(to_account.lamports, 51);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transfer_lamports_from_nonce_account_fail() {
|
||||
let from = Pubkey::new_rand();
|
||||
let mut from_account = Account::new_data(
|
||||
let mut from_account = Account::new_ref_data(
|
||||
100,
|
||||
&nonce_state::NonceState::Initialized(nonce_state::Meta::new(&from), Hash::default()),
|
||||
&system_program::id(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
get_system_account_kind(&from_account),
|
||||
get_system_account_kind(&from_account.borrow()),
|
||||
Some(SystemAccountKind::Nonce)
|
||||
);
|
||||
|
||||
@ -1009,7 +1014,7 @@ mod tests {
|
||||
.accounts
|
||||
.iter()
|
||||
.map(|meta| {
|
||||
if sysvar::recent_blockhashes::check_id(&meta.pubkey) {
|
||||
RefCell::new(if sysvar::recent_blockhashes::check_id(&meta.pubkey) {
|
||||
sysvar::recent_blockhashes::create_account_with_data(
|
||||
1,
|
||||
vec![(0u64, &Hash::default()); 32].into_iter(),
|
||||
@ -1018,7 +1023,7 @@ mod tests {
|
||||
sysvar::rent::create_account(1, &Rent::free())
|
||||
} else {
|
||||
Account::default()
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
@ -1068,7 +1073,7 @@ mod tests {
|
||||
&mut [KeyedAccount::new(
|
||||
&Pubkey::default(),
|
||||
true,
|
||||
&mut Account::default(),
|
||||
&create_default_account(),
|
||||
),],
|
||||
&serialize(&SystemInstruction::AdvanceNonceAccount).unwrap(),
|
||||
),
|
||||
@ -1082,11 +1087,11 @@ mod tests {
|
||||
super::process_instruction(
|
||||
&Pubkey::default(),
|
||||
&mut [
|
||||
KeyedAccount::new(&Pubkey::default(), true, &mut Account::default(),),
|
||||
KeyedAccount::new(&Pubkey::default(), true, &create_default_account()),
|
||||
KeyedAccount::new(
|
||||
&sysvar::recent_blockhashes::id(),
|
||||
false,
|
||||
&mut Account::default(),
|
||||
&create_default_account(),
|
||||
),
|
||||
],
|
||||
&serialize(&SystemInstruction::AdvanceNonceAccount).unwrap(),
|
||||
@ -1105,20 +1110,18 @@ mod tests {
|
||||
KeyedAccount::new(
|
||||
&sysvar::recent_blockhashes::id(),
|
||||
false,
|
||||
&mut sysvar::recent_blockhashes::create_account_with_data(
|
||||
1,
|
||||
vec![(0u64, &Hash::default()); 32].into_iter(),
|
||||
),
|
||||
),
|
||||
KeyedAccount::new(
|
||||
&sysvar::rent::id(),
|
||||
false,
|
||||
&mut sysvar::rent::create_account(1, &Rent::free()),
|
||||
&create_default_recent_blockhashes_account(),
|
||||
),
|
||||
KeyedAccount::new(&sysvar::rent::id(), false, &create_default_rent_account()),
|
||||
],
|
||||
&serialize(&SystemInstruction::InitializeNonceAccount(Pubkey::default())).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let new_recent_blockhashes_account =
|
||||
RefCell::new(sysvar::recent_blockhashes::create_account_with_data(
|
||||
1,
|
||||
vec![(0u64, &hash(&serialize(&0).unwrap())); 32].into_iter(),
|
||||
));
|
||||
assert_eq!(
|
||||
super::process_instruction(
|
||||
&Pubkey::default(),
|
||||
@ -1127,10 +1130,7 @@ mod tests {
|
||||
KeyedAccount::new(
|
||||
&sysvar::recent_blockhashes::id(),
|
||||
false,
|
||||
&mut sysvar::recent_blockhashes::create_account_with_data(
|
||||
1,
|
||||
vec![(0u64, &hash(&serialize(&0).unwrap())); 32].into_iter(),
|
||||
),
|
||||
&new_recent_blockhashes_account,
|
||||
),
|
||||
],
|
||||
&serialize(&SystemInstruction::AdvanceNonceAccount).unwrap(),
|
||||
@ -1172,7 +1172,7 @@ mod tests {
|
||||
&mut [KeyedAccount::new(
|
||||
&Pubkey::default(),
|
||||
true,
|
||||
&mut Account::default(),
|
||||
&create_default_account()
|
||||
),],
|
||||
&serialize(&SystemInstruction::WithdrawNonceAccount(42)).unwrap(),
|
||||
),
|
||||
@ -1186,12 +1186,12 @@ mod tests {
|
||||
super::process_instruction(
|
||||
&Pubkey::default(),
|
||||
&mut [
|
||||
KeyedAccount::new(&Pubkey::default(), true, &mut Account::default(),),
|
||||
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default(),),
|
||||
KeyedAccount::new(&Pubkey::default(), true, &create_default_account()),
|
||||
KeyedAccount::new(&Pubkey::default(), false, &create_default_account()),
|
||||
KeyedAccount::new(
|
||||
&sysvar::recent_blockhashes::id(),
|
||||
false,
|
||||
&mut Account::default(),
|
||||
&create_default_account()
|
||||
),
|
||||
],
|
||||
&serialize(&SystemInstruction::WithdrawNonceAccount(42)).unwrap(),
|
||||
@ -1211,16 +1211,13 @@ mod tests {
|
||||
true,
|
||||
&mut nonce_state::create_account(1_000_000),
|
||||
),
|
||||
KeyedAccount::new(&Pubkey::default(), true, &mut Account::default(),),
|
||||
KeyedAccount::new(&Pubkey::default(), true, &create_default_account()),
|
||||
KeyedAccount::new(
|
||||
&sysvar::recent_blockhashes::id(),
|
||||
false,
|
||||
&mut sysvar::recent_blockhashes::create_account_with_data(
|
||||
1,
|
||||
vec![(0u64, &Hash::default()); 32].into_iter(),
|
||||
),
|
||||
&create_default_recent_blockhashes_account(),
|
||||
),
|
||||
KeyedAccount::new(&sysvar::rent::id(), false, &mut Account::default(),),
|
||||
KeyedAccount::new(&sysvar::rent::id(), false, &create_default_account()),
|
||||
],
|
||||
&serialize(&SystemInstruction::WithdrawNonceAccount(42)).unwrap(),
|
||||
),
|
||||
@ -1239,20 +1236,13 @@ mod tests {
|
||||
true,
|
||||
&mut nonce_state::create_account(1_000_000),
|
||||
),
|
||||
KeyedAccount::new(&Pubkey::default(), true, &mut Account::default(),),
|
||||
KeyedAccount::new(&Pubkey::default(), true, &mut create_default_account()),
|
||||
KeyedAccount::new(
|
||||
&sysvar::recent_blockhashes::id(),
|
||||
false,
|
||||
&mut sysvar::recent_blockhashes::create_account_with_data(
|
||||
1,
|
||||
vec![(0u64, &Hash::default()); 32].into_iter(),
|
||||
),
|
||||
),
|
||||
KeyedAccount::new(
|
||||
&sysvar::rent::id(),
|
||||
false,
|
||||
&mut sysvar::rent::create_account(1, &Rent::free())
|
||||
&create_default_recent_blockhashes_account(),
|
||||
),
|
||||
KeyedAccount::new(&sysvar::rent::id(), false, &create_default_rent_account()),
|
||||
],
|
||||
&serialize(&SystemInstruction::WithdrawNonceAccount(42)).unwrap(),
|
||||
),
|
||||
@ -1302,7 +1292,7 @@ mod tests {
|
||||
KeyedAccount::new(
|
||||
&sysvar::recent_blockhashes::id(),
|
||||
false,
|
||||
&mut Account::default(),
|
||||
&create_default_account()
|
||||
),
|
||||
],
|
||||
&serialize(&SystemInstruction::InitializeNonceAccount(Pubkey::default())).unwrap(),
|
||||
@ -1325,12 +1315,9 @@ mod tests {
|
||||
KeyedAccount::new(
|
||||
&sysvar::recent_blockhashes::id(),
|
||||
false,
|
||||
&mut sysvar::recent_blockhashes::create_account_with_data(
|
||||
1,
|
||||
vec![(0u64, &Hash::default()); 32].into_iter(),
|
||||
),
|
||||
&create_default_recent_blockhashes_account(),
|
||||
),
|
||||
KeyedAccount::new(&sysvar::rent::id(), false, &mut Account::default(),),
|
||||
KeyedAccount::new(&sysvar::rent::id(), false, &create_default_account()),
|
||||
],
|
||||
&serialize(&SystemInstruction::InitializeNonceAccount(Pubkey::default())).unwrap(),
|
||||
),
|
||||
@ -1352,16 +1339,9 @@ mod tests {
|
||||
KeyedAccount::new(
|
||||
&sysvar::recent_blockhashes::id(),
|
||||
false,
|
||||
&mut sysvar::recent_blockhashes::create_account_with_data(
|
||||
1,
|
||||
vec![(0u64, &Hash::default()); 32].into_iter(),
|
||||
),
|
||||
),
|
||||
KeyedAccount::new(
|
||||
&sysvar::rent::id(),
|
||||
false,
|
||||
&mut sysvar::rent::create_account(1, &Rent::free())
|
||||
&create_default_recent_blockhashes_account(),
|
||||
),
|
||||
KeyedAccount::new(&sysvar::rent::id(), false, &create_default_rent_account()),
|
||||
],
|
||||
&serialize(&SystemInstruction::InitializeNonceAccount(Pubkey::default())).unwrap(),
|
||||
),
|
||||
@ -1379,16 +1359,9 @@ mod tests {
|
||||
KeyedAccount::new(
|
||||
&sysvar::recent_blockhashes::id(),
|
||||
false,
|
||||
&mut sysvar::recent_blockhashes::create_account_with_data(
|
||||
1,
|
||||
vec![(0u64, &Hash::default()); 32].into_iter(),
|
||||
),
|
||||
),
|
||||
KeyedAccount::new(
|
||||
&sysvar::rent::id(),
|
||||
false,
|
||||
&mut sysvar::rent::create_account(1, &Rent::free()),
|
||||
&create_default_recent_blockhashes_account(),
|
||||
),
|
||||
KeyedAccount::new(&sysvar::rent::id(), false, &create_default_rent_account()),
|
||||
],
|
||||
&serialize(&SystemInstruction::InitializeNonceAccount(Pubkey::default())).unwrap(),
|
||||
)
|
||||
@ -1444,7 +1417,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_get_system_account_kind_uninitialized_nonce_account_fail() {
|
||||
assert_eq!(
|
||||
get_system_account_kind(&nonce_state::create_account(42)),
|
||||
get_system_account_kind(&nonce_state::create_account(42).borrow()),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user