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",
|
"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]]
|
[[package]]
|
||||||
name = "solana-perf"
|
name = "solana-perf"
|
||||||
version = "0.21.0"
|
version = "0.21.0"
|
||||||
|
|
|
@ -38,6 +38,7 @@ members = [
|
||||||
"programs/exchange_program",
|
"programs/exchange_program",
|
||||||
"programs/failure_program",
|
"programs/failure_program",
|
||||||
"programs/noop_program",
|
"programs/noop_program",
|
||||||
|
"programs/ownable_api",
|
||||||
"programs/stake_api",
|
"programs/stake_api",
|
||||||
"programs/stake_program",
|
"programs/stake_program",
|
||||||
"programs/stake_tests",
|
"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