-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
e619e4d
commit a5b763a
Showing
8 changed files
with
563 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Feature Gate Program | ||
|
||
This program serves to manage new features on Solana. | ||
|
||
It serves two main purposes: activating new features and revoking features that | ||
are pending activation. | ||
|
||
More information & documentation will follow as this program matures, but you | ||
can follow the discussions | ||
[here](https://github.com/solana-labs/solana/issues/32780)! |
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,26 @@ | ||
[package] | ||
name = "spl-feature-gate" | ||
version = "0.1.0" | ||
description = "Solana Program Library Feature Gate Program" | ||
authors = ["Solana Labs Maintainers <[email protected]>"] | ||
repository = "https://github.com/solana-labs/solana-program-library" | ||
license = "Apache-2.0" | ||
edition = "2021" | ||
|
||
[features] | ||
no-entrypoint = [] | ||
test-sbf = [] | ||
|
||
[dependencies] | ||
solana-program = "1.16.16" | ||
spl-program-error = { version = "0.3.0", path = "../../libraries/program-error" } | ||
|
||
[dev-dependencies] | ||
solana-program-test = "1.16.16" | ||
solana-sdk = "1.16.16" | ||
|
||
[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,17 @@ | ||
//! Program entrypoint | ||
use { | ||
crate::processor, | ||
solana_program::{ | ||
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, | ||
}, | ||
}; | ||
|
||
entrypoint!(process_instruction); | ||
fn process_instruction( | ||
program_id: &Pubkey, | ||
accounts: &[AccountInfo], | ||
input: &[u8], | ||
) -> ProgramResult { | ||
processor::process(program_id, accounts, input) | ||
} |
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,17 @@ | ||
//! Program error types | ||
use spl_program_error::*; | ||
|
||
/// Program specific errors | ||
#[spl_program_error] | ||
pub enum FeatureGateError { | ||
/// Operation overflowed | ||
#[error("Operation overflowed")] | ||
Overflow, | ||
/// Feature account must be a system account | ||
#[error("Feature account must be a system account")] | ||
FeatureNotSystemAccount, | ||
/// Feature not inactive | ||
#[error("Feature not inactive")] | ||
FeatureNotInactive, | ||
} |
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,126 @@ | ||
//! Program instructions | ||
use solana_program::{ | ||
feature::Feature, | ||
instruction::{AccountMeta, Instruction}, | ||
program_error::ProgramError, | ||
pubkey::Pubkey, | ||
rent::Rent, | ||
system_instruction, system_program, | ||
}; | ||
|
||
/// Feature Gate program instructions | ||
#[derive(Clone, Debug, PartialEq)] | ||
pub enum FeatureGateInstruction { | ||
/// Submit a feature for activation. | ||
/// | ||
/// Note: This instruction expects the account to exist and be owned by the | ||
/// system program. The account should also have enough rent-exempt lamports | ||
/// to cover the cost of the account creation for a | ||
/// `solana_program::feature::Feature` state prior to invoking this | ||
/// instruction. | ||
/// | ||
/// Accounts expected by this instruction: | ||
/// | ||
/// 0. `[w+s]` Feature account (must be a system account) | ||
/// 1. `[]` System program | ||
Activate, | ||
/// Revoke a pending feature activation. | ||
/// | ||
/// Accounts expected by this instruction: | ||
/// | ||
/// 0. `[w+s]` Feature account | ||
/// 1. `[w]` Destination (for rent lamports) | ||
RevokePendingActivation, | ||
} | ||
impl FeatureGateInstruction { | ||
/// Unpacks a byte buffer into a | ||
/// [FeatureGateInstruction](enum.FeatureGateInstruction.html). | ||
pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> { | ||
if input.is_empty() { | ||
return Err(ProgramError::InvalidInstructionData); | ||
} | ||
match input[0] { | ||
0 => Ok(Self::Activate), | ||
1 => Ok(Self::RevokePendingActivation), | ||
_ => Err(ProgramError::InvalidInstructionData), | ||
} | ||
} | ||
|
||
/// Packs a [FeatureGateInstruction](enum.FeatureGateInstruction.html) into | ||
/// a byte buffer. | ||
pub fn pack(&self) -> Vec<u8> { | ||
match self { | ||
Self::Activate => vec![0], | ||
Self::RevokePendingActivation => vec![1], | ||
} | ||
} | ||
} | ||
|
||
/// Creates an 'Activate' instruction. | ||
pub fn activate(program_id: &Pubkey, feature: &Pubkey) -> Instruction { | ||
let accounts = vec![ | ||
AccountMeta::new(*feature, true), | ||
AccountMeta::new_readonly(system_program::id(), false), | ||
]; | ||
|
||
let data = FeatureGateInstruction::Activate.pack(); | ||
|
||
Instruction { | ||
program_id: *program_id, | ||
accounts, | ||
data, | ||
} | ||
} | ||
|
||
/// Creates a set of two instructions: | ||
/// * One to fund the feature account with rent-exempt lamports | ||
/// * Another is the Feature Gate Program's 'Activate' instruction | ||
pub fn activate_with_rent_transfer( | ||
program_id: &Pubkey, | ||
feature: &Pubkey, | ||
payer: &Pubkey, | ||
) -> [Instruction; 2] { | ||
let lamports = Rent::default().minimum_balance(Feature::size_of()); | ||
[ | ||
system_instruction::transfer(payer, feature, lamports), | ||
activate(program_id, feature), | ||
] | ||
} | ||
|
||
/// Creates a 'RevokePendingActivation' instruction. | ||
pub fn revoke(program_id: &Pubkey, feature: &Pubkey, destination: &Pubkey) -> Instruction { | ||
let accounts = vec![ | ||
AccountMeta::new(*feature, true), | ||
AccountMeta::new(*destination, false), | ||
]; | ||
|
||
let data = FeatureGateInstruction::RevokePendingActivation.pack(); | ||
|
||
Instruction { | ||
program_id: *program_id, | ||
accounts, | ||
data, | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
|
||
fn test_pack_unpack(instruction: &FeatureGateInstruction) { | ||
let packed = instruction.pack(); | ||
let unpacked = FeatureGateInstruction::unpack(&packed).unwrap(); | ||
assert_eq!(instruction, &unpacked); | ||
} | ||
|
||
#[test] | ||
fn test_pack_unpack_activate() { | ||
test_pack_unpack(&FeatureGateInstruction::Activate); | ||
} | ||
|
||
#[test] | ||
fn test_pack_unpack_revoke() { | ||
test_pack_unpack(&FeatureGateInstruction::RevokePendingActivation); | ||
} | ||
} |
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 @@ | ||
//! Feature Gate program | ||
#![deny(missing_docs)] | ||
#![cfg_attr(not(test), forbid(unsafe_code))] | ||
|
||
mod entrypoint; | ||
pub mod error; | ||
pub mod instruction; | ||
pub mod processor; | ||
|
||
// Export current SDK types for downstream users building with a different SDK | ||
// version | ||
pub use solana_program; | ||
|
||
solana_program::declare_id!("Feature111111111111111111111111111111111111"); |
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,93 @@ | ||
//! Program state processor | ||
use { | ||
crate::{error::FeatureGateError, instruction::FeatureGateInstruction}, | ||
solana_program::{ | ||
account_info::{next_account_info, AccountInfo}, | ||
entrypoint::ProgramResult, | ||
feature::Feature, | ||
msg, | ||
program::invoke, | ||
program_error::ProgramError, | ||
pubkey::Pubkey, | ||
system_instruction, system_program, | ||
}, | ||
}; | ||
|
||
/// Processes an [Activate](enum.FeatureGateInstruction.html) instruction. | ||
pub fn process_activate(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { | ||
let account_info_iter = &mut accounts.iter(); | ||
|
||
let feature_info = next_account_info(account_info_iter)?; | ||
let _system_program_info = next_account_info(account_info_iter)?; | ||
|
||
if !feature_info.is_signer { | ||
return Err(ProgramError::MissingRequiredSignature); | ||
} | ||
|
||
if feature_info.owner != &system_program::id() { | ||
return Err(FeatureGateError::FeatureNotSystemAccount.into()); | ||
} | ||
|
||
invoke( | ||
&system_instruction::allocate(feature_info.key, Feature::size_of() as u64), | ||
&[feature_info.clone()], | ||
)?; | ||
invoke( | ||
&system_instruction::assign(feature_info.key, program_id), | ||
&[feature_info.clone()], | ||
)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Processes an [revoke](enum.FeatureGateInstruction.html) instruction. | ||
pub fn process_revoke(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { | ||
let account_info_iter = &mut accounts.iter(); | ||
|
||
let feature_info = next_account_info(account_info_iter)?; | ||
let destination_info = next_account_info(account_info_iter)?; | ||
|
||
if !feature_info.is_signer { | ||
return Err(ProgramError::MissingRequiredSignature); | ||
} | ||
|
||
if feature_info.owner != program_id { | ||
return Err(ProgramError::IllegalOwner); | ||
} | ||
|
||
if Feature::from_account_info(feature_info)? | ||
.activated_at | ||
.is_some() | ||
{ | ||
return Err(FeatureGateError::FeatureNotInactive.into()); | ||
} | ||
|
||
let new_destination_lamports = feature_info | ||
.lamports() | ||
.checked_add(destination_info.lamports()) | ||
.ok_or::<ProgramError>(FeatureGateError::Overflow.into())?; | ||
|
||
**feature_info.try_borrow_mut_lamports()? = 0; | ||
**destination_info.try_borrow_mut_lamports()? = new_destination_lamports; | ||
|
||
feature_info.realloc(0, true)?; | ||
feature_info.assign(&system_program::id()); | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Processes an [Instruction](enum.Instruction.html). | ||
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult { | ||
let instruction = FeatureGateInstruction::unpack(input)?; | ||
match instruction { | ||
FeatureGateInstruction::Activate => { | ||
msg!("Instruction: Activate"); | ||
process_activate(program_id, accounts) | ||
} | ||
FeatureGateInstruction::RevokePendingActivation => { | ||
msg!("Instruction: RevokePendingActivation"); | ||
process_revoke(program_id, accounts) | ||
} | ||
} | ||
} |
Oops, something went wrong.