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:
Greg Fitzgerald 2019-11-18 18:09:42 -07:00 committed by GitHub
parent 6ec918fabb
commit bfa2535ea1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 299 additions and 0 deletions

13
Cargo.lock generated
View File

@ -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"

View File

@ -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",

View File

@ -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"

View File

@ -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"
);

View File

@ -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)
}

View File

@ -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());
}
}