Skip to content

Commit

Permalink
feature gate: init program with RevokePendingActivation instruction (
Browse files Browse the repository at this point in the history
…#5510)

* feature gate: init program

* move to program folder

* address latest feedback
  • Loading branch information
Joe C authored Oct 18, 2023
1 parent 0ce6561 commit fe6a570
Show file tree
Hide file tree
Showing 10 changed files with 380 additions and 0 deletions.
11 changes: 11 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
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ members = [
"examples/rust/sysvar",
"examples/rust/transfer-lamports",
"examples/rust/transfer-tokens",
"feature-gate/program",
"feature-proposal/program",
"feature-proposal/cli",
"governance/addin-mock/program",
Expand Down
9 changes: 9 additions & 0 deletions feature-gate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Feature Gate Program

This program serves to manage new features on Solana.

It serves one purpose: 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)!
27 changes: 27 additions & 0 deletions feature-gate/program/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[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]
num_enum = "0.7.0"
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)
}
14 changes: 14 additions & 0 deletions feature-gate/program/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//! Program error types
use spl_program_error::*;

/// Program specific errors
#[spl_program_error]
pub enum FeatureGateError {
/// Operation overflowed
#[error("Operation overflowed")]
Overflow,
/// Feature already activated
#[error("Feature already activated")]
FeatureAlreadyActivated,
}
77 changes: 77 additions & 0 deletions feature-gate/program/src/instruction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//! Program instructions
use {
num_enum::{IntoPrimitive, TryFromPrimitive},
solana_program::{
instruction::{AccountMeta, Instruction},
program_error::ProgramError,
pubkey::Pubkey,
},
};

/// Feature Gate program instructions
#[derive(Clone, Debug, PartialEq, IntoPrimitive, TryFromPrimitive)]
#[repr(u8)]
pub enum FeatureGateInstruction {
/// Revoke a pending feature activation.
///
/// A "pending" feature activation is a feature account that has been
/// allocated and assigned, but hasn't yet been updated by the runtime
/// with an `activation_slot`.
///
/// Features that _have_ been activated by the runtime cannot be revoked.
///
/// 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.len() != 1 {
return Err(ProgramError::InvalidInstructionData);
}
Self::try_from(input[0]).map_err(|_| ProgramError::InvalidInstructionData)
}

/// Packs a [FeatureGateInstruction](enum.FeatureGateInstruction.html) into
/// a byte buffer.
pub fn pack(&self) -> Vec<u8> {
vec![self.to_owned().into()]
}
}

/// Creates a 'RevokePendingActivation' instruction.
pub fn revoke_pending_activation(feature_id: &Pubkey, destination: &Pubkey) -> Instruction {
let accounts = vec![
AccountMeta::new(*feature_id, true),
AccountMeta::new(*destination, false),
];

let data = FeatureGateInstruction::RevokePendingActivation.pack();

Instruction {
program_id: crate::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_revoke_pending_activation() {
test_pack_unpack(&FeatureGateInstruction::RevokePendingActivation);
}
}
16 changes: 16 additions & 0 deletions feature-gate/program/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//! Feature Gate program
#![deny(missing_docs)]
#![cfg_attr(not(test), forbid(unsafe_code))]

#[cfg(not(feature = "no-entrypoint"))]
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");
62 changes: 62 additions & 0 deletions feature-gate/program/src/processor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//! Program state processor
use {
crate::{error::FeatureGateError, instruction::FeatureGateInstruction},
solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
feature::Feature,
msg,
program_error::ProgramError,
pubkey::Pubkey,
system_program,
},
};

/// Processes a [RevokePendingActivation](enum.FeatureGateInstruction.html)
/// instruction.
pub fn process_revoke_pending_activation(
_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);
}

// This will also check the program ID
if Feature::from_account_info(feature_info)?
.activated_at
.is_some()
{
return Err(FeatureGateError::FeatureAlreadyActivated.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::RevokePendingActivation => {
msg!("Instruction: RevokePendingActivation");
process_revoke_pending_activation(program_id, accounts)
}
}
}
Loading

0 comments on commit fe6a570

Please sign in to comment.