Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add non-fungible token program #7007

Merged
merged 8 commits into from Nov 19, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -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",
Expand Down
23 changes: 23 additions & 0 deletions programs/ownable_api/Cargo.toml
@@ -0,0 +1,23 @@
[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]
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"
12 changes: 12 additions & 0 deletions programs/ownable_api/src/lib.rs
@@ -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"
);
68 changes: 68 additions & 0 deletions programs/ownable_api/src/ownable_instruction.rs
@@ -0,0 +1,68 @@
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(contract_pubkey: &Pubkey, owner_pubkey: &Pubkey) -> Instruction {
let keys = vec![AccountMeta::new(*contract_pubkey, false)];
Instruction::new(crate::id(), &owner_pubkey, keys)
}

pub fn create_account(
payer_pubkey: &Pubkey,
contract_pubkey: &Pubkey,
garious marked this conversation as resolved.
Show resolved Hide resolved
owner_pubkey: &Pubkey,
lamports: u64,
) -> Vec<Instruction> {
let space = std::mem::size_of::<Pubkey>() as u64;
vec![
system_instruction::create_account(
&payer_pubkey,
contract_pubkey,
lamports,
space,
&crate::id(),
),
initialize_account(contract_pubkey, owner_pubkey),
]
}

pub fn set_owner(
contract_pubkey: &Pubkey,
old_pubkey: &Pubkey,
new_pubkey: &Pubkey,
) -> Instruction {
let keys = vec![
AccountMeta::new(*contract_pubkey, false),
AccountMeta::new(*old_pubkey, true),
];
Instruction::new(crate::id(), &new_pubkey, keys)
}
184 changes: 184 additions & 0 deletions programs/ownable_api/src/ownable_processor.rs
@@ -0,0 +1,184 @@
//! Ownable program

use crate::ownable_instruction::OwnableError;
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 = 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,
)?;
}

account_keyed_account
.account
.data
.copy_from_slice(account_owner_pubkey.as_ref());
Ok(())
}

#[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,
contract_keypair: &Keypair,
owner_pubkey: &Pubkey,
lamports: u64,
) -> Result<Signature> {
let instructions = ownable_instruction::create_account(
&payer_keypair.pubkey(),
&contract_keypair.pubkey(),
owner_pubkey,
lamports,
);
let message = Message::new(instructions);
bank_client.send_message(&[&payer_keypair, &contract_keypair], message)
}

fn send_set_owner(
bank_client: &BankClient,
payer_keypair: &Keypair,
contract_pubkey: &Pubkey,
old_owner_keypair: &Keypair,
new_owner_pubkey: &Pubkey,
) -> Result<Signature> {
let instruction = ownable_instruction::set_owner(
contract_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 contract_keypair = Keypair::new();
let contract_pubkey = contract_keypair.pubkey();
let owner_keypair = Keypair::new();
let owner_pubkey = owner_keypair.pubkey();

create_ownable_account(
&bank_client,
&payer_keypair,
&contract_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,
&contract_pubkey,
&owner_keypair,
&new_owner_pubkey,
)
.unwrap();

let account_data = bank_client
.get_account_data(&contract_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());
}
}