Skip to content

Commit

Permalink
feature gate: init program
Browse files Browse the repository at this point in the history
  • Loading branch information
buffalojoec committed Oct 11, 2023
1 parent e619e4d commit a5b763a
Show file tree
Hide file tree
Showing 8 changed files with 563 additions and 0 deletions.
10 changes: 10 additions & 0 deletions feature-gate/README.md
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)!
26 changes: 26 additions & 0 deletions feature-gate/program/Cargo.toml
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"]
17 changes: 17 additions & 0 deletions feature-gate/program/src/entrypoint.rs
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)
}
17 changes: 17 additions & 0 deletions feature-gate/program/src/error.rs
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,
}
126 changes: 126 additions & 0 deletions feature-gate/program/src/instruction.rs
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);
}
}
15 changes: 15 additions & 0 deletions feature-gate/program/src/lib.rs
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");
93 changes: 93 additions & 0 deletions feature-gate/program/src/processor.rs
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)
}
}
}
Loading

0 comments on commit a5b763a

Please sign in to comment.