Initial population of solana-program-sdk
This commit is contained in:
302
sdk/program/src/account.rs
Normal file
302
sdk/program/src/account.rs
Normal file
@ -0,0 +1,302 @@
|
||||
use crate::{clock::Epoch, instruction::InstructionError, pubkey::Pubkey};
|
||||
use std::{
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
cmp, fmt,
|
||||
iter::FromIterator,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
/// An Account with data that is stored on chain
|
||||
#[repr(C)]
|
||||
#[frozen_abi(digest = "AVG6bXqVUippoYRAE8e1ewHeZi1UvFx5r5gKspFSEbxM")]
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Default, AbiExample)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Account {
|
||||
/// lamports in the account
|
||||
pub lamports: u64,
|
||||
/// data held in this account
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub data: Vec<u8>,
|
||||
/// the program that owns this account. If executable, the program that loads this account.
|
||||
pub owner: Pubkey,
|
||||
/// this account's data contains a loaded program (and is now read-only)
|
||||
pub executable: bool,
|
||||
/// the epoch at which this account will next owe rent
|
||||
pub rent_epoch: Epoch,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Account {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let data_len = cmp::min(64, self.data.len());
|
||||
let data_str = if data_len > 0 {
|
||||
format!(" data: {}", hex::encode(self.data[..data_len].to_vec()))
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
write!(
|
||||
f,
|
||||
"Account {{ lamports: {} data.len: {} owner: {} executable: {} rent_epoch: {}{} }}",
|
||||
self.lamports,
|
||||
self.data.len(),
|
||||
self.owner,
|
||||
self.executable,
|
||||
self.rent_epoch,
|
||||
data_str,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub fn new(lamports: u64, space: usize, owner: &Pubkey) -> Self {
|
||||
Self {
|
||||
lamports,
|
||||
data: vec![0u8; space],
|
||||
owner: *owner,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
pub fn new_ref(lamports: u64, space: usize, owner: &Pubkey) -> Rc<RefCell<Self>> {
|
||||
Rc::new(RefCell::new(Self::new(lamports, space, owner)))
|
||||
}
|
||||
|
||||
pub fn new_data<T: serde::Serialize>(
|
||||
lamports: u64,
|
||||
state: &T,
|
||||
owner: &Pubkey,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
let data = bincode::serialize(state)?;
|
||||
Ok(Self {
|
||||
lamports,
|
||||
data,
|
||||
owner: *owner,
|
||||
..Self::default()
|
||||
})
|
||||
}
|
||||
pub fn new_ref_data<T: serde::Serialize>(
|
||||
lamports: u64,
|
||||
state: &T,
|
||||
owner: &Pubkey,
|
||||
) -> Result<RefCell<Self>, bincode::Error> {
|
||||
Ok(RefCell::new(Self::new_data(lamports, state, owner)?))
|
||||
}
|
||||
|
||||
pub fn new_data_with_space<T: serde::Serialize>(
|
||||
lamports: u64,
|
||||
state: &T,
|
||||
space: usize,
|
||||
owner: &Pubkey,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
let mut account = Self::new(lamports, space, owner);
|
||||
|
||||
account.serialize_data(state)?;
|
||||
|
||||
Ok(account)
|
||||
}
|
||||
pub fn new_ref_data_with_space<T: serde::Serialize>(
|
||||
lamports: u64,
|
||||
state: &T,
|
||||
space: usize,
|
||||
owner: &Pubkey,
|
||||
) -> Result<RefCell<Self>, bincode::Error> {
|
||||
Ok(RefCell::new(Self::new_data_with_space(
|
||||
lamports, state, space, owner,
|
||||
)?))
|
||||
}
|
||||
|
||||
pub fn deserialize_data<T: serde::de::DeserializeOwned>(&self) -> Result<T, bincode::Error> {
|
||||
bincode::deserialize(&self.data)
|
||||
}
|
||||
|
||||
pub fn serialize_data<T: serde::Serialize>(&mut self, state: &T) -> Result<(), bincode::Error> {
|
||||
if bincode::serialized_size(state)? > self.data.len() as u64 {
|
||||
return Err(Box::new(bincode::ErrorKind::SizeLimit));
|
||||
}
|
||||
bincode::serialize_into(&mut self.data[..], state)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct KeyedAccount<'a> {
|
||||
is_signer: bool, // Transaction was signed by this account's key
|
||||
is_writable: bool,
|
||||
key: &'a Pubkey,
|
||||
pub account: &'a RefCell<Account>,
|
||||
}
|
||||
|
||||
impl<'a> KeyedAccount<'a> {
|
||||
pub fn signer_key(&self) -> Option<&Pubkey> {
|
||||
if self.is_signer {
|
||||
Some(self.key)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unsigned_key(&self) -> &Pubkey {
|
||||
self.key
|
||||
}
|
||||
|
||||
pub fn is_writable(&self) -> bool {
|
||||
self.is_writable
|
||||
}
|
||||
|
||||
pub fn lamports(&self) -> Result<u64, InstructionError> {
|
||||
Ok(self.try_borrow()?.lamports)
|
||||
}
|
||||
|
||||
pub fn data_len(&self) -> Result<usize, InstructionError> {
|
||||
Ok(self.try_borrow()?.data.len())
|
||||
}
|
||||
|
||||
pub fn data_is_empty(&self) -> Result<bool, InstructionError> {
|
||||
Ok(self.try_borrow()?.data.is_empty())
|
||||
}
|
||||
|
||||
pub fn owner(&self) -> Result<Pubkey, InstructionError> {
|
||||
Ok(self.try_borrow()?.owner)
|
||||
}
|
||||
|
||||
pub fn executable(&self) -> Result<bool, InstructionError> {
|
||||
Ok(self.try_borrow()?.executable)
|
||||
}
|
||||
|
||||
pub fn rent_epoch(&self) -> Result<Epoch, InstructionError> {
|
||||
Ok(self.try_borrow()?.rent_epoch)
|
||||
}
|
||||
|
||||
pub fn try_account_ref(&'a self) -> Result<Ref<Account>, InstructionError> {
|
||||
self.try_borrow()
|
||||
}
|
||||
|
||||
pub fn try_account_ref_mut(&'a self) -> Result<RefMut<Account>, InstructionError> {
|
||||
self.try_borrow_mut()
|
||||
}
|
||||
|
||||
fn try_borrow(&self) -> Result<Ref<Account>, InstructionError> {
|
||||
self.account
|
||||
.try_borrow()
|
||||
.map_err(|_| InstructionError::AccountBorrowFailed)
|
||||
}
|
||||
fn try_borrow_mut(&self) -> Result<RefMut<Account>, InstructionError> {
|
||||
self.account
|
||||
.try_borrow_mut()
|
||||
.map_err(|_| InstructionError::AccountBorrowFailed)
|
||||
}
|
||||
|
||||
pub fn new(key: &'a Pubkey, is_signer: bool, account: &'a RefCell<Account>) -> Self {
|
||||
Self {
|
||||
is_signer,
|
||||
is_writable: true,
|
||||
key,
|
||||
account,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_readonly(key: &'a Pubkey, is_signer: bool, account: &'a RefCell<Account>) -> Self {
|
||||
Self {
|
||||
is_signer,
|
||||
is_writable: false,
|
||||
key,
|
||||
account,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialEq for KeyedAccount<'a> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.key == other.key
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<(&'a Pubkey, &'a RefCell<Account>)> for KeyedAccount<'a> {
|
||||
fn from((key, account): (&'a Pubkey, &'a RefCell<Account>)) -> Self {
|
||||
Self {
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
key,
|
||||
account,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<(&'a Pubkey, bool, &'a RefCell<Account>)> for KeyedAccount<'a> {
|
||||
fn from((key, is_signer, account): (&'a Pubkey, bool, &'a RefCell<Account>)) -> Self {
|
||||
Self {
|
||||
is_signer,
|
||||
is_writable: true,
|
||||
key,
|
||||
account,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a (&'a Pubkey, &'a RefCell<Account>)> for KeyedAccount<'a> {
|
||||
fn from((key, account): &'a (&'a Pubkey, &'a RefCell<Account>)) -> Self {
|
||||
Self {
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
key,
|
||||
account,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_keyed_accounts<'a>(
|
||||
accounts: &'a [(&'a Pubkey, &'a RefCell<Account>)],
|
||||
) -> Vec<KeyedAccount<'a>> {
|
||||
accounts.iter().map(Into::into).collect()
|
||||
}
|
||||
|
||||
pub fn create_keyed_is_signer_accounts<'a>(
|
||||
accounts: &'a [(&'a Pubkey, bool, &'a RefCell<Account>)],
|
||||
) -> Vec<KeyedAccount<'a>> {
|
||||
accounts
|
||||
.iter()
|
||||
.map(|(key, is_signer, account)| KeyedAccount {
|
||||
is_signer: *is_signer,
|
||||
is_writable: false,
|
||||
key,
|
||||
account,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn create_keyed_readonly_accounts(
|
||||
accounts: &[(Pubkey, RefCell<Account>)],
|
||||
) -> Vec<KeyedAccount> {
|
||||
accounts
|
||||
.iter()
|
||||
.map(|(key, account)| KeyedAccount {
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
key,
|
||||
account,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Return all the signers from a set of KeyedAccounts
|
||||
pub fn get_signers<A>(keyed_accounts: &[KeyedAccount]) -> A
|
||||
where
|
||||
A: FromIterator<Pubkey>,
|
||||
{
|
||||
keyed_accounts
|
||||
.iter()
|
||||
.filter_map(|keyed_account| keyed_account.signer_key())
|
||||
.cloned()
|
||||
.collect::<A>()
|
||||
}
|
||||
|
||||
/// Return the next KeyedAccount or a NotEnoughAccountKeys error
|
||||
pub fn next_keyed_account<'a, 'b, I: Iterator<Item = &'a KeyedAccount<'b>>>(
|
||||
iter: &mut I,
|
||||
) -> Result<I::Item, InstructionError> {
|
||||
iter.next().ok_or(InstructionError::NotEnoughAccountKeys)
|
||||
}
|
||||
|
||||
/// Return true if the first keyed_account is executable, used to determine if
|
||||
/// the loader should call a program's 'main'
|
||||
pub fn is_executable(keyed_accounts: &[KeyedAccount]) -> Result<bool, InstructionError> {
|
||||
Ok(!keyed_accounts.is_empty() && keyed_accounts[0].executable()?)
|
||||
}
|
225
sdk/program/src/account_info.rs
Normal file
225
sdk/program/src/account_info.rs
Normal file
@ -0,0 +1,225 @@
|
||||
use crate::{account::Account, clock::Epoch, program_error::ProgramError, pubkey::Pubkey};
|
||||
use std::{
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
cmp, fmt,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
/// Account information
|
||||
#[derive(Clone)]
|
||||
pub struct AccountInfo<'a> {
|
||||
/// Public key of the account
|
||||
pub key: &'a Pubkey,
|
||||
// Was the transaction signed by this account's public key?
|
||||
pub is_signer: bool,
|
||||
// Is the account writable?
|
||||
pub is_writable: bool,
|
||||
/// Account members that are mutable by the program
|
||||
pub lamports: Rc<RefCell<&'a mut u64>>,
|
||||
/// Account members that are mutable by the program
|
||||
pub data: Rc<RefCell<&'a mut [u8]>>,
|
||||
/// Program that owns this account
|
||||
pub owner: &'a Pubkey,
|
||||
/// This account's data contains a loaded program (and is now read-only)
|
||||
pub executable: bool,
|
||||
/// The epoch at which this account will next owe rent
|
||||
pub rent_epoch: Epoch,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Debug for AccountInfo<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let data_len = cmp::min(64, self.data_len());
|
||||
let data_str = if data_len > 0 {
|
||||
format!(
|
||||
" data: {} ...",
|
||||
hex::encode(self.data.borrow()[..data_len].to_vec())
|
||||
)
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
write!(
|
||||
f,
|
||||
"AccountInfo {{ key: {} owner: {} is_signer: {} is_writable: {} executable: {} rent_epoch: {} lamports: {} data.len: {} {} }}",
|
||||
self.key,
|
||||
self.owner,
|
||||
self.is_signer,
|
||||
self.is_writable,
|
||||
self.executable,
|
||||
self.rent_epoch,
|
||||
self.lamports(),
|
||||
self.data_len(),
|
||||
data_str,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AccountInfo<'a> {
|
||||
pub fn signer_key(&self) -> Option<&Pubkey> {
|
||||
if self.is_signer {
|
||||
Some(self.key)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unsigned_key(&self) -> &Pubkey {
|
||||
self.key
|
||||
}
|
||||
|
||||
pub fn lamports(&self) -> u64 {
|
||||
**self.lamports.borrow()
|
||||
}
|
||||
|
||||
pub fn try_lamports(&self) -> Result<u64, ProgramError> {
|
||||
Ok(**self.try_borrow_lamports()?)
|
||||
}
|
||||
|
||||
pub fn data_len(&self) -> usize {
|
||||
self.data.borrow().len()
|
||||
}
|
||||
|
||||
pub fn try_data_len(&self) -> Result<usize, ProgramError> {
|
||||
Ok(self.try_borrow_data()?.len())
|
||||
}
|
||||
|
||||
pub fn data_is_empty(&self) -> bool {
|
||||
self.data.borrow().is_empty()
|
||||
}
|
||||
|
||||
pub fn try_data_is_empty(&self) -> Result<bool, ProgramError> {
|
||||
Ok(self.try_borrow_data()?.is_empty())
|
||||
}
|
||||
|
||||
pub fn try_borrow_lamports(&self) -> Result<Ref<&mut u64>, ProgramError> {
|
||||
self.lamports
|
||||
.try_borrow()
|
||||
.map_err(|_| ProgramError::AccountBorrowFailed)
|
||||
}
|
||||
|
||||
pub fn try_borrow_mut_lamports(&self) -> Result<RefMut<&'a mut u64>, ProgramError> {
|
||||
self.lamports
|
||||
.try_borrow_mut()
|
||||
.map_err(|_| ProgramError::AccountBorrowFailed)
|
||||
}
|
||||
|
||||
pub fn try_borrow_data(&self) -> Result<Ref<&mut [u8]>, ProgramError> {
|
||||
self.data
|
||||
.try_borrow()
|
||||
.map_err(|_| ProgramError::AccountBorrowFailed)
|
||||
}
|
||||
|
||||
pub fn try_borrow_mut_data(&self) -> Result<RefMut<&'a mut [u8]>, ProgramError> {
|
||||
self.data
|
||||
.try_borrow_mut()
|
||||
.map_err(|_| ProgramError::AccountBorrowFailed)
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
key: &'a Pubkey,
|
||||
is_signer: bool,
|
||||
is_writable: bool,
|
||||
lamports: &'a mut u64,
|
||||
data: &'a mut [u8],
|
||||
owner: &'a Pubkey,
|
||||
executable: bool,
|
||||
rent_epoch: Epoch,
|
||||
) -> Self {
|
||||
Self {
|
||||
key,
|
||||
is_signer,
|
||||
is_writable,
|
||||
lamports: Rc::new(RefCell::new(lamports)),
|
||||
data: Rc::new(RefCell::new(data)),
|
||||
owner,
|
||||
executable,
|
||||
rent_epoch,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize_data<T: serde::de::DeserializeOwned>(&self) -> Result<T, bincode::Error> {
|
||||
bincode::deserialize(&self.data.borrow())
|
||||
}
|
||||
|
||||
pub fn serialize_data<T: serde::Serialize>(&mut self, state: &T) -> Result<(), bincode::Error> {
|
||||
if bincode::serialized_size(state)? > self.data_len() as u64 {
|
||||
return Err(Box::new(bincode::ErrorKind::SizeLimit));
|
||||
}
|
||||
bincode::serialize_into(&mut self.data.borrow_mut()[..], state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<(&'a Pubkey, &'a mut Account)> for AccountInfo<'a> {
|
||||
fn from((key, account): (&'a Pubkey, &'a mut Account)) -> Self {
|
||||
Self::new(
|
||||
key,
|
||||
false,
|
||||
false,
|
||||
&mut account.lamports,
|
||||
&mut account.data,
|
||||
&account.owner,
|
||||
account.executable,
|
||||
account.rent_epoch,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<(&'a Pubkey, bool, &'a mut Account)> for AccountInfo<'a> {
|
||||
fn from((key, is_signer, account): (&'a Pubkey, bool, &'a mut Account)) -> Self {
|
||||
Self::new(
|
||||
key,
|
||||
is_signer,
|
||||
false,
|
||||
&mut account.lamports,
|
||||
&mut account.data,
|
||||
&account.owner,
|
||||
account.executable,
|
||||
account.rent_epoch,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a mut (Pubkey, Account)> for AccountInfo<'a> {
|
||||
fn from((key, account): &'a mut (Pubkey, Account)) -> Self {
|
||||
Self::new(
|
||||
key,
|
||||
false,
|
||||
false,
|
||||
&mut account.lamports,
|
||||
&mut account.data,
|
||||
&account.owner,
|
||||
account.executable,
|
||||
account.rent_epoch,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_account_infos(accounts: &mut [(Pubkey, Account)]) -> Vec<AccountInfo> {
|
||||
accounts.iter_mut().map(Into::into).collect()
|
||||
}
|
||||
|
||||
pub fn create_is_signer_account_infos<'a>(
|
||||
accounts: &'a mut [(&'a Pubkey, bool, &'a mut Account)],
|
||||
) -> Vec<AccountInfo<'a>> {
|
||||
accounts
|
||||
.iter_mut()
|
||||
.map(|(key, is_signer, account)| {
|
||||
AccountInfo::new(
|
||||
key,
|
||||
*is_signer,
|
||||
false,
|
||||
&mut account.lamports,
|
||||
&mut account.data,
|
||||
&account.owner,
|
||||
account.executable,
|
||||
account.rent_epoch,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Return the next AccountInfo or a NotEnoughAccountKeys error
|
||||
pub fn next_account_info<'a, 'b, I: Iterator<Item = &'a AccountInfo<'b>>>(
|
||||
iter: &mut I,
|
||||
) -> Result<I::Item, ProgramError> {
|
||||
iter.next().ok_or(ProgramError::NotEnoughAccountKeys)
|
||||
}
|
65
sdk/program/src/account_utils.rs
Normal file
65
sdk/program/src/account_utils.rs
Normal file
@ -0,0 +1,65 @@
|
||||
//! useful extras for Account state
|
||||
use crate::{
|
||||
account::{Account, KeyedAccount},
|
||||
instruction::InstructionError,
|
||||
};
|
||||
use bincode::ErrorKind;
|
||||
|
||||
/// Convenience trait to covert bincode errors to instruction errors.
|
||||
pub trait StateMut<T> {
|
||||
fn state(&self) -> Result<T, InstructionError>;
|
||||
fn set_state(&mut self, state: &T) -> Result<(), InstructionError>;
|
||||
}
|
||||
pub trait State<T> {
|
||||
fn state(&self) -> Result<T, InstructionError>;
|
||||
fn set_state(&self, state: &T) -> Result<(), InstructionError>;
|
||||
}
|
||||
|
||||
impl<T> StateMut<T> for Account
|
||||
where
|
||||
T: serde::Serialize + serde::de::DeserializeOwned,
|
||||
{
|
||||
fn state(&self) -> Result<T, InstructionError> {
|
||||
self.deserialize_data()
|
||||
.map_err(|_| InstructionError::InvalidAccountData)
|
||||
}
|
||||
fn set_state(&mut self, state: &T) -> Result<(), InstructionError> {
|
||||
self.serialize_data(state).map_err(|err| match *err {
|
||||
ErrorKind::SizeLimit => InstructionError::AccountDataTooSmall,
|
||||
_ => InstructionError::GenericError,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> State<T> for KeyedAccount<'a>
|
||||
where
|
||||
T: serde::Serialize + serde::de::DeserializeOwned,
|
||||
{
|
||||
fn state(&self) -> Result<T, InstructionError> {
|
||||
self.try_account_ref()?.state()
|
||||
}
|
||||
fn set_state(&self, state: &T) -> Result<(), InstructionError> {
|
||||
self.try_account_ref_mut()?.set_state(state)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{account::Account, pubkey::Pubkey};
|
||||
|
||||
#[test]
|
||||
fn test_account_state() {
|
||||
let state = 42u64;
|
||||
|
||||
assert!(Account::default().set_state(&state).is_err());
|
||||
let res = Account::default().state() as Result<u64, InstructionError>;
|
||||
assert!(res.is_err());
|
||||
|
||||
let mut account = Account::new(0, std::mem::size_of::<u64>(), &Pubkey::default());
|
||||
|
||||
assert!(account.set_state(&state).is_ok());
|
||||
let stored_state: u64 = account.state().unwrap();
|
||||
assert_eq!(stored_state, state);
|
||||
}
|
||||
}
|
17
sdk/program/src/bpf_loader.rs
Normal file
17
sdk/program/src/bpf_loader.rs
Normal file
@ -0,0 +1,17 @@
|
||||
//! @brief The latest Solana BPF loader.
|
||||
//!
|
||||
//! The BPF loader is responsible for loading, finalizing, and executing BPF
|
||||
//! programs. Not all networks may support the latest loader. You can use the
|
||||
//! command-line tools to check if this version of the loader is supported by
|
||||
//! requesting the account info for the public key below.
|
||||
//!
|
||||
//! The program format may change between loaders, and it is crucial to build
|
||||
//! your program against the proper entrypoint semantics. All programs being
|
||||
//! deployed to this BPF loader must build against the latest entrypoint version
|
||||
//! located in `entrypoint.rs`.
|
||||
//!
|
||||
//! Note: Programs built for older loaders must use a matching entrypoint
|
||||
//! version. An example is `bpf_loader_deprecated.rs` which requires
|
||||
//! `entrypoint_deprecated.rs`.
|
||||
|
||||
crate::declare_id!("BPFLoader2111111111111111111111111111111111");
|
14
sdk/program/src/bpf_loader_deprecated.rs
Normal file
14
sdk/program/src/bpf_loader_deprecated.rs
Normal file
@ -0,0 +1,14 @@
|
||||
//! @brief The original and now deprecated Solana BPF loader.
|
||||
//!
|
||||
//! The BPF loader is responsible for loading, finalizing, and executing BPF
|
||||
//! programs.
|
||||
//!
|
||||
//! This loader is deprecated, and it is strongly encouraged to build for and
|
||||
//! deploy to the latest BPF loader. For more information see `bpf_loader.rs`
|
||||
//!
|
||||
//! The program format may change between loaders, and it is crucial to build
|
||||
//! your program against the proper entrypoint semantics. All programs being
|
||||
//! deployed to this BPF loader must build against the deprecated entrypoint
|
||||
//! version located in `entrypoint_deprecated.rs`.
|
||||
|
||||
crate::declare_id!("BPFLoader1111111111111111111111111111111111");
|
92
sdk/program/src/clock.rs
Normal file
92
sdk/program/src/clock.rs
Normal file
@ -0,0 +1,92 @@
|
||||
//! Provides information about the network's clock which is made up of ticks, slots, etc...
|
||||
|
||||
// The default tick rate that the cluster attempts to achieve. Note that the actual tick
|
||||
// rate at any given time should be expected to drift
|
||||
pub const DEFAULT_TICKS_PER_SECOND: u64 = 160;
|
||||
|
||||
pub const MS_PER_TICK: u64 = 1000 / DEFAULT_TICKS_PER_SECOND;
|
||||
|
||||
// At 160 ticks/s, 64 ticks per slot implies that leader rotation and voting will happen
|
||||
// every 400 ms. A fast voting cadence ensures faster finality and convergence
|
||||
pub const DEFAULT_TICKS_PER_SLOT: u64 = 64;
|
||||
|
||||
// GCP n1-standard hardware and also a xeon e5-2520 v4 are about this rate of hashes/s
|
||||
pub const DEFAULT_HASHES_PER_SECOND: u64 = 2_000_000;
|
||||
|
||||
// 1 Dev Epoch = 400 ms * 8192 ~= 55 minutes
|
||||
pub const DEFAULT_DEV_SLOTS_PER_EPOCH: u64 = 8192;
|
||||
|
||||
pub const SECONDS_PER_DAY: u64 = 24 * 60 * 60;
|
||||
pub const TICKS_PER_DAY: u64 = DEFAULT_TICKS_PER_SECOND * SECONDS_PER_DAY;
|
||||
|
||||
// 1 Epoch ~= 2 days
|
||||
pub const DEFAULT_SLOTS_PER_EPOCH: u64 = 2 * TICKS_PER_DAY / DEFAULT_TICKS_PER_SLOT;
|
||||
|
||||
// leader schedule is governed by this
|
||||
pub const NUM_CONSECUTIVE_LEADER_SLOTS: u64 = 4;
|
||||
|
||||
pub const DEFAULT_MS_PER_SLOT: u64 = 1_000 * DEFAULT_TICKS_PER_SLOT / DEFAULT_TICKS_PER_SECOND;
|
||||
|
||||
/// The time window of recent block hash values that the bank will track the signatures
|
||||
/// of over. Once the bank discards a block hash, it will reject any transactions that use
|
||||
/// that `recent_blockhash` in a transaction. Lowering this value reduces memory consumption,
|
||||
/// but requires clients to update its `recent_blockhash` more frequently. Raising the value
|
||||
/// lengthens the time a client must wait to be certain a missing transaction will
|
||||
/// not be processed by the network.
|
||||
pub const MAX_HASH_AGE_IN_SECONDS: usize = 120;
|
||||
|
||||
// Number of maximum recent blockhashes (one blockhash per slot)
|
||||
pub const MAX_RECENT_BLOCKHASHES: usize =
|
||||
MAX_HASH_AGE_IN_SECONDS * DEFAULT_TICKS_PER_SECOND as usize / DEFAULT_TICKS_PER_SLOT as usize;
|
||||
|
||||
// The maximum age of a blockhash that will be accepted by the leader
|
||||
pub const MAX_PROCESSING_AGE: usize = MAX_RECENT_BLOCKHASHES / 2;
|
||||
|
||||
/// This is maximum time consumed in forwarding a transaction from one node to next, before
|
||||
/// it can be processed in the target node
|
||||
pub const MAX_TRANSACTION_FORWARDING_DELAY_GPU: usize = 2;
|
||||
|
||||
/// More delay is expected if CUDA is not enabled (as signature verification takes longer)
|
||||
pub const MAX_TRANSACTION_FORWARDING_DELAY: usize = 6;
|
||||
|
||||
/// Slot is a unit of time given to a leader for encoding,
|
||||
/// is some some number of Ticks long.
|
||||
pub type Slot = u64;
|
||||
|
||||
/// Epoch is a unit of time a given leader schedule is honored,
|
||||
/// some number of Slots.
|
||||
pub type Epoch = u64;
|
||||
|
||||
pub const GENESIS_EPOCH: Epoch = 0;
|
||||
|
||||
/// SlotIndex is an index to the slots of a epoch
|
||||
pub type SlotIndex = u64;
|
||||
|
||||
/// SlotCount is the number of slots in a epoch
|
||||
pub type SlotCount = u64;
|
||||
|
||||
/// UnixTimestamp is an approximate measure of real-world time,
|
||||
/// expressed as Unix time (ie. seconds since the Unix epoch)
|
||||
pub type UnixTimestamp = i64;
|
||||
|
||||
/// Clock represents network time. Members of Clock start from 0 upon
|
||||
/// network boot. The best way to map Clock to wallclock time is to use
|
||||
/// current Slot, as Epochs vary in duration (they start short and grow
|
||||
/// as the network progresses).
|
||||
///
|
||||
#[repr(C)]
|
||||
#[derive(Serialize, Deserialize, Debug, Default, PartialEq)]
|
||||
pub struct Clock {
|
||||
/// the current network/bank Slot
|
||||
pub slot: Slot,
|
||||
/// unused
|
||||
pub unused: u64,
|
||||
/// the bank Epoch
|
||||
pub epoch: Epoch,
|
||||
/// the future Epoch for which the leader schedule has
|
||||
/// most recently been calculated
|
||||
pub leader_schedule_epoch: Epoch,
|
||||
/// computed from genesis creation time and network time
|
||||
/// in slots, drifts!
|
||||
pub unix_timestamp: UnixTimestamp,
|
||||
}
|
38
sdk/program/src/decode_error.rs
Normal file
38
sdk/program/src/decode_error.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
/// Allows custom errors to be decoded back to their original enum
|
||||
pub trait DecodeError<E> {
|
||||
fn decode_custom_error_to_enum(custom: u32) -> Option<E>
|
||||
where
|
||||
E: FromPrimitive,
|
||||
{
|
||||
E::from_u32(custom)
|
||||
}
|
||||
fn type_of() -> &'static str;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use num_derive::FromPrimitive;
|
||||
|
||||
#[test]
|
||||
fn test_decode_custom_error_to_enum() {
|
||||
#[derive(Debug, FromPrimitive, PartialEq)]
|
||||
enum TestEnum {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
}
|
||||
impl<T> DecodeError<T> for TestEnum {
|
||||
fn type_of() -> &'static str {
|
||||
"TestEnum"
|
||||
}
|
||||
}
|
||||
assert_eq!(TestEnum::decode_custom_error_to_enum(0), Some(TestEnum::A));
|
||||
assert_eq!(TestEnum::decode_custom_error_to_enum(1), Some(TestEnum::B));
|
||||
assert_eq!(TestEnum::decode_custom_error_to_enum(2), Some(TestEnum::C));
|
||||
let option: Option<TestEnum> = TestEnum::decode_custom_error_to_enum(3);
|
||||
assert_eq!(option, None);
|
||||
}
|
||||
}
|
257
sdk/program/src/entrypoint.rs
Normal file
257
sdk/program/src/entrypoint.rs
Normal file
@ -0,0 +1,257 @@
|
||||
//! @brief Solana Rust-based BPF program entry point supported by the latest
|
||||
//! BPFLoader. For more information see './bpf_loader.rs'
|
||||
|
||||
extern crate alloc;
|
||||
use crate::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};
|
||||
use alloc::vec::Vec;
|
||||
use std::{
|
||||
alloc::Layout,
|
||||
cell::RefCell,
|
||||
mem::{align_of, size_of},
|
||||
ptr::null_mut,
|
||||
rc::Rc,
|
||||
// Hide Result from bindgen gets confused about generics in non-generic type declarations
|
||||
result::Result as ResultGeneric,
|
||||
slice::{from_raw_parts, from_raw_parts_mut},
|
||||
};
|
||||
|
||||
pub type ProgramResult = ResultGeneric<(), ProgramError>;
|
||||
|
||||
/// User implemented function to process an instruction
|
||||
///
|
||||
/// program_id: Program ID of the currently executing program accounts: Accounts
|
||||
/// passed as part of the instruction instruction_data: Instruction data
|
||||
pub type ProcessInstruction =
|
||||
fn(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult;
|
||||
|
||||
/// Programs indicate success with a return value of 0
|
||||
pub const SUCCESS: u64 = 0;
|
||||
|
||||
/// Start address of the memory region used for program heap.
|
||||
pub const HEAP_START_ADDRESS: usize = 0x300000000;
|
||||
/// Length of the heap memory region used for program heap.
|
||||
pub const HEAP_LENGTH: usize = 32 * 1024;
|
||||
|
||||
/// Declare the entry point of the program and use the default local heap
|
||||
/// implementation
|
||||
///
|
||||
/// Deserialize the program input arguments and call the user defined
|
||||
/// `process_instruction` function. Users must call this macro otherwise an
|
||||
/// entry point for their program will not be created.
|
||||
///
|
||||
/// If the program defines the feature `custom-heap` then the default heap
|
||||
/// implementation will not be included and the program is free to implement
|
||||
/// their own `#[global_allocator]`
|
||||
#[macro_export]
|
||||
macro_rules! entrypoint {
|
||||
($process_instruction:ident) => {
|
||||
#[cfg(all(not(feature = "custom-heap"), not(test)))]
|
||||
#[global_allocator]
|
||||
static A: $crate::entrypoint::BumpAllocator = $crate::entrypoint::BumpAllocator {
|
||||
start: $crate::entrypoint::HEAP_START_ADDRESS,
|
||||
len: $crate::entrypoint::HEAP_LENGTH,
|
||||
};
|
||||
|
||||
/// # Safety
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
|
||||
let (program_id, accounts, instruction_data) =
|
||||
unsafe { $crate::entrypoint::deserialize(input) };
|
||||
match $process_instruction(&program_id, &accounts, &instruction_data) {
|
||||
Ok(()) => $crate::entrypoint::SUCCESS,
|
||||
Err(error) => error.into(),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// The bump allocator used as the default rust heap when running programs.
|
||||
pub struct BumpAllocator {
|
||||
pub start: usize,
|
||||
pub len: usize,
|
||||
}
|
||||
unsafe impl std::alloc::GlobalAlloc for BumpAllocator {
|
||||
#[inline]
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
let pos_ptr = self.start as *mut usize;
|
||||
|
||||
let mut pos = *pos_ptr;
|
||||
if pos == 0 {
|
||||
// First time, set starting position
|
||||
pos = self.start + self.len;
|
||||
}
|
||||
pos = pos.saturating_sub(layout.size());
|
||||
pos &= !(layout.align().wrapping_sub(1));
|
||||
if pos < self.start + size_of::<*mut u8>() {
|
||||
return null_mut();
|
||||
}
|
||||
*pos_ptr = pos;
|
||||
pos as *mut u8
|
||||
}
|
||||
#[inline]
|
||||
unsafe fn dealloc(&self, _: *mut u8, _: Layout) {
|
||||
// I'm a bump allocator, I don't free
|
||||
}
|
||||
}
|
||||
|
||||
/// Maximum number of bytes a program may add to an account during a single realloc
|
||||
pub const MAX_PERMITTED_DATA_INCREASE: usize = 1_024 * 10;
|
||||
|
||||
/// Deserialize the input arguments
|
||||
///
|
||||
/// # Safety
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec<AccountInfo<'a>>, &'a [u8]) {
|
||||
let mut offset: usize = 0;
|
||||
|
||||
// Number of accounts present
|
||||
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let num_accounts = *(input.add(offset) as *const u64) as usize;
|
||||
offset += size_of::<u64>();
|
||||
|
||||
// Account Infos
|
||||
|
||||
let mut accounts = Vec::with_capacity(num_accounts);
|
||||
for _ in 0..num_accounts {
|
||||
let dup_info = *(input.add(offset) as *const u8);
|
||||
offset += size_of::<u8>();
|
||||
if dup_info == std::u8::MAX {
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let is_signer = *(input.add(offset) as *const u8) != 0;
|
||||
offset += size_of::<u8>();
|
||||
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let is_writable = *(input.add(offset) as *const u8) != 0;
|
||||
offset += size_of::<u8>();
|
||||
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let executable = *(input.add(offset) as *const u8) != 0;
|
||||
offset += size_of::<u8>();
|
||||
|
||||
offset += size_of::<u32>(); // padding to u64
|
||||
|
||||
let key: &Pubkey = &*(input.add(offset) as *const Pubkey);
|
||||
offset += size_of::<Pubkey>();
|
||||
|
||||
let owner: &Pubkey = &*(input.add(offset) as *const Pubkey);
|
||||
offset += size_of::<Pubkey>();
|
||||
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let lamports = Rc::new(RefCell::new(&mut *(input.add(offset) as *mut u64)));
|
||||
offset += size_of::<u64>();
|
||||
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let data_len = *(input.add(offset) as *const u64) as usize;
|
||||
offset += size_of::<u64>();
|
||||
|
||||
let data = Rc::new(RefCell::new({
|
||||
from_raw_parts_mut(input.add(offset), data_len)
|
||||
}));
|
||||
offset += data_len + MAX_PERMITTED_DATA_INCREASE;
|
||||
offset += (offset as *const u8).align_offset(align_of::<u128>()); // padding
|
||||
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let rent_epoch = *(input.add(offset) as *const u64);
|
||||
offset += size_of::<u64>();
|
||||
|
||||
accounts.push(AccountInfo {
|
||||
is_signer,
|
||||
is_writable,
|
||||
key,
|
||||
lamports,
|
||||
data,
|
||||
owner,
|
||||
executable,
|
||||
rent_epoch,
|
||||
});
|
||||
} else {
|
||||
offset += 7; // padding
|
||||
|
||||
// Duplicate account, clone the original
|
||||
accounts.push(accounts[dup_info as usize].clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Instruction data
|
||||
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let instruction_data_len = *(input.add(offset) as *const u64) as usize;
|
||||
offset += size_of::<u64>();
|
||||
|
||||
let instruction_data = { from_raw_parts(input.add(offset), instruction_data_len) };
|
||||
offset += instruction_data_len;
|
||||
|
||||
// Program Id
|
||||
|
||||
let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
|
||||
|
||||
(program_id, accounts, instruction_data)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::alloc::GlobalAlloc;
|
||||
|
||||
#[test]
|
||||
fn test_bump_allocator() {
|
||||
// alloc the entire
|
||||
{
|
||||
let heap = vec![0u8; 128];
|
||||
let allocator = BumpAllocator {
|
||||
start: heap.as_ptr() as *const _ as usize,
|
||||
len: heap.len(),
|
||||
};
|
||||
for i in 0..128 - size_of::<*mut u8>() {
|
||||
let ptr = unsafe {
|
||||
allocator.alloc(Layout::from_size_align(1, size_of::<u8>()).unwrap())
|
||||
};
|
||||
assert_eq!(
|
||||
ptr as *const _ as usize,
|
||||
heap.as_ptr() as *const _ as usize + heap.len() - 1 - i
|
||||
);
|
||||
}
|
||||
assert_eq!(null_mut(), unsafe {
|
||||
allocator.alloc(Layout::from_size_align(1, 1).unwrap())
|
||||
});
|
||||
}
|
||||
// check alignment
|
||||
{
|
||||
let heap = vec![0u8; 128];
|
||||
let allocator = BumpAllocator {
|
||||
start: heap.as_ptr() as *const _ as usize,
|
||||
len: heap.len(),
|
||||
};
|
||||
let ptr =
|
||||
unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u8>()).unwrap()) };
|
||||
assert_eq!(0, ptr.align_offset(size_of::<u8>()));
|
||||
let ptr =
|
||||
unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u16>()).unwrap()) };
|
||||
assert_eq!(0, ptr.align_offset(size_of::<u16>()));
|
||||
let ptr =
|
||||
unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u32>()).unwrap()) };
|
||||
assert_eq!(0, ptr.align_offset(size_of::<u32>()));
|
||||
let ptr =
|
||||
unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u64>()).unwrap()) };
|
||||
assert_eq!(0, ptr.align_offset(size_of::<u64>()));
|
||||
let ptr =
|
||||
unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u128>()).unwrap()) };
|
||||
assert_eq!(0, ptr.align_offset(size_of::<u128>()));
|
||||
let ptr = unsafe { allocator.alloc(Layout::from_size_align(1, 64).unwrap()) };
|
||||
assert_eq!(0, ptr.align_offset(64));
|
||||
}
|
||||
// alloc entire block (minus the pos ptr)
|
||||
{
|
||||
let heap = vec![0u8; 128];
|
||||
let allocator = BumpAllocator {
|
||||
start: heap.as_ptr() as *const _ as usize,
|
||||
len: heap.len(),
|
||||
};
|
||||
let ptr =
|
||||
unsafe { allocator.alloc(Layout::from_size_align(120, size_of::<u8>()).unwrap()) };
|
||||
assert_ne!(ptr, null_mut());
|
||||
assert_eq!(0, ptr.align_offset(size_of::<u64>()));
|
||||
}
|
||||
}
|
||||
}
|
137
sdk/program/src/entrypoint_deprecated.rs
Normal file
137
sdk/program/src/entrypoint_deprecated.rs
Normal file
@ -0,0 +1,137 @@
|
||||
//! @brief Solana Rust-based BPF program entry point supported by the original
|
||||
//! and now deprecated BPFLoader. For more information see
|
||||
//! './bpf_loader_deprecated.rs'
|
||||
|
||||
extern crate alloc;
|
||||
use crate::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};
|
||||
use alloc::vec::Vec;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
mem::size_of,
|
||||
rc::Rc,
|
||||
// Hide Result from bindgen gets confused about generics in non-generic type declarations
|
||||
result::Result as ResultGeneric,
|
||||
slice::{from_raw_parts, from_raw_parts_mut},
|
||||
};
|
||||
|
||||
pub type ProgramResult = ResultGeneric<(), ProgramError>;
|
||||
|
||||
/// User implemented function to process an instruction
|
||||
///
|
||||
/// program_id: Program ID of the currently executing program
|
||||
/// accounts: Accounts passed as part of the instruction
|
||||
/// instruction_data: Instruction data
|
||||
pub type ProcessInstruction =
|
||||
fn(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult;
|
||||
|
||||
/// Programs indicate success with a return value of 0
|
||||
pub const SUCCESS: u64 = 0;
|
||||
|
||||
/// Declare the entry point of the program.
|
||||
///
|
||||
/// Deserialize the program input arguments and call
|
||||
/// the user defined `process_instruction` function.
|
||||
/// Users must call this macro otherwise an entry point for
|
||||
/// their program will not be created.
|
||||
#[macro_export]
|
||||
macro_rules! entrypoint_deprecated {
|
||||
($process_instruction:ident) => {
|
||||
/// # Safety
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
|
||||
let (program_id, accounts, instruction_data) =
|
||||
unsafe { $crate::entrypoint_deprecated::deserialize(input) };
|
||||
match $process_instruction(&program_id, &accounts, &instruction_data) {
|
||||
Ok(()) => $crate::entrypoint_deprecated::SUCCESS,
|
||||
Err(error) => error.into(),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Deserialize the input arguments
|
||||
///
|
||||
/// # Safety
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec<AccountInfo<'a>>, &'a [u8]) {
|
||||
let mut offset: usize = 0;
|
||||
|
||||
// Number of accounts present
|
||||
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let num_accounts = *(input.add(offset) as *const u64) as usize;
|
||||
offset += size_of::<u64>();
|
||||
|
||||
// Account Infos
|
||||
|
||||
let mut accounts = Vec::with_capacity(num_accounts);
|
||||
for _ in 0..num_accounts {
|
||||
let dup_info = *(input.add(offset) as *const u8);
|
||||
offset += size_of::<u8>();
|
||||
if dup_info == std::u8::MAX {
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let is_signer = *(input.add(offset) as *const u8) != 0;
|
||||
offset += size_of::<u8>();
|
||||
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let is_writable = *(input.add(offset) as *const u8) != 0;
|
||||
offset += size_of::<u8>();
|
||||
|
||||
let key: &Pubkey = &*(input.add(offset) as *const Pubkey);
|
||||
offset += size_of::<Pubkey>();
|
||||
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let lamports = Rc::new(RefCell::new(&mut *(input.add(offset) as *mut u64)));
|
||||
offset += size_of::<u64>();
|
||||
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let data_len = *(input.add(offset) as *const u64) as usize;
|
||||
offset += size_of::<u64>();
|
||||
|
||||
let data = Rc::new(RefCell::new({
|
||||
from_raw_parts_mut(input.add(offset), data_len)
|
||||
}));
|
||||
offset += data_len;
|
||||
|
||||
let owner: &Pubkey = &*(input.add(offset) as *const Pubkey);
|
||||
offset += size_of::<Pubkey>();
|
||||
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let executable = *(input.add(offset) as *const u8) != 0;
|
||||
offset += size_of::<u8>();
|
||||
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let rent_epoch = *(input.add(offset) as *const u64);
|
||||
offset += size_of::<u64>();
|
||||
|
||||
accounts.push(AccountInfo {
|
||||
is_signer,
|
||||
is_writable,
|
||||
key,
|
||||
lamports,
|
||||
data,
|
||||
owner,
|
||||
executable,
|
||||
rent_epoch,
|
||||
});
|
||||
} else {
|
||||
// Duplicate account, clone the original
|
||||
accounts.push(accounts[dup_info as usize].clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Instruction data
|
||||
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let instruction_data_len = *(input.add(offset) as *const u64) as usize;
|
||||
offset += size_of::<u64>();
|
||||
|
||||
let instruction_data = { from_raw_parts(input.add(offset), instruction_data_len) };
|
||||
offset += instruction_data_len;
|
||||
|
||||
// Program Id
|
||||
|
||||
let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
|
||||
|
||||
(program_id, accounts, instruction_data)
|
||||
}
|
202
sdk/program/src/epoch_schedule.rs
Normal file
202
sdk/program/src/epoch_schedule.rs
Normal file
@ -0,0 +1,202 @@
|
||||
//! configuration for epochs, slots
|
||||
|
||||
/// 1 Epoch = 400 * 8192 ms ~= 55 minutes
|
||||
pub use crate::clock::{Epoch, Slot, DEFAULT_SLOTS_PER_EPOCH};
|
||||
|
||||
/// The number of slots before an epoch starts to calculate the leader schedule.
|
||||
/// Default is an entire epoch, i.e. leader schedule for epoch X is calculated at
|
||||
/// the beginning of epoch X - 1.
|
||||
pub const DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET: u64 = DEFAULT_SLOTS_PER_EPOCH;
|
||||
|
||||
/// The maximum number of slots before an epoch starts to calculate the leader schedule.
|
||||
/// Default is an entire epoch, i.e. leader schedule for epoch X is calculated at
|
||||
/// the beginning of epoch X - 1.
|
||||
pub const MAX_LEADER_SCHEDULE_EPOCH_OFFSET: u64 = 3;
|
||||
|
||||
/// based on MAX_LOCKOUT_HISTORY from vote_program
|
||||
pub const MINIMUM_SLOTS_PER_EPOCH: u64 = 32;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, AbiExample)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EpochSchedule {
|
||||
/// The maximum number of slots in each epoch.
|
||||
pub slots_per_epoch: u64,
|
||||
|
||||
/// A number of slots before beginning of an epoch to calculate
|
||||
/// a leader schedule for that epoch
|
||||
pub leader_schedule_slot_offset: u64,
|
||||
|
||||
/// whether epochs start short and grow
|
||||
pub warmup: bool,
|
||||
|
||||
/// basically: log2(slots_per_epoch) - log2(MINIMUM_SLOTS_PER_EPOCH)
|
||||
pub first_normal_epoch: Epoch,
|
||||
|
||||
/// basically: MINIMUM_SLOTS_PER_EPOCH * (2.pow(first_normal_epoch) - 1)
|
||||
pub first_normal_slot: Slot,
|
||||
}
|
||||
|
||||
impl Default for EpochSchedule {
|
||||
fn default() -> Self {
|
||||
Self::custom(
|
||||
DEFAULT_SLOTS_PER_EPOCH,
|
||||
DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET,
|
||||
true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl EpochSchedule {
|
||||
pub fn new(slots_per_epoch: u64) -> Self {
|
||||
Self::custom(slots_per_epoch, slots_per_epoch, true)
|
||||
}
|
||||
pub fn custom(slots_per_epoch: u64, leader_schedule_slot_offset: u64, warmup: bool) -> Self {
|
||||
assert!(slots_per_epoch >= MINIMUM_SLOTS_PER_EPOCH as u64);
|
||||
let (first_normal_epoch, first_normal_slot) = if warmup {
|
||||
let next_power_of_two = slots_per_epoch.next_power_of_two();
|
||||
let log2_slots_per_epoch = next_power_of_two
|
||||
.trailing_zeros()
|
||||
.saturating_sub(MINIMUM_SLOTS_PER_EPOCH.trailing_zeros());
|
||||
|
||||
(
|
||||
u64::from(log2_slots_per_epoch),
|
||||
next_power_of_two.saturating_sub(MINIMUM_SLOTS_PER_EPOCH),
|
||||
)
|
||||
} else {
|
||||
(0, 0)
|
||||
};
|
||||
EpochSchedule {
|
||||
slots_per_epoch,
|
||||
leader_schedule_slot_offset,
|
||||
warmup,
|
||||
first_normal_epoch,
|
||||
first_normal_slot,
|
||||
}
|
||||
}
|
||||
|
||||
/// get the length of the given epoch (in slots)
|
||||
pub fn get_slots_in_epoch(&self, epoch: Epoch) -> u64 {
|
||||
if epoch < self.first_normal_epoch {
|
||||
2u64.pow(epoch as u32 + MINIMUM_SLOTS_PER_EPOCH.trailing_zeros() as u32)
|
||||
} else {
|
||||
self.slots_per_epoch
|
||||
}
|
||||
}
|
||||
|
||||
/// get the epoch for which the given slot should save off
|
||||
/// information about stakers
|
||||
pub fn get_leader_schedule_epoch(&self, slot: Slot) -> Epoch {
|
||||
if slot < self.first_normal_slot {
|
||||
// until we get to normal slots, behave as if leader_schedule_slot_offset == slots_per_epoch
|
||||
self.get_epoch_and_slot_index(slot).0 + 1
|
||||
} else {
|
||||
self.first_normal_epoch
|
||||
+ (slot - self.first_normal_slot + self.leader_schedule_slot_offset)
|
||||
/ self.slots_per_epoch
|
||||
}
|
||||
}
|
||||
|
||||
/// get epoch for the given slot
|
||||
pub fn get_epoch(&self, slot: Slot) -> Epoch {
|
||||
self.get_epoch_and_slot_index(slot).0
|
||||
}
|
||||
|
||||
/// get epoch and offset into the epoch for the given slot
|
||||
pub fn get_epoch_and_slot_index(&self, slot: Slot) -> (Epoch, u64) {
|
||||
if slot < self.first_normal_slot {
|
||||
let epoch = (slot + MINIMUM_SLOTS_PER_EPOCH + 1)
|
||||
.next_power_of_two()
|
||||
.trailing_zeros()
|
||||
- MINIMUM_SLOTS_PER_EPOCH.trailing_zeros()
|
||||
- 1;
|
||||
|
||||
let epoch_len = 2u64.pow(epoch + MINIMUM_SLOTS_PER_EPOCH.trailing_zeros());
|
||||
|
||||
(
|
||||
u64::from(epoch),
|
||||
slot - (epoch_len - MINIMUM_SLOTS_PER_EPOCH),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
self.first_normal_epoch + ((slot - self.first_normal_slot) / self.slots_per_epoch),
|
||||
(slot - self.first_normal_slot) % self.slots_per_epoch,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_first_slot_in_epoch(&self, epoch: Epoch) -> Slot {
|
||||
if epoch <= self.first_normal_epoch {
|
||||
(2u64.pow(epoch as u32) - 1) * MINIMUM_SLOTS_PER_EPOCH
|
||||
} else {
|
||||
(epoch - self.first_normal_epoch) * self.slots_per_epoch + self.first_normal_slot
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_last_slot_in_epoch(&self, epoch: Epoch) -> Slot {
|
||||
self.get_first_slot_in_epoch(epoch) + self.get_slots_in_epoch(epoch) - 1
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_epoch_schedule() {
|
||||
// one week of slots at 8 ticks/slot, 10 ticks/sec is
|
||||
// (1 * 7 * 24 * 4500u64).next_power_of_two();
|
||||
|
||||
// test values between MINIMUM_SLOT_LEN and MINIMUM_SLOT_LEN * 16, should cover a good mix
|
||||
for slots_per_epoch in MINIMUM_SLOTS_PER_EPOCH..=MINIMUM_SLOTS_PER_EPOCH * 16 {
|
||||
let epoch_schedule = EpochSchedule::custom(slots_per_epoch, slots_per_epoch / 2, true);
|
||||
|
||||
assert_eq!(epoch_schedule.get_first_slot_in_epoch(0), 0);
|
||||
assert_eq!(
|
||||
epoch_schedule.get_last_slot_in_epoch(0),
|
||||
MINIMUM_SLOTS_PER_EPOCH - 1
|
||||
);
|
||||
|
||||
let mut last_leader_schedule = 0;
|
||||
let mut last_epoch = 0;
|
||||
let mut last_slots_in_epoch = MINIMUM_SLOTS_PER_EPOCH;
|
||||
for slot in 0..(2 * slots_per_epoch) {
|
||||
// verify that leader_schedule_epoch is continuous over the warmup
|
||||
// and into the first normal epoch
|
||||
|
||||
let leader_schedule = epoch_schedule.get_leader_schedule_epoch(slot);
|
||||
if leader_schedule != last_leader_schedule {
|
||||
assert_eq!(leader_schedule, last_leader_schedule + 1);
|
||||
last_leader_schedule = leader_schedule;
|
||||
}
|
||||
|
||||
let (epoch, offset) = epoch_schedule.get_epoch_and_slot_index(slot);
|
||||
|
||||
// verify that epoch increases continuously
|
||||
if epoch != last_epoch {
|
||||
assert_eq!(epoch, last_epoch + 1);
|
||||
last_epoch = epoch;
|
||||
assert_eq!(epoch_schedule.get_first_slot_in_epoch(epoch), slot);
|
||||
assert_eq!(epoch_schedule.get_last_slot_in_epoch(epoch - 1), slot - 1);
|
||||
|
||||
// verify that slots in an epoch double continuously
|
||||
// until they reach slots_per_epoch
|
||||
|
||||
let slots_in_epoch = epoch_schedule.get_slots_in_epoch(epoch);
|
||||
if slots_in_epoch != last_slots_in_epoch && slots_in_epoch != slots_per_epoch {
|
||||
assert_eq!(slots_in_epoch, last_slots_in_epoch * 2);
|
||||
}
|
||||
last_slots_in_epoch = slots_in_epoch;
|
||||
}
|
||||
// verify that the slot offset is less than slots_in_epoch
|
||||
assert!(offset < last_slots_in_epoch);
|
||||
}
|
||||
|
||||
// assert that these changed ;)
|
||||
assert!(last_leader_schedule != 0); // t
|
||||
assert!(last_epoch != 0);
|
||||
// assert that we got to "normal" mode
|
||||
assert!(last_slots_in_epoch == slots_per_epoch);
|
||||
}
|
||||
}
|
||||
}
|
368
sdk/program/src/fee_calculator.rs
Normal file
368
sdk/program/src/fee_calculator.rs
Normal file
@ -0,0 +1,368 @@
|
||||
use crate::clock::{DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT};
|
||||
use crate::message::Message;
|
||||
use crate::secp256k1_program;
|
||||
use log::*;
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, AbiExample)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FeeCalculator {
|
||||
// The current cost of a signature This amount may increase/decrease over time based on
|
||||
// cluster processing load.
|
||||
pub lamports_per_signature: u64,
|
||||
}
|
||||
|
||||
impl Default for FeeCalculator {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
lamports_per_signature: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FeeConfig {
|
||||
pub secp256k1_program_enabled: bool,
|
||||
}
|
||||
|
||||
impl Default for FeeConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
secp256k1_program_enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FeeCalculator {
|
||||
pub fn new(lamports_per_signature: u64) -> Self {
|
||||
Self {
|
||||
lamports_per_signature,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_fee(&self, message: &Message) -> u64 {
|
||||
self.calculate_fee_with_config(message, &FeeConfig::default())
|
||||
}
|
||||
|
||||
pub fn calculate_fee_with_config(&self, message: &Message, fee_config: &FeeConfig) -> u64 {
|
||||
let mut num_secp256k1_signatures: u64 = 0;
|
||||
if fee_config.secp256k1_program_enabled {
|
||||
for instruction in &message.instructions {
|
||||
let program_index = instruction.program_id_index as usize;
|
||||
// Transaction may not be sanitized here
|
||||
if program_index < message.account_keys.len() {
|
||||
let id = message.account_keys[program_index];
|
||||
if secp256k1_program::check_id(&id) && !instruction.data.is_empty() {
|
||||
num_secp256k1_signatures += instruction.data[0] as u64;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.lamports_per_signature
|
||||
* (u64::from(message.header.num_required_signatures) + num_secp256k1_signatures)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, AbiExample)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FeeRateGovernor {
|
||||
// The current cost of a signature This amount may increase/decrease over time based on
|
||||
// cluster processing load.
|
||||
#[serde(skip)]
|
||||
pub lamports_per_signature: u64,
|
||||
|
||||
// The target cost of a signature when the cluster is operating around target_signatures_per_slot
|
||||
// signatures
|
||||
pub target_lamports_per_signature: u64,
|
||||
|
||||
// Used to estimate the desired processing capacity of the cluster. As the signatures for
|
||||
// recent slots are fewer/greater than this value, lamports_per_signature will decrease/increase
|
||||
// for the next slot. A value of 0 disables lamports_per_signature fee adjustments
|
||||
pub target_signatures_per_slot: u64,
|
||||
|
||||
pub min_lamports_per_signature: u64,
|
||||
pub max_lamports_per_signature: u64,
|
||||
|
||||
// What portion of collected fees are to be destroyed, as a fraction of std::u8::MAX
|
||||
pub burn_percent: u8,
|
||||
}
|
||||
|
||||
pub const DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE: u64 = 10_000;
|
||||
pub const DEFAULT_TARGET_SIGNATURES_PER_SLOT: u64 =
|
||||
50_000 * DEFAULT_TICKS_PER_SLOT / DEFAULT_TICKS_PER_SECOND;
|
||||
|
||||
// Percentage of tx fees to burn
|
||||
pub const DEFAULT_BURN_PERCENT: u8 = 50;
|
||||
|
||||
impl Default for FeeRateGovernor {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
lamports_per_signature: 0,
|
||||
target_lamports_per_signature: DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE,
|
||||
target_signatures_per_slot: DEFAULT_TARGET_SIGNATURES_PER_SLOT,
|
||||
min_lamports_per_signature: 0,
|
||||
max_lamports_per_signature: 0,
|
||||
burn_percent: DEFAULT_BURN_PERCENT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FeeRateGovernor {
|
||||
pub fn new(target_lamports_per_signature: u64, target_signatures_per_slot: u64) -> Self {
|
||||
let base_fee_rate_governor = Self {
|
||||
target_lamports_per_signature,
|
||||
lamports_per_signature: target_lamports_per_signature,
|
||||
target_signatures_per_slot,
|
||||
..FeeRateGovernor::default()
|
||||
};
|
||||
|
||||
Self::new_derived(&base_fee_rate_governor, 0)
|
||||
}
|
||||
|
||||
pub fn new_derived(
|
||||
base_fee_rate_governor: &FeeRateGovernor,
|
||||
latest_signatures_per_slot: u64,
|
||||
) -> Self {
|
||||
let mut me = base_fee_rate_governor.clone();
|
||||
|
||||
if me.target_signatures_per_slot > 0 {
|
||||
// lamports_per_signature can range from 50% to 1000% of
|
||||
// target_lamports_per_signature
|
||||
me.min_lamports_per_signature = std::cmp::max(1, me.target_lamports_per_signature / 2);
|
||||
me.max_lamports_per_signature = me.target_lamports_per_signature * 10;
|
||||
|
||||
// What the cluster should charge at `latest_signatures_per_slot`
|
||||
let desired_lamports_per_signature =
|
||||
me.max_lamports_per_signature
|
||||
.min(me.min_lamports_per_signature.max(
|
||||
me.target_lamports_per_signature
|
||||
* std::cmp::min(latest_signatures_per_slot, std::u32::MAX as u64)
|
||||
as u64
|
||||
/ me.target_signatures_per_slot as u64,
|
||||
));
|
||||
|
||||
trace!(
|
||||
"desired_lamports_per_signature: {}",
|
||||
desired_lamports_per_signature
|
||||
);
|
||||
|
||||
let gap = desired_lamports_per_signature as i64
|
||||
- base_fee_rate_governor.lamports_per_signature as i64;
|
||||
|
||||
if gap == 0 {
|
||||
me.lamports_per_signature = desired_lamports_per_signature;
|
||||
} else {
|
||||
// Adjust fee by 5% of target_lamports_per_signature to produce a smooth
|
||||
// increase/decrease in fees over time.
|
||||
let gap_adjust =
|
||||
std::cmp::max(1, me.target_lamports_per_signature / 20) as i64 * gap.signum();
|
||||
|
||||
trace!(
|
||||
"lamports_per_signature gap is {}, adjusting by {}",
|
||||
gap,
|
||||
gap_adjust
|
||||
);
|
||||
|
||||
me.lamports_per_signature =
|
||||
me.max_lamports_per_signature
|
||||
.min(me.min_lamports_per_signature.max(
|
||||
(base_fee_rate_governor.lamports_per_signature as i64 + gap_adjust)
|
||||
as u64,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
me.lamports_per_signature = base_fee_rate_governor.target_lamports_per_signature;
|
||||
me.min_lamports_per_signature = me.target_lamports_per_signature;
|
||||
me.max_lamports_per_signature = me.target_lamports_per_signature;
|
||||
}
|
||||
debug!(
|
||||
"new_derived(): lamports_per_signature: {}",
|
||||
me.lamports_per_signature
|
||||
);
|
||||
me
|
||||
}
|
||||
|
||||
/// calculate unburned fee from a fee total, returns (unburned, burned)
|
||||
pub fn burn(&self, fees: u64) -> (u64, u64) {
|
||||
let burned = fees * u64::from(self.burn_percent) / 100;
|
||||
(fees - burned, burned)
|
||||
}
|
||||
|
||||
/// create a FeeCalculator based on current cluster signature throughput
|
||||
pub fn create_fee_calculator(&self) -> FeeCalculator {
|
||||
FeeCalculator::new(self.lamports_per_signature)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{pubkey::Pubkey, system_instruction};
|
||||
|
||||
#[test]
|
||||
fn test_fee_rate_governor_burn() {
|
||||
let mut fee_rate_governor = FeeRateGovernor::default();
|
||||
assert_eq!(fee_rate_governor.burn(2), (1, 1));
|
||||
|
||||
fee_rate_governor.burn_percent = 0;
|
||||
assert_eq!(fee_rate_governor.burn(2), (2, 0));
|
||||
|
||||
fee_rate_governor.burn_percent = 100;
|
||||
assert_eq!(fee_rate_governor.burn(2), (0, 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fee_calculator_calculate_fee() {
|
||||
// Default: no fee.
|
||||
let message = Message::default();
|
||||
assert_eq!(FeeCalculator::default().calculate_fee(&message), 0);
|
||||
|
||||
// No signature, no fee.
|
||||
assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 0);
|
||||
|
||||
// One signature, a fee.
|
||||
let pubkey0 = Pubkey::new(&[0; 32]);
|
||||
let pubkey1 = Pubkey::new(&[1; 32]);
|
||||
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
|
||||
let message = Message::new(&[ix0], Some(&pubkey0));
|
||||
assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 2);
|
||||
|
||||
// Two signatures, double the fee.
|
||||
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
|
||||
let ix1 = system_instruction::transfer(&pubkey1, &pubkey0, 1);
|
||||
let message = Message::new(&[ix0, ix1], Some(&pubkey0));
|
||||
assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fee_calculator_calculate_fee_secp256k1() {
|
||||
use crate::instruction::Instruction;
|
||||
let pubkey0 = Pubkey::new(&[0; 32]);
|
||||
let pubkey1 = Pubkey::new(&[1; 32]);
|
||||
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
|
||||
let mut secp_instruction = Instruction {
|
||||
program_id: crate::secp256k1_program::id(),
|
||||
accounts: vec![],
|
||||
data: vec![],
|
||||
};
|
||||
let mut secp_instruction2 = Instruction {
|
||||
program_id: crate::secp256k1_program::id(),
|
||||
accounts: vec![],
|
||||
data: vec![1],
|
||||
};
|
||||
|
||||
let message = Message::new(
|
||||
&[
|
||||
ix0.clone(),
|
||||
secp_instruction.clone(),
|
||||
secp_instruction2.clone(),
|
||||
],
|
||||
Some(&pubkey0),
|
||||
);
|
||||
assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 2);
|
||||
assert_eq!(
|
||||
FeeCalculator::new(1).calculate_fee_with_config(
|
||||
&message,
|
||||
&FeeConfig {
|
||||
secp256k1_program_enabled: false
|
||||
}
|
||||
),
|
||||
1
|
||||
);
|
||||
|
||||
secp_instruction.data = vec![0];
|
||||
secp_instruction2.data = vec![10];
|
||||
let message = Message::new(&[ix0, secp_instruction, secp_instruction2], Some(&pubkey0));
|
||||
assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fee_rate_governor_derived_default() {
|
||||
solana_logger::setup();
|
||||
|
||||
let f0 = FeeRateGovernor::default();
|
||||
assert_eq!(
|
||||
f0.target_signatures_per_slot,
|
||||
DEFAULT_TARGET_SIGNATURES_PER_SLOT
|
||||
);
|
||||
assert_eq!(
|
||||
f0.target_lamports_per_signature,
|
||||
DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE
|
||||
);
|
||||
assert_eq!(f0.lamports_per_signature, 0);
|
||||
|
||||
let f1 = FeeRateGovernor::new_derived(&f0, DEFAULT_TARGET_SIGNATURES_PER_SLOT);
|
||||
assert_eq!(
|
||||
f1.target_signatures_per_slot,
|
||||
DEFAULT_TARGET_SIGNATURES_PER_SLOT
|
||||
);
|
||||
assert_eq!(
|
||||
f1.target_lamports_per_signature,
|
||||
DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE
|
||||
);
|
||||
assert_eq!(
|
||||
f1.lamports_per_signature,
|
||||
DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE / 2
|
||||
); // min
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fee_rate_governor_derived_adjust() {
|
||||
solana_logger::setup();
|
||||
|
||||
let mut f = FeeRateGovernor::default();
|
||||
f.target_lamports_per_signature = 100;
|
||||
f.target_signatures_per_slot = 100;
|
||||
f = FeeRateGovernor::new_derived(&f, 0);
|
||||
|
||||
// Ramp fees up
|
||||
let mut count = 0;
|
||||
loop {
|
||||
let last_lamports_per_signature = f.lamports_per_signature;
|
||||
|
||||
f = FeeRateGovernor::new_derived(&f, std::u64::MAX);
|
||||
info!("[up] f.lamports_per_signature={}", f.lamports_per_signature);
|
||||
|
||||
// some maximum target reached
|
||||
if f.lamports_per_signature == last_lamports_per_signature {
|
||||
break;
|
||||
}
|
||||
// shouldn't take more than 1000 steps to get to minimum
|
||||
assert!(count < 1000);
|
||||
count += 1;
|
||||
}
|
||||
|
||||
// Ramp fees down
|
||||
let mut count = 0;
|
||||
loop {
|
||||
let last_lamports_per_signature = f.lamports_per_signature;
|
||||
f = FeeRateGovernor::new_derived(&f, 0);
|
||||
|
||||
info!(
|
||||
"[down] f.lamports_per_signature={}",
|
||||
f.lamports_per_signature
|
||||
);
|
||||
|
||||
// some minimum target reached
|
||||
if f.lamports_per_signature == last_lamports_per_signature {
|
||||
break;
|
||||
}
|
||||
|
||||
// shouldn't take more than 1000 steps to get to minimum
|
||||
assert!(count < 1000);
|
||||
count += 1;
|
||||
}
|
||||
|
||||
// Arrive at target rate
|
||||
let mut count = 0;
|
||||
while f.lamports_per_signature != f.target_lamports_per_signature {
|
||||
f = FeeRateGovernor::new_derived(&f, f.target_signatures_per_slot);
|
||||
info!(
|
||||
"[target] f.lamports_per_signature={}",
|
||||
f.lamports_per_signature
|
||||
);
|
||||
// shouldn't take more than 100 steps to get to target
|
||||
assert!(count < 100);
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
186
sdk/program/src/hash.rs
Normal file
186
sdk/program/src/hash.rs
Normal file
@ -0,0 +1,186 @@
|
||||
//! The `hash` module provides functions for creating SHA-256 hashes.
|
||||
|
||||
use crate::sanitize::Sanitize;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{convert::TryFrom, fmt, mem, str::FromStr};
|
||||
use thiserror::Error;
|
||||
|
||||
pub const HASH_BYTES: usize = 32;
|
||||
#[derive(
|
||||
Serialize, Deserialize, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash, AbiExample,
|
||||
)]
|
||||
#[repr(transparent)]
|
||||
pub struct Hash(pub [u8; HASH_BYTES]);
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Hasher {
|
||||
hasher: Sha256,
|
||||
}
|
||||
|
||||
impl Hasher {
|
||||
pub fn hash(&mut self, val: &[u8]) {
|
||||
self.hasher.input(val);
|
||||
}
|
||||
pub fn hashv(&mut self, vals: &[&[u8]]) {
|
||||
for val in vals {
|
||||
self.hash(val);
|
||||
}
|
||||
}
|
||||
pub fn result(self) -> Hash {
|
||||
// At the time of this writing, the sha2 library is stuck on an old version
|
||||
// of generic_array (0.9.0). Decouple ourselves with a clone to our version.
|
||||
Hash(<[u8; HASH_BYTES]>::try_from(self.hasher.result().as_slice()).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl Sanitize for Hash {}
|
||||
|
||||
impl AsRef<[u8]> for Hash {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Hash {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", bs58::encode(self.0).into_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Hash {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", bs58::encode(self.0).into_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Error)]
|
||||
pub enum ParseHashError {
|
||||
#[error("string decoded to wrong size for hash")]
|
||||
WrongSize,
|
||||
#[error("failed to decoded string to hash")]
|
||||
Invalid,
|
||||
}
|
||||
|
||||
impl FromStr for Hash {
|
||||
type Err = ParseHashError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let bytes = bs58::decode(s)
|
||||
.into_vec()
|
||||
.map_err(|_| ParseHashError::Invalid)?;
|
||||
if bytes.len() != mem::size_of::<Hash>() {
|
||||
Err(ParseHashError::WrongSize)
|
||||
} else {
|
||||
Ok(Hash::new(&bytes))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash {
|
||||
pub fn new(hash_slice: &[u8]) -> Self {
|
||||
Hash(<[u8; HASH_BYTES]>::try_from(hash_slice).unwrap())
|
||||
}
|
||||
|
||||
pub const fn new_from_array(hash_array: [u8; HASH_BYTES]) -> Self {
|
||||
Self(hash_array)
|
||||
}
|
||||
|
||||
/// unique Hash for tests and benchmarks.
|
||||
pub fn new_unique() -> Self {
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
static I: AtomicU64 = AtomicU64::new(1);
|
||||
|
||||
let mut b = [0u8; HASH_BYTES];
|
||||
let i = I.fetch_add(1, Ordering::Relaxed);
|
||||
b[0..8].copy_from_slice(&i.to_le_bytes());
|
||||
Self::new(&b)
|
||||
}
|
||||
|
||||
pub fn to_bytes(self) -> [u8; HASH_BYTES] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a Sha256 hash for the given data.
|
||||
pub fn hashv(vals: &[&[u8]]) -> Hash {
|
||||
// Perform the calculation inline, calling this from within a program is
|
||||
// not supported
|
||||
#[cfg(not(target_arch = "bpf"))]
|
||||
{
|
||||
let mut hasher = Hasher::default();
|
||||
hasher.hashv(vals);
|
||||
hasher.result()
|
||||
}
|
||||
// Call via a system call to perform the calculation
|
||||
#[cfg(target_arch = "bpf")]
|
||||
{
|
||||
extern "C" {
|
||||
fn sol_sha256(vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64;
|
||||
};
|
||||
let mut hash_result = [0; HASH_BYTES];
|
||||
unsafe {
|
||||
sol_sha256(
|
||||
vals as *const _ as *const u8,
|
||||
vals.len() as u64,
|
||||
&mut hash_result as *mut _ as *mut u8,
|
||||
);
|
||||
}
|
||||
Hash::new_from_array(hash_result)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a Sha256 hash for the given data.
|
||||
pub fn hash(val: &[u8]) -> Hash {
|
||||
hashv(&[val])
|
||||
}
|
||||
|
||||
/// Return the hash of the given hash extended with the given value.
|
||||
pub fn extend_and_hash(id: &Hash, val: &[u8]) -> Hash {
|
||||
let mut hash_data = id.as_ref().to_vec();
|
||||
hash_data.extend_from_slice(val);
|
||||
hash(&hash_data)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_new_unique() {
|
||||
assert!(Hash::new_unique() != Hash::new_unique());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_fromstr() {
|
||||
let hash = hash(&[1u8]);
|
||||
|
||||
let mut hash_base58_str = bs58::encode(hash).into_string();
|
||||
|
||||
assert_eq!(hash_base58_str.parse::<Hash>(), Ok(hash));
|
||||
|
||||
hash_base58_str.push_str(&bs58::encode(hash.0).into_string());
|
||||
assert_eq!(
|
||||
hash_base58_str.parse::<Hash>(),
|
||||
Err(ParseHashError::WrongSize)
|
||||
);
|
||||
|
||||
hash_base58_str.truncate(hash_base58_str.len() / 2);
|
||||
assert_eq!(hash_base58_str.parse::<Hash>(), Ok(hash));
|
||||
|
||||
hash_base58_str.truncate(hash_base58_str.len() / 2);
|
||||
assert_eq!(
|
||||
hash_base58_str.parse::<Hash>(),
|
||||
Err(ParseHashError::WrongSize)
|
||||
);
|
||||
|
||||
let mut hash_base58_str = bs58::encode(hash.0).into_string();
|
||||
assert_eq!(hash_base58_str.parse::<Hash>(), Ok(hash));
|
||||
|
||||
// throw some non-base58 stuff in there
|
||||
hash_base58_str.replace_range(..1, "I");
|
||||
assert_eq!(
|
||||
hash_base58_str.parse::<Hash>(),
|
||||
Err(ParseHashError::Invalid)
|
||||
);
|
||||
}
|
||||
}
|
4
sdk/program/src/incinerator.rs
Normal file
4
sdk/program/src/incinerator.rs
Normal file
@ -0,0 +1,4 @@
|
||||
//! Lamports credited to this address will be removed from the total supply (burned) at the end of
|
||||
//! the current block.
|
||||
|
||||
crate::declare_id!("1nc1nerator11111111111111111111111111111111");
|
299
sdk/program/src/instruction.rs
Normal file
299
sdk/program/src/instruction.rs
Normal file
@ -0,0 +1,299 @@
|
||||
//! Defines a composable Instruction type and a memory-efficient CompiledInstruction.
|
||||
|
||||
use crate::sanitize::Sanitize;
|
||||
use crate::{pubkey::Pubkey, short_vec};
|
||||
use bincode::serialize;
|
||||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Reasons the runtime might have rejected an instruction.
|
||||
#[derive(Serialize, Deserialize, Debug, Error, PartialEq, Eq, Clone, AbiExample, AbiEnumVisitor)]
|
||||
pub enum InstructionError {
|
||||
/// Deprecated! Use CustomError instead!
|
||||
/// The program instruction returned an error
|
||||
#[error("generic instruction error")]
|
||||
GenericError,
|
||||
|
||||
/// The arguments provided to a program were invalid
|
||||
#[error("invalid program argument")]
|
||||
InvalidArgument,
|
||||
|
||||
/// An instruction's data contents were invalid
|
||||
#[error("invalid instruction data")]
|
||||
InvalidInstructionData,
|
||||
|
||||
/// An account's data contents was invalid
|
||||
#[error("invalid account data for instruction")]
|
||||
InvalidAccountData,
|
||||
|
||||
/// An account's data was too small
|
||||
#[error("account data too small for instruction")]
|
||||
AccountDataTooSmall,
|
||||
|
||||
/// An account's balance was too small to complete the instruction
|
||||
#[error("insufficient funds for instruction")]
|
||||
InsufficientFunds,
|
||||
|
||||
/// The account did not have the expected program id
|
||||
#[error("incorrect program id for instruction")]
|
||||
IncorrectProgramId,
|
||||
|
||||
/// A signature was required but not found
|
||||
#[error("missing required signature for instruction")]
|
||||
MissingRequiredSignature,
|
||||
|
||||
/// An initialize instruction was sent to an account that has already been initialized.
|
||||
#[error("instruction requires an uninitialized account")]
|
||||
AccountAlreadyInitialized,
|
||||
|
||||
/// An attempt to operate on an account that hasn't been initialized.
|
||||
#[error("instruction requires an initialized account")]
|
||||
UninitializedAccount,
|
||||
|
||||
/// Program's instruction lamport balance does not equal the balance after the instruction
|
||||
#[error("sum of account balances before and after instruction do not match")]
|
||||
UnbalancedInstruction,
|
||||
|
||||
/// Program modified an account's program id
|
||||
#[error("instruction modified the program id of an account")]
|
||||
ModifiedProgramId,
|
||||
|
||||
/// Program spent the lamports of an account that doesn't belong to it
|
||||
#[error("instruction spent from the balance of an account it does not own")]
|
||||
ExternalAccountLamportSpend,
|
||||
|
||||
/// Program modified the data of an account that doesn't belong to it
|
||||
#[error("instruction modified data of an account it does not own")]
|
||||
ExternalAccountDataModified,
|
||||
|
||||
/// Read-only account's lamports modified
|
||||
#[error("instruction changed the balance of a read-only account")]
|
||||
ReadonlyLamportChange,
|
||||
|
||||
/// Read-only account's data was modified
|
||||
#[error("instruction modified data of a read-only account")]
|
||||
ReadonlyDataModified,
|
||||
|
||||
/// An account was referenced more than once in a single instruction
|
||||
// Deprecated, instructions can now contain duplicate accounts
|
||||
#[error("instruction contains duplicate accounts")]
|
||||
DuplicateAccountIndex,
|
||||
|
||||
/// Executable bit on account changed, but shouldn't have
|
||||
#[error("instruction changed executable bit of an account")]
|
||||
ExecutableModified,
|
||||
|
||||
/// Rent_epoch account changed, but shouldn't have
|
||||
#[error("instruction modified rent epoch of an account")]
|
||||
RentEpochModified,
|
||||
|
||||
/// The instruction expected additional account keys
|
||||
#[error("insufficient account keys for instruction")]
|
||||
NotEnoughAccountKeys,
|
||||
|
||||
/// A non-system program changed the size of the account data
|
||||
#[error("non-system instruction changed account size")]
|
||||
AccountDataSizeChanged,
|
||||
|
||||
/// The instruction expected an executable account
|
||||
#[error("instruction expected an executable account")]
|
||||
AccountNotExecutable,
|
||||
|
||||
/// Failed to borrow a reference to account data, already borrowed
|
||||
#[error("instruction tries to borrow reference for an account which is already borrowed")]
|
||||
AccountBorrowFailed,
|
||||
|
||||
/// Account data has an outstanding reference after a program's execution
|
||||
#[error("instruction left account with an outstanding reference borrowed")]
|
||||
AccountBorrowOutstanding,
|
||||
|
||||
/// The same account was multiply passed to an on-chain program's entrypoint, but the program
|
||||
/// modified them differently. A program can only modify one instance of the account because
|
||||
/// the runtime cannot determine which changes to pick or how to merge them if both are modified
|
||||
#[error("instruction modifications of multiply-passed account differ")]
|
||||
DuplicateAccountOutOfSync,
|
||||
|
||||
/// Allows on-chain programs to implement program-specific error types and see them returned
|
||||
/// by the Solana runtime. A program-specific error may be any type that is represented as
|
||||
/// or serialized to a u32 integer.
|
||||
#[error("custom program error: {0:#x}")]
|
||||
Custom(u32),
|
||||
|
||||
/// The return value from the program was invalid. Valid errors are either a defined builtin
|
||||
/// error value or a user-defined error in the lower 32 bits.
|
||||
#[error("program returned invalid error code")]
|
||||
InvalidError,
|
||||
|
||||
/// Executable account's data was modified
|
||||
#[error("instruction changed executable accounts data")]
|
||||
ExecutableDataModified,
|
||||
|
||||
/// Executable account's lamports modified
|
||||
#[error("instruction changed the balance of a executable account")]
|
||||
ExecutableLamportChange,
|
||||
|
||||
/// Executable accounts must be rent exempt
|
||||
#[error("executable accounts must be rent exempt")]
|
||||
ExecutableAccountNotRentExempt,
|
||||
|
||||
/// Unsupported program id
|
||||
#[error("Unsupported program id")]
|
||||
UnsupportedProgramId,
|
||||
|
||||
/// Cross-program invocation call depth too deep
|
||||
#[error("Cross-program invocation call depth too deep")]
|
||||
CallDepth,
|
||||
|
||||
/// An account required by the instruction is missing
|
||||
#[error("An account required by the instruction is missing")]
|
||||
MissingAccount,
|
||||
|
||||
/// Cross-program invocation reentrancy not allowed for this instruction
|
||||
#[error("Cross-program invocation reentrancy not allowed for this instruction")]
|
||||
ReentrancyNotAllowed,
|
||||
|
||||
/// Length of the seed is too long for address generation
|
||||
#[error("Length of the seed is too long for address generation")]
|
||||
MaxSeedLengthExceeded,
|
||||
|
||||
/// Provided seeds do not result in a valid address
|
||||
#[error("Provided seeds do not result in a valid address")]
|
||||
InvalidSeeds,
|
||||
|
||||
/// Failed to reallocate account data of this length
|
||||
#[error("Failed to reallocate account data")]
|
||||
InvalidRealloc,
|
||||
|
||||
/// Computational budget exceeded
|
||||
#[error("Computational budget exceeded")]
|
||||
ComputationalBudgetExceeded,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct Instruction {
|
||||
/// Pubkey of the instruction processor that executes this instruction
|
||||
pub program_id: Pubkey,
|
||||
/// Metadata for what accounts should be passed to the instruction processor
|
||||
pub accounts: Vec<AccountMeta>,
|
||||
/// Opaque data passed to the instruction processor
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
pub fn new<T: Serialize>(program_id: Pubkey, data: &T, accounts: Vec<AccountMeta>) -> Self {
|
||||
let data = serialize(data).unwrap();
|
||||
Self {
|
||||
program_id,
|
||||
data,
|
||||
accounts,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Account metadata used to define Instructions
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct AccountMeta {
|
||||
/// An account's public key
|
||||
pub pubkey: Pubkey,
|
||||
/// True if an Instruction requires a Transaction signature matching `pubkey`.
|
||||
pub is_signer: bool,
|
||||
/// True if the `pubkey` can be loaded as a read-write account.
|
||||
pub is_writable: bool,
|
||||
}
|
||||
|
||||
impl AccountMeta {
|
||||
pub fn new(pubkey: Pubkey, is_signer: bool) -> Self {
|
||||
Self {
|
||||
pubkey,
|
||||
is_signer,
|
||||
is_writable: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_readonly(pubkey: Pubkey, is_signer: bool) -> Self {
|
||||
Self {
|
||||
pubkey,
|
||||
is_signer,
|
||||
is_writable: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An instruction to execute a program
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompiledInstruction {
|
||||
/// Index into the transaction keys array indicating the program account that executes this instruction
|
||||
pub program_id_index: u8,
|
||||
/// Ordered indices into the transaction keys array indicating which accounts to pass to the program
|
||||
#[serde(with = "short_vec")]
|
||||
pub accounts: Vec<u8>,
|
||||
/// The program input data
|
||||
#[serde(with = "short_vec")]
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Sanitize for CompiledInstruction {}
|
||||
|
||||
impl CompiledInstruction {
|
||||
pub fn new<T: Serialize>(program_ids_index: u8, data: &T, accounts: Vec<u8>) -> Self {
|
||||
let data = serialize(data).unwrap();
|
||||
Self {
|
||||
program_id_index: program_ids_index,
|
||||
data,
|
||||
accounts,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn program_id<'a>(&self, program_ids: &'a [Pubkey]) -> &'a Pubkey {
|
||||
&program_ids[self.program_id_index as usize]
|
||||
}
|
||||
|
||||
/// Visit each unique instruction account index once
|
||||
pub fn visit_each_account(
|
||||
&self,
|
||||
work: &mut dyn FnMut(usize, usize) -> Result<(), InstructionError>,
|
||||
) -> Result<(), InstructionError> {
|
||||
let mut unique_index = 0;
|
||||
'root: for (i, account_index) in self.accounts.iter().enumerate() {
|
||||
// Note: This is an O(n^2) algorithm,
|
||||
// but performed on a very small slice and requires no heap allocations
|
||||
for account_index_before in self.accounts[..i].iter() {
|
||||
if account_index_before == account_index {
|
||||
continue 'root; // skip dups
|
||||
}
|
||||
}
|
||||
work(unique_index, *account_index as usize)?;
|
||||
unique_index += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_visit_each_account() {
|
||||
let do_work = |accounts: &[u8]| -> (usize, usize) {
|
||||
let mut unique_total = 0;
|
||||
let mut account_total = 0;
|
||||
let mut work = |unique_index: usize, account_index: usize| {
|
||||
unique_total += unique_index;
|
||||
account_total += account_index;
|
||||
Ok(())
|
||||
};
|
||||
let instruction = CompiledInstruction::new(0, &[0], accounts.to_vec());
|
||||
instruction.visit_each_account(&mut work).unwrap();
|
||||
|
||||
(unique_total, account_total)
|
||||
};
|
||||
|
||||
assert_eq!((6, 6), do_work(&[0, 1, 2, 3]));
|
||||
assert_eq!((6, 6), do_work(&[0, 1, 1, 2, 3]));
|
||||
assert_eq!((6, 6), do_work(&[0, 1, 2, 3, 3]));
|
||||
assert_eq!((6, 6), do_work(&[0, 0, 1, 1, 2, 2, 3, 3]));
|
||||
assert_eq!((0, 2), do_work(&[2, 2]));
|
||||
}
|
||||
}
|
@ -1,2 +1,71 @@
|
||||
#![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(specialization))]
|
||||
#![cfg_attr(RUSTC_NEEDS_PROC_MACRO_HYGIENE, feature(proc_macro_hygiene))]
|
||||
|
||||
// Allows macro expansion of `use ::solana_program_sdk::*` to work within this crate
|
||||
extern crate self as solana_program_sdk;
|
||||
|
||||
pub mod account;
|
||||
pub mod account_info;
|
||||
pub mod account_utils;
|
||||
pub mod bpf_loader;
|
||||
pub mod bpf_loader_deprecated;
|
||||
pub mod clock;
|
||||
pub mod decode_error;
|
||||
pub mod entrypoint;
|
||||
pub mod entrypoint_deprecated;
|
||||
pub mod epoch_schedule;
|
||||
pub mod fee_calculator;
|
||||
pub mod hash;
|
||||
pub mod incinerator;
|
||||
pub mod instruction;
|
||||
pub mod loader_instruction;
|
||||
pub mod log;
|
||||
pub mod message;
|
||||
pub mod native_token;
|
||||
pub mod nonce;
|
||||
pub mod program;
|
||||
pub mod program_error;
|
||||
pub mod program_option;
|
||||
pub mod program_pack;
|
||||
pub mod program_stubs;
|
||||
pub mod pubkey;
|
||||
pub mod rent;
|
||||
pub mod sanitize;
|
||||
pub mod secp256k1_program;
|
||||
pub mod serialize_utils;
|
||||
pub mod short_vec;
|
||||
pub mod slot_hashes;
|
||||
pub mod slot_history;
|
||||
pub mod stake_history;
|
||||
pub mod system_instruction;
|
||||
pub mod system_program;
|
||||
pub mod sysvar;
|
||||
|
||||
/// Convenience macro to declare a static public key and functions to interact with it
|
||||
///
|
||||
/// Input: a single literal base58 string representation of a program's id
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # // wrapper is used so that the macro invocation occurs in the item position
|
||||
/// # // rather than in the statement position which isn't allowed.
|
||||
/// use std::str::FromStr;
|
||||
/// use solana_program_sdk::{declare_id, pubkey::Pubkey};
|
||||
///
|
||||
/// # mod item_wrapper {
|
||||
/// # use solana_program_sdk::declare_id;
|
||||
/// declare_id!("My11111111111111111111111111111111111111111");
|
||||
/// # }
|
||||
/// # use item_wrapper::id;
|
||||
///
|
||||
/// let my_id = Pubkey::from_str("My11111111111111111111111111111111111111111").unwrap();
|
||||
/// assert_eq!(id(), my_id);
|
||||
/// ```
|
||||
pub use solana_sdk_macro::program_sdk_declare_id as declare_id;
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
#[macro_use]
|
||||
extern crate solana_frozen_abi_macro;
|
||||
|
53
sdk/program/src/loader_instruction.rs
Normal file
53
sdk/program/src/loader_instruction.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use crate::{
|
||||
instruction::{AccountMeta, Instruction},
|
||||
pubkey::Pubkey,
|
||||
sysvar::rent,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum LoaderInstruction {
|
||||
/// Write program data into an account
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. [WRITE, SIGNER] Account to write to
|
||||
Write {
|
||||
/// Offset at which to write the given bytes
|
||||
offset: u32,
|
||||
|
||||
/// Serialized program data
|
||||
#[serde(with = "serde_bytes")]
|
||||
bytes: Vec<u8>,
|
||||
},
|
||||
|
||||
/// Finalize an account loaded with program data for execution
|
||||
///
|
||||
/// The exact preparation steps is loader specific but on success the loader must set the executable
|
||||
/// bit of the account.
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. [WRITE, SIGNER] The account to prepare for execution
|
||||
/// 1. [] Rent sysvar
|
||||
Finalize,
|
||||
}
|
||||
|
||||
pub fn write(
|
||||
account_pubkey: &Pubkey,
|
||||
program_id: &Pubkey,
|
||||
offset: u32,
|
||||
bytes: Vec<u8>,
|
||||
) -> Instruction {
|
||||
let account_metas = vec![AccountMeta::new(*account_pubkey, true)];
|
||||
Instruction::new(
|
||||
*program_id,
|
||||
&LoaderInstruction::Write { offset, bytes },
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn finalize(account_pubkey: &Pubkey, program_id: &Pubkey) -> Instruction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*account_pubkey, true),
|
||||
AccountMeta::new(rent::id(), false),
|
||||
];
|
||||
Instruction::new(*program_id, &LoaderInstruction::Finalize, account_metas)
|
||||
}
|
98
sdk/program/src/log.rs
Normal file
98
sdk/program/src/log.rs
Normal file
@ -0,0 +1,98 @@
|
||||
//! @brief Solana Rust-based BPF program logging
|
||||
|
||||
use crate::account_info::AccountInfo;
|
||||
|
||||
/// Prints a string
|
||||
/// There are two forms and are fast
|
||||
/// 1. Single string
|
||||
/// 2. 5 integers
|
||||
#[macro_export]
|
||||
macro_rules! info {
|
||||
($msg:expr) => {
|
||||
$crate::log::sol_log($msg)
|
||||
};
|
||||
($arg1:expr, $arg2:expr, $arg3:expr, $arg4:expr, $arg5:expr) => {
|
||||
$crate::log::sol_log_64(
|
||||
$arg1 as u64,
|
||||
$arg2 as u64,
|
||||
$arg3 as u64,
|
||||
$arg4 as u64,
|
||||
$arg5 as u64,
|
||||
)
|
||||
}; // `format!()` is not supported yet, Issue #3099
|
||||
// `format!()` incurs a very large runtime overhead so it should be used with care
|
||||
// ($($arg:tt)*) => ($crate::log::sol_log(&format!($($arg)*)));
|
||||
}
|
||||
|
||||
/// Prints a string to stdout
|
||||
///
|
||||
/// @param message - Message to print
|
||||
#[inline]
|
||||
pub fn sol_log(message: &str) {
|
||||
#[cfg(target_arch = "bpf")]
|
||||
unsafe {
|
||||
sol_log_(message.as_ptr(), message.len() as u64);
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "bpf"))]
|
||||
crate::program_stubs::sol_log(message);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "bpf")]
|
||||
extern "C" {
|
||||
fn sol_log_(message: *const u8, len: u64);
|
||||
}
|
||||
|
||||
/// Prints 64 bit values represented as hexadecimal to stdout
|
||||
///
|
||||
/// @param argx - integer arguments to print
|
||||
|
||||
#[inline]
|
||||
pub fn sol_log_64(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64) {
|
||||
#[cfg(target_arch = "bpf")]
|
||||
unsafe {
|
||||
sol_log_64_(arg1, arg2, arg3, arg4, arg5);
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "bpf"))]
|
||||
crate::program_stubs::sol_log_64(arg1, arg2, arg3, arg4, arg5);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "bpf")]
|
||||
extern "C" {
|
||||
fn sol_log_64_(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64);
|
||||
}
|
||||
|
||||
/// Prints the hexadecimal representation of a slice
|
||||
///
|
||||
/// @param slice - The array to print
|
||||
#[allow(dead_code)]
|
||||
pub fn sol_log_slice(slice: &[u8]) {
|
||||
for (i, s) in slice.iter().enumerate() {
|
||||
info!(0, 0, 0, i, *s);
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints the hexadecimal representation of the program's input parameters
|
||||
///
|
||||
/// @param ka - A pointer to an array of `AccountInfo` to print
|
||||
/// @param data - A pointer to the instruction data to print
|
||||
#[allow(dead_code)]
|
||||
pub fn sol_log_params(accounts: &[AccountInfo], data: &[u8]) {
|
||||
for (i, account) in accounts.iter().enumerate() {
|
||||
info!("AccountInfo");
|
||||
info!(0, 0, 0, 0, i);
|
||||
info!("- Is signer");
|
||||
info!(0, 0, 0, 0, account.is_signer);
|
||||
info!("- Key");
|
||||
account.key.log();
|
||||
info!("- Lamports");
|
||||
info!(0, 0, 0, 0, account.lamports());
|
||||
info!("- Account data length");
|
||||
info!(0, 0, 0, 0, account.data_len());
|
||||
info!("- Owner");
|
||||
account.owner.log();
|
||||
}
|
||||
info!("Instruction data");
|
||||
sol_log_slice(data);
|
||||
}
|
797
sdk/program/src/message.rs
Normal file
797
sdk/program/src/message.rs
Normal file
@ -0,0 +1,797 @@
|
||||
//! A library for generating a message from a sequence of instructions
|
||||
|
||||
use crate::sanitize::{Sanitize, SanitizeError};
|
||||
use crate::serialize_utils::{
|
||||
append_slice, append_u16, append_u8, read_pubkey, read_slice, read_u16, read_u8,
|
||||
};
|
||||
use crate::{
|
||||
hash::Hash,
|
||||
instruction::{AccountMeta, CompiledInstruction, Instruction},
|
||||
pubkey::Pubkey,
|
||||
short_vec, system_instruction,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
fn position(keys: &[Pubkey], key: &Pubkey) -> u8 {
|
||||
keys.iter().position(|k| k == key).unwrap() as u8
|
||||
}
|
||||
|
||||
fn compile_instruction(ix: &Instruction, keys: &[Pubkey]) -> CompiledInstruction {
|
||||
let accounts: Vec<_> = ix
|
||||
.accounts
|
||||
.iter()
|
||||
.map(|account_meta| position(keys, &account_meta.pubkey))
|
||||
.collect();
|
||||
|
||||
CompiledInstruction {
|
||||
program_id_index: position(keys, &ix.program_id),
|
||||
data: ix.data.clone(),
|
||||
accounts,
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_instructions(ixs: &[Instruction], keys: &[Pubkey]) -> Vec<CompiledInstruction> {
|
||||
ixs.iter().map(|ix| compile_instruction(ix, keys)).collect()
|
||||
}
|
||||
|
||||
/// A helper struct to collect pubkeys referenced by a set of instructions and read-only counts
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct InstructionKeys {
|
||||
pub signed_keys: Vec<Pubkey>,
|
||||
pub unsigned_keys: Vec<Pubkey>,
|
||||
pub num_readonly_signed_accounts: u8,
|
||||
pub num_readonly_unsigned_accounts: u8,
|
||||
}
|
||||
|
||||
impl InstructionKeys {
|
||||
fn new(
|
||||
signed_keys: Vec<Pubkey>,
|
||||
unsigned_keys: Vec<Pubkey>,
|
||||
num_readonly_signed_accounts: u8,
|
||||
num_readonly_unsigned_accounts: u8,
|
||||
) -> Self {
|
||||
Self {
|
||||
signed_keys,
|
||||
unsigned_keys,
|
||||
num_readonly_signed_accounts,
|
||||
num_readonly_unsigned_accounts,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return pubkeys referenced by all instructions, with the ones needing signatures first. If the
|
||||
/// payer key is provided, it is always placed first in the list of signed keys. Read-only signed
|
||||
/// accounts are placed last in the set of signed accounts. Read-only unsigned accounts,
|
||||
/// including program ids, are placed last in the set. No duplicates and order is preserved.
|
||||
fn get_keys(instructions: &[Instruction], payer: Option<&Pubkey>) -> InstructionKeys {
|
||||
let programs: Vec<_> = get_program_ids(instructions)
|
||||
.iter()
|
||||
.map(|program_id| AccountMeta {
|
||||
pubkey: *program_id,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
})
|
||||
.collect();
|
||||
let mut keys_and_signed: Vec<_> = instructions
|
||||
.iter()
|
||||
.flat_map(|ix| ix.accounts.iter())
|
||||
.collect();
|
||||
keys_and_signed.extend(&programs);
|
||||
keys_and_signed.sort_by(|x, y| {
|
||||
y.is_signer
|
||||
.cmp(&x.is_signer)
|
||||
.then(y.is_writable.cmp(&x.is_writable))
|
||||
});
|
||||
|
||||
let payer_account_meta;
|
||||
if let Some(payer) = payer {
|
||||
payer_account_meta = AccountMeta {
|
||||
pubkey: *payer,
|
||||
is_signer: true,
|
||||
is_writable: true,
|
||||
};
|
||||
keys_and_signed.insert(0, &payer_account_meta);
|
||||
}
|
||||
|
||||
let mut unique_metas: Vec<AccountMeta> = vec![];
|
||||
for account_meta in keys_and_signed {
|
||||
// Promote to writable if a later AccountMeta requires it
|
||||
if let Some(x) = unique_metas
|
||||
.iter_mut()
|
||||
.find(|x| x.pubkey == account_meta.pubkey)
|
||||
{
|
||||
x.is_writable |= account_meta.is_writable;
|
||||
continue;
|
||||
}
|
||||
unique_metas.push(account_meta.clone());
|
||||
}
|
||||
|
||||
let mut signed_keys = vec![];
|
||||
let mut unsigned_keys = vec![];
|
||||
let mut num_readonly_signed_accounts = 0;
|
||||
let mut num_readonly_unsigned_accounts = 0;
|
||||
for account_meta in unique_metas {
|
||||
if account_meta.is_signer {
|
||||
signed_keys.push(account_meta.pubkey);
|
||||
if !account_meta.is_writable {
|
||||
num_readonly_signed_accounts += 1;
|
||||
}
|
||||
} else {
|
||||
unsigned_keys.push(account_meta.pubkey);
|
||||
if !account_meta.is_writable {
|
||||
num_readonly_unsigned_accounts += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
InstructionKeys::new(
|
||||
signed_keys,
|
||||
unsigned_keys,
|
||||
num_readonly_signed_accounts,
|
||||
num_readonly_unsigned_accounts,
|
||||
)
|
||||
}
|
||||
|
||||
/// Return program ids referenced by all instructions. No duplicates and order is preserved.
|
||||
fn get_program_ids(instructions: &[Instruction]) -> Vec<Pubkey> {
|
||||
instructions
|
||||
.iter()
|
||||
.map(|ix| ix.program_id)
|
||||
.unique()
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[frozen_abi(digest = "BVC5RhetsNpheGipt5rUrkR6RDDUHtD5sCLK1UjymL4S")]
|
||||
#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MessageHeader {
|
||||
/// The number of signatures required for this message to be considered valid. The
|
||||
/// signatures must match the first `num_required_signatures` of `account_keys`.
|
||||
/// NOTE: Serialization-related changes must be paired with the direct read at sigverify.
|
||||
pub num_required_signatures: u8,
|
||||
|
||||
/// The last num_readonly_signed_accounts of the signed keys are read-only accounts. Programs
|
||||
/// may process multiple transactions that load read-only accounts within a single PoH entry,
|
||||
/// but are not permitted to credit or debit lamports or modify account data. Transactions
|
||||
/// targeting the same read-write account are evaluated sequentially.
|
||||
pub num_readonly_signed_accounts: u8,
|
||||
|
||||
/// The last num_readonly_unsigned_accounts of the unsigned keys are read-only accounts.
|
||||
pub num_readonly_unsigned_accounts: u8,
|
||||
}
|
||||
|
||||
#[frozen_abi(digest = "A18PN3BWKw4hU69STY79SyRS3tS6w54nCgYRRx77vQiL")]
|
||||
#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Message {
|
||||
/// The message header, identifying signed and read-only `account_keys`
|
||||
/// NOTE: Serialization-related changes must be paired with the direct read at sigverify.
|
||||
pub header: MessageHeader,
|
||||
|
||||
/// All the account keys used by this transaction
|
||||
#[serde(with = "short_vec")]
|
||||
pub account_keys: Vec<Pubkey>,
|
||||
|
||||
/// The id of a recent ledger entry.
|
||||
pub recent_blockhash: Hash,
|
||||
|
||||
/// Programs that will be executed in sequence and committed in one atomic transaction if all
|
||||
/// succeed.
|
||||
#[serde(with = "short_vec")]
|
||||
pub instructions: Vec<CompiledInstruction>,
|
||||
}
|
||||
|
||||
impl Sanitize for Message {
|
||||
fn sanitize(&self) -> std::result::Result<(), SanitizeError> {
|
||||
// signing area and read-only non-signing area should not overlap
|
||||
if self.header.num_required_signatures as usize
|
||||
+ self.header.num_readonly_unsigned_accounts as usize
|
||||
> self.account_keys.len()
|
||||
{
|
||||
return Err(SanitizeError::IndexOutOfBounds);
|
||||
}
|
||||
|
||||
// there should be at least 1 RW fee-payer account.
|
||||
if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
|
||||
return Err(SanitizeError::IndexOutOfBounds);
|
||||
}
|
||||
|
||||
for ci in &self.instructions {
|
||||
if ci.program_id_index as usize >= self.account_keys.len() {
|
||||
return Err(SanitizeError::IndexOutOfBounds);
|
||||
}
|
||||
// A program cannot be a payer.
|
||||
if ci.program_id_index == 0 {
|
||||
return Err(SanitizeError::IndexOutOfBounds);
|
||||
}
|
||||
for ai in &ci.accounts {
|
||||
if *ai as usize >= self.account_keys.len() {
|
||||
return Err(SanitizeError::IndexOutOfBounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.account_keys.sanitize()?;
|
||||
self.recent_blockhash.sanitize()?;
|
||||
self.instructions.sanitize()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Message {
|
||||
pub fn new_with_compiled_instructions(
|
||||
num_required_signatures: u8,
|
||||
num_readonly_signed_accounts: u8,
|
||||
num_readonly_unsigned_accounts: u8,
|
||||
account_keys: Vec<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
instructions: Vec<CompiledInstruction>,
|
||||
) -> Self {
|
||||
Self {
|
||||
header: MessageHeader {
|
||||
num_required_signatures,
|
||||
num_readonly_signed_accounts,
|
||||
num_readonly_unsigned_accounts,
|
||||
},
|
||||
account_keys,
|
||||
recent_blockhash,
|
||||
instructions,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(instructions: &[Instruction], payer: Option<&Pubkey>) -> Self {
|
||||
let InstructionKeys {
|
||||
mut signed_keys,
|
||||
unsigned_keys,
|
||||
num_readonly_signed_accounts,
|
||||
num_readonly_unsigned_accounts,
|
||||
} = get_keys(instructions, payer);
|
||||
let num_required_signatures = signed_keys.len() as u8;
|
||||
signed_keys.extend(&unsigned_keys);
|
||||
let instructions = compile_instructions(instructions, &signed_keys);
|
||||
Self::new_with_compiled_instructions(
|
||||
num_required_signatures,
|
||||
num_readonly_signed_accounts,
|
||||
num_readonly_unsigned_accounts,
|
||||
signed_keys,
|
||||
Hash::default(),
|
||||
instructions,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_with_nonce(
|
||||
mut instructions: Vec<Instruction>,
|
||||
payer: Option<&Pubkey>,
|
||||
nonce_account_pubkey: &Pubkey,
|
||||
nonce_authority_pubkey: &Pubkey,
|
||||
) -> Self {
|
||||
let nonce_ix = system_instruction::advance_nonce_account(
|
||||
&nonce_account_pubkey,
|
||||
&nonce_authority_pubkey,
|
||||
);
|
||||
instructions.insert(0, nonce_ix);
|
||||
Self::new(&instructions, payer)
|
||||
}
|
||||
|
||||
pub fn compile_instruction(&self, ix: &Instruction) -> CompiledInstruction {
|
||||
compile_instruction(ix, &self.account_keys)
|
||||
}
|
||||
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
bincode::serialize(self).unwrap()
|
||||
}
|
||||
|
||||
pub fn program_ids(&self) -> Vec<&Pubkey> {
|
||||
self.instructions
|
||||
.iter()
|
||||
.map(|ix| &self.account_keys[ix.program_id_index as usize])
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn is_key_passed_to_program(&self, index: usize) -> bool {
|
||||
if let Ok(index) = u8::try_from(index) {
|
||||
for ix in self.instructions.iter() {
|
||||
if ix.accounts.contains(&index) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn program_position(&self, index: usize) -> Option<usize> {
|
||||
let program_ids = self.program_ids();
|
||||
program_ids
|
||||
.iter()
|
||||
.position(|&&pubkey| pubkey == self.account_keys[index])
|
||||
}
|
||||
|
||||
pub fn is_writable(&self, i: usize) -> bool {
|
||||
i < (self.header.num_required_signatures - self.header.num_readonly_signed_accounts)
|
||||
as usize
|
||||
|| (i >= self.header.num_required_signatures as usize
|
||||
&& i < self.account_keys.len()
|
||||
- self.header.num_readonly_unsigned_accounts as usize)
|
||||
}
|
||||
|
||||
pub fn is_signer(&self, i: usize) -> bool {
|
||||
i < self.header.num_required_signatures as usize
|
||||
}
|
||||
|
||||
pub fn get_account_keys_by_lock_type(&self) -> (Vec<&Pubkey>, Vec<&Pubkey>) {
|
||||
let mut writable_keys = vec![];
|
||||
let mut readonly_keys = vec![];
|
||||
for (i, key) in self.account_keys.iter().enumerate() {
|
||||
if self.is_writable(i) {
|
||||
writable_keys.push(key);
|
||||
} else {
|
||||
readonly_keys.push(key);
|
||||
}
|
||||
}
|
||||
(writable_keys, readonly_keys)
|
||||
}
|
||||
|
||||
// First encode the number of instructions:
|
||||
// [0..2 - num_instructions
|
||||
//
|
||||
// Then a table of offsets of where to find them in the data
|
||||
// 3..2*num_instructions table of instruction offsets
|
||||
//
|
||||
// Each instruction is then encoded as:
|
||||
// 0..2 - num_accounts
|
||||
// 3 - meta_byte -> (bit 0 signer, bit 1 is_writable)
|
||||
// 4..36 - pubkey - 32 bytes
|
||||
// 36..64 - program_id
|
||||
// 33..34 - data len - u16
|
||||
// 35..data_len - data
|
||||
pub fn serialize_instructions(&self) -> Vec<u8> {
|
||||
// 64 bytes is a reasonable guess, calculating exactly is slower in benchmarks
|
||||
let mut data = Vec::with_capacity(self.instructions.len() * (32 * 2));
|
||||
append_u16(&mut data, self.instructions.len() as u16);
|
||||
for _ in 0..self.instructions.len() {
|
||||
append_u16(&mut data, 0);
|
||||
}
|
||||
for (i, instruction) in self.instructions.iter().enumerate() {
|
||||
let start_instruction_offset = data.len() as u16;
|
||||
let start = 2 + (2 * i);
|
||||
data[start..start + 2].copy_from_slice(&start_instruction_offset.to_le_bytes());
|
||||
append_u16(&mut data, instruction.accounts.len() as u16);
|
||||
for account_index in &instruction.accounts {
|
||||
let account_index = *account_index as usize;
|
||||
let is_signer = self.is_signer(account_index);
|
||||
let is_writable = self.is_writable(account_index);
|
||||
let mut meta_byte = 0;
|
||||
if is_signer {
|
||||
meta_byte |= 1 << Self::IS_SIGNER_BIT;
|
||||
}
|
||||
if is_writable {
|
||||
meta_byte |= 1 << Self::IS_WRITABLE_BIT;
|
||||
}
|
||||
append_u8(&mut data, meta_byte);
|
||||
append_slice(&mut data, self.account_keys[account_index].as_ref());
|
||||
}
|
||||
|
||||
let program_id = &self.account_keys[instruction.program_id_index as usize];
|
||||
append_slice(&mut data, program_id.as_ref());
|
||||
append_u16(&mut data, instruction.data.len() as u16);
|
||||
append_slice(&mut data, &instruction.data);
|
||||
}
|
||||
data
|
||||
}
|
||||
|
||||
const IS_SIGNER_BIT: usize = 0;
|
||||
const IS_WRITABLE_BIT: usize = 1;
|
||||
|
||||
pub fn deserialize_instruction(
|
||||
index: usize,
|
||||
data: &[u8],
|
||||
) -> Result<Instruction, SanitizeError> {
|
||||
let mut current = 0;
|
||||
let _num_instructions = read_u16(&mut current, &data)?;
|
||||
|
||||
// index into the instruction byte-offset table.
|
||||
current += index * 2;
|
||||
let start = read_u16(&mut current, &data)?;
|
||||
|
||||
current = start as usize;
|
||||
let num_accounts = read_u16(&mut current, &data)?;
|
||||
let mut accounts = Vec::with_capacity(num_accounts as usize);
|
||||
for _ in 0..num_accounts {
|
||||
let meta_byte = read_u8(&mut current, &data)?;
|
||||
let mut is_signer = false;
|
||||
let mut is_writable = false;
|
||||
if meta_byte & (1 << Self::IS_SIGNER_BIT) != 0 {
|
||||
is_signer = true;
|
||||
}
|
||||
if meta_byte & (1 << Self::IS_WRITABLE_BIT) != 0 {
|
||||
is_writable = true;
|
||||
}
|
||||
let pubkey = read_pubkey(&mut current, &data)?;
|
||||
accounts.push(AccountMeta {
|
||||
pubkey,
|
||||
is_signer,
|
||||
is_writable,
|
||||
});
|
||||
}
|
||||
let program_id = read_pubkey(&mut current, &data)?;
|
||||
let data_len = read_u16(&mut current, &data)?;
|
||||
let data = read_slice(&mut current, &data, data_len as usize)?;
|
||||
Ok(Instruction {
|
||||
program_id,
|
||||
data,
|
||||
accounts,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::instruction::AccountMeta;
|
||||
|
||||
#[test]
|
||||
fn test_message_unique_program_ids() {
|
||||
let program_id0 = Pubkey::default();
|
||||
let program_ids = get_program_ids(&[
|
||||
Instruction::new(program_id0, &0, vec![]),
|
||||
Instruction::new(program_id0, &0, vec![]),
|
||||
]);
|
||||
assert_eq!(program_ids, vec![program_id0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_unique_program_ids_not_adjacent() {
|
||||
let program_id0 = Pubkey::default();
|
||||
let program_id1 = Pubkey::new_unique();
|
||||
let program_ids = get_program_ids(&[
|
||||
Instruction::new(program_id0, &0, vec![]),
|
||||
Instruction::new(program_id1, &0, vec![]),
|
||||
Instruction::new(program_id0, &0, vec![]),
|
||||
]);
|
||||
assert_eq!(program_ids, vec![program_id0, program_id1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_unique_program_ids_order_preserved() {
|
||||
let program_id0 = Pubkey::new_unique();
|
||||
let program_id1 = Pubkey::default(); // Key less than program_id0
|
||||
let program_ids = get_program_ids(&[
|
||||
Instruction::new(program_id0, &0, vec![]),
|
||||
Instruction::new(program_id1, &0, vec![]),
|
||||
Instruction::new(program_id0, &0, vec![]),
|
||||
]);
|
||||
assert_eq!(program_ids, vec![program_id0, program_id1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_unique_keys_both_signed() {
|
||||
let program_id = Pubkey::default();
|
||||
let id0 = Pubkey::default();
|
||||
let keys = get_keys(
|
||||
&[
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]),
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]),
|
||||
],
|
||||
None,
|
||||
);
|
||||
assert_eq!(keys, InstructionKeys::new(vec![id0], vec![], 0, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_unique_keys_signed_and_payer() {
|
||||
let program_id = Pubkey::default();
|
||||
let id0 = Pubkey::default();
|
||||
let keys = get_keys(
|
||||
&[Instruction::new(
|
||||
program_id,
|
||||
&0,
|
||||
vec![AccountMeta::new(id0, true)],
|
||||
)],
|
||||
Some(&id0),
|
||||
);
|
||||
assert_eq!(keys, InstructionKeys::new(vec![id0], vec![], 0, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_unique_keys_unsigned_and_payer() {
|
||||
let program_id = Pubkey::default();
|
||||
let id0 = Pubkey::default();
|
||||
let keys = get_keys(
|
||||
&[Instruction::new(
|
||||
program_id,
|
||||
&0,
|
||||
vec![AccountMeta::new(id0, false)],
|
||||
)],
|
||||
Some(&id0),
|
||||
);
|
||||
assert_eq!(keys, InstructionKeys::new(vec![id0], vec![], 0, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_unique_keys_one_signed() {
|
||||
let program_id = Pubkey::default();
|
||||
let id0 = Pubkey::default();
|
||||
let keys = get_keys(
|
||||
&[
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]),
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]),
|
||||
],
|
||||
None,
|
||||
);
|
||||
assert_eq!(keys, InstructionKeys::new(vec![id0], vec![], 0, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_unique_keys_one_readonly_signed() {
|
||||
let program_id = Pubkey::default();
|
||||
let id0 = Pubkey::default();
|
||||
let keys = get_keys(
|
||||
&[
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id0, true)]),
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]),
|
||||
],
|
||||
None,
|
||||
);
|
||||
|
||||
// Ensure the key is no longer readonly
|
||||
assert_eq!(keys, InstructionKeys::new(vec![id0], vec![], 0, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_unique_keys_one_readonly_unsigned() {
|
||||
let program_id = Pubkey::default();
|
||||
let id0 = Pubkey::default();
|
||||
let keys = get_keys(
|
||||
&[
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id0, false)]),
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]),
|
||||
],
|
||||
None,
|
||||
);
|
||||
|
||||
// Ensure the key is no longer readonly
|
||||
assert_eq!(keys, InstructionKeys::new(vec![], vec![id0], 0, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_unique_keys_order_preserved() {
|
||||
let program_id = Pubkey::default();
|
||||
let id0 = Pubkey::new_unique();
|
||||
let id1 = Pubkey::default(); // Key less than id0
|
||||
let keys = get_keys(
|
||||
&[
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]),
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new(id1, false)]),
|
||||
],
|
||||
None,
|
||||
);
|
||||
assert_eq!(keys, InstructionKeys::new(vec![], vec![id0, id1], 0, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_unique_keys_not_adjacent() {
|
||||
let program_id = Pubkey::default();
|
||||
let id0 = Pubkey::default();
|
||||
let id1 = Pubkey::new_unique();
|
||||
let keys = get_keys(
|
||||
&[
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]),
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new(id1, false)]),
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]),
|
||||
],
|
||||
None,
|
||||
);
|
||||
assert_eq!(keys, InstructionKeys::new(vec![id0], vec![id1], 0, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_signed_keys_first() {
|
||||
let program_id = Pubkey::default();
|
||||
let id0 = Pubkey::default();
|
||||
let id1 = Pubkey::new_unique();
|
||||
let keys = get_keys(
|
||||
&[
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]),
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new(id1, true)]),
|
||||
],
|
||||
None,
|
||||
);
|
||||
assert_eq!(keys, InstructionKeys::new(vec![id1], vec![id0], 0, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Ensure there's a way to calculate the number of required signatures.
|
||||
fn test_message_signed_keys_len() {
|
||||
let program_id = Pubkey::default();
|
||||
let id0 = Pubkey::default();
|
||||
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]);
|
||||
let message = Message::new(&[ix], None);
|
||||
assert_eq!(message.header.num_required_signatures, 0);
|
||||
|
||||
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]);
|
||||
let message = Message::new(&[ix], Some(&id0));
|
||||
assert_eq!(message.header.num_required_signatures, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_readonly_keys_last() {
|
||||
let program_id = Pubkey::default();
|
||||
let id0 = Pubkey::default(); // Identical key/program_id should be de-duped
|
||||
let id1 = Pubkey::new_unique();
|
||||
let id2 = Pubkey::new_unique();
|
||||
let id3 = Pubkey::new_unique();
|
||||
let keys = get_keys(
|
||||
&[
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id0, false)]),
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id1, true)]),
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new(id2, false)]),
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new(id3, true)]),
|
||||
],
|
||||
None,
|
||||
);
|
||||
assert_eq!(
|
||||
keys,
|
||||
InstructionKeys::new(vec![id3, id1], vec![id2, id0], 1, 1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_kitchen_sink() {
|
||||
let program_id0 = Pubkey::new_unique();
|
||||
let program_id1 = Pubkey::new_unique();
|
||||
let id0 = Pubkey::default();
|
||||
let id1 = Pubkey::new_unique();
|
||||
let message = Message::new(
|
||||
&[
|
||||
Instruction::new(program_id0, &0, vec![AccountMeta::new(id0, false)]),
|
||||
Instruction::new(program_id1, &0, vec![AccountMeta::new(id1, true)]),
|
||||
Instruction::new(program_id0, &0, vec![AccountMeta::new(id1, false)]),
|
||||
],
|
||||
Some(&id1),
|
||||
);
|
||||
assert_eq!(
|
||||
message.instructions[0],
|
||||
CompiledInstruction::new(2, &0, vec![1])
|
||||
);
|
||||
assert_eq!(
|
||||
message.instructions[1],
|
||||
CompiledInstruction::new(3, &0, vec![0])
|
||||
);
|
||||
assert_eq!(
|
||||
message.instructions[2],
|
||||
CompiledInstruction::new(2, &0, vec![0])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_payer_first() {
|
||||
let program_id = Pubkey::default();
|
||||
let payer = Pubkey::new_unique();
|
||||
let id0 = Pubkey::default();
|
||||
|
||||
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]);
|
||||
let message = Message::new(&[ix], Some(&payer));
|
||||
assert_eq!(message.header.num_required_signatures, 1);
|
||||
|
||||
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]);
|
||||
let message = Message::new(&[ix], Some(&payer));
|
||||
assert_eq!(message.header.num_required_signatures, 2);
|
||||
|
||||
let ix = Instruction::new(
|
||||
program_id,
|
||||
&0,
|
||||
vec![AccountMeta::new(payer, true), AccountMeta::new(id0, true)],
|
||||
);
|
||||
let message = Message::new(&[ix], Some(&payer));
|
||||
assert_eq!(message.header.num_required_signatures, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_program_last() {
|
||||
let program_id = Pubkey::default();
|
||||
let id0 = Pubkey::new_unique();
|
||||
let id1 = Pubkey::new_unique();
|
||||
let keys = get_keys(
|
||||
&[
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id0, false)]),
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id1, true)]),
|
||||
],
|
||||
None,
|
||||
);
|
||||
assert_eq!(
|
||||
keys,
|
||||
InstructionKeys::new(vec![id1], vec![id0, program_id], 1, 2)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_program_position() {
|
||||
let program_id0 = Pubkey::default();
|
||||
let program_id1 = Pubkey::new_unique();
|
||||
let id = Pubkey::new_unique();
|
||||
let message = Message::new(
|
||||
&[
|
||||
Instruction::new(program_id0, &0, vec![AccountMeta::new(id, false)]),
|
||||
Instruction::new(program_id1, &0, vec![AccountMeta::new(id, true)]),
|
||||
],
|
||||
Some(&id),
|
||||
);
|
||||
assert_eq!(message.program_position(0), None);
|
||||
assert_eq!(message.program_position(1), Some(0));
|
||||
assert_eq!(message.program_position(2), Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_writable() {
|
||||
let key0 = Pubkey::new_unique();
|
||||
let key1 = Pubkey::new_unique();
|
||||
let key2 = Pubkey::new_unique();
|
||||
let key3 = Pubkey::new_unique();
|
||||
let key4 = Pubkey::new_unique();
|
||||
let key5 = Pubkey::new_unique();
|
||||
|
||||
let message = Message {
|
||||
header: MessageHeader {
|
||||
num_required_signatures: 3,
|
||||
num_readonly_signed_accounts: 2,
|
||||
num_readonly_unsigned_accounts: 1,
|
||||
},
|
||||
account_keys: vec![key0, key1, key2, key3, key4, key5],
|
||||
recent_blockhash: Hash::default(),
|
||||
instructions: vec![],
|
||||
};
|
||||
assert_eq!(message.is_writable(0), true);
|
||||
assert_eq!(message.is_writable(1), false);
|
||||
assert_eq!(message.is_writable(2), false);
|
||||
assert_eq!(message.is_writable(3), true);
|
||||
assert_eq!(message.is_writable(4), true);
|
||||
assert_eq!(message.is_writable(5), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_account_keys_by_lock_type() {
|
||||
let program_id = Pubkey::default();
|
||||
let id0 = Pubkey::new_unique();
|
||||
let id1 = Pubkey::new_unique();
|
||||
let id2 = Pubkey::new_unique();
|
||||
let id3 = Pubkey::new_unique();
|
||||
let message = Message::new(
|
||||
&[
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]),
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new(id1, true)]),
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id2, false)]),
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id3, true)]),
|
||||
],
|
||||
Some(&id1),
|
||||
);
|
||||
assert_eq!(
|
||||
message.get_account_keys_by_lock_type(),
|
||||
(vec![&id1, &id0], vec![&id3, &id2, &program_id])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decompile_instructions() {
|
||||
solana_logger::setup();
|
||||
let program_id0 = Pubkey::new_unique();
|
||||
let program_id1 = Pubkey::new_unique();
|
||||
let id0 = Pubkey::new_unique();
|
||||
let id1 = Pubkey::new_unique();
|
||||
let id2 = Pubkey::new_unique();
|
||||
let id3 = Pubkey::new_unique();
|
||||
let instructions = vec![
|
||||
Instruction::new(program_id0, &0, vec![AccountMeta::new(id0, false)]),
|
||||
Instruction::new(program_id0, &0, vec![AccountMeta::new(id1, true)]),
|
||||
Instruction::new(program_id1, &0, vec![AccountMeta::new_readonly(id2, false)]),
|
||||
Instruction::new(program_id1, &0, vec![AccountMeta::new_readonly(id3, true)]),
|
||||
];
|
||||
|
||||
let message = Message::new(&instructions, Some(&id1));
|
||||
let serialized = message.serialize_instructions();
|
||||
for (i, instruction) in instructions.iter().enumerate() {
|
||||
assert_eq!(
|
||||
Message::deserialize_instruction(i, &serialized).unwrap(),
|
||||
*instruction
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
38
sdk/program/src/native_token.rs
Normal file
38
sdk/program/src/native_token.rs
Normal file
@ -0,0 +1,38 @@
|
||||
/// There are 10^9 lamports in one SOL
|
||||
pub const LAMPORTS_PER_SOL: u64 = 1_000_000_000;
|
||||
|
||||
/// Approximately convert fractional native tokens (lamports) into native tokens (SOL)
|
||||
pub fn lamports_to_sol(lamports: u64) -> f64 {
|
||||
lamports as f64 / LAMPORTS_PER_SOL as f64
|
||||
}
|
||||
|
||||
/// Approximately convert native tokens (SOL) into fractional native tokens (lamports)
|
||||
pub fn sol_to_lamports(sol: f64) -> u64 {
|
||||
(sol * LAMPORTS_PER_SOL as f64) as u64
|
||||
}
|
||||
|
||||
use std::fmt::{Debug, Display, Formatter, Result};
|
||||
pub struct Sol(pub u64);
|
||||
|
||||
impl Sol {
|
||||
fn write_in_sol(&self, f: &mut Formatter) -> Result {
|
||||
write!(
|
||||
f,
|
||||
"◎{}.{:09}",
|
||||
self.0 / LAMPORTS_PER_SOL,
|
||||
self.0 % LAMPORTS_PER_SOL
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Sol {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
self.write_in_sol(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Sol {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
self.write_in_sol(f)
|
||||
}
|
||||
}
|
884
sdk/program/src/nonce/account.rs
Normal file
884
sdk/program/src/nonce/account.rs
Normal file
@ -0,0 +1,884 @@
|
||||
use crate::{
|
||||
account::{self, KeyedAccount},
|
||||
account_utils::State as AccountUtilsState,
|
||||
instruction::InstructionError,
|
||||
nonce::{self, state::Versions, State},
|
||||
pubkey::Pubkey,
|
||||
system_instruction::NonceError,
|
||||
system_program,
|
||||
sysvar::{recent_blockhashes::RecentBlockhashes, rent::Rent},
|
||||
};
|
||||
use std::{cell::RefCell, collections::HashSet};
|
||||
|
||||
pub trait Account {
|
||||
fn advance_nonce_account(
|
||||
&self,
|
||||
recent_blockhashes: &RecentBlockhashes,
|
||||
signers: &HashSet<Pubkey>,
|
||||
) -> Result<(), InstructionError>;
|
||||
fn withdraw_nonce_account(
|
||||
&self,
|
||||
lamports: u64,
|
||||
to: &KeyedAccount,
|
||||
recent_blockhashes: &RecentBlockhashes,
|
||||
rent: &Rent,
|
||||
signers: &HashSet<Pubkey>,
|
||||
) -> Result<(), InstructionError>;
|
||||
fn initialize_nonce_account(
|
||||
&self,
|
||||
nonce_authority: &Pubkey,
|
||||
recent_blockhashes: &RecentBlockhashes,
|
||||
rent: &Rent,
|
||||
) -> Result<(), InstructionError>;
|
||||
fn authorize_nonce_account(
|
||||
&self,
|
||||
nonce_authority: &Pubkey,
|
||||
signers: &HashSet<Pubkey>,
|
||||
) -> Result<(), InstructionError>;
|
||||
}
|
||||
|
||||
impl<'a> Account for KeyedAccount<'a> {
|
||||
fn advance_nonce_account(
|
||||
&self,
|
||||
recent_blockhashes: &RecentBlockhashes,
|
||||
signers: &HashSet<Pubkey>,
|
||||
) -> Result<(), InstructionError> {
|
||||
if recent_blockhashes.is_empty() {
|
||||
return Err(NonceError::NoRecentBlockhashes.into());
|
||||
}
|
||||
|
||||
let state = AccountUtilsState::<Versions>::state(self)?.convert_to_current();
|
||||
match state {
|
||||
State::Initialized(data) => {
|
||||
if !signers.contains(&data.authority) {
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
let recent_blockhash = recent_blockhashes[0].blockhash;
|
||||
if data.blockhash == recent_blockhash {
|
||||
return Err(NonceError::NotExpired.into());
|
||||
}
|
||||
|
||||
let new_data = nonce::state::Data {
|
||||
blockhash: recent_blockhash,
|
||||
fee_calculator: recent_blockhashes[0].fee_calculator.clone(),
|
||||
..data
|
||||
};
|
||||
self.set_state(&Versions::new_current(State::Initialized(new_data)))
|
||||
}
|
||||
_ => Err(NonceError::BadAccountState.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn withdraw_nonce_account(
|
||||
&self,
|
||||
lamports: u64,
|
||||
to: &KeyedAccount,
|
||||
recent_blockhashes: &RecentBlockhashes,
|
||||
rent: &Rent,
|
||||
signers: &HashSet<Pubkey>,
|
||||
) -> Result<(), InstructionError> {
|
||||
let signer = match AccountUtilsState::<Versions>::state(self)?.convert_to_current() {
|
||||
State::Uninitialized => {
|
||||
if lamports > self.lamports()? {
|
||||
return Err(InstructionError::InsufficientFunds);
|
||||
}
|
||||
*self.unsigned_key()
|
||||
}
|
||||
State::Initialized(ref data) => {
|
||||
if lamports == self.lamports()? {
|
||||
if data.blockhash == recent_blockhashes[0].blockhash {
|
||||
return Err(NonceError::NotExpired.into());
|
||||
}
|
||||
} else {
|
||||
let min_balance = rent.minimum_balance(self.data_len()?);
|
||||
if lamports + min_balance > self.lamports()? {
|
||||
return Err(InstructionError::InsufficientFunds);
|
||||
}
|
||||
}
|
||||
data.authority
|
||||
}
|
||||
};
|
||||
|
||||
if !signers.contains(&signer) {
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
|
||||
self.try_account_ref_mut()?.lamports -= lamports;
|
||||
to.try_account_ref_mut()?.lamports += lamports;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn initialize_nonce_account(
|
||||
&self,
|
||||
nonce_authority: &Pubkey,
|
||||
recent_blockhashes: &RecentBlockhashes,
|
||||
rent: &Rent,
|
||||
) -> Result<(), InstructionError> {
|
||||
if recent_blockhashes.is_empty() {
|
||||
return Err(NonceError::NoRecentBlockhashes.into());
|
||||
}
|
||||
|
||||
match AccountUtilsState::<Versions>::state(self)?.convert_to_current() {
|
||||
State::Uninitialized => {
|
||||
let min_balance = rent.minimum_balance(self.data_len()?);
|
||||
if self.lamports()? < min_balance {
|
||||
return Err(InstructionError::InsufficientFunds);
|
||||
}
|
||||
let data = nonce::state::Data {
|
||||
authority: *nonce_authority,
|
||||
blockhash: recent_blockhashes[0].blockhash,
|
||||
fee_calculator: recent_blockhashes[0].fee_calculator.clone(),
|
||||
};
|
||||
self.set_state(&Versions::new_current(State::Initialized(data)))
|
||||
}
|
||||
_ => Err(NonceError::BadAccountState.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn authorize_nonce_account(
|
||||
&self,
|
||||
nonce_authority: &Pubkey,
|
||||
signers: &HashSet<Pubkey>,
|
||||
) -> Result<(), InstructionError> {
|
||||
match AccountUtilsState::<Versions>::state(self)?.convert_to_current() {
|
||||
State::Initialized(data) => {
|
||||
if !signers.contains(&data.authority) {
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
let new_data = nonce::state::Data {
|
||||
authority: *nonce_authority,
|
||||
..data
|
||||
};
|
||||
self.set_state(&Versions::new_current(State::Initialized(new_data)))
|
||||
}
|
||||
_ => Err(NonceError::BadAccountState.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_account(lamports: u64) -> RefCell<account::Account> {
|
||||
RefCell::new(
|
||||
account::Account::new_data_with_space(
|
||||
lamports,
|
||||
&Versions::new_current(State::Uninitialized),
|
||||
State::size(),
|
||||
&system_program::id(),
|
||||
)
|
||||
.expect("nonce_account"),
|
||||
)
|
||||
}
|
||||
|
||||
/// Convenience function for working with keyed accounts in tests
|
||||
pub fn with_test_keyed_account<F>(lamports: u64, signer: bool, f: F)
|
||||
where
|
||||
F: Fn(&KeyedAccount),
|
||||
{
|
||||
let pubkey = Pubkey::new_unique();
|
||||
let account = create_account(lamports);
|
||||
let keyed_account = KeyedAccount::new(&pubkey, signer, &account);
|
||||
f(&keyed_account)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{
|
||||
account::KeyedAccount,
|
||||
account_utils::State as AccountUtilsState,
|
||||
nonce::{self, State},
|
||||
system_instruction::NonceError,
|
||||
sysvar::recent_blockhashes::{create_test_recent_blockhashes, RecentBlockhashes},
|
||||
};
|
||||
use std::iter::FromIterator;
|
||||
|
||||
#[test]
|
||||
fn default_is_uninitialized() {
|
||||
assert_eq!(State::default(), State::Uninitialized)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keyed_account_expected_behavior() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, true, |keyed_account| {
|
||||
let data = nonce::state::Data {
|
||||
authority: *keyed_account.unsigned_key(),
|
||||
..nonce::state::Data::default()
|
||||
};
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(*keyed_account.signer_key().unwrap());
|
||||
let state = AccountUtilsState::<Versions>::state(keyed_account)
|
||||
.unwrap()
|
||||
.convert_to_current();
|
||||
// New is in Uninitialzed state
|
||||
assert_eq!(state, State::Uninitialized);
|
||||
let recent_blockhashes = create_test_recent_blockhashes(95);
|
||||
let authorized = keyed_account.unsigned_key();
|
||||
keyed_account
|
||||
.initialize_nonce_account(&authorized, &recent_blockhashes, &rent)
|
||||
.unwrap();
|
||||
let state = AccountUtilsState::<Versions>::state(keyed_account)
|
||||
.unwrap()
|
||||
.convert_to_current();
|
||||
let data = nonce::state::Data {
|
||||
blockhash: recent_blockhashes[0].blockhash,
|
||||
fee_calculator: recent_blockhashes[0].fee_calculator.clone(),
|
||||
..data
|
||||
};
|
||||
// First nonce instruction drives state from Uninitialized to Initialized
|
||||
assert_eq!(state, State::Initialized(data.clone()));
|
||||
let recent_blockhashes = create_test_recent_blockhashes(63);
|
||||
keyed_account
|
||||
.advance_nonce_account(&recent_blockhashes, &signers)
|
||||
.unwrap();
|
||||
let state = AccountUtilsState::<Versions>::state(keyed_account)
|
||||
.unwrap()
|
||||
.convert_to_current();
|
||||
let data = nonce::state::Data {
|
||||
blockhash: recent_blockhashes[0].blockhash,
|
||||
fee_calculator: recent_blockhashes[0].fee_calculator.clone(),
|
||||
..data
|
||||
};
|
||||
// Second nonce instruction consumes and replaces stored nonce
|
||||
assert_eq!(state, State::Initialized(data.clone()));
|
||||
let recent_blockhashes = create_test_recent_blockhashes(31);
|
||||
keyed_account
|
||||
.advance_nonce_account(&recent_blockhashes, &signers)
|
||||
.unwrap();
|
||||
let state = AccountUtilsState::<Versions>::state(keyed_account)
|
||||
.unwrap()
|
||||
.convert_to_current();
|
||||
let data = nonce::state::Data {
|
||||
blockhash: recent_blockhashes[0].blockhash,
|
||||
fee_calculator: recent_blockhashes[0].fee_calculator.clone(),
|
||||
..data
|
||||
};
|
||||
// Third nonce instruction for fun and profit
|
||||
assert_eq!(state, State::Initialized(data));
|
||||
with_test_keyed_account(42, false, |to_keyed| {
|
||||
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||
let withdraw_lamports = keyed_account.account.borrow().lamports;
|
||||
let expect_nonce_lamports =
|
||||
keyed_account.account.borrow().lamports - withdraw_lamports;
|
||||
let expect_to_lamports = to_keyed.account.borrow().lamports + withdraw_lamports;
|
||||
keyed_account
|
||||
.withdraw_nonce_account(
|
||||
withdraw_lamports,
|
||||
&to_keyed,
|
||||
&recent_blockhashes,
|
||||
&rent,
|
||||
&signers,
|
||||
)
|
||||
.unwrap();
|
||||
// Empties Account balance
|
||||
assert_eq!(
|
||||
keyed_account.account.borrow().lamports,
|
||||
expect_nonce_lamports
|
||||
);
|
||||
// Account balance goes to `to`
|
||||
assert_eq!(to_keyed.account.borrow().lamports, expect_to_lamports);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonce_inx_initialized_account_not_signer_fail() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, true, |nonce_account| {
|
||||
let recent_blockhashes = create_test_recent_blockhashes(31);
|
||||
let authority = *nonce_account.unsigned_key();
|
||||
nonce_account
|
||||
.initialize_nonce_account(&authority, &recent_blockhashes, &rent)
|
||||
.unwrap();
|
||||
let pubkey = nonce_account.account.borrow().owner;
|
||||
let nonce_account = KeyedAccount::new(&pubkey, false, nonce_account.account);
|
||||
let state = AccountUtilsState::<Versions>::state(&nonce_account)
|
||||
.unwrap()
|
||||
.convert_to_current();
|
||||
let data = nonce::state::Data {
|
||||
authority,
|
||||
blockhash: recent_blockhashes[0].blockhash,
|
||||
fee_calculator: recent_blockhashes[0].fee_calculator.clone(),
|
||||
};
|
||||
assert_eq!(state, State::Initialized(data));
|
||||
let signers = HashSet::new();
|
||||
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||
let result = nonce_account.advance_nonce_account(&recent_blockhashes, &signers);
|
||||
assert_eq!(result, Err(InstructionError::MissingRequiredSignature),);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonce_inx_with_empty_recent_blockhashes_fail() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, true, |keyed_account| {
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(*keyed_account.signer_key().unwrap());
|
||||
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||
let authorized = *keyed_account.unsigned_key();
|
||||
keyed_account
|
||||
.initialize_nonce_account(&authorized, &recent_blockhashes, &rent)
|
||||
.unwrap();
|
||||
let recent_blockhashes = RecentBlockhashes::from_iter(vec![].into_iter());
|
||||
let result = keyed_account.advance_nonce_account(&recent_blockhashes, &signers);
|
||||
assert_eq!(result, Err(NonceError::NoRecentBlockhashes.into()));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonce_inx_too_early_fail() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, true, |keyed_account| {
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(*keyed_account.signer_key().unwrap());
|
||||
let recent_blockhashes = create_test_recent_blockhashes(63);
|
||||
let authorized = *keyed_account.unsigned_key();
|
||||
keyed_account
|
||||
.initialize_nonce_account(&authorized, &recent_blockhashes, &rent)
|
||||
.unwrap();
|
||||
let result = keyed_account.advance_nonce_account(&recent_blockhashes, &signers);
|
||||
assert_eq!(result, Err(NonceError::NotExpired.into()));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonce_inx_uninitialized_account_fail() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, true, |keyed_account| {
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(*keyed_account.signer_key().unwrap());
|
||||
let recent_blockhashes = create_test_recent_blockhashes(63);
|
||||
let result = keyed_account.advance_nonce_account(&recent_blockhashes, &signers);
|
||||
assert_eq!(result, Err(NonceError::BadAccountState.into()));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonce_inx_independent_nonce_authority_ok() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, true, |nonce_account| {
|
||||
with_test_keyed_account(42, true, |nonce_authority| {
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(*nonce_account.signer_key().unwrap());
|
||||
let recent_blockhashes = create_test_recent_blockhashes(63);
|
||||
let authorized = *nonce_authority.unsigned_key();
|
||||
nonce_account
|
||||
.initialize_nonce_account(&authorized, &recent_blockhashes, &rent)
|
||||
.unwrap();
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(*nonce_authority.signer_key().unwrap());
|
||||
let recent_blockhashes = create_test_recent_blockhashes(31);
|
||||
let result = nonce_account.advance_nonce_account(&recent_blockhashes, &signers);
|
||||
assert_eq!(result, Ok(()));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonce_inx_no_nonce_authority_sig_fail() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, true, |nonce_account| {
|
||||
with_test_keyed_account(42, false, |nonce_authority| {
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(*nonce_account.signer_key().unwrap());
|
||||
let recent_blockhashes = create_test_recent_blockhashes(63);
|
||||
let authorized = *nonce_authority.unsigned_key();
|
||||
nonce_account
|
||||
.initialize_nonce_account(&authorized, &recent_blockhashes, &rent)
|
||||
.unwrap();
|
||||
let result = nonce_account.advance_nonce_account(&recent_blockhashes, &signers);
|
||||
assert_eq!(result, Err(InstructionError::MissingRequiredSignature),);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn withdraw_inx_unintialized_acc_ok() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, true, |nonce_keyed| {
|
||||
let state = AccountUtilsState::<Versions>::state(nonce_keyed)
|
||||
.unwrap()
|
||||
.convert_to_current();
|
||||
assert_eq!(state, State::Uninitialized);
|
||||
with_test_keyed_account(42, false, |to_keyed| {
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(*nonce_keyed.signer_key().unwrap());
|
||||
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||
let withdraw_lamports = nonce_keyed.account.borrow().lamports;
|
||||
let expect_nonce_lamports =
|
||||
nonce_keyed.account.borrow().lamports - withdraw_lamports;
|
||||
let expect_to_lamports = to_keyed.account.borrow().lamports + withdraw_lamports;
|
||||
nonce_keyed
|
||||
.withdraw_nonce_account(
|
||||
withdraw_lamports,
|
||||
&to_keyed,
|
||||
&recent_blockhashes,
|
||||
&rent,
|
||||
&signers,
|
||||
)
|
||||
.unwrap();
|
||||
let state = AccountUtilsState::<Versions>::state(nonce_keyed)
|
||||
.unwrap()
|
||||
.convert_to_current();
|
||||
// Withdraw instruction...
|
||||
// Deinitializes Account state
|
||||
assert_eq!(state, State::Uninitialized);
|
||||
// Empties Account balance
|
||||
assert_eq!(nonce_keyed.account.borrow().lamports, expect_nonce_lamports);
|
||||
// Account balance goes to `to`
|
||||
assert_eq!(to_keyed.account.borrow().lamports, expect_to_lamports);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn withdraw_inx_unintialized_acc_unsigned_fail() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, false, |nonce_keyed| {
|
||||
let state = AccountUtilsState::<Versions>::state(nonce_keyed)
|
||||
.unwrap()
|
||||
.convert_to_current();
|
||||
assert_eq!(state, State::Uninitialized);
|
||||
with_test_keyed_account(42, false, |to_keyed| {
|
||||
let signers = HashSet::new();
|
||||
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||
let lamports = nonce_keyed.account.borrow().lamports;
|
||||
let result = nonce_keyed.withdraw_nonce_account(
|
||||
lamports,
|
||||
&to_keyed,
|
||||
&recent_blockhashes,
|
||||
&rent,
|
||||
&signers,
|
||||
);
|
||||
assert_eq!(result, Err(InstructionError::MissingRequiredSignature),);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn withdraw_inx_unintialized_acc_insuff_funds_fail() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, true, |nonce_keyed| {
|
||||
let state = AccountUtilsState::<Versions>::state(nonce_keyed)
|
||||
.unwrap()
|
||||
.convert_to_current();
|
||||
assert_eq!(state, State::Uninitialized);
|
||||
with_test_keyed_account(42, false, |to_keyed| {
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(*nonce_keyed.signer_key().unwrap());
|
||||
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||
let lamports = nonce_keyed.account.borrow().lamports + 1;
|
||||
let result = nonce_keyed.withdraw_nonce_account(
|
||||
lamports,
|
||||
&to_keyed,
|
||||
&recent_blockhashes,
|
||||
&rent,
|
||||
&signers,
|
||||
);
|
||||
assert_eq!(result, Err(InstructionError::InsufficientFunds));
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn withdraw_inx_uninitialized_acc_two_withdraws_ok() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, true, |nonce_keyed| {
|
||||
with_test_keyed_account(42, false, |to_keyed| {
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(*nonce_keyed.signer_key().unwrap());
|
||||
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||
let withdraw_lamports = nonce_keyed.account.borrow().lamports / 2;
|
||||
let nonce_expect_lamports =
|
||||
nonce_keyed.account.borrow().lamports - withdraw_lamports;
|
||||
let to_expect_lamports = to_keyed.account.borrow().lamports + withdraw_lamports;
|
||||
nonce_keyed
|
||||
.withdraw_nonce_account(
|
||||
withdraw_lamports,
|
||||
&to_keyed,
|
||||
&recent_blockhashes,
|
||||
&rent,
|
||||
&signers,
|
||||
)
|
||||
.unwrap();
|
||||
let state = AccountUtilsState::<Versions>::state(nonce_keyed)
|
||||
.unwrap()
|
||||
.convert_to_current();
|
||||
assert_eq!(state, State::Uninitialized);
|
||||
assert_eq!(nonce_keyed.account.borrow().lamports, nonce_expect_lamports);
|
||||
assert_eq!(to_keyed.account.borrow().lamports, to_expect_lamports);
|
||||
let withdraw_lamports = nonce_keyed.account.borrow().lamports;
|
||||
let nonce_expect_lamports =
|
||||
nonce_keyed.account.borrow().lamports - withdraw_lamports;
|
||||
let to_expect_lamports = to_keyed.account.borrow().lamports + withdraw_lamports;
|
||||
nonce_keyed
|
||||
.withdraw_nonce_account(
|
||||
withdraw_lamports,
|
||||
&to_keyed,
|
||||
&recent_blockhashes,
|
||||
&rent,
|
||||
&signers,
|
||||
)
|
||||
.unwrap();
|
||||
let state = AccountUtilsState::<Versions>::state(nonce_keyed)
|
||||
.unwrap()
|
||||
.convert_to_current();
|
||||
assert_eq!(state, State::Uninitialized);
|
||||
assert_eq!(nonce_keyed.account.borrow().lamports, nonce_expect_lamports);
|
||||
assert_eq!(to_keyed.account.borrow().lamports, to_expect_lamports);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn withdraw_inx_initialized_acc_two_withdraws_ok() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, true, |nonce_keyed| {
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(*nonce_keyed.signer_key().unwrap());
|
||||
let recent_blockhashes = create_test_recent_blockhashes(31);
|
||||
let authority = *nonce_keyed.unsigned_key();
|
||||
nonce_keyed
|
||||
.initialize_nonce_account(&authority, &recent_blockhashes, &rent)
|
||||
.unwrap();
|
||||
let state = AccountUtilsState::<Versions>::state(nonce_keyed)
|
||||
.unwrap()
|
||||
.convert_to_current();
|
||||
let data = nonce::state::Data {
|
||||
authority,
|
||||
blockhash: recent_blockhashes[0].blockhash,
|
||||
fee_calculator: recent_blockhashes[0].fee_calculator.clone(),
|
||||
};
|
||||
assert_eq!(state, State::Initialized(data.clone()));
|
||||
with_test_keyed_account(42, false, |to_keyed| {
|
||||
let withdraw_lamports = nonce_keyed.account.borrow().lamports - min_lamports;
|
||||
let nonce_expect_lamports =
|
||||
nonce_keyed.account.borrow().lamports - withdraw_lamports;
|
||||
let to_expect_lamports = to_keyed.account.borrow().lamports + withdraw_lamports;
|
||||
nonce_keyed
|
||||
.withdraw_nonce_account(
|
||||
withdraw_lamports,
|
||||
&to_keyed,
|
||||
&recent_blockhashes,
|
||||
&rent,
|
||||
&signers,
|
||||
)
|
||||
.unwrap();
|
||||
let state = AccountUtilsState::<Versions>::state(nonce_keyed)
|
||||
.unwrap()
|
||||
.convert_to_current();
|
||||
let data = nonce::state::Data {
|
||||
blockhash: recent_blockhashes[0].blockhash,
|
||||
fee_calculator: recent_blockhashes[0].fee_calculator.clone(),
|
||||
..data.clone()
|
||||
};
|
||||
assert_eq!(state, State::Initialized(data));
|
||||
assert_eq!(nonce_keyed.account.borrow().lamports, nonce_expect_lamports);
|
||||
assert_eq!(to_keyed.account.borrow().lamports, to_expect_lamports);
|
||||
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||
let withdraw_lamports = nonce_keyed.account.borrow().lamports;
|
||||
let nonce_expect_lamports =
|
||||
nonce_keyed.account.borrow().lamports - withdraw_lamports;
|
||||
let to_expect_lamports = to_keyed.account.borrow().lamports + withdraw_lamports;
|
||||
nonce_keyed
|
||||
.withdraw_nonce_account(
|
||||
withdraw_lamports,
|
||||
&to_keyed,
|
||||
&recent_blockhashes,
|
||||
&rent,
|
||||
&signers,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(nonce_keyed.account.borrow().lamports, nonce_expect_lamports);
|
||||
assert_eq!(to_keyed.account.borrow().lamports, to_expect_lamports);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn withdraw_inx_initialized_acc_nonce_too_early_fail() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, true, |nonce_keyed| {
|
||||
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||
let authorized = *nonce_keyed.unsigned_key();
|
||||
nonce_keyed
|
||||
.initialize_nonce_account(&authorized, &recent_blockhashes, &rent)
|
||||
.unwrap();
|
||||
with_test_keyed_account(42, false, |to_keyed| {
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(*nonce_keyed.signer_key().unwrap());
|
||||
let withdraw_lamports = nonce_keyed.account.borrow().lamports;
|
||||
let result = nonce_keyed.withdraw_nonce_account(
|
||||
withdraw_lamports,
|
||||
&to_keyed,
|
||||
&recent_blockhashes,
|
||||
&rent,
|
||||
&signers,
|
||||
);
|
||||
assert_eq!(result, Err(NonceError::NotExpired.into()));
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn withdraw_inx_initialized_acc_insuff_funds_fail() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, true, |nonce_keyed| {
|
||||
let recent_blockhashes = create_test_recent_blockhashes(95);
|
||||
let authorized = *nonce_keyed.unsigned_key();
|
||||
nonce_keyed
|
||||
.initialize_nonce_account(&authorized, &recent_blockhashes, &rent)
|
||||
.unwrap();
|
||||
with_test_keyed_account(42, false, |to_keyed| {
|
||||
let recent_blockhashes = create_test_recent_blockhashes(63);
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(*nonce_keyed.signer_key().unwrap());
|
||||
let withdraw_lamports = nonce_keyed.account.borrow().lamports + 1;
|
||||
let result = nonce_keyed.withdraw_nonce_account(
|
||||
withdraw_lamports,
|
||||
&to_keyed,
|
||||
&recent_blockhashes,
|
||||
&rent,
|
||||
&signers,
|
||||
);
|
||||
assert_eq!(result, Err(InstructionError::InsufficientFunds));
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn withdraw_inx_initialized_acc_insuff_rent_fail() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, true, |nonce_keyed| {
|
||||
let recent_blockhashes = create_test_recent_blockhashes(95);
|
||||
let authorized = *nonce_keyed.unsigned_key();
|
||||
nonce_keyed
|
||||
.initialize_nonce_account(&authorized, &recent_blockhashes, &rent)
|
||||
.unwrap();
|
||||
with_test_keyed_account(42, false, |to_keyed| {
|
||||
let recent_blockhashes = create_test_recent_blockhashes(63);
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(*nonce_keyed.signer_key().unwrap());
|
||||
let withdraw_lamports = nonce_keyed.account.borrow().lamports - min_lamports + 1;
|
||||
let result = nonce_keyed.withdraw_nonce_account(
|
||||
withdraw_lamports,
|
||||
&to_keyed,
|
||||
&recent_blockhashes,
|
||||
&rent,
|
||||
&signers,
|
||||
);
|
||||
assert_eq!(result, Err(InstructionError::InsufficientFunds));
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initialize_inx_ok() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, true, |keyed_account| {
|
||||
let state = AccountUtilsState::<Versions>::state(keyed_account)
|
||||
.unwrap()
|
||||
.convert_to_current();
|
||||
assert_eq!(state, State::Uninitialized);
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(*keyed_account.signer_key().unwrap());
|
||||
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||
let authority = *keyed_account.unsigned_key();
|
||||
let result =
|
||||
keyed_account.initialize_nonce_account(&authority, &recent_blockhashes, &rent);
|
||||
let data = nonce::state::Data {
|
||||
authority,
|
||||
blockhash: recent_blockhashes[0].blockhash,
|
||||
fee_calculator: recent_blockhashes[0].fee_calculator.clone(),
|
||||
};
|
||||
assert_eq!(result, Ok(()));
|
||||
let state = AccountUtilsState::<Versions>::state(keyed_account)
|
||||
.unwrap()
|
||||
.convert_to_current();
|
||||
assert_eq!(state, State::Initialized(data));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initialize_inx_empty_recent_blockhashes_fail() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, true, |keyed_account| {
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(*keyed_account.signer_key().unwrap());
|
||||
let recent_blockhashes = RecentBlockhashes::from_iter(vec![].into_iter());
|
||||
let authorized = *keyed_account.unsigned_key();
|
||||
let result =
|
||||
keyed_account.initialize_nonce_account(&authorized, &recent_blockhashes, &rent);
|
||||
assert_eq!(result, Err(NonceError::NoRecentBlockhashes.into()));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initialize_inx_initialized_account_fail() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, true, |keyed_account| {
|
||||
let recent_blockhashes = create_test_recent_blockhashes(31);
|
||||
let authorized = *keyed_account.unsigned_key();
|
||||
keyed_account
|
||||
.initialize_nonce_account(&authorized, &recent_blockhashes, &rent)
|
||||
.unwrap();
|
||||
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||
let result =
|
||||
keyed_account.initialize_nonce_account(&authorized, &recent_blockhashes, &rent);
|
||||
assert_eq!(result, Err(NonceError::BadAccountState.into()));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initialize_inx_uninitialized_acc_insuff_funds_fail() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports - 42, true, |keyed_account| {
|
||||
let recent_blockhashes = create_test_recent_blockhashes(63);
|
||||
let authorized = *keyed_account.unsigned_key();
|
||||
let result =
|
||||
keyed_account.initialize_nonce_account(&authorized, &recent_blockhashes, &rent);
|
||||
assert_eq!(result, Err(InstructionError::InsufficientFunds));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authorize_inx_ok() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, true, |nonce_account| {
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(*nonce_account.signer_key().unwrap());
|
||||
let recent_blockhashes = create_test_recent_blockhashes(31);
|
||||
let authorized = *nonce_account.unsigned_key();
|
||||
nonce_account
|
||||
.initialize_nonce_account(&authorized, &recent_blockhashes, &rent)
|
||||
.unwrap();
|
||||
let authority = Pubkey::default();
|
||||
let data = nonce::state::Data {
|
||||
authority,
|
||||
blockhash: recent_blockhashes[0].blockhash,
|
||||
fee_calculator: recent_blockhashes[0].fee_calculator.clone(),
|
||||
};
|
||||
let result = nonce_account.authorize_nonce_account(&Pubkey::default(), &signers);
|
||||
assert_eq!(result, Ok(()));
|
||||
let state = AccountUtilsState::<Versions>::state(nonce_account)
|
||||
.unwrap()
|
||||
.convert_to_current();
|
||||
assert_eq!(state, State::Initialized(data));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authorize_inx_uninitialized_state_fail() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, true, |nonce_account| {
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(*nonce_account.signer_key().unwrap());
|
||||
let result = nonce_account.authorize_nonce_account(&Pubkey::default(), &signers);
|
||||
assert_eq!(result, Err(NonceError::BadAccountState.into()));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authorize_inx_bad_authority_fail() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
};
|
||||
let min_lamports = rent.minimum_balance(State::size());
|
||||
with_test_keyed_account(min_lamports + 42, true, |nonce_account| {
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(*nonce_account.signer_key().unwrap());
|
||||
let recent_blockhashes = create_test_recent_blockhashes(31);
|
||||
let authorized = &Pubkey::default().clone();
|
||||
nonce_account
|
||||
.initialize_nonce_account(&authorized, &recent_blockhashes, &rent)
|
||||
.unwrap();
|
||||
let result = nonce_account.authorize_nonce_account(&Pubkey::default(), &signers);
|
||||
assert_eq!(result, Err(InstructionError::MissingRequiredSignature));
|
||||
})
|
||||
}
|
||||
}
|
5
sdk/program/src/nonce/mod.rs
Normal file
5
sdk/program/src/nonce/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod account;
|
||||
pub use account::{create_account, Account};
|
||||
pub mod state;
|
||||
pub use state::State;
|
||||
pub mod utils;
|
39
sdk/program/src/nonce/state/current.rs
Normal file
39
sdk/program/src/nonce/state/current.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use super::Versions;
|
||||
use crate::{fee_calculator::FeeCalculator, hash::Hash, pubkey::Pubkey};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct Data {
|
||||
pub authority: Pubkey,
|
||||
pub blockhash: Hash,
|
||||
pub fee_calculator: FeeCalculator,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub enum State {
|
||||
Uninitialized,
|
||||
Initialized(Data),
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
State::Uninitialized
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn size() -> usize {
|
||||
let data = Versions::new_current(State::Initialized(Data::default()));
|
||||
bincode::serialized_size(&data).unwrap() as usize
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn default_is_uninitialized() {
|
||||
assert_eq!(State::default(), State::Uninitialized)
|
||||
}
|
||||
}
|
21
sdk/program/src/nonce/state/mod.rs
Normal file
21
sdk/program/src/nonce/state/mod.rs
Normal file
@ -0,0 +1,21 @@
|
||||
mod current;
|
||||
pub use current::{Data, State};
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub enum Versions {
|
||||
Current(Box<State>),
|
||||
}
|
||||
|
||||
impl Versions {
|
||||
pub fn new_current(state: State) -> Self {
|
||||
Self::Current(Box::new(state))
|
||||
}
|
||||
|
||||
pub fn convert_to_current(self) -> State {
|
||||
match self {
|
||||
Self::Current(state) => *state,
|
||||
}
|
||||
}
|
||||
}
|
86
sdk/program/src/nonce/utils.rs
Normal file
86
sdk/program/src/nonce/utils.rs
Normal file
@ -0,0 +1,86 @@
|
||||
use crate::{
|
||||
account::Account,
|
||||
account_utils::StateMut,
|
||||
fee_calculator::FeeCalculator,
|
||||
hash::Hash,
|
||||
nonce::{state::Versions, State},
|
||||
};
|
||||
|
||||
pub fn verify_nonce_account(acc: &Account, hash: &Hash) -> bool {
|
||||
match StateMut::<Versions>::state(acc).map(|v| v.convert_to_current()) {
|
||||
Ok(State::Initialized(ref data)) => *hash == data.blockhash,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fee_calculator_of(account: &Account) -> Option<FeeCalculator> {
|
||||
let state = StateMut::<Versions>::state(account)
|
||||
.ok()?
|
||||
.convert_to_current();
|
||||
match state {
|
||||
State::Initialized(data) => Some(data.fee_calculator),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
account_utils::State as AccountUtilsState,
|
||||
hash::Hash,
|
||||
nonce::{account::with_test_keyed_account, Account as NonceAccount, State},
|
||||
sysvar::{recent_blockhashes::create_test_recent_blockhashes, rent::Rent},
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn verify_nonce_ok() {
|
||||
with_test_keyed_account(42, true, |nonce_account| {
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(nonce_account.signer_key().unwrap());
|
||||
let state: State = nonce_account.state().unwrap();
|
||||
// New is in Uninitialzed state
|
||||
assert_eq!(state, State::Uninitialized);
|
||||
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||
let authorized = nonce_account.unsigned_key();
|
||||
nonce_account
|
||||
.initialize_nonce_account(&authorized, &recent_blockhashes, &Rent::free())
|
||||
.unwrap();
|
||||
assert!(verify_nonce_account(
|
||||
&nonce_account.account.borrow(),
|
||||
&recent_blockhashes[0].blockhash,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_nonce_bad_acc_state_fail() {
|
||||
with_test_keyed_account(42, true, |nonce_account| {
|
||||
assert!(!verify_nonce_account(
|
||||
&nonce_account.account.borrow(),
|
||||
&Hash::default()
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_nonce_bad_query_hash_fail() {
|
||||
with_test_keyed_account(42, true, |nonce_account| {
|
||||
let mut signers = HashSet::new();
|
||||
signers.insert(nonce_account.signer_key().unwrap());
|
||||
let state: State = nonce_account.state().unwrap();
|
||||
// New is in Uninitialzed state
|
||||
assert_eq!(state, State::Uninitialized);
|
||||
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||
let authorized = nonce_account.unsigned_key();
|
||||
nonce_account
|
||||
.initialize_nonce_account(&authorized, &recent_blockhashes, &Rent::free())
|
||||
.unwrap();
|
||||
assert!(!verify_nonce_account(
|
||||
&nonce_account.account.borrow(),
|
||||
&recent_blockhashes[1].blockhash,
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
60
sdk/program/src/program.rs
Normal file
60
sdk/program/src/program.rs
Normal file
@ -0,0 +1,60 @@
|
||||
use crate::{account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction};
|
||||
|
||||
/// Invoke a cross-program instruction
|
||||
pub fn invoke(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult {
|
||||
invoke_signed(instruction, account_infos, &[])
|
||||
}
|
||||
|
||||
/// Invoke a cross-program instruction with program signatures
|
||||
pub fn invoke_signed(
|
||||
instruction: &Instruction,
|
||||
account_infos: &[AccountInfo],
|
||||
signers_seeds: &[&[&[u8]]],
|
||||
) -> ProgramResult {
|
||||
// Check that the account RefCells are consistent with the request
|
||||
for account_meta in instruction.accounts.iter() {
|
||||
for account_info in account_infos.iter() {
|
||||
if account_meta.pubkey == *account_info.key {
|
||||
if account_meta.is_writable {
|
||||
let _ = account_info.try_borrow_mut_lamports()?;
|
||||
let _ = account_info.try_borrow_mut_data()?;
|
||||
} else {
|
||||
let _ = account_info.try_borrow_lamports()?;
|
||||
let _ = account_info.try_borrow_data()?;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "bpf")]
|
||||
{
|
||||
let result = unsafe {
|
||||
sol_invoke_signed_rust(
|
||||
instruction as *const _ as *const u8,
|
||||
account_infos as *const _ as *const u8,
|
||||
account_infos.len() as u64,
|
||||
signers_seeds as *const _ as *const u8,
|
||||
signers_seeds.len() as u64,
|
||||
)
|
||||
};
|
||||
match result {
|
||||
crate::entrypoint::SUCCESS => Ok(()),
|
||||
_ => Err(result.into()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "bpf"))]
|
||||
crate::program_stubs::sol_invoke_signed(instruction, account_infos, signers_seeds)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "bpf")]
|
||||
extern "C" {
|
||||
fn sol_invoke_signed_rust(
|
||||
instruction_addr: *const u8,
|
||||
account_infos_addr: *const u8,
|
||||
account_infos_len: u64,
|
||||
signers_seeds_addr: *const u8,
|
||||
signers_seeds_len: u64,
|
||||
) -> u64;
|
||||
}
|
215
sdk/program/src/program_error.rs
Normal file
215
sdk/program/src/program_error.rs
Normal file
@ -0,0 +1,215 @@
|
||||
use crate::info;
|
||||
use crate::{decode_error::DecodeError, instruction::InstructionError, pubkey::PubkeyError};
|
||||
use num_traits::{FromPrimitive, ToPrimitive};
|
||||
use std::convert::TryFrom;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Reasons the program may fail
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Error, PartialEq, Serialize)]
|
||||
pub enum ProgramError {
|
||||
/// Allows on-chain programs to implement program-specific error types and see them returned
|
||||
/// by the Solana runtime. A program-specific error may be any type that is represented as
|
||||
/// or serialized to a u32 integer.
|
||||
#[error("Custom program error: {0:#x}")]
|
||||
Custom(u32),
|
||||
#[error("The arguments provided to a program instruction where invalid")]
|
||||
InvalidArgument,
|
||||
#[error("An instruction's data contents was invalid")]
|
||||
InvalidInstructionData,
|
||||
#[error("An account's data contents was invalid")]
|
||||
InvalidAccountData,
|
||||
#[error("An account's data was too small")]
|
||||
AccountDataTooSmall,
|
||||
#[error("An account's balance was too small to complete the instruction")]
|
||||
InsufficientFunds,
|
||||
#[error("The account did not have the expected program id")]
|
||||
IncorrectProgramId,
|
||||
#[error("A signature was required but not found")]
|
||||
MissingRequiredSignature,
|
||||
#[error("An initialize instruction was sent to an account that has already been initialized")]
|
||||
AccountAlreadyInitialized,
|
||||
#[error("An attempt to operate on an account that hasn't been initialized")]
|
||||
UninitializedAccount,
|
||||
#[error("The instruction expected additional account keys")]
|
||||
NotEnoughAccountKeys,
|
||||
#[error("Failed to borrow a reference to account data, already borrowed")]
|
||||
AccountBorrowFailed,
|
||||
#[error("Length of the seed is too long for address generation")]
|
||||
MaxSeedLengthExceeded,
|
||||
#[error("Provided seeds do not result in a valid address")]
|
||||
InvalidSeeds,
|
||||
}
|
||||
|
||||
pub trait PrintProgramError {
|
||||
fn print<E>(&self)
|
||||
where
|
||||
E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive;
|
||||
}
|
||||
|
||||
impl PrintProgramError for ProgramError {
|
||||
fn print<E>(&self)
|
||||
where
|
||||
E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
|
||||
{
|
||||
match self {
|
||||
Self::Custom(error) => {
|
||||
if let Some(custom_error) = E::decode_custom_error_to_enum(*error) {
|
||||
custom_error.print::<E>();
|
||||
} else {
|
||||
info!("Error: Unknown");
|
||||
}
|
||||
}
|
||||
Self::InvalidArgument => info!("Error: InvalidArgument"),
|
||||
Self::InvalidInstructionData => info!("Error: InvalidInstructionData"),
|
||||
Self::InvalidAccountData => info!("Error: InvalidAccountData"),
|
||||
Self::AccountDataTooSmall => info!("Error: AccountDataTooSmall"),
|
||||
Self::InsufficientFunds => info!("Error: InsufficientFunds"),
|
||||
Self::IncorrectProgramId => info!("Error: IncorrectProgramId"),
|
||||
Self::MissingRequiredSignature => info!("Error: MissingRequiredSignature"),
|
||||
Self::AccountAlreadyInitialized => info!("Error: AccountAlreadyInitialized"),
|
||||
Self::UninitializedAccount => info!("Error: UninitializedAccount"),
|
||||
Self::NotEnoughAccountKeys => info!("Error: NotEnoughAccountKeys"),
|
||||
Self::AccountBorrowFailed => info!("Error: AccountBorrowFailed"),
|
||||
Self::MaxSeedLengthExceeded => info!("Error: MaxSeedLengthExceeded"),
|
||||
Self::InvalidSeeds => info!("Error: InvalidSeeds"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builtin return values occupy the upper 32 bits
|
||||
const BUILTIN_BIT_SHIFT: usize = 32;
|
||||
macro_rules! to_builtin {
|
||||
($error:expr) => {
|
||||
($error as u64) << BUILTIN_BIT_SHIFT
|
||||
};
|
||||
}
|
||||
|
||||
pub const CUSTOM_ZERO: u64 = to_builtin!(1);
|
||||
pub const INVALID_ARGUMENT: u64 = to_builtin!(2);
|
||||
pub const INVALID_INSTRUCTION_DATA: u64 = to_builtin!(3);
|
||||
pub const INVALID_ACCOUNT_DATA: u64 = to_builtin!(4);
|
||||
pub const ACCOUNT_DATA_TOO_SMALL: u64 = to_builtin!(5);
|
||||
pub const INSUFFICIENT_FUNDS: u64 = to_builtin!(6);
|
||||
pub const INCORRECT_PROGRAM_ID: u64 = to_builtin!(7);
|
||||
pub const MISSING_REQUIRED_SIGNATURES: u64 = to_builtin!(8);
|
||||
pub const ACCOUNT_ALREADY_INITIALIZED: u64 = to_builtin!(9);
|
||||
pub const UNINITIALIZED_ACCOUNT: u64 = to_builtin!(10);
|
||||
pub const NOT_ENOUGH_ACCOUNT_KEYS: u64 = to_builtin!(11);
|
||||
pub const ACCOUNT_BORROW_FAILED: u64 = to_builtin!(12);
|
||||
pub const MAX_SEED_LENGTH_EXCEEDED: u64 = to_builtin!(13);
|
||||
pub const INVALID_SEEDS: u64 = to_builtin!(14);
|
||||
|
||||
impl From<ProgramError> for u64 {
|
||||
fn from(error: ProgramError) -> Self {
|
||||
match error {
|
||||
ProgramError::InvalidArgument => INVALID_ARGUMENT,
|
||||
ProgramError::InvalidInstructionData => INVALID_INSTRUCTION_DATA,
|
||||
ProgramError::InvalidAccountData => INVALID_ACCOUNT_DATA,
|
||||
ProgramError::AccountDataTooSmall => ACCOUNT_DATA_TOO_SMALL,
|
||||
ProgramError::InsufficientFunds => INSUFFICIENT_FUNDS,
|
||||
ProgramError::IncorrectProgramId => INCORRECT_PROGRAM_ID,
|
||||
ProgramError::MissingRequiredSignature => MISSING_REQUIRED_SIGNATURES,
|
||||
ProgramError::AccountAlreadyInitialized => ACCOUNT_ALREADY_INITIALIZED,
|
||||
ProgramError::UninitializedAccount => UNINITIALIZED_ACCOUNT,
|
||||
ProgramError::NotEnoughAccountKeys => NOT_ENOUGH_ACCOUNT_KEYS,
|
||||
ProgramError::AccountBorrowFailed => ACCOUNT_BORROW_FAILED,
|
||||
ProgramError::MaxSeedLengthExceeded => MAX_SEED_LENGTH_EXCEEDED,
|
||||
ProgramError::InvalidSeeds => INVALID_SEEDS,
|
||||
|
||||
ProgramError::Custom(error) => {
|
||||
if error == 0 {
|
||||
CUSTOM_ZERO
|
||||
} else {
|
||||
error as u64
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for ProgramError {
|
||||
fn from(error: u64) -> Self {
|
||||
match error {
|
||||
INVALID_ARGUMENT => ProgramError::InvalidArgument,
|
||||
INVALID_INSTRUCTION_DATA => ProgramError::InvalidInstructionData,
|
||||
INVALID_ACCOUNT_DATA => ProgramError::InvalidAccountData,
|
||||
ACCOUNT_DATA_TOO_SMALL => ProgramError::AccountDataTooSmall,
|
||||
INSUFFICIENT_FUNDS => ProgramError::InsufficientFunds,
|
||||
INCORRECT_PROGRAM_ID => ProgramError::IncorrectProgramId,
|
||||
MISSING_REQUIRED_SIGNATURES => ProgramError::MissingRequiredSignature,
|
||||
ACCOUNT_ALREADY_INITIALIZED => ProgramError::AccountAlreadyInitialized,
|
||||
UNINITIALIZED_ACCOUNT => ProgramError::UninitializedAccount,
|
||||
NOT_ENOUGH_ACCOUNT_KEYS => ProgramError::NotEnoughAccountKeys,
|
||||
ACCOUNT_BORROW_FAILED => ProgramError::AccountBorrowFailed,
|
||||
MAX_SEED_LENGTH_EXCEEDED => ProgramError::MaxSeedLengthExceeded,
|
||||
INVALID_SEEDS => ProgramError::InvalidSeeds,
|
||||
CUSTOM_ZERO => ProgramError::Custom(0),
|
||||
_ => ProgramError::Custom(error as u32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<InstructionError> for ProgramError {
|
||||
type Error = InstructionError;
|
||||
|
||||
fn try_from(error: InstructionError) -> Result<Self, Self::Error> {
|
||||
match error {
|
||||
Self::Error::Custom(err) => Ok(Self::Custom(err)),
|
||||
Self::Error::InvalidArgument => Ok(Self::InvalidArgument),
|
||||
Self::Error::InvalidInstructionData => Ok(Self::InvalidInstructionData),
|
||||
Self::Error::InvalidAccountData => Ok(Self::InvalidAccountData),
|
||||
Self::Error::AccountDataTooSmall => Ok(Self::AccountDataTooSmall),
|
||||
Self::Error::InsufficientFunds => Ok(Self::InsufficientFunds),
|
||||
Self::Error::IncorrectProgramId => Ok(Self::IncorrectProgramId),
|
||||
Self::Error::MissingRequiredSignature => Ok(Self::MissingRequiredSignature),
|
||||
Self::Error::AccountAlreadyInitialized => Ok(Self::AccountAlreadyInitialized),
|
||||
Self::Error::UninitializedAccount => Ok(Self::UninitializedAccount),
|
||||
Self::Error::NotEnoughAccountKeys => Ok(Self::NotEnoughAccountKeys),
|
||||
Self::Error::AccountBorrowFailed => Ok(Self::AccountBorrowFailed),
|
||||
Self::Error::MaxSeedLengthExceeded => Ok(Self::MaxSeedLengthExceeded),
|
||||
_ => Err(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for InstructionError
|
||||
where
|
||||
T: ToPrimitive,
|
||||
{
|
||||
fn from(error: T) -> Self {
|
||||
let error = error.to_u64().unwrap_or(0xbad_c0de);
|
||||
match error {
|
||||
CUSTOM_ZERO => InstructionError::Custom(0),
|
||||
INVALID_ARGUMENT => InstructionError::InvalidArgument,
|
||||
INVALID_INSTRUCTION_DATA => InstructionError::InvalidInstructionData,
|
||||
INVALID_ACCOUNT_DATA => InstructionError::InvalidAccountData,
|
||||
ACCOUNT_DATA_TOO_SMALL => InstructionError::AccountDataTooSmall,
|
||||
INSUFFICIENT_FUNDS => InstructionError::InsufficientFunds,
|
||||
INCORRECT_PROGRAM_ID => InstructionError::IncorrectProgramId,
|
||||
MISSING_REQUIRED_SIGNATURES => InstructionError::MissingRequiredSignature,
|
||||
ACCOUNT_ALREADY_INITIALIZED => InstructionError::AccountAlreadyInitialized,
|
||||
UNINITIALIZED_ACCOUNT => InstructionError::UninitializedAccount,
|
||||
NOT_ENOUGH_ACCOUNT_KEYS => InstructionError::NotEnoughAccountKeys,
|
||||
ACCOUNT_BORROW_FAILED => InstructionError::AccountBorrowFailed,
|
||||
MAX_SEED_LENGTH_EXCEEDED => InstructionError::MaxSeedLengthExceeded,
|
||||
INVALID_SEEDS => InstructionError::InvalidSeeds,
|
||||
_ => {
|
||||
// A valid custom error has no bits set in the upper 32
|
||||
if error >> BUILTIN_BIT_SHIFT == 0 {
|
||||
InstructionError::Custom(error as u32)
|
||||
} else {
|
||||
Self::InvalidError
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PubkeyError> for ProgramError {
|
||||
fn from(error: PubkeyError) -> Self {
|
||||
match error {
|
||||
PubkeyError::MaxSeedLengthExceeded => ProgramError::MaxSeedLengthExceeded,
|
||||
PubkeyError::InvalidSeeds => ProgramError::InvalidSeeds,
|
||||
}
|
||||
}
|
||||
}
|
980
sdk/program/src/program_option.rs
Normal file
980
sdk/program/src/program_option.rs
Normal file
@ -0,0 +1,980 @@
|
||||
//! A C representation of Rust's `std::option::Option` used accross the FFI
|
||||
//! boundary for Solana program interfaces
|
||||
//!
|
||||
//! This implementation mostly matches `std::option` except iterators since the iteration
|
||||
//! trait requires returning `std::option::Option`
|
||||
|
||||
use std::{
|
||||
convert, mem,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
/// A C representation of Rust's `std::option::Option`
|
||||
#[repr(C)]
|
||||
#[derive(Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
|
||||
pub enum COption<T> {
|
||||
/// No value
|
||||
None,
|
||||
/// Some value `T`
|
||||
Some(T),
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Type implementation
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
impl<T> COption<T> {
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// Querying the contained values
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Returns `true` if the option is a [`COption::Some`] value.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let x: COption<u32> = COption::Some(2);
|
||||
/// assert_eq!(x.is_some(), true);
|
||||
///
|
||||
/// let x: COption<u32> = COption::None;
|
||||
/// assert_eq!(x.is_some(), false);
|
||||
/// ```
|
||||
///
|
||||
/// [`COption::Some`]: #variant.COption::Some
|
||||
#[must_use = "if you intended to assert that this has a value, consider `.unwrap()` instead"]
|
||||
#[inline]
|
||||
pub fn is_some(&self) -> bool {
|
||||
match *self {
|
||||
COption::Some(_) => true,
|
||||
COption::None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the option is a [`COption::None`] value.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let x: COption<u32> = COption::Some(2);
|
||||
/// assert_eq!(x.is_none(), false);
|
||||
///
|
||||
/// let x: COption<u32> = COption::None;
|
||||
/// assert_eq!(x.is_none(), true);
|
||||
/// ```
|
||||
///
|
||||
/// [`COption::None`]: #variant.COption::None
|
||||
#[must_use = "if you intended to assert that this doesn't have a value, consider \
|
||||
`.and_then(|| panic!(\"`COption` had a value when expected `COption::None`\"))` instead"]
|
||||
#[inline]
|
||||
pub fn is_none(&self) -> bool {
|
||||
!self.is_some()
|
||||
}
|
||||
|
||||
/// Returns `true` if the option is a [`COption::Some`] value containing the given value.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// #![feature(option_result_contains)]
|
||||
///
|
||||
/// let x: COption<u32> = COption::Some(2);
|
||||
/// assert_eq!(x.contains(&2), true);
|
||||
///
|
||||
/// let x: COption<u32> = COption::Some(3);
|
||||
/// assert_eq!(x.contains(&2), false);
|
||||
///
|
||||
/// let x: COption<u32> = COption::None;
|
||||
/// assert_eq!(x.contains(&2), false);
|
||||
/// ```
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn contains<U>(&self, x: &U) -> bool
|
||||
where
|
||||
U: PartialEq<T>,
|
||||
{
|
||||
match self {
|
||||
COption::Some(y) => x == y,
|
||||
COption::None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// Adapter for working with references
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Converts from `&COption<T>` to `COption<&T>`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Converts an `COption<`[`String`]`>` into an `COption<`[`usize`]`>`, preserving the original.
|
||||
/// The [`map`] method takes the `self` argument by value, consuming the original,
|
||||
/// so this technique uses `as_ref` to first take an `COption` to a reference
|
||||
/// to the value inside the original.
|
||||
///
|
||||
/// [`map`]: enum.COption.html#method.map
|
||||
/// [`String`]: ../../std/string/struct.String.html
|
||||
/// [`usize`]: ../../std/primitive.usize.html
|
||||
///
|
||||
/// ```ignore
|
||||
/// let text: COption<String> = COption::Some("Hello, world!".to_string());
|
||||
/// // First, cast `COption<String>` to `COption<&String>` with `as_ref`,
|
||||
/// // then consume *that* with `map`, leaving `text` on the stack.
|
||||
/// let text_length: COption<usize> = text.as_ref().map(|s| s.len());
|
||||
/// println!("still can print text: {:?}", text);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn as_ref(&self) -> COption<&T> {
|
||||
match *self {
|
||||
COption::Some(ref x) => COption::Some(x),
|
||||
COption::None => COption::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts from `&mut COption<T>` to `COption<&mut T>`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let mut x = COption::Some(2);
|
||||
/// match x.as_mut() {
|
||||
/// COption::Some(v) => *v = 42,
|
||||
/// COption::None => {},
|
||||
/// }
|
||||
/// assert_eq!(x, COption::Some(42));
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn as_mut(&mut self) -> COption<&mut T> {
|
||||
match *self {
|
||||
COption::Some(ref mut x) => COption::Some(x),
|
||||
COption::None => COption::None,
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// Getting to contained values
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Unwraps an option, yielding the content of a [`COption::Some`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the value is a [`COption::None`] with a custom panic message provided by
|
||||
/// `msg`.
|
||||
///
|
||||
/// [`COption::Some`]: #variant.COption::Some
|
||||
/// [`COption::None`]: #variant.COption::None
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let x = COption::Some("value");
|
||||
/// assert_eq!(x.expect("the world is ending"), "value");
|
||||
/// ```
|
||||
///
|
||||
/// ```ignore{.should_panic}
|
||||
/// let x: COption<&str> = COption::None;
|
||||
/// x.expect("the world is ending"); // panics with `the world is ending`
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn expect(self, msg: &str) -> T {
|
||||
match self {
|
||||
COption::Some(val) => val,
|
||||
COption::None => expect_failed(msg),
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves the value `v` out of the `COption<T>` if it is [`COption::Some(v)`].
|
||||
///
|
||||
/// In general, because this function may panic, its use is discouraged.
|
||||
/// Instead, prefer to use pattern matching and handle the [`COption::None`]
|
||||
/// case explicitly.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the self value equals [`COption::None`].
|
||||
///
|
||||
/// [`COption::Some(v)`]: #variant.COption::Some
|
||||
/// [`COption::None`]: #variant.COption::None
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let x = COption::Some("air");
|
||||
/// assert_eq!(x.unwrap(), "air");
|
||||
/// ```
|
||||
///
|
||||
/// ```ignore{.should_panic}
|
||||
/// let x: COption<&str> = COption::None;
|
||||
/// assert_eq!(x.unwrap(), "air"); // fails
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn unwrap(self) -> T {
|
||||
match self {
|
||||
COption::Some(val) => val,
|
||||
COption::None => panic!("called `COption::unwrap()` on a `COption::None` value"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the contained value or a default.
|
||||
///
|
||||
/// Arguments passed to `unwrap_or` are eagerly evaluated; if you are passing
|
||||
/// the result of a function call, it is recommended to use [`unwrap_or_else`],
|
||||
/// which is lazily evaluated.
|
||||
///
|
||||
/// [`unwrap_or_else`]: #method.unwrap_or_else
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// assert_eq!(COption::Some("car").unwrap_or("bike"), "car");
|
||||
/// assert_eq!(COption::None.unwrap_or("bike"), "bike");
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn unwrap_or(self, def: T) -> T {
|
||||
match self {
|
||||
COption::Some(x) => x,
|
||||
COption::None => def,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the contained value or computes it from a closure.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let k = 10;
|
||||
/// assert_eq!(COption::Some(4).unwrap_or_else(|| 2 * k), 4);
|
||||
/// assert_eq!(COption::None.unwrap_or_else(|| 2 * k), 20);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn unwrap_or_else<F: FnOnce() -> T>(self, f: F) -> T {
|
||||
match self {
|
||||
COption::Some(x) => x,
|
||||
COption::None => f(),
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// Transforming contained values
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Maps an `COption<T>` to `COption<U>` by applying a function to a contained value.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Converts an `COption<`[`String`]`>` into an `COption<`[`usize`]`>`, consuming the original:
|
||||
///
|
||||
/// [`String`]: ../../std/string/struct.String.html
|
||||
/// [`usize`]: ../../std/primitive.usize.html
|
||||
///
|
||||
/// ```ignore
|
||||
/// let maybe_some_string = COption::Some(String::from("Hello, World!"));
|
||||
/// // `COption::map` takes self *by value*, consuming `maybe_some_string`
|
||||
/// let maybe_some_len = maybe_some_string.map(|s| s.len());
|
||||
///
|
||||
/// assert_eq!(maybe_some_len, COption::Some(13));
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> COption<U> {
|
||||
match self {
|
||||
COption::Some(x) => COption::Some(f(x)),
|
||||
COption::None => COption::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a function to the contained value (if any),
|
||||
/// or returns the provided default (if not).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let x = COption::Some("foo");
|
||||
/// assert_eq!(x.map_or(42, |v| v.len()), 3);
|
||||
///
|
||||
/// let x: COption<&str> = COption::None;
|
||||
/// assert_eq!(x.map_or(42, |v| v.len()), 42);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn map_or<U, F: FnOnce(T) -> U>(self, default: U, f: F) -> U {
|
||||
match self {
|
||||
COption::Some(t) => f(t),
|
||||
COption::None => default,
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a function to the contained value (if any),
|
||||
/// or computes a default (if not).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let k = 21;
|
||||
///
|
||||
/// let x = COption::Some("foo");
|
||||
/// assert_eq!(x.map_or_else(|| 2 * k, |v| v.len()), 3);
|
||||
///
|
||||
/// let x: COption<&str> = COption::None;
|
||||
/// assert_eq!(x.map_or_else(|| 2 * k, |v| v.len()), 42);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn map_or_else<U, D: FnOnce() -> U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U {
|
||||
match self {
|
||||
COption::Some(t) => f(t),
|
||||
COption::None => default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Transforms the `COption<T>` into a [`Result<T, E>`], mapping [`COption::Some(v)`] to
|
||||
/// [`Ok(v)`] and [`COption::None`] to [`Err(err)`].
|
||||
///
|
||||
/// Arguments passed to `ok_or` are eagerly evaluated; if you are passing the
|
||||
/// result of a function call, it is recommended to use [`ok_or_else`], which is
|
||||
/// lazily evaluated.
|
||||
///
|
||||
/// [`Result<T, E>`]: ../../std/result/enum.Result.html
|
||||
/// [`Ok(v)`]: ../../std/result/enum.Result.html#variant.Ok
|
||||
/// [`Err(err)`]: ../../std/result/enum.Result.html#variant.Err
|
||||
/// [`COption::None`]: #variant.COption::None
|
||||
/// [`COption::Some(v)`]: #variant.COption::Some
|
||||
/// [`ok_or_else`]: #method.ok_or_else
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let x = COption::Some("foo");
|
||||
/// assert_eq!(x.ok_or(0), Ok("foo"));
|
||||
///
|
||||
/// let x: COption<&str> = COption::None;
|
||||
/// assert_eq!(x.ok_or(0), Err(0));
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn ok_or<E>(self, err: E) -> Result<T, E> {
|
||||
match self {
|
||||
COption::Some(v) => Ok(v),
|
||||
COption::None => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Transforms the `COption<T>` into a [`Result<T, E>`], mapping [`COption::Some(v)`] to
|
||||
/// [`Ok(v)`] and [`COption::None`] to [`Err(err())`].
|
||||
///
|
||||
/// [`Result<T, E>`]: ../../std/result/enum.Result.html
|
||||
/// [`Ok(v)`]: ../../std/result/enum.Result.html#variant.Ok
|
||||
/// [`Err(err())`]: ../../std/result/enum.Result.html#variant.Err
|
||||
/// [`COption::None`]: #variant.COption::None
|
||||
/// [`COption::Some(v)`]: #variant.COption::Some
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let x = COption::Some("foo");
|
||||
/// assert_eq!(x.ok_or_else(|| 0), Ok("foo"));
|
||||
///
|
||||
/// let x: COption<&str> = COption::None;
|
||||
/// assert_eq!(x.ok_or_else(|| 0), Err(0));
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn ok_or_else<E, F: FnOnce() -> E>(self, err: F) -> Result<T, E> {
|
||||
match self {
|
||||
COption::Some(v) => Ok(v),
|
||||
COption::None => Err(err()),
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// Boolean operations on the values, eager and lazy
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Returns [`COption::None`] if the option is [`COption::None`], otherwise returns `optb`.
|
||||
///
|
||||
/// [`COption::None`]: #variant.COption::None
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let x = COption::Some(2);
|
||||
/// let y: COption<&str> = COption::None;
|
||||
/// assert_eq!(x.and(y), COption::None);
|
||||
///
|
||||
/// let x: COption<u32> = COption::None;
|
||||
/// let y = COption::Some("foo");
|
||||
/// assert_eq!(x.and(y), COption::None);
|
||||
///
|
||||
/// let x = COption::Some(2);
|
||||
/// let y = COption::Some("foo");
|
||||
/// assert_eq!(x.and(y), COption::Some("foo"));
|
||||
///
|
||||
/// let x: COption<u32> = COption::None;
|
||||
/// let y: COption<&str> = COption::None;
|
||||
/// assert_eq!(x.and(y), COption::None);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn and<U>(self, optb: COption<U>) -> COption<U> {
|
||||
match self {
|
||||
COption::Some(_) => optb,
|
||||
COption::None => COption::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns [`COption::None`] if the option is [`COption::None`], otherwise calls `f` with the
|
||||
/// wrapped value and returns the result.
|
||||
///
|
||||
/// COption::Some languages call this operation flatmap.
|
||||
///
|
||||
/// [`COption::None`]: #variant.COption::None
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn sq(x: u32) -> COption<u32> { COption::Some(x * x) }
|
||||
/// fn nope(_: u32) -> COption<u32> { COption::None }
|
||||
///
|
||||
/// assert_eq!(COption::Some(2).and_then(sq).and_then(sq), COption::Some(16));
|
||||
/// assert_eq!(COption::Some(2).and_then(sq).and_then(nope), COption::None);
|
||||
/// assert_eq!(COption::Some(2).and_then(nope).and_then(sq), COption::None);
|
||||
/// assert_eq!(COption::None.and_then(sq).and_then(sq), COption::None);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn and_then<U, F: FnOnce(T) -> COption<U>>(self, f: F) -> COption<U> {
|
||||
match self {
|
||||
COption::Some(x) => f(x),
|
||||
COption::None => COption::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns [`COption::None`] if the option is [`COption::None`], otherwise calls `predicate`
|
||||
/// with the wrapped value and returns:
|
||||
///
|
||||
/// - [`COption::Some(t)`] if `predicate` returns `true` (where `t` is the wrapped
|
||||
/// value), and
|
||||
/// - [`COption::None`] if `predicate` returns `false`.
|
||||
///
|
||||
/// This function works similar to [`Iterator::filter()`]. You can imagine
|
||||
/// the `COption<T>` being an iterator over one or zero elements. `filter()`
|
||||
/// lets you decide which elements to keep.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn is_even(n: &i32) -> bool {
|
||||
/// n % 2 == 0
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(COption::None.filter(is_even), COption::None);
|
||||
/// assert_eq!(COption::Some(3).filter(is_even), COption::None);
|
||||
/// assert_eq!(COption::Some(4).filter(is_even), COption::Some(4));
|
||||
/// ```
|
||||
///
|
||||
/// [`COption::None`]: #variant.COption::None
|
||||
/// [`COption::Some(t)`]: #variant.COption::Some
|
||||
/// [`Iterator::filter()`]: ../../std/iter/trait.Iterator.html#method.filter
|
||||
#[inline]
|
||||
pub fn filter<P: FnOnce(&T) -> bool>(self, predicate: P) -> Self {
|
||||
if let COption::Some(x) = self {
|
||||
if predicate(&x) {
|
||||
return COption::Some(x);
|
||||
}
|
||||
}
|
||||
COption::None
|
||||
}
|
||||
|
||||
/// Returns the option if it contains a value, otherwise returns `optb`.
|
||||
///
|
||||
/// Arguments passed to `or` are eagerly evaluated; if you are passing the
|
||||
/// result of a function call, it is recommended to use [`or_else`], which is
|
||||
/// lazily evaluated.
|
||||
///
|
||||
/// [`or_else`]: #method.or_else
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let x = COption::Some(2);
|
||||
/// let y = COption::None;
|
||||
/// assert_eq!(x.or(y), COption::Some(2));
|
||||
///
|
||||
/// let x = COption::None;
|
||||
/// let y = COption::Some(100);
|
||||
/// assert_eq!(x.or(y), COption::Some(100));
|
||||
///
|
||||
/// let x = COption::Some(2);
|
||||
/// let y = COption::Some(100);
|
||||
/// assert_eq!(x.or(y), COption::Some(2));
|
||||
///
|
||||
/// let x: COption<u32> = COption::None;
|
||||
/// let y = COption::None;
|
||||
/// assert_eq!(x.or(y), COption::None);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn or(self, optb: COption<T>) -> COption<T> {
|
||||
match self {
|
||||
COption::Some(_) => self,
|
||||
COption::None => optb,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the option if it contains a value, otherwise calls `f` and
|
||||
/// returns the result.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn nobody() -> COption<&'static str> { COption::None }
|
||||
/// fn vikings() -> COption<&'static str> { COption::Some("vikings") }
|
||||
///
|
||||
/// assert_eq!(COption::Some("barbarians").or_else(vikings), COption::Some("barbarians"));
|
||||
/// assert_eq!(COption::None.or_else(vikings), COption::Some("vikings"));
|
||||
/// assert_eq!(COption::None.or_else(nobody), COption::None);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn or_else<F: FnOnce() -> COption<T>>(self, f: F) -> COption<T> {
|
||||
match self {
|
||||
COption::Some(_) => self,
|
||||
COption::None => f(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns [`COption::Some`] if exactly one of `self`, `optb` is [`COption::Some`], otherwise returns [`COption::None`].
|
||||
///
|
||||
/// [`COption::Some`]: #variant.COption::Some
|
||||
/// [`COption::None`]: #variant.COption::None
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let x = COption::Some(2);
|
||||
/// let y: COption<u32> = COption::None;
|
||||
/// assert_eq!(x.xor(y), COption::Some(2));
|
||||
///
|
||||
/// let x: COption<u32> = COption::None;
|
||||
/// let y = COption::Some(2);
|
||||
/// assert_eq!(x.xor(y), COption::Some(2));
|
||||
///
|
||||
/// let x = COption::Some(2);
|
||||
/// let y = COption::Some(2);
|
||||
/// assert_eq!(x.xor(y), COption::None);
|
||||
///
|
||||
/// let x: COption<u32> = COption::None;
|
||||
/// let y: COption<u32> = COption::None;
|
||||
/// assert_eq!(x.xor(y), COption::None);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn xor(self, optb: COption<T>) -> COption<T> {
|
||||
match (self, optb) {
|
||||
(COption::Some(a), COption::None) => COption::Some(a),
|
||||
(COption::None, COption::Some(b)) => COption::Some(b),
|
||||
_ => COption::None,
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// Entry-like operations to insert if COption::None and return a reference
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Inserts `v` into the option if it is [`COption::None`], then
|
||||
/// returns a mutable reference to the contained value.
|
||||
///
|
||||
/// [`COption::None`]: #variant.COption::None
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let mut x = COption::None;
|
||||
///
|
||||
/// {
|
||||
/// let y: &mut u32 = x.get_or_insert(5);
|
||||
/// assert_eq!(y, &5);
|
||||
///
|
||||
/// *y = 7;
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(x, COption::Some(7));
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn get_or_insert(&mut self, v: T) -> &mut T {
|
||||
self.get_or_insert_with(|| v)
|
||||
}
|
||||
|
||||
/// Inserts a value computed from `f` into the option if it is [`COption::None`], then
|
||||
/// returns a mutable reference to the contained value.
|
||||
///
|
||||
/// [`COption::None`]: #variant.COption::None
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let mut x = COption::None;
|
||||
///
|
||||
/// {
|
||||
/// let y: &mut u32 = x.get_or_insert_with(|| 5);
|
||||
/// assert_eq!(y, &5);
|
||||
///
|
||||
/// *y = 7;
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(x, COption::Some(7));
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn get_or_insert_with<F: FnOnce() -> T>(&mut self, f: F) -> &mut T {
|
||||
if let COption::None = *self {
|
||||
*self = COption::Some(f())
|
||||
}
|
||||
|
||||
match *self {
|
||||
COption::Some(ref mut v) => v,
|
||||
COption::None => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// Misc
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Replaces the actual value in the option by the value given in parameter,
|
||||
/// returning the old value if present,
|
||||
/// leaving a [`COption::Some`] in its place without deinitializing either one.
|
||||
///
|
||||
/// [`COption::Some`]: #variant.COption::Some
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let mut x = COption::Some(2);
|
||||
/// let old = x.replace(5);
|
||||
/// assert_eq!(x, COption::Some(5));
|
||||
/// assert_eq!(old, COption::Some(2));
|
||||
///
|
||||
/// let mut x = COption::None;
|
||||
/// let old = x.replace(3);
|
||||
/// assert_eq!(x, COption::Some(3));
|
||||
/// assert_eq!(old, COption::None);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn replace(&mut self, value: T) -> COption<T> {
|
||||
mem::replace(self, COption::Some(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> COption<&T> {
|
||||
/// Maps an `COption<&T>` to an `COption<T>` by copying the contents of the
|
||||
/// option.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let x = 12;
|
||||
/// let opt_x = COption::Some(&x);
|
||||
/// assert_eq!(opt_x, COption::Some(&12));
|
||||
/// let copied = opt_x.copied();
|
||||
/// assert_eq!(copied, COption::Some(12));
|
||||
/// ```
|
||||
pub fn copied(self) -> COption<T> {
|
||||
self.map(|&t| t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> COption<&mut T> {
|
||||
/// Maps an `COption<&mut T>` to an `COption<T>` by copying the contents of the
|
||||
/// option.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let mut x = 12;
|
||||
/// let opt_x = COption::Some(&mut x);
|
||||
/// assert_eq!(opt_x, COption::Some(&mut 12));
|
||||
/// let copied = opt_x.copied();
|
||||
/// assert_eq!(copied, COption::Some(12));
|
||||
/// ```
|
||||
pub fn copied(self) -> COption<T> {
|
||||
self.map(|&mut t| t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> COption<&T> {
|
||||
/// Maps an `COption<&T>` to an `COption<T>` by cloning the contents of the
|
||||
/// option.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let x = 12;
|
||||
/// let opt_x = COption::Some(&x);
|
||||
/// assert_eq!(opt_x, COption::Some(&12));
|
||||
/// let cloned = opt_x.cloned();
|
||||
/// assert_eq!(cloned, COption::Some(12));
|
||||
/// ```
|
||||
pub fn cloned(self) -> COption<T> {
|
||||
self.map(|t| t.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> COption<&mut T> {
|
||||
/// Maps an `COption<&mut T>` to an `COption<T>` by cloning the contents of the
|
||||
/// option.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let mut x = 12;
|
||||
/// let opt_x = COption::Some(&mut x);
|
||||
/// assert_eq!(opt_x, COption::Some(&mut 12));
|
||||
/// let cloned = opt_x.cloned();
|
||||
/// assert_eq!(cloned, COption::Some(12));
|
||||
/// ```
|
||||
pub fn cloned(self) -> COption<T> {
|
||||
self.map(|t| t.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default> COption<T> {
|
||||
/// Returns the contained value or a default
|
||||
///
|
||||
/// Consumes the `self` argument then, if [`COption::Some`], returns the contained
|
||||
/// value, otherwise if [`COption::None`], returns the [default value] for that
|
||||
/// type.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Converts a string to an integer, turning poorly-formed strings
|
||||
/// into 0 (the default value for integers). [`parse`] converts
|
||||
/// a string to any other type that implements [`FromStr`], returning
|
||||
/// [`COption::None`] on error.
|
||||
///
|
||||
/// ```ignore
|
||||
/// let good_year_from_input = "1909";
|
||||
/// let bad_year_from_input = "190blarg";
|
||||
/// let good_year = good_year_from_input.parse().ok().unwrap_or_default();
|
||||
/// let bad_year = bad_year_from_input.parse().ok().unwrap_or_default();
|
||||
///
|
||||
/// assert_eq!(1909, good_year);
|
||||
/// assert_eq!(0, bad_year);
|
||||
/// ```
|
||||
///
|
||||
/// [`COption::Some`]: #variant.COption::Some
|
||||
/// [`COption::None`]: #variant.COption::None
|
||||
/// [default value]: ../default/trait.Default.html#tymethod.default
|
||||
/// [`parse`]: ../../std/primitive.str.html#method.parse
|
||||
/// [`FromStr`]: ../../std/str/trait.FromStr.html
|
||||
#[inline]
|
||||
pub fn unwrap_or_default(self) -> T {
|
||||
match self {
|
||||
COption::Some(x) => x,
|
||||
COption::None => T::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Deref> COption<T> {
|
||||
/// Converts from `COption<T>` (or `&COption<T>`) to `COption<&T::Target>`.
|
||||
///
|
||||
/// Leaves the original COption in-place, creating a new one with a reference
|
||||
/// to the original one, additionally coercing the contents via [`Deref`].
|
||||
///
|
||||
/// [`Deref`]: ../../std/ops/trait.Deref.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// #![feature(inner_deref)]
|
||||
///
|
||||
/// let x: COption<String> = COption::Some("hey".to_owned());
|
||||
/// assert_eq!(x.as_deref(), COption::Some("hey"));
|
||||
///
|
||||
/// let x: COption<String> = COption::None;
|
||||
/// assert_eq!(x.as_deref(), COption::None);
|
||||
/// ```
|
||||
pub fn as_deref(&self) -> COption<&T::Target> {
|
||||
self.as_ref().map(|t| t.deref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: DerefMut> COption<T> {
|
||||
/// Converts from `COption<T>` (or `&mut COption<T>`) to `COption<&mut T::Target>`.
|
||||
///
|
||||
/// Leaves the original `COption` in-place, creating a new one containing a mutable reference to
|
||||
/// the inner type's `Deref::Target` type.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// #![feature(inner_deref)]
|
||||
///
|
||||
/// let mut x: COption<String> = COption::Some("hey".to_owned());
|
||||
/// assert_eq!(x.as_deref_mut().map(|x| {
|
||||
/// x.make_ascii_uppercase();
|
||||
/// x
|
||||
/// }), COption::Some("HEY".to_owned().as_mut_str()));
|
||||
/// ```
|
||||
pub fn as_deref_mut(&mut self) -> COption<&mut T::Target> {
|
||||
self.as_mut().map(|t| t.deref_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> COption<Result<T, E>> {
|
||||
/// Transposes an `COption` of a [`Result`] into a [`Result`] of an `COption`.
|
||||
///
|
||||
/// [`COption::None`] will be mapped to [`Ok`]`(`[`COption::None`]`)`.
|
||||
/// [`COption::Some`]`(`[`Ok`]`(_))` and [`COption::Some`]`(`[`Err`]`(_))` will be mapped to
|
||||
/// [`Ok`]`(`[`COption::Some`]`(_))` and [`Err`]`(_)`.
|
||||
///
|
||||
/// [`COption::None`]: #variant.COption::None
|
||||
/// [`Ok`]: ../../std/result/enum.Result.html#variant.Ok
|
||||
/// [`COption::Some`]: #variant.COption::Some
|
||||
/// [`Err`]: ../../std/result/enum.Result.html#variant.Err
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[derive(Debug, Eq, PartialEq)]
|
||||
/// struct COption::SomeErr;
|
||||
///
|
||||
/// let x: Result<COption<i32>, COption::SomeErr> = Ok(COption::Some(5));
|
||||
/// let y: COption<Result<i32, COption::SomeErr>> = COption::Some(Ok(5));
|
||||
/// assert_eq!(x, y.transpose());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn transpose(self) -> Result<COption<T>, E> {
|
||||
match self {
|
||||
COption::Some(Ok(x)) => Ok(COption::Some(x)),
|
||||
COption::Some(Err(e)) => Err(e),
|
||||
COption::None => Ok(COption::None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is a separate function to reduce the code size of .expect() itself.
|
||||
#[inline(never)]
|
||||
#[cold]
|
||||
fn expect_failed(msg: &str) -> ! {
|
||||
panic!("{}", msg)
|
||||
}
|
||||
|
||||
// // This is a separate function to reduce the code size of .expect_none() itself.
|
||||
// #[inline(never)]
|
||||
// #[cold]
|
||||
// fn expect_none_failed(msg: &str, value: &dyn fmt::Debug) -> ! {
|
||||
// panic!("{}: {:?}", msg, value)
|
||||
// }
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Trait implementations
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
impl<T: Clone> Clone for COption<T> {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
COption::Some(x) => COption::Some(x.clone()),
|
||||
COption::None => COption::None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clone_from(&mut self, source: &Self) {
|
||||
match (self, source) {
|
||||
(COption::Some(to), COption::Some(from)) => to.clone_from(from),
|
||||
(to, from) => *to = from.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for COption<T> {
|
||||
/// Returns [`COption::None`][COption::COption::None].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let opt: COption<u32> = COption::default();
|
||||
/// assert!(opt.is_none());
|
||||
/// ```
|
||||
#[inline]
|
||||
fn default() -> COption<T> {
|
||||
COption::None
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for COption<T> {
|
||||
fn from(val: T) -> COption<T> {
|
||||
COption::Some(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> From<&'a COption<T>> for COption<&'a T> {
|
||||
fn from(o: &'a COption<T>) -> COption<&'a T> {
|
||||
o.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> From<&'a mut COption<T>> for COption<&'a mut T> {
|
||||
fn from(o: &'a mut COption<T>) -> COption<&'a mut T> {
|
||||
o.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> COption<COption<T>> {
|
||||
/// Converts from `COption<COption<T>>` to `COption<T>`
|
||||
///
|
||||
/// # Examples
|
||||
/// Basic usage:
|
||||
/// ```ignore
|
||||
/// #![feature(option_flattening)]
|
||||
/// let x: COption<COption<u32>> = COption::Some(COption::Some(6));
|
||||
/// assert_eq!(COption::Some(6), x.flatten());
|
||||
///
|
||||
/// let x: COption<COption<u32>> = COption::Some(COption::None);
|
||||
/// assert_eq!(COption::None, x.flatten());
|
||||
///
|
||||
/// let x: COption<COption<u32>> = COption::None;
|
||||
/// assert_eq!(COption::None, x.flatten());
|
||||
/// ```
|
||||
/// Flattening once only removes one level of nesting:
|
||||
/// ```ignore
|
||||
/// #![feature(option_flattening)]
|
||||
/// let x: COption<COption<COption<u32>>> = COption::Some(COption::Some(COption::Some(6)));
|
||||
/// assert_eq!(COption::Some(COption::Some(6)), x.flatten());
|
||||
/// assert_eq!(COption::Some(6), x.flatten().flatten());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn flatten(self) -> COption<T> {
|
||||
self.and_then(convert::identity)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Option<T>> for COption<T> {
|
||||
fn from(option: Option<T>) -> Self {
|
||||
match option {
|
||||
Some(value) => COption::Some(value),
|
||||
None => COption::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Into<Option<T>> for COption<T> {
|
||||
fn into(self) -> Option<T> {
|
||||
match self {
|
||||
COption::Some(value) => Some(value),
|
||||
COption::None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_from_rust_option() {
|
||||
let option = Some(99u64);
|
||||
let c_option: COption<u64> = option.into();
|
||||
assert_eq!(c_option, COption::Some(99u64));
|
||||
let expected = c_option.into();
|
||||
assert_eq!(option, expected);
|
||||
|
||||
let option = None;
|
||||
let c_option: COption<u64> = option.into();
|
||||
assert_eq!(c_option, COption::None);
|
||||
let expected = c_option.into();
|
||||
assert_eq!(option, expected);
|
||||
}
|
||||
}
|
57
sdk/program/src/program_pack.rs
Normal file
57
sdk/program/src/program_pack.rs
Normal file
@ -0,0 +1,57 @@
|
||||
//! State transition types
|
||||
|
||||
use crate::program_error::ProgramError;
|
||||
|
||||
/// Check if a program account state is initialized
|
||||
pub trait IsInitialized {
|
||||
/// Is initialized
|
||||
fn is_initialized(&self) -> bool;
|
||||
}
|
||||
|
||||
/// Depends on Sized
|
||||
pub trait Sealed: Sized {}
|
||||
|
||||
/// Safely and efficiently (de)serialize account state
|
||||
pub trait Pack: Sealed {
|
||||
/// The length, in bytes, of the packed representation
|
||||
const LEN: usize;
|
||||
#[doc(hidden)]
|
||||
fn pack_into_slice(&self, dst: &mut [u8]);
|
||||
#[doc(hidden)]
|
||||
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError>;
|
||||
|
||||
/// Get the packed length
|
||||
fn get_packed_len() -> usize {
|
||||
Self::LEN
|
||||
}
|
||||
|
||||
/// Unpack from slice and check if initialized
|
||||
fn unpack(input: &[u8]) -> Result<Self, ProgramError>
|
||||
where
|
||||
Self: IsInitialized,
|
||||
{
|
||||
let value = Self::unpack_unchecked(input)?;
|
||||
if value.is_initialized() {
|
||||
Ok(value)
|
||||
} else {
|
||||
Err(ProgramError::UninitializedAccount)
|
||||
}
|
||||
}
|
||||
|
||||
/// Unpack from slice without checking if initialized
|
||||
fn unpack_unchecked(input: &[u8]) -> Result<Self, ProgramError> {
|
||||
if input.len() != Self::LEN {
|
||||
return Err(ProgramError::InvalidAccountData);
|
||||
}
|
||||
Ok(Self::unpack_from_slice(input)?)
|
||||
}
|
||||
|
||||
/// Pack into slice
|
||||
fn pack(src: Self, dst: &mut [u8]) -> Result<(), ProgramError> {
|
||||
if dst.len() != Self::LEN {
|
||||
return Err(ProgramError::InvalidAccountData);
|
||||
}
|
||||
src.pack_into_slice(dst);
|
||||
Ok(())
|
||||
}
|
||||
}
|
53
sdk/program/src/program_stubs.rs
Normal file
53
sdk/program/src/program_stubs.rs
Normal file
@ -0,0 +1,53 @@
|
||||
//! @brief Syscall stubs when building for programs for non-BPF targets
|
||||
|
||||
#![cfg(not(target_arch = "bpf"))]
|
||||
|
||||
use crate::{account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref SYSCALL_STUBS: Arc<RwLock<Box<dyn SyscallStubs>>> = Arc::new(RwLock::new(Box::new(DefaultSyscallStubs {})));
|
||||
}
|
||||
|
||||
// The default syscall stubs don't do much, but `set_syscalls()` can be used to swap in
|
||||
// alternatives
|
||||
pub fn set_syscall_stubs(syscall_stubs: Box<dyn SyscallStubs>) -> Box<dyn SyscallStubs> {
|
||||
std::mem::replace(&mut SYSCALL_STUBS.write().unwrap(), syscall_stubs)
|
||||
}
|
||||
|
||||
pub trait SyscallStubs: Sync + Send {
|
||||
fn sol_log(&self, message: &str) {
|
||||
println!("{}", message);
|
||||
}
|
||||
fn sol_invoke_signed(
|
||||
&self,
|
||||
_instruction: &Instruction,
|
||||
_account_infos: &[AccountInfo],
|
||||
_signers_seeds: &[&[&[u8]]],
|
||||
) -> ProgramResult {
|
||||
sol_log("SyscallStubs: sol_invoke_signed() not available");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct DefaultSyscallStubs {}
|
||||
impl SyscallStubs for DefaultSyscallStubs {}
|
||||
|
||||
pub(crate) fn sol_log(message: &str) {
|
||||
SYSCALL_STUBS.read().unwrap().sol_log(message);
|
||||
}
|
||||
|
||||
pub(crate) fn sol_log_64(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64) {
|
||||
sol_log(&format!("{} {} {} {} {}", arg1, arg2, arg3, arg4, arg5));
|
||||
}
|
||||
|
||||
pub(crate) fn sol_invoke_signed(
|
||||
instruction: &Instruction,
|
||||
account_infos: &[AccountInfo],
|
||||
signers_seeds: &[&[&[u8]]],
|
||||
) -> ProgramResult {
|
||||
SYSCALL_STUBS
|
||||
.read()
|
||||
.unwrap()
|
||||
.sol_invoke_signed(instruction, account_infos, signers_seeds)
|
||||
}
|
426
sdk/program/src/pubkey.rs
Normal file
426
sdk/program/src/pubkey.rs
Normal file
@ -0,0 +1,426 @@
|
||||
use crate::{decode_error::DecodeError, hash::hashv};
|
||||
use num_derive::{FromPrimitive, ToPrimitive};
|
||||
use std::{convert::TryFrom, fmt, mem, str::FromStr};
|
||||
use thiserror::Error;
|
||||
|
||||
/// maximum length of derived pubkey seed
|
||||
pub const MAX_SEED_LEN: usize = 32;
|
||||
|
||||
#[derive(Error, Debug, Serialize, Clone, PartialEq, FromPrimitive, ToPrimitive)]
|
||||
pub enum PubkeyError {
|
||||
/// Length of the seed is too long for address generation
|
||||
#[error("Length of the seed is too long for address generation")]
|
||||
MaxSeedLengthExceeded,
|
||||
#[error("Provided seeds do not result in a valid address")]
|
||||
InvalidSeeds,
|
||||
}
|
||||
impl<T> DecodeError<T> for PubkeyError {
|
||||
fn type_of() -> &'static str {
|
||||
"PubkeyError"
|
||||
}
|
||||
}
|
||||
impl From<u64> for PubkeyError {
|
||||
fn from(error: u64) -> Self {
|
||||
match error {
|
||||
0 => PubkeyError::MaxSeedLengthExceeded,
|
||||
1 => PubkeyError::InvalidSeeds,
|
||||
_ => panic!("Unsupported PubkeyError"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash, AbiExample,
|
||||
)]
|
||||
pub struct Pubkey([u8; 32]);
|
||||
|
||||
impl crate::sanitize::Sanitize for Pubkey {}
|
||||
|
||||
#[derive(Error, Debug, Serialize, Clone, PartialEq, FromPrimitive, ToPrimitive)]
|
||||
pub enum ParsePubkeyError {
|
||||
#[error("String is the wrong size")]
|
||||
WrongSize,
|
||||
#[error("Invalid Base58 string")]
|
||||
Invalid,
|
||||
}
|
||||
impl<T> DecodeError<T> for ParsePubkeyError {
|
||||
fn type_of() -> &'static str {
|
||||
"ParsePubkeyError"
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Pubkey {
|
||||
type Err = ParsePubkeyError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let pubkey_vec = bs58::decode(s)
|
||||
.into_vec()
|
||||
.map_err(|_| ParsePubkeyError::Invalid)?;
|
||||
if pubkey_vec.len() != mem::size_of::<Pubkey>() {
|
||||
Err(ParsePubkeyError::WrongSize)
|
||||
} else {
|
||||
Ok(Pubkey::new(&pubkey_vec))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pubkey {
|
||||
pub fn new(pubkey_vec: &[u8]) -> Self {
|
||||
Self(
|
||||
<[u8; 32]>::try_from(<&[u8]>::clone(&pubkey_vec))
|
||||
.expect("Slice must be the same length as a Pubkey"),
|
||||
)
|
||||
}
|
||||
|
||||
pub const fn new_from_array(pubkey_array: [u8; 32]) -> Self {
|
||||
Self(pubkey_array)
|
||||
}
|
||||
|
||||
#[deprecated(since = "1.3.9", note = "Please use 'Pubkey::new_unique' instead")]
|
||||
#[cfg(not(target_arch = "bpf"))]
|
||||
pub fn new_rand() -> Self {
|
||||
// Consider removing Pubkey::new_rand() entirely in the v1.5 or v1.6 timeframe
|
||||
Pubkey::new(&rand::random::<[u8; 32]>())
|
||||
}
|
||||
|
||||
/// unique Pubkey for tests and benchmarks.
|
||||
pub fn new_unique() -> Self {
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
static I: AtomicU64 = AtomicU64::new(1);
|
||||
|
||||
let mut b = [0u8; 32];
|
||||
let i = I.fetch_add(1, Ordering::Relaxed);
|
||||
b[0..8].copy_from_slice(&i.to_le_bytes());
|
||||
Self::new(&b)
|
||||
}
|
||||
|
||||
pub fn create_with_seed(
|
||||
base: &Pubkey,
|
||||
seed: &str,
|
||||
owner: &Pubkey,
|
||||
) -> Result<Pubkey, PubkeyError> {
|
||||
if seed.len() > MAX_SEED_LEN {
|
||||
return Err(PubkeyError::MaxSeedLengthExceeded);
|
||||
}
|
||||
|
||||
Ok(Pubkey::new(
|
||||
hashv(&[base.as_ref(), seed.as_ref(), owner.as_ref()]).as_ref(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Create a program address
|
||||
///
|
||||
/// Program addresses are account keys that only the program has the
|
||||
/// authority to sign. The address is of the same form as a Solana
|
||||
/// `Pubkey`, except they are ensured to not be on the ed25519 curve and
|
||||
/// thus have no associated private key. When performing cross-program
|
||||
/// invocations the program can "sign" for the key by calling
|
||||
/// `invoke_signed` and passing the same seeds used to generate the address.
|
||||
/// The runtime will check that indeed the program associated with this
|
||||
/// address is the caller and thus authorized to be the signer.
|
||||
///
|
||||
/// Because the program address cannot lie on the ed25519 curve there may be
|
||||
/// seed and program id combinations that are invalid. In these cases an
|
||||
/// extra seed (bump seed) can be calculated that results in a point off the
|
||||
/// curve. Use `find_program_address` to calculate that bump seed.
|
||||
///
|
||||
/// Warning: Because of the way the seeds are hashed there is a potential
|
||||
/// for program address collisions for the same program id. The seeds are
|
||||
/// hashed sequentially which means that seeds {"abcdef"}, {"abc", "def"},
|
||||
/// and {"ab", "cd", "ef"} will all result in the same program address given
|
||||
/// the same program id. Since the change of collision is local to a given
|
||||
/// program id the developer of that program must take care to choose seeds
|
||||
/// that do not collide with themselves.
|
||||
pub fn create_program_address(
|
||||
seeds: &[&[u8]],
|
||||
program_id: &Pubkey,
|
||||
) -> Result<Pubkey, PubkeyError> {
|
||||
// Perform the calculation inline, calling this from within a program is
|
||||
// not supported
|
||||
#[cfg(not(target_arch = "bpf"))]
|
||||
{
|
||||
let mut hasher = crate::hash::Hasher::default();
|
||||
for seed in seeds.iter() {
|
||||
if seed.len() > MAX_SEED_LEN {
|
||||
return Err(PubkeyError::MaxSeedLengthExceeded);
|
||||
}
|
||||
hasher.hash(seed);
|
||||
}
|
||||
hasher.hashv(&[program_id.as_ref(), "ProgramDerivedAddress".as_ref()]);
|
||||
let hash = hasher.result();
|
||||
|
||||
if curve25519_dalek::edwards::CompressedEdwardsY::from_slice(hash.as_ref())
|
||||
.decompress()
|
||||
.is_some()
|
||||
{
|
||||
return Err(PubkeyError::InvalidSeeds);
|
||||
}
|
||||
|
||||
Ok(Pubkey::new(hash.as_ref()))
|
||||
}
|
||||
// Call via a system call to perform the calculation
|
||||
#[cfg(target_arch = "bpf")]
|
||||
{
|
||||
extern "C" {
|
||||
fn sol_create_program_address(
|
||||
seeds_addr: *const u8,
|
||||
seeds_len: u64,
|
||||
program_id_addr: *const u8,
|
||||
address_bytes_addr: *const u8,
|
||||
) -> u64;
|
||||
};
|
||||
let mut bytes = [0; 32];
|
||||
let result = unsafe {
|
||||
sol_create_program_address(
|
||||
seeds as *const _ as *const u8,
|
||||
seeds.len() as u64,
|
||||
program_id as *const _ as *const u8,
|
||||
&mut bytes as *mut _ as *mut u8,
|
||||
)
|
||||
};
|
||||
match result {
|
||||
crate::entrypoint::SUCCESS => Ok(Pubkey::new(&bytes)),
|
||||
_ => Err(result.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Find a valid program address and its corresponding bump seed which must be passed
|
||||
/// as an additional seed when calling `invoke_signed`
|
||||
#[allow(clippy::same_item_push)]
|
||||
pub fn find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8) {
|
||||
let mut bump_seed = [std::u8::MAX];
|
||||
for _ in 0..std::u8::MAX {
|
||||
{
|
||||
let mut seeds_with_bump = seeds.to_vec();
|
||||
seeds_with_bump.push(&bump_seed);
|
||||
if let Ok(address) = Self::create_program_address(&seeds_with_bump, program_id) {
|
||||
return (address, bump_seed[0]);
|
||||
}
|
||||
}
|
||||
bump_seed[0] -= 1;
|
||||
}
|
||||
panic!("Unable to find a viable program address bump seed");
|
||||
}
|
||||
|
||||
pub fn to_bytes(self) -> [u8; 32] {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Log a `Pubkey` from a program
|
||||
pub fn log(&self) {
|
||||
#[cfg(target_arch = "bpf")]
|
||||
{
|
||||
extern "C" {
|
||||
fn sol_log_pubkey(pubkey_addr: *const u8);
|
||||
};
|
||||
unsafe { sol_log_pubkey(self.as_ref() as *const _ as *const u8) };
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "bpf"))]
|
||||
crate::program_stubs::sol_log(&self.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for Pubkey {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Pubkey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", bs58::encode(self.0).into_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Pubkey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", bs58::encode(self.0).into_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::str::from_utf8;
|
||||
|
||||
#[test]
|
||||
fn test_new_unique() {
|
||||
assert!(Pubkey::new_unique() != Pubkey::new_unique());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pubkey_fromstr() {
|
||||
let pubkey = Pubkey::new_unique();
|
||||
let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string();
|
||||
|
||||
assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
|
||||
|
||||
pubkey_base58_str.push_str(&bs58::encode(pubkey.0).into_string());
|
||||
assert_eq!(
|
||||
pubkey_base58_str.parse::<Pubkey>(),
|
||||
Err(ParsePubkeyError::WrongSize)
|
||||
);
|
||||
|
||||
pubkey_base58_str.truncate(pubkey_base58_str.len() / 2);
|
||||
assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
|
||||
|
||||
pubkey_base58_str.truncate(pubkey_base58_str.len() / 2);
|
||||
assert_eq!(
|
||||
pubkey_base58_str.parse::<Pubkey>(),
|
||||
Err(ParsePubkeyError::WrongSize)
|
||||
);
|
||||
|
||||
let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string();
|
||||
assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
|
||||
|
||||
// throw some non-base58 stuff in there
|
||||
pubkey_base58_str.replace_range(..1, "I");
|
||||
assert_eq!(
|
||||
pubkey_base58_str.parse::<Pubkey>(),
|
||||
Err(ParsePubkeyError::Invalid)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_with_seed() {
|
||||
assert!(
|
||||
Pubkey::create_with_seed(&Pubkey::new_unique(), "☉", &Pubkey::new_unique()).is_ok()
|
||||
);
|
||||
assert_eq!(
|
||||
Pubkey::create_with_seed(
|
||||
&Pubkey::new_unique(),
|
||||
from_utf8(&[127; MAX_SEED_LEN + 1]).unwrap(),
|
||||
&Pubkey::new_unique()
|
||||
),
|
||||
Err(PubkeyError::MaxSeedLengthExceeded)
|
||||
);
|
||||
assert!(Pubkey::create_with_seed(
|
||||
&Pubkey::new_unique(),
|
||||
"\
|
||||
\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
|
||||
",
|
||||
&Pubkey::new_unique()
|
||||
)
|
||||
.is_ok());
|
||||
// utf-8 abuse ;)
|
||||
assert_eq!(
|
||||
Pubkey::create_with_seed(
|
||||
&Pubkey::new_unique(),
|
||||
"\
|
||||
x\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
|
||||
",
|
||||
&Pubkey::new_unique()
|
||||
),
|
||||
Err(PubkeyError::MaxSeedLengthExceeded)
|
||||
);
|
||||
|
||||
assert!(Pubkey::create_with_seed(
|
||||
&Pubkey::new_unique(),
|
||||
std::str::from_utf8(&[0; MAX_SEED_LEN]).unwrap(),
|
||||
&Pubkey::new_unique(),
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
assert!(
|
||||
Pubkey::create_with_seed(&Pubkey::new_unique(), "", &Pubkey::new_unique(),).is_ok()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Pubkey::create_with_seed(
|
||||
&Pubkey::default(),
|
||||
"limber chicken: 4/45",
|
||||
&Pubkey::default(),
|
||||
),
|
||||
Ok("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq"
|
||||
.parse()
|
||||
.unwrap())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_program_address() {
|
||||
let exceeded_seed = &[127; MAX_SEED_LEN + 1];
|
||||
let max_seed = &[0; MAX_SEED_LEN];
|
||||
let program_id = Pubkey::from_str("BPFLoader1111111111111111111111111111111111").unwrap();
|
||||
let public_key = Pubkey::from_str("SeedPubey1111111111111111111111111111111111").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Pubkey::create_program_address(&[exceeded_seed], &program_id),
|
||||
Err(PubkeyError::MaxSeedLengthExceeded)
|
||||
);
|
||||
assert_eq!(
|
||||
Pubkey::create_program_address(&[b"short_seed", exceeded_seed], &program_id),
|
||||
Err(PubkeyError::MaxSeedLengthExceeded)
|
||||
);
|
||||
assert!(Pubkey::create_program_address(&[max_seed], &program_id).is_ok());
|
||||
assert_eq!(
|
||||
Pubkey::create_program_address(&[b"", &[1]], &program_id),
|
||||
Ok("3gF2KMe9KiC6FNVBmfg9i267aMPvK37FewCip4eGBFcT"
|
||||
.parse()
|
||||
.unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
Pubkey::create_program_address(&["☉".as_ref()], &program_id),
|
||||
Ok("7ytmC1nT1xY4RfxCV2ZgyA7UakC93do5ZdyhdF3EtPj7"
|
||||
.parse()
|
||||
.unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id),
|
||||
Ok("HwRVBufQ4haG5XSgpspwKtNd3PC9GM9m1196uJW36vds"
|
||||
.parse()
|
||||
.unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
Pubkey::create_program_address(&[public_key.as_ref()], &program_id),
|
||||
Ok("GUs5qLUfsEHkcMB9T38vjr18ypEhRuNWiePW2LoK4E3K"
|
||||
.parse()
|
||||
.unwrap())
|
||||
);
|
||||
assert_ne!(
|
||||
Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id).unwrap(),
|
||||
Pubkey::create_program_address(&[b"Talking"], &program_id).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pubkey_off_curve() {
|
||||
// try a bunch of random input, all successful generated program
|
||||
// addresses must land off the curve and be unique
|
||||
let mut addresses = vec![];
|
||||
for _ in 0..1_000 {
|
||||
let program_id = Pubkey::new_unique();
|
||||
let bytes1 = rand::random::<[u8; 10]>();
|
||||
let bytes2 = rand::random::<[u8; 32]>();
|
||||
if let Ok(program_address) =
|
||||
Pubkey::create_program_address(&[&bytes1, &bytes2], &program_id)
|
||||
{
|
||||
let is_on_curve = curve25519_dalek::edwards::CompressedEdwardsY::from_slice(
|
||||
&program_address.to_bytes(),
|
||||
)
|
||||
.decompress()
|
||||
.is_some();
|
||||
assert!(!is_on_curve);
|
||||
assert!(!addresses.contains(&program_address));
|
||||
addresses.push(program_address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_program_address() {
|
||||
for _ in 0..1_000 {
|
||||
let program_id = Pubkey::new_unique();
|
||||
let (address, bump_seed) =
|
||||
Pubkey::find_program_address(&[b"Lil'", b"Bits"], &program_id);
|
||||
assert_eq!(
|
||||
address,
|
||||
Pubkey::create_program_address(&[b"Lil'", b"Bits", &[bump_seed]], &program_id)
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
168
sdk/program/src/rent.rs
Normal file
168
sdk/program/src/rent.rs
Normal file
@ -0,0 +1,168 @@
|
||||
//! configuration for network rent
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Serialize, Deserialize, PartialEq, Clone, Copy, Debug, AbiExample)]
|
||||
pub struct Rent {
|
||||
/// Rental rate
|
||||
pub lamports_per_byte_year: u64,
|
||||
|
||||
/// exemption threshold, in years
|
||||
pub exemption_threshold: f64,
|
||||
|
||||
// What portion of collected rent are to be destroyed, percentage-wise
|
||||
pub burn_percent: u8,
|
||||
}
|
||||
|
||||
/// default rental rate in lamports/byte-year, based on:
|
||||
/// 10^9 lamports per SOL
|
||||
/// $1 per SOL
|
||||
/// $0.01 per megabyte day
|
||||
/// $3.65 per megabyte year
|
||||
pub const DEFAULT_LAMPORTS_PER_BYTE_YEAR: u64 = 1_000_000_000 / 100 * 365 / (1024 * 1024);
|
||||
|
||||
/// default amount of time (in years) the balance has to include rent for
|
||||
pub const DEFAULT_EXEMPTION_THRESHOLD: f64 = 2.0;
|
||||
|
||||
/// default percentage of rent to burn (Valid values are 0 to 100)
|
||||
pub const DEFAULT_BURN_PERCENT: u8 = 50;
|
||||
|
||||
/// account storage overhead for calculation of base rent
|
||||
pub const ACCOUNT_STORAGE_OVERHEAD: u64 = 128;
|
||||
|
||||
impl Default for Rent {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
lamports_per_byte_year: DEFAULT_LAMPORTS_PER_BYTE_YEAR,
|
||||
exemption_threshold: DEFAULT_EXEMPTION_THRESHOLD,
|
||||
burn_percent: DEFAULT_BURN_PERCENT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rent {
|
||||
/// calculate how much rent to burn from the collected rent
|
||||
pub fn calculate_burn(&self, rent_collected: u64) -> (u64, u64) {
|
||||
let burned_portion = (rent_collected * u64::from(self.burn_percent)) / 100;
|
||||
(burned_portion, rent_collected - burned_portion)
|
||||
}
|
||||
/// minimum balance due for a given size Account::data.len()
|
||||
pub fn minimum_balance(&self, data_len: usize) -> u64 {
|
||||
let bytes = data_len as u64;
|
||||
(((ACCOUNT_STORAGE_OVERHEAD + bytes) * self.lamports_per_byte_year) as f64
|
||||
* self.exemption_threshold) as u64
|
||||
}
|
||||
|
||||
/// whether a given balance and data_len would be exempt
|
||||
pub fn is_exempt(&self, balance: u64, data_len: usize) -> bool {
|
||||
balance >= self.minimum_balance(data_len)
|
||||
}
|
||||
|
||||
/// rent due on account's data_len with balance
|
||||
pub fn due(&self, balance: u64, data_len: usize, years_elapsed: f64) -> (u64, bool) {
|
||||
if self.is_exempt(balance, data_len) {
|
||||
(0, true)
|
||||
} else {
|
||||
(
|
||||
((self.lamports_per_byte_year * (data_len as u64 + ACCOUNT_STORAGE_OVERHEAD))
|
||||
as f64
|
||||
* years_elapsed) as u64,
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free() -> Self {
|
||||
Self {
|
||||
lamports_per_byte_year: 0,
|
||||
..Rent::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_due() {
|
||||
let default_rent = Rent::default();
|
||||
|
||||
assert_eq!(
|
||||
default_rent.due(0, 2, 1.2),
|
||||
(
|
||||
(((2 + ACCOUNT_STORAGE_OVERHEAD) * DEFAULT_LAMPORTS_PER_BYTE_YEAR) as f64 * 1.2)
|
||||
as u64,
|
||||
DEFAULT_LAMPORTS_PER_BYTE_YEAR == 0
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
default_rent.due(
|
||||
(((2 + ACCOUNT_STORAGE_OVERHEAD) * DEFAULT_LAMPORTS_PER_BYTE_YEAR) as f64
|
||||
* DEFAULT_EXEMPTION_THRESHOLD) as u64,
|
||||
2,
|
||||
1.2
|
||||
),
|
||||
(0, true)
|
||||
);
|
||||
|
||||
let mut custom_rent = Rent::default();
|
||||
custom_rent.lamports_per_byte_year = 5;
|
||||
custom_rent.exemption_threshold = 2.5;
|
||||
|
||||
assert_eq!(
|
||||
custom_rent.due(0, 2, 1.2),
|
||||
(
|
||||
(((2 + ACCOUNT_STORAGE_OVERHEAD) * custom_rent.lamports_per_byte_year) as f64 * 1.2)
|
||||
as u64,
|
||||
false
|
||||
)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
custom_rent.due(
|
||||
(((2 + ACCOUNT_STORAGE_OVERHEAD) * custom_rent.lamports_per_byte_year) as f64
|
||||
* custom_rent.exemption_threshold) as u64,
|
||||
2,
|
||||
1.2
|
||||
),
|
||||
(0, true)
|
||||
);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn show_rent_model() {
|
||||
use crate::{clock::*, sysvar::Sysvar};
|
||||
|
||||
const SECONDS_PER_YEAR: f64 = 365.242_199 * 24.0 * 60.0 * 60.0;
|
||||
const SLOTS_PER_YEAR: f64 =
|
||||
SECONDS_PER_YEAR / (DEFAULT_TICKS_PER_SLOT as f64 / DEFAULT_TICKS_PER_SECOND as f64);
|
||||
|
||||
let rent = Rent::default();
|
||||
panic!(
|
||||
"\n\n\
|
||||
==================================================\n\
|
||||
empty account, no data:\n\
|
||||
\t{} lamports per epoch, {} lamports to be rent_exempt\n\n\
|
||||
stake_history, which is {}kB of data:\n\
|
||||
\t{} lamports per epoch, {} lamports to be rent_exempt\n\
|
||||
==================================================\n\n",
|
||||
rent.due(
|
||||
0,
|
||||
0,
|
||||
(1.0 / SLOTS_PER_YEAR) * DEFAULT_SLOTS_PER_EPOCH as f64,
|
||||
)
|
||||
.0,
|
||||
rent.minimum_balance(0),
|
||||
crate::sysvar::stake_history::StakeHistory::size_of() / 1024,
|
||||
rent.due(
|
||||
0,
|
||||
crate::sysvar::stake_history::StakeHistory::size_of(),
|
||||
(1.0 / SLOTS_PER_YEAR) * DEFAULT_SLOTS_PER_EPOCH as f64,
|
||||
)
|
||||
.0,
|
||||
rent.minimum_balance(crate::sysvar::stake_history::StakeHistory::size_of()),
|
||||
);
|
||||
}
|
||||
}
|
33
sdk/program/src/sanitize.rs
Normal file
33
sdk/program/src/sanitize.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(PartialEq, Debug, Error, Eq, Clone)]
|
||||
pub enum SanitizeError {
|
||||
#[error("index out of bounds")]
|
||||
IndexOutOfBounds,
|
||||
#[error("value out of bounds")]
|
||||
ValueOutOfBounds,
|
||||
#[error("invalid value")]
|
||||
InvalidValue,
|
||||
}
|
||||
|
||||
/// Trait for sanitizing values and members of over the wire messages.
|
||||
/// Implementation should recursively decent through the data structure
|
||||
/// and sanitize all struct members and enum clauses. Sanitize excludes
|
||||
/// signature verification checks, those are handled by another pass.
|
||||
/// Sanitize checks should include but are not limited too:
|
||||
/// * All index values are in range
|
||||
/// * All values are within their static max/min bounds
|
||||
pub trait Sanitize {
|
||||
fn sanitize(&self) -> Result<(), SanitizeError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Sanitize> Sanitize for Vec<T> {
|
||||
fn sanitize(&self) -> Result<(), SanitizeError> {
|
||||
for x in self.iter() {
|
||||
x.sanitize()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
1
sdk/program/src/secp256k1_program.rs
Normal file
1
sdk/program/src/secp256k1_program.rs
Normal file
@ -0,0 +1 @@
|
||||
crate::declare_id!("KeccakSecp256k11111111111111111111111111111");
|
65
sdk/program/src/serialize_utils.rs
Normal file
65
sdk/program/src/serialize_utils.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use crate::pubkey::Pubkey;
|
||||
use crate::sanitize::SanitizeError;
|
||||
|
||||
pub fn append_u16(buf: &mut Vec<u8>, data: u16) {
|
||||
let start = buf.len();
|
||||
buf.resize(buf.len() + 2, 0);
|
||||
let end = buf.len();
|
||||
buf[start..end].copy_from_slice(&data.to_le_bytes());
|
||||
}
|
||||
|
||||
pub fn append_u8(buf: &mut Vec<u8>, data: u8) {
|
||||
let start = buf.len();
|
||||
buf.resize(buf.len() + 1, 0);
|
||||
buf[start] = data;
|
||||
}
|
||||
|
||||
pub fn append_slice(buf: &mut Vec<u8>, data: &[u8]) {
|
||||
let start = buf.len();
|
||||
buf.resize(buf.len() + data.len(), 0);
|
||||
let end = buf.len();
|
||||
buf[start..end].copy_from_slice(data);
|
||||
}
|
||||
|
||||
pub fn read_u8(current: &mut usize, data: &[u8]) -> Result<u8, SanitizeError> {
|
||||
if data.len() < *current + 1 {
|
||||
return Err(SanitizeError::IndexOutOfBounds);
|
||||
}
|
||||
let e = data[*current];
|
||||
*current += 1;
|
||||
Ok(e)
|
||||
}
|
||||
|
||||
pub fn read_pubkey(current: &mut usize, data: &[u8]) -> Result<Pubkey, SanitizeError> {
|
||||
let len = std::mem::size_of::<Pubkey>();
|
||||
if data.len() < *current + len {
|
||||
return Err(SanitizeError::IndexOutOfBounds);
|
||||
}
|
||||
let e = Pubkey::new(&data[*current..*current + len]);
|
||||
*current += len;
|
||||
Ok(e)
|
||||
}
|
||||
|
||||
pub fn read_u16(current: &mut usize, data: &[u8]) -> Result<u16, SanitizeError> {
|
||||
if data.len() < *current + 2 {
|
||||
return Err(SanitizeError::IndexOutOfBounds);
|
||||
}
|
||||
let mut fixed_data = [0u8; 2];
|
||||
fixed_data.copy_from_slice(&data[*current..*current + 2]);
|
||||
let e = u16::from_le_bytes(fixed_data);
|
||||
*current += 2;
|
||||
Ok(e)
|
||||
}
|
||||
|
||||
pub fn read_slice(
|
||||
current: &mut usize,
|
||||
data: &[u8],
|
||||
data_len: usize,
|
||||
) -> Result<Vec<u8>, SanitizeError> {
|
||||
if data.len() < *current + data_len {
|
||||
return Err(SanitizeError::IndexOutOfBounds);
|
||||
}
|
||||
let e = data[*current..*current + data_len].to_vec();
|
||||
*current += data_len;
|
||||
Ok(e)
|
||||
}
|
293
sdk/program/src/short_vec.rs
Normal file
293
sdk/program/src/short_vec.rs
Normal file
@ -0,0 +1,293 @@
|
||||
use serde::{
|
||||
de::{self, Deserializer, SeqAccess, Visitor},
|
||||
ser::{self, SerializeTuple, Serializer},
|
||||
{Deserialize, Serialize},
|
||||
};
|
||||
use std::{fmt, marker::PhantomData, mem::size_of};
|
||||
|
||||
/// Same as u16, but serialized with 1 to 3 bytes. If the value is above
|
||||
/// 0x7f, the top bit is set and the remaining value is stored in the next
|
||||
/// bytes. Each byte follows the same pattern until the 3rd byte. The 3rd
|
||||
/// byte, if needed, uses all 8 bits to store the last byte of the original
|
||||
/// value.
|
||||
#[derive(AbiExample)]
|
||||
pub struct ShortU16(pub u16);
|
||||
|
||||
impl Serialize for ShortU16 {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
// Pass a non-zero value to serialize_tuple() so that serde_json will
|
||||
// generate an open bracket.
|
||||
let mut seq = serializer.serialize_tuple(1)?;
|
||||
|
||||
let mut rem_len = self.0;
|
||||
loop {
|
||||
let mut elem = (rem_len & 0x7f) as u8;
|
||||
rem_len >>= 7;
|
||||
if rem_len == 0 {
|
||||
seq.serialize_element(&elem)?;
|
||||
break;
|
||||
} else {
|
||||
elem |= 0x80;
|
||||
seq.serialize_element(&elem)?;
|
||||
}
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
|
||||
enum VisitResult {
|
||||
Done(usize, usize),
|
||||
More(usize, usize),
|
||||
Err,
|
||||
}
|
||||
|
||||
fn visit_byte(elem: u8, len: usize, size: usize) -> VisitResult {
|
||||
let len = len | (elem as usize & 0x7f) << (size * 7);
|
||||
let size = size + 1;
|
||||
let more = elem as usize & 0x80 == 0x80;
|
||||
|
||||
if size > size_of::<u16>() + 1 {
|
||||
VisitResult::Err
|
||||
} else if more {
|
||||
VisitResult::More(len, size)
|
||||
} else {
|
||||
VisitResult::Done(len, size)
|
||||
}
|
||||
}
|
||||
|
||||
struct ShortLenVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ShortLenVisitor {
|
||||
type Value = ShortU16;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a multi-byte length")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<ShortU16, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
let mut len: usize = 0;
|
||||
let mut size: usize = 0;
|
||||
loop {
|
||||
let elem: u8 = seq
|
||||
.next_element()?
|
||||
.ok_or_else(|| de::Error::invalid_length(size, &self))?;
|
||||
|
||||
match visit_byte(elem, len, size) {
|
||||
VisitResult::Done(l, _) => {
|
||||
len = l;
|
||||
break;
|
||||
}
|
||||
VisitResult::More(l, s) => {
|
||||
len = l;
|
||||
size = s;
|
||||
}
|
||||
VisitResult::Err => return Err(de::Error::invalid_length(size + 1, &self)),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ShortU16(len as u16))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ShortU16 {
|
||||
fn deserialize<D>(deserializer: D) -> Result<ShortU16, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_tuple(3, ShortLenVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
/// If you don't want to use the ShortVec newtype, you can do ShortVec
|
||||
/// serialization on an ordinary vector with the following field annotation:
|
||||
///
|
||||
/// #[serde(with = "short_vec")]
|
||||
///
|
||||
pub fn serialize<S: Serializer, T: Serialize>(
|
||||
elements: &[T],
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
// Pass a non-zero value to serialize_tuple() so that serde_json will
|
||||
// generate an open bracket.
|
||||
let mut seq = serializer.serialize_tuple(1)?;
|
||||
|
||||
let len = elements.len();
|
||||
if len > std::u16::MAX as usize {
|
||||
return Err(ser::Error::custom("length larger than u16"));
|
||||
}
|
||||
let short_len = ShortU16(len as u16);
|
||||
seq.serialize_element(&short_len)?;
|
||||
|
||||
for element in elements {
|
||||
seq.serialize_element(element)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
|
||||
struct ShortVecVisitor<T> {
|
||||
_t: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'de, T> Visitor<'de> for ShortVecVisitor<T>
|
||||
where
|
||||
T: Deserialize<'de>,
|
||||
{
|
||||
type Value = Vec<T>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a Vec with a multi-byte length")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Vec<T>, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
let short_len: ShortU16 = seq
|
||||
.next_element()?
|
||||
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
|
||||
let len = short_len.0 as usize;
|
||||
|
||||
let mut result = Vec::with_capacity(len);
|
||||
for i in 0..len {
|
||||
let elem = seq
|
||||
.next_element()?
|
||||
.ok_or_else(|| de::Error::invalid_length(i, &self))?;
|
||||
result.push(elem);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// If you don't want to use the ShortVec newtype, you can do ShortVec
|
||||
/// deserialization on an ordinary vector with the following field annotation:
|
||||
///
|
||||
/// #[serde(with = "short_vec")]
|
||||
///
|
||||
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: Deserialize<'de>,
|
||||
{
|
||||
let visitor = ShortVecVisitor { _t: PhantomData };
|
||||
deserializer.deserialize_tuple(std::usize::MAX, visitor)
|
||||
}
|
||||
|
||||
pub struct ShortVec<T>(pub Vec<T>);
|
||||
|
||||
impl<T: Serialize> Serialize for ShortVec<T> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serialize(&self.0, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T: Deserialize<'de>> Deserialize<'de> for ShortVec<T> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<ShortVec<T>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserialize(deserializer).map(ShortVec)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the decoded value and how many bytes it consumed.
|
||||
pub fn decode_len(bytes: &[u8]) -> Result<(usize, usize), ()> {
|
||||
let mut len = 0;
|
||||
let mut size = 0;
|
||||
for byte in bytes.iter() {
|
||||
match visit_byte(*byte, len, size) {
|
||||
VisitResult::More(l, s) => {
|
||||
len = l;
|
||||
size = s;
|
||||
}
|
||||
VisitResult::Done(len, size) => return Ok((len, size)),
|
||||
VisitResult::Err => return Err(()),
|
||||
}
|
||||
}
|
||||
Err(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use assert_matches::assert_matches;
|
||||
use bincode::{deserialize, serialize};
|
||||
|
||||
/// Return the serialized length.
|
||||
fn encode_len(len: u16) -> Vec<u8> {
|
||||
bincode::serialize(&ShortU16(len)).unwrap()
|
||||
}
|
||||
|
||||
fn assert_len_encoding(len: u16, bytes: &[u8]) {
|
||||
assert_eq!(encode_len(len), bytes, "unexpected usize encoding");
|
||||
assert_eq!(
|
||||
decode_len(bytes).unwrap(),
|
||||
(len as usize, bytes.len()),
|
||||
"unexpected usize decoding"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_short_vec_encode_len() {
|
||||
assert_len_encoding(0x0, &[0x0]);
|
||||
assert_len_encoding(0x7f, &[0x7f]);
|
||||
assert_len_encoding(0x80, &[0x80, 0x01]);
|
||||
assert_len_encoding(0xff, &[0xff, 0x01]);
|
||||
assert_len_encoding(0x100, &[0x80, 0x02]);
|
||||
assert_len_encoding(0x7fff, &[0xff, 0xff, 0x01]);
|
||||
assert_len_encoding(0xffff, &[0xff, 0xff, 0x03]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_short_vec_decode_zero_len() {
|
||||
decode_len(&[]).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_short_vec_u8() {
|
||||
let vec = ShortVec(vec![4u8; 32]);
|
||||
let bytes = serialize(&vec).unwrap();
|
||||
assert_eq!(bytes.len(), vec.0.len() + 1);
|
||||
|
||||
let vec1: ShortVec<u8> = deserialize(&bytes).unwrap();
|
||||
assert_eq!(vec.0, vec1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_short_vec_u8_too_long() {
|
||||
let vec = ShortVec(vec![4u8; std::u16::MAX as usize]);
|
||||
assert_matches!(serialize(&vec), Ok(_));
|
||||
|
||||
let vec = ShortVec(vec![4u8; std::u16::MAX as usize + 1]);
|
||||
assert_matches!(serialize(&vec), Err(_));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_short_vec_json() {
|
||||
let vec = ShortVec(vec![0, 1, 2]);
|
||||
let s = serde_json::to_string(&vec).unwrap();
|
||||
assert_eq!(s, "[[3],0,1,2]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_len_aliased_values() {
|
||||
let one1 = [0x01];
|
||||
let one2 = [0x81, 0x00];
|
||||
let one3 = [0x81, 0x80, 0x00];
|
||||
let one4 = [0x81, 0x80, 0x80, 0x00];
|
||||
|
||||
assert_eq!(decode_len(&one1).unwrap(), (1, 1));
|
||||
assert_eq!(decode_len(&one2).unwrap(), (1, 2));
|
||||
assert_eq!(decode_len(&one3).unwrap(), (1, 3));
|
||||
assert!(decode_len(&one4).is_err());
|
||||
}
|
||||
}
|
83
sdk/program/src/slot_hashes.rs
Normal file
83
sdk/program/src/slot_hashes.rs
Normal file
@ -0,0 +1,83 @@
|
||||
//! named accounts for synthesized data accounts for bank state, etc.
|
||||
//!
|
||||
//! this account carries the Bank's most recent bank hashes for some N parents
|
||||
//!
|
||||
use crate::hash::Hash;
|
||||
use std::{iter::FromIterator, ops::Deref};
|
||||
|
||||
pub const MAX_ENTRIES: usize = 512; // about 2.5 minutes to get your vote in
|
||||
|
||||
pub use crate::clock::Slot;
|
||||
|
||||
pub type SlotHash = (Slot, Hash);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Default)]
|
||||
pub struct SlotHashes(Vec<SlotHash>);
|
||||
|
||||
impl SlotHashes {
|
||||
pub fn add(&mut self, slot: Slot, hash: Hash) {
|
||||
match self.binary_search_by(|(probe, _)| slot.cmp(&probe)) {
|
||||
Ok(index) => (self.0)[index] = (slot, hash),
|
||||
Err(index) => (self.0).insert(index, (slot, hash)),
|
||||
}
|
||||
(self.0).truncate(MAX_ENTRIES);
|
||||
}
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
pub fn get(&self, slot: &Slot) -> Option<&Hash> {
|
||||
self.binary_search_by(|(probe, _)| slot.cmp(&probe))
|
||||
.ok()
|
||||
.map(|index| &self[index].1)
|
||||
}
|
||||
pub fn new(slot_hashes: &[SlotHash]) -> Self {
|
||||
let mut slot_hashes = slot_hashes.to_vec();
|
||||
slot_hashes.sort_by(|(a, _), (b, _)| b.cmp(a));
|
||||
Self(slot_hashes)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(Slot, Hash)> for SlotHashes {
|
||||
fn from_iter<I: IntoIterator<Item = (Slot, Hash)>>(iter: I) -> Self {
|
||||
Self(iter.into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for SlotHashes {
|
||||
type Target = Vec<SlotHash>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::hash::hash;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let mut slot_hashes = SlotHashes::new(&[(1, Hash::default()), (3, Hash::default())]);
|
||||
slot_hashes.add(2, Hash::default());
|
||||
assert_eq!(
|
||||
slot_hashes,
|
||||
SlotHashes(vec![
|
||||
(3, Hash::default()),
|
||||
(2, Hash::default()),
|
||||
(1, Hash::default()),
|
||||
])
|
||||
);
|
||||
|
||||
let mut slot_hashes = SlotHashes::new(&[]);
|
||||
for i in 0..MAX_ENTRIES + 1 {
|
||||
slot_hashes.add(
|
||||
i as u64,
|
||||
hash(&[(i >> 24) as u8, (i >> 16) as u8, (i >> 8) as u8, i as u8]),
|
||||
);
|
||||
}
|
||||
for i in 0..MAX_ENTRIES {
|
||||
assert_eq!(slot_hashes[i].0, (MAX_ENTRIES - i) as u64);
|
||||
}
|
||||
|
||||
assert_eq!(slot_hashes.len(), MAX_ENTRIES);
|
||||
}
|
||||
}
|
206
sdk/program/src/slot_history.rs
Normal file
206
sdk/program/src/slot_history.rs
Normal file
@ -0,0 +1,206 @@
|
||||
//!
|
||||
//! slot history
|
||||
//!
|
||||
pub use crate::clock::Slot;
|
||||
use bv::BitVec;
|
||||
use bv::BitsMut;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Serialize, Deserialize, PartialEq)]
|
||||
pub struct SlotHistory {
|
||||
pub bits: BitVec<u64>,
|
||||
pub next_slot: Slot,
|
||||
}
|
||||
|
||||
impl Default for SlotHistory {
|
||||
fn default() -> Self {
|
||||
let mut bits = BitVec::new_fill(false, MAX_ENTRIES);
|
||||
bits.set(0, true);
|
||||
Self { bits, next_slot: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for SlotHistory {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "SlotHistory {{ slot: {} bits:", self.next_slot)?;
|
||||
for i in 0..MAX_ENTRIES {
|
||||
if self.bits.get(i) {
|
||||
write!(f, "1")?;
|
||||
} else {
|
||||
write!(f, "0")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub const MAX_ENTRIES: u64 = 1024 * 1024; // 1 million slots is about 5 days
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum Check {
|
||||
Future,
|
||||
TooOld,
|
||||
Found,
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl SlotHistory {
|
||||
pub fn add(&mut self, slot: Slot) {
|
||||
if slot > self.next_slot && slot - self.next_slot >= MAX_ENTRIES {
|
||||
// Wrapped past current history,
|
||||
// clear entire bitvec.
|
||||
let full_blocks = (MAX_ENTRIES as usize) / 64;
|
||||
for i in 0..full_blocks {
|
||||
self.bits.set_block(i, 0);
|
||||
}
|
||||
} else {
|
||||
for skipped in self.next_slot..slot {
|
||||
self.bits.set(skipped % MAX_ENTRIES, false);
|
||||
}
|
||||
}
|
||||
self.bits.set(slot % MAX_ENTRIES, true);
|
||||
self.next_slot = slot + 1;
|
||||
}
|
||||
|
||||
pub fn check(&self, slot: Slot) -> Check {
|
||||
if slot > self.newest() {
|
||||
Check::Future
|
||||
} else if slot < self.oldest() {
|
||||
Check::TooOld
|
||||
} else if self.bits.get(slot % MAX_ENTRIES) {
|
||||
Check::Found
|
||||
} else {
|
||||
Check::NotFound
|
||||
}
|
||||
}
|
||||
|
||||
pub fn oldest(&self) -> Slot {
|
||||
self.next_slot.saturating_sub(MAX_ENTRIES)
|
||||
}
|
||||
|
||||
pub fn newest(&self) -> Slot {
|
||||
self.next_slot - 1
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use log::*;
|
||||
|
||||
#[test]
|
||||
fn slot_history_test1() {
|
||||
solana_logger::setup();
|
||||
// should be divisible by 64 since the clear logic works on blocks
|
||||
assert_eq!(MAX_ENTRIES % 64, 0);
|
||||
let mut slot_history = SlotHistory::default();
|
||||
info!("add 2");
|
||||
slot_history.add(2);
|
||||
assert_eq!(slot_history.check(0), Check::Found);
|
||||
assert_eq!(slot_history.check(1), Check::NotFound);
|
||||
for i in 3..MAX_ENTRIES {
|
||||
assert_eq!(slot_history.check(i), Check::Future);
|
||||
}
|
||||
info!("add 20");
|
||||
slot_history.add(20);
|
||||
info!("add max_entries");
|
||||
slot_history.add(MAX_ENTRIES);
|
||||
assert_eq!(slot_history.check(0), Check::TooOld);
|
||||
assert_eq!(slot_history.check(1), Check::NotFound);
|
||||
for i in &[2, 20, MAX_ENTRIES] {
|
||||
assert_eq!(slot_history.check(*i), Check::Found);
|
||||
}
|
||||
for i in 3..20 {
|
||||
assert_eq!(slot_history.check(i), Check::NotFound, "i: {}", i);
|
||||
}
|
||||
for i in 21..MAX_ENTRIES {
|
||||
assert_eq!(slot_history.check(i), Check::NotFound, "i: {}", i);
|
||||
}
|
||||
assert_eq!(slot_history.check(MAX_ENTRIES + 1), Check::Future);
|
||||
|
||||
info!("add max_entries + 3");
|
||||
let slot = 3 * MAX_ENTRIES + 3;
|
||||
slot_history.add(slot);
|
||||
for i in &[0, 1, 2, 20, 21, MAX_ENTRIES] {
|
||||
assert_eq!(slot_history.check(*i), Check::TooOld);
|
||||
}
|
||||
let start = slot - MAX_ENTRIES + 1;
|
||||
let end = slot;
|
||||
for i in start..end {
|
||||
assert_eq!(slot_history.check(i), Check::NotFound, "i: {}", i);
|
||||
}
|
||||
assert_eq!(slot_history.check(slot), Check::Found);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slot_history_test_wrap() {
|
||||
solana_logger::setup();
|
||||
let mut slot_history = SlotHistory::default();
|
||||
info!("add 2");
|
||||
slot_history.add(2);
|
||||
assert_eq!(slot_history.check(0), Check::Found);
|
||||
assert_eq!(slot_history.check(1), Check::NotFound);
|
||||
for i in 3..MAX_ENTRIES {
|
||||
assert_eq!(slot_history.check(i), Check::Future);
|
||||
}
|
||||
info!("add 20");
|
||||
slot_history.add(20);
|
||||
info!("add max_entries + 19");
|
||||
slot_history.add(MAX_ENTRIES + 19);
|
||||
for i in 0..19 {
|
||||
assert_eq!(slot_history.check(i), Check::TooOld);
|
||||
}
|
||||
assert_eq!(slot_history.check(MAX_ENTRIES), Check::NotFound);
|
||||
assert_eq!(slot_history.check(20), Check::Found);
|
||||
assert_eq!(slot_history.check(MAX_ENTRIES + 19), Check::Found);
|
||||
assert_eq!(slot_history.check(20), Check::Found);
|
||||
for i in 21..MAX_ENTRIES + 19 {
|
||||
assert_eq!(slot_history.check(i), Check::NotFound, "found: {}", i);
|
||||
}
|
||||
assert_eq!(slot_history.check(MAX_ENTRIES + 20), Check::Future);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slot_history_test_same_index() {
|
||||
solana_logger::setup();
|
||||
let mut slot_history = SlotHistory::default();
|
||||
info!("add 3,4");
|
||||
slot_history.add(3);
|
||||
slot_history.add(4);
|
||||
assert_eq!(slot_history.check(1), Check::NotFound);
|
||||
assert_eq!(slot_history.check(2), Check::NotFound);
|
||||
assert_eq!(slot_history.check(3), Check::Found);
|
||||
assert_eq!(slot_history.check(4), Check::Found);
|
||||
slot_history.add(MAX_ENTRIES + 5);
|
||||
assert_eq!(slot_history.check(5), Check::TooOld);
|
||||
for i in 6..MAX_ENTRIES + 5 {
|
||||
assert_eq!(slot_history.check(i), Check::NotFound, "i: {}", i);
|
||||
}
|
||||
assert_eq!(slot_history.check(MAX_ENTRIES + 5), Check::Found);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_older_slot() {
|
||||
let mut slot_history = SlotHistory::default();
|
||||
slot_history.add(10);
|
||||
slot_history.add(5);
|
||||
assert_eq!(slot_history.check(0), Check::Found);
|
||||
assert_eq!(slot_history.check(5), Check::Found);
|
||||
// If we go backwards we reset?
|
||||
assert_eq!(slot_history.check(10), Check::Future);
|
||||
assert_eq!(slot_history.check(6), Check::Future);
|
||||
assert_eq!(slot_history.check(11), Check::Future);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oldest() {
|
||||
let mut slot_history = SlotHistory::default();
|
||||
assert_eq!(slot_history.oldest(), 0);
|
||||
slot_history.add(10);
|
||||
assert_eq!(slot_history.oldest(), 0);
|
||||
slot_history.add(MAX_ENTRIES - 1);
|
||||
assert_eq!(slot_history.oldest(), 0);
|
||||
slot_history.add(MAX_ENTRIES);
|
||||
assert_eq!(slot_history.oldest(), 1);
|
||||
}
|
||||
}
|
74
sdk/program/src/stake_history.rs
Normal file
74
sdk/program/src/stake_history.rs
Normal file
@ -0,0 +1,74 @@
|
||||
//! named accounts for synthesized data accounts for bank state, etc.
|
||||
//!
|
||||
//! this account carries history about stake activations and de-activations
|
||||
//!
|
||||
pub use crate::clock::Epoch;
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
pub const MAX_ENTRIES: usize = 512; // it should never take as many as 512 epochs to warm up or cool down
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default, Clone, AbiExample)]
|
||||
pub struct StakeHistoryEntry {
|
||||
pub effective: u64, // effective stake at this epoch
|
||||
pub activating: u64, // sum of portion of stakes not fully warmed up
|
||||
pub deactivating: u64, // requested to be cooled down, not fully deactivated yet
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default, Clone, AbiExample)]
|
||||
pub struct StakeHistory(Vec<(Epoch, StakeHistoryEntry)>);
|
||||
|
||||
impl StakeHistory {
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
pub fn get(&self, epoch: &Epoch) -> Option<&StakeHistoryEntry> {
|
||||
self.binary_search_by(|probe| epoch.cmp(&probe.0))
|
||||
.ok()
|
||||
.map(|index| &self[index].1)
|
||||
}
|
||||
|
||||
pub fn add(&mut self, epoch: Epoch, entry: StakeHistoryEntry) {
|
||||
match self.binary_search_by(|probe| epoch.cmp(&probe.0)) {
|
||||
Ok(index) => (self.0)[index] = (epoch, entry),
|
||||
Err(index) => (self.0).insert(index, (epoch, entry)),
|
||||
}
|
||||
(self.0).truncate(MAX_ENTRIES);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for StakeHistory {
|
||||
type Target = Vec<(Epoch, StakeHistoryEntry)>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_stake_history() {
|
||||
let mut stake_history = StakeHistory::default();
|
||||
|
||||
for i in 0..MAX_ENTRIES as u64 + 1 {
|
||||
stake_history.add(
|
||||
i,
|
||||
StakeHistoryEntry {
|
||||
activating: i,
|
||||
..StakeHistoryEntry::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
assert_eq!(stake_history.len(), MAX_ENTRIES);
|
||||
assert_eq!(stake_history.iter().map(|entry| entry.0).min().unwrap(), 1);
|
||||
assert_eq!(stake_history.get(&0), None);
|
||||
assert_eq!(
|
||||
stake_history.get(&1),
|
||||
Some(&StakeHistoryEntry {
|
||||
activating: 1,
|
||||
..StakeHistoryEntry::default()
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
556
sdk/program/src/system_instruction.rs
Normal file
556
sdk/program/src/system_instruction.rs
Normal file
@ -0,0 +1,556 @@
|
||||
use crate::{
|
||||
decode_error::DecodeError,
|
||||
instruction::{AccountMeta, Instruction},
|
||||
nonce,
|
||||
pubkey::Pubkey,
|
||||
system_program,
|
||||
sysvar::{recent_blockhashes, rent},
|
||||
};
|
||||
use num_derive::{FromPrimitive, ToPrimitive};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug, Serialize, Clone, PartialEq, FromPrimitive, ToPrimitive)]
|
||||
pub enum SystemError {
|
||||
#[error("an account with the same address already exists")]
|
||||
AccountAlreadyInUse,
|
||||
#[error("account does not have enough SOL to perform the operation")]
|
||||
ResultWithNegativeLamports,
|
||||
#[error("cannot assign account to this program id")]
|
||||
InvalidProgramId,
|
||||
#[error("cannot allocate account data of this length")]
|
||||
InvalidAccountDataLength,
|
||||
#[error("length of requested seed is too long")]
|
||||
MaxSeedLengthExceeded,
|
||||
#[error("provided address does not match addressed derived from seed")]
|
||||
AddressWithSeedMismatch,
|
||||
}
|
||||
|
||||
impl<T> DecodeError<T> for SystemError {
|
||||
fn type_of() -> &'static str {
|
||||
"SystemError"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)]
|
||||
pub enum NonceError {
|
||||
#[error("recent blockhash list is empty")]
|
||||
NoRecentBlockhashes,
|
||||
#[error("stored nonce is still in recent_blockhashes")]
|
||||
NotExpired,
|
||||
#[error("specified nonce does not match stored nonce")]
|
||||
UnexpectedValue,
|
||||
#[error("cannot handle request in current account state")]
|
||||
BadAccountState,
|
||||
}
|
||||
|
||||
impl<E> DecodeError<E> for NonceError {
|
||||
fn type_of() -> &'static str {
|
||||
"NonceError"
|
||||
}
|
||||
}
|
||||
|
||||
/// maximum permitted size of data: 10 MB
|
||||
pub const MAX_PERMITTED_DATA_LENGTH: u64 = 10 * 1024 * 1024;
|
||||
|
||||
#[frozen_abi(digest = "EpsptsKTYzMoQGSdoWRfPbwT3odGNfK3imEUTrxpLF1i")]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, AbiExample, AbiEnumVisitor)]
|
||||
pub enum SystemInstruction {
|
||||
/// Create a new account
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. [WRITE, SIGNER] Funding account
|
||||
/// 1. [WRITE, SIGNER] New account
|
||||
CreateAccount {
|
||||
/// Number of lamports to transfer to the new account
|
||||
lamports: u64,
|
||||
|
||||
/// Number of bytes of memory to allocate
|
||||
space: u64,
|
||||
|
||||
/// Address of program that will own the new account
|
||||
owner: Pubkey,
|
||||
},
|
||||
|
||||
/// Assign account to a program
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. [WRITE, SIGNER] Assigned account public key
|
||||
Assign {
|
||||
/// Owner program account
|
||||
owner: Pubkey,
|
||||
},
|
||||
|
||||
/// Transfer lamports
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. [WRITE, SIGNER] Funding account
|
||||
/// 1. [WRITE] Recipient account
|
||||
Transfer { lamports: u64 },
|
||||
|
||||
/// Create a new account at an address derived from a base pubkey and a seed
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. [WRITE, SIGNER] Funding account
|
||||
/// 1. [WRITE] Created account
|
||||
/// 2. [SIGNER] Base account
|
||||
CreateAccountWithSeed {
|
||||
/// Base public key
|
||||
base: Pubkey,
|
||||
|
||||
/// String of ASCII chars, no longer than `Pubkey::MAX_SEED_LEN`
|
||||
seed: String,
|
||||
|
||||
/// Number of lamports to transfer to the new account
|
||||
lamports: u64,
|
||||
|
||||
/// Number of bytes of memory to allocate
|
||||
space: u64,
|
||||
|
||||
/// Owner program account address
|
||||
owner: Pubkey,
|
||||
},
|
||||
|
||||
/// Consumes a stored nonce, replacing it with a successor
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. [WRITE, SIGNER] Nonce account
|
||||
/// 1. [] RecentBlockhashes sysvar
|
||||
/// 2. [SIGNER] Nonce authority
|
||||
AdvanceNonceAccount,
|
||||
|
||||
/// Withdraw funds from a nonce account
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. [WRITE] Nonce account
|
||||
/// 1. [WRITE] Recipient account
|
||||
/// 2. [] RecentBlockhashes sysvar
|
||||
/// 3. [] Rent sysvar
|
||||
/// 4. [SIGNER] Nonce authority
|
||||
///
|
||||
/// The `u64` parameter is the lamports to withdraw, which must leave the
|
||||
/// account balance above the rent exempt reserve or at zero.
|
||||
WithdrawNonceAccount(u64),
|
||||
|
||||
/// Drive state of Uninitalized nonce account to Initialized, setting the nonce value
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. [WRITE] Nonce account
|
||||
/// 1. [] RecentBlockhashes sysvar
|
||||
/// 2. [] Rent sysvar
|
||||
///
|
||||
/// The `Pubkey` parameter specifies the entity authorized to execute nonce
|
||||
/// instruction on the account
|
||||
///
|
||||
/// No signatures are required to execute this instruction, enabling derived
|
||||
/// nonce account addresses
|
||||
InitializeNonceAccount(Pubkey),
|
||||
|
||||
/// Change the entity authorized to execute nonce instructions on the account
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. [WRITE] Nonce account
|
||||
/// 1. [SIGNER] Nonce authority
|
||||
///
|
||||
/// The `Pubkey` parameter identifies the entity to authorize
|
||||
AuthorizeNonceAccount(Pubkey),
|
||||
|
||||
/// Allocate space in a (possibly new) account without funding
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. [WRITE, SIGNER] New account
|
||||
Allocate {
|
||||
/// Number of bytes of memory to allocate
|
||||
space: u64,
|
||||
},
|
||||
|
||||
/// Allocate space for and assign an account at an address
|
||||
/// derived from a base public key and a seed
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. [WRITE] Allocated account
|
||||
/// 1. [SIGNER] Base account
|
||||
AllocateWithSeed {
|
||||
/// Base public key
|
||||
base: Pubkey,
|
||||
|
||||
/// String of ASCII chars, no longer than `pubkey::MAX_SEED_LEN`
|
||||
seed: String,
|
||||
|
||||
/// Number of bytes of memory to allocate
|
||||
space: u64,
|
||||
|
||||
/// Owner program account
|
||||
owner: Pubkey,
|
||||
},
|
||||
|
||||
/// Assign account to a program based on a seed
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. [WRITE] Assigned account
|
||||
/// 1. [SIGNER] Base account
|
||||
AssignWithSeed {
|
||||
/// Base public key
|
||||
base: Pubkey,
|
||||
|
||||
/// String of ASCII chars, no longer than `pubkey::MAX_SEED_LEN`
|
||||
seed: String,
|
||||
|
||||
/// Owner program account
|
||||
owner: Pubkey,
|
||||
},
|
||||
|
||||
/// Transfer lamports from a derived address
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. [WRITE] Funding account
|
||||
/// 1. [SIGNER] Base for funding account
|
||||
/// 2. [WRITE] Recipient account
|
||||
TransferWithSeed {
|
||||
/// Amount to transfer
|
||||
lamports: u64,
|
||||
|
||||
/// Seed to use to derive the funding account address
|
||||
from_seed: String,
|
||||
|
||||
/// Owner to use to derive the funding account address
|
||||
from_owner: Pubkey,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn create_account(
|
||||
from_pubkey: &Pubkey,
|
||||
to_pubkey: &Pubkey,
|
||||
lamports: u64,
|
||||
space: u64,
|
||||
owner: &Pubkey,
|
||||
) -> Instruction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*from_pubkey, true),
|
||||
AccountMeta::new(*to_pubkey, true),
|
||||
];
|
||||
Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::CreateAccount {
|
||||
lamports,
|
||||
space,
|
||||
owner: *owner,
|
||||
},
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
// we accept `to` as a parameter so that callers do their own error handling when
|
||||
// calling create_address_with_seed()
|
||||
pub fn create_account_with_seed(
|
||||
from_pubkey: &Pubkey,
|
||||
to_pubkey: &Pubkey, // must match create_address_with_seed(base, seed, owner)
|
||||
base: &Pubkey,
|
||||
seed: &str,
|
||||
lamports: u64,
|
||||
space: u64,
|
||||
owner: &Pubkey,
|
||||
) -> Instruction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*from_pubkey, true),
|
||||
AccountMeta::new(*to_pubkey, false),
|
||||
AccountMeta::new_readonly(*base, true),
|
||||
];
|
||||
|
||||
Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::CreateAccountWithSeed {
|
||||
base: *base,
|
||||
seed: seed.to_string(),
|
||||
lamports,
|
||||
space,
|
||||
owner: *owner,
|
||||
},
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn assign(pubkey: &Pubkey, owner: &Pubkey) -> Instruction {
|
||||
let account_metas = vec![AccountMeta::new(*pubkey, true)];
|
||||
Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::Assign { owner: *owner },
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn assign_with_seed(
|
||||
address: &Pubkey, // must match create_address_with_seed(base, seed, owner)
|
||||
base: &Pubkey,
|
||||
seed: &str,
|
||||
owner: &Pubkey,
|
||||
) -> Instruction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*address, false),
|
||||
AccountMeta::new_readonly(*base, true),
|
||||
];
|
||||
Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::AssignWithSeed {
|
||||
base: *base,
|
||||
seed: seed.to_string(),
|
||||
owner: *owner,
|
||||
},
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn transfer(from_pubkey: &Pubkey, to_pubkey: &Pubkey, lamports: u64) -> Instruction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*from_pubkey, true),
|
||||
AccountMeta::new(*to_pubkey, false),
|
||||
];
|
||||
Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::Transfer { lamports },
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn transfer_with_seed(
|
||||
from_pubkey: &Pubkey, // must match create_address_with_seed(base, seed, owner)
|
||||
from_base: &Pubkey,
|
||||
from_seed: String,
|
||||
from_owner: &Pubkey,
|
||||
to_pubkey: &Pubkey,
|
||||
lamports: u64,
|
||||
) -> Instruction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*from_pubkey, false),
|
||||
AccountMeta::new_readonly(*from_base, true),
|
||||
AccountMeta::new(*to_pubkey, false),
|
||||
];
|
||||
Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::TransferWithSeed {
|
||||
lamports,
|
||||
from_seed,
|
||||
from_owner: *from_owner,
|
||||
},
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn allocate(pubkey: &Pubkey, space: u64) -> Instruction {
|
||||
let account_metas = vec![AccountMeta::new(*pubkey, true)];
|
||||
Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::Allocate { space },
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn allocate_with_seed(
|
||||
address: &Pubkey, // must match create_address_with_seed(base, seed, owner)
|
||||
base: &Pubkey,
|
||||
seed: &str,
|
||||
space: u64,
|
||||
owner: &Pubkey,
|
||||
) -> Instruction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*address, false),
|
||||
AccountMeta::new_readonly(*base, true),
|
||||
];
|
||||
Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::AllocateWithSeed {
|
||||
base: *base,
|
||||
seed: seed.to_string(),
|
||||
space,
|
||||
owner: *owner,
|
||||
},
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
/// Create and sign new SystemInstruction::Transfer transaction to many destinations
|
||||
pub fn transfer_many(from_pubkey: &Pubkey, to_lamports: &[(Pubkey, u64)]) -> Vec<Instruction> {
|
||||
to_lamports
|
||||
.iter()
|
||||
.map(|(to_pubkey, lamports)| transfer(from_pubkey, to_pubkey, *lamports))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn create_nonce_account_with_seed(
|
||||
from_pubkey: &Pubkey,
|
||||
nonce_pubkey: &Pubkey,
|
||||
base: &Pubkey,
|
||||
seed: &str,
|
||||
authority: &Pubkey,
|
||||
lamports: u64,
|
||||
) -> Vec<Instruction> {
|
||||
vec![
|
||||
create_account_with_seed(
|
||||
from_pubkey,
|
||||
nonce_pubkey,
|
||||
base,
|
||||
seed,
|
||||
lamports,
|
||||
nonce::State::size() as u64,
|
||||
&system_program::id(),
|
||||
),
|
||||
Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::InitializeNonceAccount(*authority),
|
||||
vec![
|
||||
AccountMeta::new(*nonce_pubkey, false),
|
||||
AccountMeta::new_readonly(recent_blockhashes::id(), false),
|
||||
AccountMeta::new_readonly(rent::id(), false),
|
||||
],
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn create_nonce_account(
|
||||
from_pubkey: &Pubkey,
|
||||
nonce_pubkey: &Pubkey,
|
||||
authority: &Pubkey,
|
||||
lamports: u64,
|
||||
) -> Vec<Instruction> {
|
||||
vec![
|
||||
create_account(
|
||||
from_pubkey,
|
||||
nonce_pubkey,
|
||||
lamports,
|
||||
nonce::State::size() as u64,
|
||||
&system_program::id(),
|
||||
),
|
||||
Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::InitializeNonceAccount(*authority),
|
||||
vec![
|
||||
AccountMeta::new(*nonce_pubkey, false),
|
||||
AccountMeta::new_readonly(recent_blockhashes::id(), false),
|
||||
AccountMeta::new_readonly(rent::id(), false),
|
||||
],
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn advance_nonce_account(nonce_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> Instruction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*nonce_pubkey, false),
|
||||
AccountMeta::new_readonly(recent_blockhashes::id(), false),
|
||||
AccountMeta::new_readonly(*authorized_pubkey, true),
|
||||
];
|
||||
Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::AdvanceNonceAccount,
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn withdraw_nonce_account(
|
||||
nonce_pubkey: &Pubkey,
|
||||
authorized_pubkey: &Pubkey,
|
||||
to_pubkey: &Pubkey,
|
||||
lamports: u64,
|
||||
) -> Instruction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*nonce_pubkey, false),
|
||||
AccountMeta::new(*to_pubkey, false),
|
||||
AccountMeta::new_readonly(recent_blockhashes::id(), false),
|
||||
AccountMeta::new_readonly(rent::id(), false),
|
||||
AccountMeta::new_readonly(*authorized_pubkey, true),
|
||||
];
|
||||
Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::WithdrawNonceAccount(lamports),
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn authorize_nonce_account(
|
||||
nonce_pubkey: &Pubkey,
|
||||
authorized_pubkey: &Pubkey,
|
||||
new_authority: &Pubkey,
|
||||
) -> Instruction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*nonce_pubkey, false),
|
||||
AccountMeta::new_readonly(*authorized_pubkey, true),
|
||||
];
|
||||
Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::AuthorizeNonceAccount(*new_authority),
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::instruction::{Instruction, InstructionError};
|
||||
|
||||
fn get_keys(instruction: &Instruction) -> Vec<Pubkey> {
|
||||
instruction.accounts.iter().map(|x| x.pubkey).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_many() {
|
||||
let alice_pubkey = Pubkey::new_unique();
|
||||
let bob_pubkey = Pubkey::new_unique();
|
||||
let carol_pubkey = Pubkey::new_unique();
|
||||
let to_lamports = vec![(bob_pubkey, 1), (carol_pubkey, 2)];
|
||||
|
||||
let instructions = transfer_many(&alice_pubkey, &to_lamports);
|
||||
assert_eq!(instructions.len(), 2);
|
||||
assert_eq!(get_keys(&instructions[0]), vec![alice_pubkey, bob_pubkey]);
|
||||
assert_eq!(get_keys(&instructions[1]), vec![alice_pubkey, carol_pubkey]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_nonce_account() {
|
||||
let from_pubkey = Pubkey::new_unique();
|
||||
let nonce_pubkey = Pubkey::new_unique();
|
||||
let authorized = nonce_pubkey;
|
||||
let ixs = create_nonce_account(&from_pubkey, &nonce_pubkey, &authorized, 42);
|
||||
assert_eq!(ixs.len(), 2);
|
||||
let ix = &ixs[0];
|
||||
assert_eq!(ix.program_id, system_program::id());
|
||||
let pubkeys: Vec<_> = ix.accounts.iter().map(|am| am.pubkey).collect();
|
||||
assert!(pubkeys.contains(&from_pubkey));
|
||||
assert!(pubkeys.contains(&nonce_pubkey));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nonce_error_decode() {
|
||||
use num_traits::FromPrimitive;
|
||||
fn pretty_err<T>(err: InstructionError) -> String
|
||||
where
|
||||
T: 'static + std::error::Error + DecodeError<T> + FromPrimitive,
|
||||
{
|
||||
if let InstructionError::Custom(code) = err {
|
||||
let specific_error: T = T::decode_custom_error_to_enum(code).unwrap();
|
||||
format!(
|
||||
"{:?}: {}::{:?} - {}",
|
||||
err,
|
||||
T::type_of(),
|
||||
specific_error,
|
||||
specific_error,
|
||||
)
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
"Custom(0): NonceError::NoRecentBlockhashes - recent blockhash list is empty",
|
||||
pretty_err::<NonceError>(NonceError::NoRecentBlockhashes.into())
|
||||
);
|
||||
assert_eq!(
|
||||
"Custom(1): NonceError::NotExpired - stored nonce is still in recent_blockhashes",
|
||||
pretty_err::<NonceError>(NonceError::NotExpired.into())
|
||||
);
|
||||
assert_eq!(
|
||||
"Custom(2): NonceError::UnexpectedValue - specified nonce does not match stored nonce",
|
||||
pretty_err::<NonceError>(NonceError::UnexpectedValue.into())
|
||||
);
|
||||
assert_eq!(
|
||||
"Custom(3): NonceError::BadAccountState - cannot handle request in current account state",
|
||||
pretty_err::<NonceError>(NonceError::BadAccountState.into())
|
||||
);
|
||||
}
|
||||
}
|
1
sdk/program/src/system_program.rs
Normal file
1
sdk/program/src/system_program.rs
Normal file
@ -0,0 +1 @@
|
||||
crate::declare_id!("11111111111111111111111111111111");
|
9
sdk/program/src/sysvar/clock.rs
Normal file
9
sdk/program/src/sysvar/clock.rs
Normal file
@ -0,0 +1,9 @@
|
||||
//! This account contains the clock slot, epoch, and leader_schedule_epoch
|
||||
//!
|
||||
pub use crate::clock::Clock;
|
||||
|
||||
use crate::sysvar::Sysvar;
|
||||
|
||||
crate::declare_sysvar_id!("SysvarC1ock11111111111111111111111111111111", Clock);
|
||||
|
||||
impl Sysvar for Clock {}
|
24
sdk/program/src/sysvar/epoch_schedule.rs
Normal file
24
sdk/program/src/sysvar/epoch_schedule.rs
Normal file
@ -0,0 +1,24 @@
|
||||
//! This account contains the current cluster rent
|
||||
//!
|
||||
pub use crate::epoch_schedule::EpochSchedule;
|
||||
use crate::{account::Account, sysvar::Sysvar};
|
||||
|
||||
crate::declare_sysvar_id!("SysvarEpochSchedu1e111111111111111111111111", EpochSchedule);
|
||||
|
||||
impl Sysvar for EpochSchedule {}
|
||||
|
||||
pub fn create_account(lamports: u64, epoch_schedule: &EpochSchedule) -> Account {
|
||||
epoch_schedule.create_account(lamports)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_create_account() {
|
||||
let account = create_account(42, &EpochSchedule::default());
|
||||
let epoch_schedule = EpochSchedule::from_account(&account).unwrap();
|
||||
assert_eq!(epoch_schedule, EpochSchedule::default());
|
||||
}
|
||||
}
|
33
sdk/program/src/sysvar/fees.rs
Normal file
33
sdk/program/src/sysvar/fees.rs
Normal file
@ -0,0 +1,33 @@
|
||||
//! This account contains the current cluster fees
|
||||
//!
|
||||
use crate::{account::Account, fee_calculator::FeeCalculator, sysvar::Sysvar};
|
||||
|
||||
crate::declare_sysvar_id!("SysvarFees111111111111111111111111111111111", Fees);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||
pub struct Fees {
|
||||
pub fee_calculator: FeeCalculator,
|
||||
}
|
||||
|
||||
impl Sysvar for Fees {}
|
||||
|
||||
pub fn create_account(lamports: u64, fee_calculator: &FeeCalculator) -> Account {
|
||||
Fees {
|
||||
fee_calculator: fee_calculator.clone(),
|
||||
}
|
||||
.create_account(lamports)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_fees_create_account() {
|
||||
let lamports = 42;
|
||||
let account = create_account(lamports, &FeeCalculator::default());
|
||||
let fees = Fees::from_account(&account).unwrap();
|
||||
assert_eq!(fees.fee_calculator, FeeCalculator::default());
|
||||
}
|
||||
}
|
38
sdk/program/src/sysvar/instructions.rs
Normal file
38
sdk/program/src/sysvar/instructions.rs
Normal file
@ -0,0 +1,38 @@
|
||||
//! This account contains the serialized transaction instructions
|
||||
|
||||
use crate::{instruction::Instruction, sanitize::SanitizeError, sysvar::Sysvar};
|
||||
|
||||
pub type Instructions = Vec<Instruction>;
|
||||
|
||||
crate::declare_sysvar_id!("Sysvar1nstructions1111111111111111111111111", Instructions);
|
||||
|
||||
impl Sysvar for Instructions {}
|
||||
|
||||
pub fn load_current_index(data: &[u8]) -> u16 {
|
||||
let mut instr_fixed_data = [0u8; 2];
|
||||
let len = data.len();
|
||||
instr_fixed_data.copy_from_slice(&data[len - 2..len]);
|
||||
u16::from_le_bytes(instr_fixed_data)
|
||||
}
|
||||
|
||||
pub fn store_current_index(data: &mut [u8], instruction_index: u16) {
|
||||
let last_index = data.len() - 2;
|
||||
data[last_index..last_index + 2].copy_from_slice(&instruction_index.to_le_bytes());
|
||||
}
|
||||
|
||||
pub fn load_instruction_at(index: usize, data: &[u8]) -> Result<Instruction, SanitizeError> {
|
||||
crate::message::Message::deserialize_instruction(index, data)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_load_store_instruction() {
|
||||
let mut data = [4u8; 10];
|
||||
store_current_index(&mut data, 3);
|
||||
assert_eq!(load_current_index(&data), 3);
|
||||
assert_eq!([4u8; 8], data[0..8]);
|
||||
}
|
||||
}
|
95
sdk/program/src/sysvar/mod.rs
Normal file
95
sdk/program/src/sysvar/mod.rs
Normal file
@ -0,0 +1,95 @@
|
||||
//! named accounts for synthesized data accounts for bank state, etc.
|
||||
//!
|
||||
use crate::{
|
||||
account::{Account, KeyedAccount},
|
||||
account_info::AccountInfo,
|
||||
instruction::InstructionError,
|
||||
program_error::ProgramError,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
pub mod clock;
|
||||
pub mod epoch_schedule;
|
||||
pub mod fees;
|
||||
pub mod instructions;
|
||||
pub mod recent_blockhashes;
|
||||
pub mod rent;
|
||||
pub mod rewards;
|
||||
pub mod slot_hashes;
|
||||
pub mod slot_history;
|
||||
pub mod stake_history;
|
||||
|
||||
pub fn is_sysvar_id(id: &Pubkey) -> bool {
|
||||
clock::check_id(id)
|
||||
|| epoch_schedule::check_id(id)
|
||||
|| fees::check_id(id)
|
||||
|| recent_blockhashes::check_id(id)
|
||||
|| rent::check_id(id)
|
||||
|| rewards::check_id(id)
|
||||
|| slot_hashes::check_id(id)
|
||||
|| slot_history::check_id(id)
|
||||
|| stake_history::check_id(id)
|
||||
|| instructions::check_id(id)
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! declare_sysvar_id(
|
||||
($name:expr, $type:ty) => (
|
||||
$crate::declare_id!($name);
|
||||
|
||||
impl $crate::sysvar::SysvarId for $type {
|
||||
fn check_id(pubkey: &$crate::pubkey::Pubkey) -> bool {
|
||||
check_id(pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_sysvar_id() {
|
||||
if !$crate::sysvar::is_sysvar_id(&id()) {
|
||||
panic!("sysvar::is_sysvar_id() doesn't know about {}", $name);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// owner pubkey for sysvar accounts
|
||||
crate::declare_id!("Sysvar1111111111111111111111111111111111111");
|
||||
|
||||
pub trait SysvarId {
|
||||
fn check_id(pubkey: &Pubkey) -> bool;
|
||||
}
|
||||
|
||||
// utilities for moving into and out of Accounts
|
||||
pub trait Sysvar:
|
||||
SysvarId + Default + Sized + serde::Serialize + serde::de::DeserializeOwned
|
||||
{
|
||||
fn size_of() -> usize {
|
||||
bincode::serialized_size(&Self::default()).unwrap() as usize
|
||||
}
|
||||
fn from_account(account: &Account) -> Option<Self> {
|
||||
bincode::deserialize(&account.data).ok()
|
||||
}
|
||||
fn to_account(&self, account: &mut Account) -> Option<()> {
|
||||
bincode::serialize_into(&mut account.data[..], self).ok()
|
||||
}
|
||||
fn from_account_info(account_info: &AccountInfo) -> Result<Self, ProgramError> {
|
||||
bincode::deserialize(&account_info.data.borrow()).map_err(|_| ProgramError::InvalidArgument)
|
||||
}
|
||||
fn to_account_info(&self, account_info: &mut AccountInfo) -> Option<()> {
|
||||
bincode::serialize_into(&mut account_info.data.borrow_mut()[..], self).ok()
|
||||
}
|
||||
fn from_keyed_account(keyed_account: &KeyedAccount) -> Result<Self, InstructionError> {
|
||||
if !Self::check_id(keyed_account.unsigned_key()) {
|
||||
return Err(InstructionError::InvalidArgument);
|
||||
}
|
||||
Self::from_account(&*keyed_account.try_account_ref()?)
|
||||
.ok_or(InstructionError::InvalidArgument)
|
||||
}
|
||||
fn create_account(&self, lamports: u64) -> Account {
|
||||
let data_len = Self::size_of().max(bincode::serialized_size(self).unwrap() as usize);
|
||||
let mut account = Account::new(lamports, data_len, &id());
|
||||
self.to_account(&mut account).unwrap();
|
||||
account
|
||||
}
|
||||
}
|
253
sdk/program/src/sysvar/recent_blockhashes.rs
Normal file
253
sdk/program/src/sysvar/recent_blockhashes.rs
Normal file
@ -0,0 +1,253 @@
|
||||
use crate::{
|
||||
account::Account,
|
||||
declare_sysvar_id,
|
||||
fee_calculator::FeeCalculator,
|
||||
hash::{hash, Hash},
|
||||
sysvar::Sysvar,
|
||||
};
|
||||
use std::{cmp::Ordering, collections::BinaryHeap, iter::FromIterator, ops::Deref};
|
||||
|
||||
pub const MAX_ENTRIES: usize = 150;
|
||||
|
||||
declare_sysvar_id!(
|
||||
"SysvarRecentB1ockHashes11111111111111111111",
|
||||
RecentBlockhashes
|
||||
);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
|
||||
pub struct Entry {
|
||||
pub blockhash: Hash,
|
||||
pub fee_calculator: FeeCalculator,
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
pub fn new(blockhash: &Hash, fee_calculator: &FeeCalculator) -> Self {
|
||||
Self {
|
||||
blockhash: *blockhash,
|
||||
fee_calculator: fee_calculator.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct IterItem<'a>(pub u64, pub &'a Hash, pub &'a FeeCalculator);
|
||||
|
||||
impl<'a> Eq for IterItem<'a> {}
|
||||
|
||||
impl<'a> PartialEq for IterItem<'a> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Ord for IterItem<'a> {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.0.cmp(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialOrd for IterItem<'a> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub struct RecentBlockhashes(Vec<Entry>);
|
||||
|
||||
impl Default for RecentBlockhashes {
|
||||
fn default() -> Self {
|
||||
Self(Vec::with_capacity(MAX_ENTRIES))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromIterator<IterItem<'a>> for RecentBlockhashes {
|
||||
fn from_iter<I>(iter: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = IterItem<'a>>,
|
||||
{
|
||||
let mut new = Self::default();
|
||||
for i in iter {
|
||||
new.0.push(Entry::new(i.1, i.2))
|
||||
}
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
// This is cherry-picked from HEAD of rust-lang's master (ref1) because it's
|
||||
// a nightly-only experimental API.
|
||||
// (binary_heap_into_iter_sorted [rustc issue #59278])
|
||||
// Remove this and use the standard API once BinaryHeap::into_iter_sorted (ref2)
|
||||
// is stabilized.
|
||||
// ref1: https://github.com/rust-lang/rust/blob/2f688ac602d50129388bb2a5519942049096cbff/src/liballoc/collections/binary_heap.rs#L1149
|
||||
// ref2: https://doc.rust-lang.org/std/collections/struct.BinaryHeap.html#into_iter_sorted.v
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct IntoIterSorted<T> {
|
||||
inner: BinaryHeap<T>,
|
||||
}
|
||||
|
||||
impl<T: Ord> Iterator for IntoIterSorted<T> {
|
||||
type Item = T;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<T> {
|
||||
self.inner.pop()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let exact = self.inner.len();
|
||||
(exact, Some(exact))
|
||||
}
|
||||
}
|
||||
|
||||
impl Sysvar for RecentBlockhashes {
|
||||
fn size_of() -> usize {
|
||||
// hard-coded so that we don't have to construct an empty
|
||||
6008 // golden, update if MAX_ENTRIES changes
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for RecentBlockhashes {
|
||||
type Target = Vec<Entry>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_account(lamports: u64) -> Account {
|
||||
RecentBlockhashes::default().create_account(lamports)
|
||||
}
|
||||
|
||||
pub fn update_account<'a, I>(account: &mut Account, recent_blockhash_iter: I) -> Option<()>
|
||||
where
|
||||
I: IntoIterator<Item = IterItem<'a>>,
|
||||
{
|
||||
let sorted = BinaryHeap::from_iter(recent_blockhash_iter);
|
||||
let sorted_iter = IntoIterSorted { inner: sorted };
|
||||
let recent_blockhash_iter = sorted_iter.take(MAX_ENTRIES);
|
||||
let recent_blockhashes = RecentBlockhashes::from_iter(recent_blockhash_iter);
|
||||
recent_blockhashes.to_account(account)
|
||||
}
|
||||
|
||||
pub fn create_account_with_data<'a, I>(lamports: u64, recent_blockhash_iter: I) -> Account
|
||||
where
|
||||
I: IntoIterator<Item = IterItem<'a>>,
|
||||
{
|
||||
let mut account = create_account(lamports);
|
||||
update_account(&mut account, recent_blockhash_iter).unwrap();
|
||||
account
|
||||
}
|
||||
|
||||
pub fn create_test_recent_blockhashes(start: usize) -> RecentBlockhashes {
|
||||
let blocks: Vec<_> = (start..start + MAX_ENTRIES)
|
||||
.map(|i| {
|
||||
(
|
||||
i as u64,
|
||||
hash(&bincode::serialize(&i).unwrap()),
|
||||
FeeCalculator::new(i as u64 * 100),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let bhq: Vec<_> = blocks
|
||||
.iter()
|
||||
.map(|(i, hash, fee_calc)| IterItem(*i, hash, fee_calc))
|
||||
.collect();
|
||||
RecentBlockhashes::from_iter(bhq.into_iter())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{clock::MAX_PROCESSING_AGE, hash::HASH_BYTES};
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::assertions_on_constants)]
|
||||
fn test_sysvar_can_hold_all_active_blockhashes() {
|
||||
// Ensure we can still hold all of the active entries in `BlockhashQueue`
|
||||
assert!(MAX_PROCESSING_AGE <= MAX_ENTRIES);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_size_of() {
|
||||
let entry = Entry::new(&Hash::default(), &FeeCalculator::default());
|
||||
assert_eq!(
|
||||
bincode::serialized_size(&RecentBlockhashes(vec![entry; MAX_ENTRIES])).unwrap()
|
||||
as usize,
|
||||
RecentBlockhashes::size_of()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_account_empty() {
|
||||
let account = create_account_with_data(42, vec![].into_iter());
|
||||
let recent_blockhashes = RecentBlockhashes::from_account(&account).unwrap();
|
||||
assert_eq!(recent_blockhashes, RecentBlockhashes::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_account_full() {
|
||||
let def_hash = Hash::default();
|
||||
let def_fees = FeeCalculator::default();
|
||||
let account = create_account_with_data(
|
||||
42,
|
||||
vec![IterItem(0u64, &def_hash, &def_fees); MAX_ENTRIES].into_iter(),
|
||||
);
|
||||
let recent_blockhashes = RecentBlockhashes::from_account(&account).unwrap();
|
||||
assert_eq!(recent_blockhashes.len(), MAX_ENTRIES);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_account_truncate() {
|
||||
let def_hash = Hash::default();
|
||||
let def_fees = FeeCalculator::default();
|
||||
let account = create_account_with_data(
|
||||
42,
|
||||
vec![IterItem(0u64, &def_hash, &def_fees); MAX_ENTRIES + 1].into_iter(),
|
||||
);
|
||||
let recent_blockhashes = RecentBlockhashes::from_account(&account).unwrap();
|
||||
assert_eq!(recent_blockhashes.len(), MAX_ENTRIES);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_account_unsorted() {
|
||||
let def_fees = FeeCalculator::default();
|
||||
let mut unsorted_blocks: Vec<_> = (0..MAX_ENTRIES)
|
||||
.map(|i| {
|
||||
(i as u64, {
|
||||
// create hash with visibly recognizable ordering
|
||||
let mut h = [0; HASH_BYTES];
|
||||
h[HASH_BYTES - 1] = i as u8;
|
||||
Hash::new(&h)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
unsorted_blocks.shuffle(&mut thread_rng());
|
||||
|
||||
let account = create_account_with_data(
|
||||
42,
|
||||
unsorted_blocks
|
||||
.iter()
|
||||
.map(|(i, hash)| IterItem(*i, hash, &def_fees)),
|
||||
);
|
||||
let recent_blockhashes = RecentBlockhashes::from_account(&account).unwrap();
|
||||
|
||||
let mut unsorted_recent_blockhashes: Vec<_> = unsorted_blocks
|
||||
.iter()
|
||||
.map(|(i, hash)| IterItem(*i, hash, &def_fees))
|
||||
.collect();
|
||||
unsorted_recent_blockhashes.sort();
|
||||
unsorted_recent_blockhashes.reverse();
|
||||
let expected_recent_blockhashes: Vec<_> = (unsorted_recent_blockhashes
|
||||
.into_iter()
|
||||
.map(|IterItem(_, b, f)| Entry::new(b, f)))
|
||||
.collect();
|
||||
|
||||
assert_eq!(*recent_blockhashes, expected_recent_blockhashes);
|
||||
}
|
||||
}
|
42
sdk/program/src/sysvar/rent.rs
Normal file
42
sdk/program/src/sysvar/rent.rs
Normal file
@ -0,0 +1,42 @@
|
||||
//! This account contains the current cluster rent
|
||||
//!
|
||||
pub use crate::rent::Rent;
|
||||
|
||||
use crate::{
|
||||
account::{Account, KeyedAccount},
|
||||
instruction::InstructionError,
|
||||
sysvar::Sysvar,
|
||||
};
|
||||
|
||||
crate::declare_sysvar_id!("SysvarRent111111111111111111111111111111111", Rent);
|
||||
|
||||
impl Sysvar for Rent {}
|
||||
|
||||
pub fn create_account(lamports: u64, rent: &Rent) -> Account {
|
||||
rent.create_account(lamports)
|
||||
}
|
||||
|
||||
pub fn verify_rent_exemption(
|
||||
keyed_account: &KeyedAccount,
|
||||
rent_sysvar_account: &KeyedAccount,
|
||||
) -> Result<(), InstructionError> {
|
||||
let rent = Rent::from_keyed_account(rent_sysvar_account)?;
|
||||
if !rent.is_exempt(keyed_account.lamports()?, keyed_account.data_len()?) {
|
||||
Err(InstructionError::InsufficientFunds)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_rent_create_account() {
|
||||
let lamports = 42;
|
||||
let account = create_account(lamports, &Rent::default());
|
||||
let rent = Rent::from_account(&account).unwrap();
|
||||
assert_eq!(rent, Rent::default());
|
||||
}
|
||||
}
|
34
sdk/program/src/sysvar/rewards.rs
Normal file
34
sdk/program/src/sysvar/rewards.rs
Normal file
@ -0,0 +1,34 @@
|
||||
//! DEPRECATED: This sysvar can be removed once the pico-inflation feature is enabled
|
||||
//!
|
||||
use crate::{account::Account, sysvar::Sysvar};
|
||||
|
||||
crate::declare_sysvar_id!("SysvarRewards111111111111111111111111111111", Rewards);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Serialize, Deserialize, Debug, Default, PartialEq)]
|
||||
pub struct Rewards {
|
||||
pub validator_point_value: f64,
|
||||
pub unused: f64,
|
||||
}
|
||||
|
||||
impl Sysvar for Rewards {}
|
||||
|
||||
pub fn create_account(lamports: u64, validator_point_value: f64) -> Account {
|
||||
Rewards {
|
||||
validator_point_value,
|
||||
unused: 0.0,
|
||||
}
|
||||
.create_account(lamports)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_create_account() {
|
||||
let account = create_account(1, 0.0);
|
||||
let rewards = Rewards::from_account(&account).unwrap();
|
||||
assert_eq!(rewards, Rewards::default());
|
||||
}
|
||||
}
|
45
sdk/program/src/sysvar/slot_hashes.rs
Normal file
45
sdk/program/src/sysvar/slot_hashes.rs
Normal file
@ -0,0 +1,45 @@
|
||||
//! named accounts for synthesized data accounts for bank state, etc.
|
||||
//!
|
||||
//! this account carries the Bank's most recent bank hashes for some N parents
|
||||
//!
|
||||
pub use crate::slot_hashes::SlotHashes;
|
||||
|
||||
use crate::sysvar::Sysvar;
|
||||
|
||||
crate::declare_sysvar_id!("SysvarS1otHashes111111111111111111111111111", SlotHashes);
|
||||
|
||||
impl Sysvar for SlotHashes {
|
||||
// override
|
||||
fn size_of() -> usize {
|
||||
// hard-coded so that we don't have to construct an empty
|
||||
20_488 // golden, update if MAX_ENTRIES changes
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{clock::Slot, hash::Hash, slot_hashes::MAX_ENTRIES};
|
||||
|
||||
#[test]
|
||||
fn test_size_of() {
|
||||
assert_eq!(
|
||||
SlotHashes::size_of(),
|
||||
bincode::serialized_size(
|
||||
&(0..MAX_ENTRIES)
|
||||
.map(|slot| (slot as Slot, Hash::default()))
|
||||
.collect::<SlotHashes>()
|
||||
)
|
||||
.unwrap() as usize
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_account() {
|
||||
let lamports = 42;
|
||||
let account = SlotHashes::new(&[]).create_account(lamports);
|
||||
assert_eq!(account.data.len(), SlotHashes::size_of());
|
||||
let slot_hashes = SlotHashes::from_account(&account);
|
||||
assert_eq!(slot_hashes, Some(SlotHashes::default()));
|
||||
}
|
||||
}
|
30
sdk/program/src/sysvar/slot_history.rs
Normal file
30
sdk/program/src/sysvar/slot_history.rs
Normal file
@ -0,0 +1,30 @@
|
||||
//! named accounts for synthesized data accounts for bank state, etc.
|
||||
//!
|
||||
//! this account carries a bitvector of slots present over the past
|
||||
//! epoch
|
||||
//!
|
||||
pub use crate::slot_history::SlotHistory;
|
||||
|
||||
use crate::sysvar::Sysvar;
|
||||
|
||||
crate::declare_sysvar_id!("SysvarS1otHistory11111111111111111111111111", SlotHistory);
|
||||
|
||||
impl Sysvar for SlotHistory {
|
||||
// override
|
||||
fn size_of() -> usize {
|
||||
// hard-coded so that we don't have to construct an empty
|
||||
131_097 // golden, update if MAX_ENTRIES changes
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_size_of() {
|
||||
assert_eq!(
|
||||
SlotHistory::size_of(),
|
||||
bincode::serialized_size(&SlotHistory::default()).unwrap() as usize
|
||||
);
|
||||
}
|
||||
}
|
82
sdk/program/src/sysvar/stake_history.rs
Normal file
82
sdk/program/src/sysvar/stake_history.rs
Normal file
@ -0,0 +1,82 @@
|
||||
//! named accounts for synthesized data accounts for bank state, etc.
|
||||
//!
|
||||
//! this account carries history about stake activations and de-activations
|
||||
//!
|
||||
pub use crate::stake_history::StakeHistory;
|
||||
|
||||
use crate::{account::Account, sysvar::Sysvar};
|
||||
|
||||
crate::declare_sysvar_id!("SysvarStakeHistory1111111111111111111111111", StakeHistory);
|
||||
|
||||
impl Sysvar for StakeHistory {
|
||||
// override
|
||||
fn size_of() -> usize {
|
||||
// hard-coded so that we don't have to construct an empty
|
||||
16392 // golden, update if MAX_ENTRIES changes
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_account(lamports: u64, stake_history: &StakeHistory) -> Account {
|
||||
stake_history.create_account(lamports)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::stake_history::*;
|
||||
|
||||
#[test]
|
||||
fn test_size_of() {
|
||||
let mut stake_history = StakeHistory::default();
|
||||
for i in 0..MAX_ENTRIES as u64 {
|
||||
stake_history.add(
|
||||
i,
|
||||
StakeHistoryEntry {
|
||||
activating: i,
|
||||
..StakeHistoryEntry::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
bincode::serialized_size(&stake_history).unwrap() as usize,
|
||||
StakeHistory::size_of()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_account() {
|
||||
let lamports = 42;
|
||||
let account = StakeHistory::default().create_account(lamports);
|
||||
assert_eq!(account.data.len(), StakeHistory::size_of());
|
||||
|
||||
let stake_history = StakeHistory::from_account(&account);
|
||||
assert_eq!(stake_history, Some(StakeHistory::default()));
|
||||
|
||||
let mut stake_history = stake_history.unwrap();
|
||||
for i in 0..MAX_ENTRIES as u64 + 1 {
|
||||
stake_history.add(
|
||||
i,
|
||||
StakeHistoryEntry {
|
||||
activating: i,
|
||||
..StakeHistoryEntry::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
assert_eq!(stake_history.len(), MAX_ENTRIES);
|
||||
assert_eq!(stake_history.iter().map(|entry| entry.0).min().unwrap(), 1);
|
||||
assert_eq!(stake_history.get(&0), None);
|
||||
assert_eq!(
|
||||
stake_history.get(&1),
|
||||
Some(&StakeHistoryEntry {
|
||||
activating: 1,
|
||||
..StakeHistoryEntry::default()
|
||||
})
|
||||
);
|
||||
// verify the account can hold a full instance
|
||||
assert_eq!(
|
||||
StakeHistory::from_account(&stake_history.create_account(lamports)),
|
||||
Some(stake_history)
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user