Add non-fungible token program (#7007)
* Add non-fungible token program * Remove issuer and id from state * Boot NftInstruction and NftState * Rename NFT to Ownable Maybe this should be "Owned" to avoid confusion with an Ownable trait? * Rename directory * Delete unreachable branch * Don't use copy_from_slice - need an error, not a panic. * Rename contract_pubkey to account_pubkey
This commit is contained in:
parent
6ec918fabb
commit
bfa2535ea1
|
@ -3789,6 +3789,19 @@ dependencies = [
|
|||
"solana-sdk 0.21.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solana-ownable-api"
|
||||
version = "0.21.0"
|
||||
dependencies = [
|
||||
"bincode 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"solana-runtime 0.21.0",
|
||||
"solana-sdk 0.21.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solana-perf"
|
||||
version = "0.21.0"
|
||||
|
|
|
@ -38,6 +38,7 @@ members = [
|
|||
"programs/exchange_program",
|
||||
"programs/failure_program",
|
||||
"programs/noop_program",
|
||||
"programs/ownable_api",
|
||||
"programs/stake_api",
|
||||
"programs/stake_program",
|
||||
"programs/stake_tests",
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "solana-ownable-api"
|
||||
version = "0.21.0"
|
||||
description = "ownable program API"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.2.0"
|
||||
serde = "1.0.102"
|
||||
serde_derive = "1.0.102"
|
||||
solana-sdk = { path = "../../sdk", version = "0.21.0" }
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-runtime = { path = "../../runtime", version = "0.21.0" }
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
name = "solana_ownable_api"
|
|
@ -0,0 +1,12 @@
|
|||
pub mod ownable_instruction;
|
||||
pub mod ownable_processor;
|
||||
|
||||
const OWNABLE_PROGRAM_ID: [u8; 32] = [
|
||||
12, 6, 169, 236, 232, 53, 216, 159, 221, 186, 8, 8, 33, 45, 166, 249, 243, 55, 177, 184, 195,
|
||||
132, 141, 34, 63, 108, 219, 80, 0, 0, 0, 0,
|
||||
];
|
||||
|
||||
solana_sdk::solana_name_id!(
|
||||
OWNABLE_PROGRAM_ID,
|
||||
"ownab1e111111111111111111111111111111111111"
|
||||
);
|
|
@ -0,0 +1,64 @@
|
|||
use num_derive::{FromPrimitive, ToPrimitive};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use solana_sdk::{
|
||||
instruction::{AccountMeta, Instruction},
|
||||
instruction_processor_utils::DecodeError,
|
||||
pubkey::Pubkey,
|
||||
system_instruction,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)]
|
||||
pub enum OwnableError {
|
||||
IncorrectOwner,
|
||||
}
|
||||
|
||||
impl<T> DecodeError<T> for OwnableError {
|
||||
fn type_of() -> &'static str {
|
||||
"OwnableError"
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for OwnableError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
OwnableError::IncorrectOwner => "incorrect owner",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
impl std::error::Error for OwnableError {}
|
||||
|
||||
fn initialize_account(account_pubkey: &Pubkey, owner_pubkey: &Pubkey) -> Instruction {
|
||||
let keys = vec![AccountMeta::new(*account_pubkey, false)];
|
||||
Instruction::new(crate::id(), &owner_pubkey, keys)
|
||||
}
|
||||
|
||||
pub fn create_account(
|
||||
payer_pubkey: &Pubkey,
|
||||
account_pubkey: &Pubkey,
|
||||
owner_pubkey: &Pubkey,
|
||||
lamports: u64,
|
||||
) -> Vec<Instruction> {
|
||||
let space = std::mem::size_of::<Pubkey>() as u64;
|
||||
vec![
|
||||
system_instruction::create_account(
|
||||
&payer_pubkey,
|
||||
account_pubkey,
|
||||
lamports,
|
||||
space,
|
||||
&crate::id(),
|
||||
),
|
||||
initialize_account(account_pubkey, owner_pubkey),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn set_owner(account_pubkey: &Pubkey, old_pubkey: &Pubkey, new_pubkey: &Pubkey) -> Instruction {
|
||||
let keys = vec![
|
||||
AccountMeta::new(*account_pubkey, false),
|
||||
AccountMeta::new(*old_pubkey, true),
|
||||
];
|
||||
Instruction::new(crate::id(), &new_pubkey, keys)
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
//! Ownable program
|
||||
|
||||
use crate::ownable_instruction::OwnableError;
|
||||
use bincode::serialize_into;
|
||||
use solana_sdk::{
|
||||
account::KeyedAccount,
|
||||
instruction::InstructionError,
|
||||
instruction_processor_utils::{limited_deserialize, next_keyed_account},
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
fn set_owner(
|
||||
account_owner_pubkey: &mut Pubkey,
|
||||
new_owner_pubkey: Pubkey,
|
||||
owner_keyed_account: &KeyedAccount,
|
||||
) -> Result<(), InstructionError> {
|
||||
match owner_keyed_account.signer_key() {
|
||||
None => return Err(InstructionError::MissingRequiredSignature),
|
||||
Some(signer_key) => {
|
||||
if account_owner_pubkey != signer_key {
|
||||
return Err(OwnableError::IncorrectOwner.into());
|
||||
}
|
||||
*account_owner_pubkey = new_owner_pubkey;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_instruction(
|
||||
_program_id: &Pubkey,
|
||||
keyed_accounts: &mut [KeyedAccount],
|
||||
data: &[u8],
|
||||
) -> Result<(), InstructionError> {
|
||||
let new_owner_pubkey: Pubkey = limited_deserialize(data)?;
|
||||
let keyed_accounts_iter = &mut keyed_accounts.iter_mut();
|
||||
let account_keyed_account = &mut next_keyed_account(keyed_accounts_iter)?;
|
||||
let mut account_owner_pubkey: Pubkey =
|
||||
limited_deserialize(&account_keyed_account.account.data)?;
|
||||
|
||||
if account_owner_pubkey == Pubkey::default() {
|
||||
account_owner_pubkey = new_owner_pubkey;
|
||||
} else {
|
||||
let owner_keyed_account = &mut next_keyed_account(keyed_accounts_iter)?;
|
||||
set_owner(
|
||||
&mut account_owner_pubkey,
|
||||
new_owner_pubkey,
|
||||
&owner_keyed_account,
|
||||
)?;
|
||||
}
|
||||
|
||||
serialize_into(
|
||||
&mut account_keyed_account.account.data[..],
|
||||
&account_owner_pubkey,
|
||||
)
|
||||
.map_err(|_| InstructionError::AccountDataTooSmall)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ownable_instruction;
|
||||
use solana_runtime::{bank::Bank, bank_client::BankClient};
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
client::SyncClient,
|
||||
genesis_config::create_genesis_config,
|
||||
message::Message,
|
||||
signature::{Keypair, KeypairUtil, Signature},
|
||||
system_program,
|
||||
transport::Result,
|
||||
};
|
||||
|
||||
fn create_bank(lamports: u64) -> (Bank, Keypair) {
|
||||
let (genesis_config, mint_keypair) = create_genesis_config(lamports);
|
||||
let mut bank = Bank::new(&genesis_config);
|
||||
bank.add_instruction_processor(crate::id(), process_instruction);
|
||||
(bank, mint_keypair)
|
||||
}
|
||||
|
||||
fn create_bank_client(lamports: u64) -> (BankClient, Keypair) {
|
||||
let (bank, mint_keypair) = create_bank(lamports);
|
||||
(BankClient::new(bank), mint_keypair)
|
||||
}
|
||||
|
||||
fn create_ownable_account(
|
||||
bank_client: &BankClient,
|
||||
payer_keypair: &Keypair,
|
||||
account_keypair: &Keypair,
|
||||
owner_pubkey: &Pubkey,
|
||||
lamports: u64,
|
||||
) -> Result<Signature> {
|
||||
let instructions = ownable_instruction::create_account(
|
||||
&payer_keypair.pubkey(),
|
||||
&account_keypair.pubkey(),
|
||||
owner_pubkey,
|
||||
lamports,
|
||||
);
|
||||
let message = Message::new(instructions);
|
||||
bank_client.send_message(&[&payer_keypair, &account_keypair], message)
|
||||
}
|
||||
|
||||
fn send_set_owner(
|
||||
bank_client: &BankClient,
|
||||
payer_keypair: &Keypair,
|
||||
account_pubkey: &Pubkey,
|
||||
old_owner_keypair: &Keypair,
|
||||
new_owner_pubkey: &Pubkey,
|
||||
) -> Result<Signature> {
|
||||
let instruction = ownable_instruction::set_owner(
|
||||
account_pubkey,
|
||||
&old_owner_keypair.pubkey(),
|
||||
new_owner_pubkey,
|
||||
);
|
||||
let message = Message::new_with_payer(vec![instruction], Some(&payer_keypair.pubkey()));
|
||||
bank_client.send_message(&[&payer_keypair, &old_owner_keypair], message)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ownable_set_owner() {
|
||||
let (bank_client, payer_keypair) = create_bank_client(2);
|
||||
let account_keypair = Keypair::new();
|
||||
let account_pubkey = account_keypair.pubkey();
|
||||
let owner_keypair = Keypair::new();
|
||||
let owner_pubkey = owner_keypair.pubkey();
|
||||
|
||||
create_ownable_account(
|
||||
&bank_client,
|
||||
&payer_keypair,
|
||||
&account_keypair,
|
||||
&owner_pubkey,
|
||||
1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let new_owner_keypair = Keypair::new();
|
||||
let new_owner_pubkey = new_owner_keypair.pubkey();
|
||||
send_set_owner(
|
||||
&bank_client,
|
||||
&payer_keypair,
|
||||
&account_pubkey,
|
||||
&owner_keypair,
|
||||
&new_owner_pubkey,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let account_data = bank_client
|
||||
.get_account_data(&account_pubkey)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let account_owner_pubkey: Pubkey = limited_deserialize(&account_data).unwrap();
|
||||
assert_eq!(account_owner_pubkey, new_owner_pubkey);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ownable_missing_owner_signature() {
|
||||
let mut account_owner_pubkey = Pubkey::new_rand();
|
||||
let owner_pubkey = account_owner_pubkey;
|
||||
let new_owner_pubkey = Pubkey::new_rand();
|
||||
let mut account = Account::new(1, 0, &system_program::id());
|
||||
let owner_keyed_account = KeyedAccount::new(&owner_pubkey, false, &mut account); // <-- Attack! Setting owner without the original owner's signature.
|
||||
let err = set_owner(
|
||||
&mut account_owner_pubkey,
|
||||
new_owner_pubkey,
|
||||
&owner_keyed_account,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(err, InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ownable_incorrect_owner() {
|
||||
let mut account_owner_pubkey = Pubkey::new_rand();
|
||||
let new_owner_pubkey = Pubkey::new_rand();
|
||||
let mut account = Account::new(1, 0, &system_program::id());
|
||||
let mallory_pubkey = Pubkey::new_rand(); // <-- Attack! Signing with wrong pubkey
|
||||
let owner_keyed_account = KeyedAccount::new(&mallory_pubkey, true, &mut account);
|
||||
let err = set_owner(
|
||||
&mut account_owner_pubkey,
|
||||
new_owner_pubkey,
|
||||
&owner_keyed_account,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(err, OwnableError::IncorrectOwner.into());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue