forked from solana-labs/solana
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
83096bc
commit c4ec3b3
Showing
12 changed files
with
1,034 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
[package] | ||
name = "spl-feature-proposal" | ||
version = "1.0.0-pre1" | ||
description = "Solana Program Library Feature Proposal Program" | ||
authors = ["Solana Maintainers <[email protected]>"] | ||
repository = "https://github.com/solana-labs/solana-program-library" | ||
license = "Apache-2.0" | ||
edition = "2018" | ||
|
||
[features] | ||
no-entrypoint = [] | ||
test-bpf = [] | ||
|
||
[dependencies] | ||
borsh = "0.7.1" | ||
borsh-derive = "0.7.1" | ||
solana-program = "1.4.5" | ||
spl-token = { version = "3.0", path = "../../token/program", features = ["no-entrypoint"] } | ||
|
||
|
||
[dev-dependencies] | ||
futures = "0.3" | ||
solana-program-test = "1.4.5" | ||
solana-sdk = "1.4.5" | ||
tokio = { version = "0.3", features = ["macros"]} | ||
|
||
[lib] | ||
crate-type = ["cdylib", "lib"] | ||
|
||
[package.metadata.docs.rs] | ||
targets = ["x86_64-unknown-linux-gnu"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[target.bpfel-unknown-unknown.dependencies.std] | ||
features = [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
badkenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8kn |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
#!/usr/bin/env bash | ||
|
||
set -ex | ||
cd "$(dirname "$0")" | ||
cargo fmt -- --check | ||
cargo clippy | ||
cargo build | ||
cargo build-bpf | ||
|
||
if [[ $1 = -v ]]; then | ||
export RUST_LOG=solana=debug | ||
fi | ||
|
||
cargo test | ||
cargo test-bpf |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
//! Borsh utils | ||
use borsh::schema::{BorshSchema, Declaration, Definition, Fields}; | ||
use std::collections::HashMap; | ||
|
||
/// Get packed length for the given BorchSchema Declaration | ||
fn get_declaration_packed_len( | ||
declaration: &str, | ||
definitions: &HashMap<Declaration, Definition>, | ||
) -> usize { | ||
match definitions.get(declaration) { | ||
Some(Definition::Array { length, elements }) => { | ||
*length as usize * get_declaration_packed_len(elements, definitions) | ||
} | ||
Some(Definition::Enum { variants }) => { | ||
1 + variants | ||
.iter() | ||
.map(|(_, declaration)| get_declaration_packed_len(declaration, definitions)) | ||
.max() | ||
.unwrap_or(0) | ||
} | ||
Some(Definition::Struct { fields }) => match fields { | ||
Fields::NamedFields(named_fields) => named_fields | ||
.iter() | ||
.map(|(_, declaration)| get_declaration_packed_len(declaration, definitions)) | ||
.sum(), | ||
Fields::UnnamedFields(declarations) => declarations | ||
.iter() | ||
.map(|declaration| get_declaration_packed_len(declaration, definitions)) | ||
.sum(), | ||
Fields::Empty => 0, | ||
}, | ||
Some(Definition::Sequence { | ||
elements: _elements, | ||
}) => panic!("Missing support for Definition::Sequence"), | ||
Some(Definition::Tuple { elements }) => elements | ||
.iter() | ||
.map(|element| get_declaration_packed_len(element, definitions)) | ||
.sum(), | ||
None => match declaration { | ||
"u8" | "i8" => 1, | ||
"u16" | "i16" => 2, | ||
"u32" | "i32" => 2, | ||
"u64" | "i64" => 8, | ||
"u128" | "i128" => 16, | ||
"nil" => 0, | ||
_ => panic!("Missing primitive type: {}", declaration), | ||
}, | ||
} | ||
} | ||
|
||
/// Get the worst-case packed length for the given BorshSchema | ||
pub fn get_packed_len<S: BorshSchema>() -> usize { | ||
let schema_container = S::schema_container(); | ||
get_declaration_packed_len(&schema_container.declaration, &schema_container.definitions) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
//! Program entrypoint | ||
#![cfg(all(target_arch = "bpf", not(feature = "no-entrypoint")))] | ||
|
||
use solana_program::{ | ||
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, | ||
}; | ||
|
||
entrypoint!(process_instruction); | ||
fn process_instruction( | ||
program_id: &Pubkey, | ||
accounts: &[AccountInfo], | ||
instruction_data: &[u8], | ||
) -> ProgramResult { | ||
crate::processor::process_instruction(program_id, accounts, instruction_data) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
//! Program instructions | ||
use crate::{state::AcceptanceCriteria, *}; | ||
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; | ||
use solana_program::{ | ||
info, | ||
instruction::{AccountMeta, Instruction}, | ||
program_error::ProgramError, | ||
program_pack::{Pack, Sealed}, | ||
pubkey::Pubkey, | ||
sysvar, | ||
}; | ||
|
||
/// Instructions supported by the Feature Proposal program | ||
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq)] | ||
pub enum FeatureProposalInstruction { | ||
/// Propose a new feature. | ||
/// | ||
/// This instruction will create a variety of accounts to support the feature proposal, all | ||
/// funded by account 0: | ||
/// * A new token mint with a supply of `tokens_to_mint`, owned by the program and never | ||
/// modified again | ||
/// * A new "delivery" token account that holds the total supply, owned by account 0. | ||
/// * A new "acceptance" token account that holds 0 tokens, owned by the program. Tokens | ||
/// transfers to this address are irrevocable and permanent. | ||
/// * A new feature id account that has been funded and allocated (as described in | ||
/// `solana_program::feature`) | ||
/// | ||
/// On successful execution of the instruction, the feature proposer is expected to distribute | ||
/// the tokens in the delivery token account out to all participating parties. | ||
/// | ||
/// Based on the provided acceptance criteria, if `AcceptanceCriteria::tokens_required` | ||
/// tokens are transferred into the acceptance token account before | ||
/// `AcceptanceCriteria::deadline` then the proposal is eligible to be accepted. | ||
/// | ||
/// The `FeatureProposalInstruction::Tally` instruction must be executed, by any party, to | ||
/// complete the feature acceptance process. | ||
/// | ||
/// Accounts expected by this instruction: | ||
/// | ||
/// 0. `[writeable,signer]` Funding account (must be a system account) | ||
/// 1. `[writeable,signer]` Unallocated feature proposal account to create | ||
/// 2. `[writeable]` Token mint address from `get_mint_address` | ||
/// 3. `[writeable]` Delivery token account address from `get_delivery_token_address` | ||
/// 4. `[writeable]` Acceptance token account address from `get_acceptance_token_address` | ||
/// 5. `[writeable]` Feature id account address from `get_feature_id_address` | ||
/// 6. `[]` System program | ||
/// 7. `[]` SPL Token program | ||
/// 8. `[]` Rent sysvar | ||
/// | ||
Propose { | ||
/// Total number of tokens to mint for this proposal | ||
#[allow(dead_code)] // not dead code.. | ||
tokens_to_mint: u64, | ||
|
||
/// Criteria for how this proposal may be activated | ||
#[allow(dead_code)] // not dead code.. | ||
acceptance_criteria: AcceptanceCriteria, | ||
}, | ||
|
||
/// `Tally` is a permission-less instruction to check the acceptance criteria for the feature | ||
/// proposal, which may result in: | ||
/// * No action | ||
/// * Feature proposal acceptance | ||
/// * Feature proposal expiration | ||
/// | ||
/// Accounts expected by this instruction: | ||
/// | ||
/// 0. `[writeable]` Feature proposal account | ||
/// 1. `[]` Acceptance token account address from `get_acceptance_token_address` | ||
/// 2. `[writeable]` Derived feature id account address from `get_feature_id_address` | ||
/// 3. `[]` System program | ||
/// 4. `[]` Clock sysvar | ||
Tally, | ||
} | ||
|
||
impl Sealed for FeatureProposalInstruction {} | ||
impl Pack for FeatureProposalInstruction { | ||
const LEN: usize = 26; // see `test_get_packed_len()` for justification of "18" | ||
|
||
fn pack_into_slice(&self, dst: &mut [u8]) { | ||
let data = self.pack_into_vec(); | ||
dst[..data.len()].copy_from_slice(&data); | ||
} | ||
|
||
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> { | ||
let mut mut_src: &[u8] = src; | ||
Self::deserialize(&mut mut_src).map_err(|err| { | ||
info!(&format!( | ||
"Error: failed to deserialize feature proposal instruction: {}", | ||
err | ||
)); | ||
ProgramError::InvalidInstructionData | ||
}) | ||
} | ||
} | ||
|
||
impl FeatureProposalInstruction { | ||
fn pack_into_vec(&self) -> Vec<u8> { | ||
self.try_to_vec().expect("try_to_vec") | ||
} | ||
} | ||
|
||
/// Create a `FeatureProposalInstruction::Propose` instruction | ||
pub fn propose( | ||
funding_address: &Pubkey, | ||
feature_proposal_address: &Pubkey, | ||
tokens_to_mint: u64, | ||
acceptance_criteria: AcceptanceCriteria, | ||
) -> Instruction { | ||
let mint_address = get_mint_address(feature_proposal_address); | ||
let delivery_token_address = get_delivery_token_address(feature_proposal_address); | ||
let acceptance_token_address = get_acceptance_token_address(feature_proposal_address); | ||
let feature_id_address = get_feature_id_address(feature_proposal_address); | ||
|
||
Instruction { | ||
program_id: id(), | ||
accounts: vec![ | ||
AccountMeta::new(*funding_address, true), | ||
AccountMeta::new(*feature_proposal_address, true), | ||
AccountMeta::new(mint_address, false), | ||
AccountMeta::new(delivery_token_address, false), | ||
AccountMeta::new(acceptance_token_address, false), | ||
AccountMeta::new(feature_id_address, false), | ||
AccountMeta::new_readonly(solana_program::system_program::id(), false), | ||
AccountMeta::new_readonly(spl_token::id(), false), | ||
AccountMeta::new_readonly(sysvar::rent::id(), false), | ||
], | ||
data: FeatureProposalInstruction::Propose { | ||
tokens_to_mint, | ||
acceptance_criteria, | ||
} | ||
.pack_into_vec(), | ||
} | ||
} | ||
|
||
/// Create a `FeatureProposalInstruction::Tally` instruction | ||
pub fn tally(feature_proposal_address: &Pubkey) -> Instruction { | ||
let acceptance_token_address = get_acceptance_token_address(feature_proposal_address); | ||
let feature_id_address = get_feature_id_address(feature_proposal_address); | ||
|
||
Instruction { | ||
program_id: id(), | ||
accounts: vec![ | ||
AccountMeta::new(*feature_proposal_address, false), | ||
AccountMeta::new_readonly(acceptance_token_address, false), | ||
AccountMeta::new(feature_id_address, false), | ||
AccountMeta::new_readonly(solana_program::system_program::id(), false), | ||
AccountMeta::new_readonly(sysvar::clock::id(), false), | ||
], | ||
data: FeatureProposalInstruction::Tally.pack_into_vec(), | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use crate::borsh_utils; | ||
|
||
#[test] | ||
fn test_get_packed_len() { | ||
assert_eq!( | ||
FeatureProposalInstruction::get_packed_len(), | ||
borsh_utils::get_packed_len::<FeatureProposalInstruction>() | ||
) | ||
} | ||
|
||
#[test] | ||
fn test_serialize_bytes() { | ||
assert_eq!( | ||
FeatureProposalInstruction::Tally.try_to_vec().unwrap(), | ||
vec![1] | ||
); | ||
|
||
assert_eq!( | ||
FeatureProposalInstruction::Propose { | ||
tokens_to_mint: 42, | ||
acceptance_criteria: AcceptanceCriteria { | ||
tokens_required: 0xdeadbeefdeadbeef, | ||
deadline: None, | ||
} | ||
} | ||
.try_to_vec() | ||
.unwrap(), | ||
vec![0, 42, 0, 0, 0, 0, 0, 0, 0, 239, 190, 173, 222, 239, 190, 173, 222, 0] | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_serialize_large_slice() { | ||
let mut dst = vec![0xff; 4]; | ||
FeatureProposalInstruction::Tally.pack_into_slice(&mut dst); | ||
|
||
// Extra bytes (0xff) ignored | ||
assert_eq!(dst, vec![1, 0xff, 0xff, 0xff]); | ||
} | ||
|
||
#[test] | ||
fn state_deserialize_invalid() { | ||
assert_eq!( | ||
FeatureProposalInstruction::unpack_from_slice(&[1]), | ||
Ok(FeatureProposalInstruction::Tally), | ||
); | ||
|
||
// Extra bytes (0xff) ignored... | ||
assert_eq!( | ||
FeatureProposalInstruction::unpack_from_slice(&[1, 0xff, 0xff, 0xff]), | ||
Ok(FeatureProposalInstruction::Tally), | ||
); | ||
|
||
assert_eq!( | ||
FeatureProposalInstruction::unpack_from_slice(&[2]), | ||
Err(ProgramError::InvalidInstructionData), | ||
); | ||
} | ||
} |
Oops, something went wrong.