Skip to content

Commit

Permalink
Add interface crate (#17)
Browse files Browse the repository at this point in the history
* Use individual dependencies

* Add interface crate

* Review comments

* Rearrange dependencies

* Update dependencies
  • Loading branch information
febo authored Nov 22, 2024
1 parent 08122b3 commit eef358e
Show file tree
Hide file tree
Showing 11 changed files with 1,144 additions and 490 deletions.
776 changes: 555 additions & 221 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@
resolver = "2"
members = [
"clients/rust",
"interface",
"program",
]

[workspace.metadata.cli]
solana = "edge"
solana = "2.1.0"

# Specify Rust toolchains for rustfmt, clippy, and build.
# Any unprovided toolchains default to stable.
[workspace.metadata.toolchains]
format = "nightly-2024-08-08"
lint = "nightly-2024-08-08"
61 changes: 61 additions & 0 deletions interface/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
[package]
name = "solana-stake-interface"
version = "0.1.0"
description = "Instructions and constructors for the Stake program"
repository = "https://github.com/solana-program/stake"
edition = "2021"
readme = "README.md"
license-file = "../LICENSE"

[dependencies]
borsh = { version = "1.5.1", features = ["derive", "unstable__schema"], optional = true }
borsh0-10 = { package = "borsh", version = "0.10.3", optional = true }
num-traits = "0.2"
serde = { version = "1.0.210", optional = true }
serde_derive = { version = "1.0.210", optional = true }
solana-decode-error = "^2.1"
solana-clock = "^2.1"
solana-cpi = { version = "^2.1", optional = true }
solana-frozen-abi = { version = "^2.1", features = ["frozen-abi"], optional = true }
solana-frozen-abi-macro = { version = "^2.1", features = ["frozen-abi"], optional = true }
solana-instruction = "^2.1"
solana-program-error = { version = "^2.1", optional = true }
solana-pubkey = { version = "^2.1", default-features = false }
solana-system-interface = "^1.0"

[dev-dependencies]
assert_matches = "1.5.0"
bincode = "1.3.3"
solana-borsh = "^2.1"
solana-program = { version = "^2.1", default-features = false }
static_assertions = "1.1.0"
strum = "0.24"
strum_macros = "0.24"

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
all-features = true
rustdoc-args = ["--cfg=docsrs"]

[features]
bincode = [
"dep:solana-cpi",
"dep:solana-program-error",
"solana-instruction/bincode",
"solana-instruction/serde",
"serde"
]
borsh = [
"dep:borsh",
"dep:borsh0-10",
"solana-instruction/borsh",
"solana-program-error/borsh",
"solana-pubkey/borsh"
]
frozen-abi = [
"dep:solana-frozen-abi",
"dep:solana-frozen-abi-macro",
"solana-instruction/frozen-abi",
"solana-pubkey/frozen-abi"
]
serde = ["dep:serde", "dep:serde_derive", "solana-pubkey/serde"]
19 changes: 12 additions & 7 deletions interface/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
//! config for staking
//! carries variables that the stake program cares about
//! Config for staking.
//!
//! It carries variables that the stake program cares about.
#[deprecated(
since = "1.16.7",
note = "Please use `solana_sdk::stake::state::{DEFAULT_SLASH_PENALTY, DEFAULT_WARMUP_COOLDOWN_RATE}` instead"
note = "Please use `crate::state::{DEFAULT_SLASH_PENALTY, DEFAULT_WARMUP_COOLDOWN_RATE}` instead"
)]
pub use super::state::{DEFAULT_SLASH_PENALTY, DEFAULT_WARMUP_COOLDOWN_RATE};
use serde_derive::{Deserialize, Serialize};
use solana_pubkey::declare_deprecated_id;

// stake config ID
crate::declare_deprecated_id!("StakeConfig11111111111111111111111111111111");
declare_deprecated_id!("StakeConfig11111111111111111111111111111111");

#[deprecated(
since = "1.16.7",
note = "Please use `solana_sdk::stake::state::warmup_cooldown_rate()` instead"
note = "Please use `crate::state::warmup_cooldown_rate()` instead"
)]
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
#[cfg_attr(
feature = "serde",
derive(serde_derive::Deserialize, serde_derive::Serialize)
)]
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct Config {
/// how much stake we can activate/deactivate per-epoch as a fraction of currently effective stake
pub warmup_cooldown_rate: f64,
Expand Down
249 changes: 249 additions & 0 deletions interface/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
use {
num_traits::{FromPrimitive, ToPrimitive},
solana_decode_error::DecodeError,
};

/// Reasons the Stake might have had an error.
#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
#[cfg_attr(
feature = "serde",
derive(serde_derive::Deserialize, serde_derive::Serialize)
)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StakeError {
// 0
/// Not enough credits to redeem.
NoCreditsToRedeem,

/// Lockup has not yet expired.
LockupInForce,

/// Stake already deactivated.
AlreadyDeactivated,

/// One re-delegation permitted per epoch.
TooSoonToRedelegate,

/// Split amount is more than is staked.
InsufficientStake,

// 5
/// Stake account with transient stake cannot be merged.
MergeTransientStake,

/// Stake account merge failed due to different authority, lockups or state.
MergeMismatch,

/// Custodian address not present.
CustodianMissing,

/// Custodian signature not present.
CustodianSignatureMissing,

/// Insufficient voting activity in the reference vote account.
InsufficientReferenceVotes,

// 10
/// Stake account is not delegated to the provided vote account.
VoteAddressMismatch,

/// Stake account has not been delinquent for the minimum epochs required
/// for deactivation.
MinimumDelinquentEpochsForDeactivationNotMet,

/// Delegation amount is less than the minimum.
InsufficientDelegation,

/// Stake account with transient or inactive stake cannot be redelegated.
RedelegateTransientOrInactiveStake,

/// Stake redelegation to the same vote account is not permitted.
RedelegateToSameVoteAccount,

// 15
/// Redelegated stake must be fully activated before deactivation.
RedelegatedStakeMustFullyActivateBeforeDeactivationIsPermitted,

/// Stake action is not permitted while the epoch rewards period is active.
EpochRewardsActive,
}

impl FromPrimitive for StakeError {
#[inline]
fn from_i64(n: i64) -> Option<Self> {
if n == Self::NoCreditsToRedeem as i64 {
Some(Self::NoCreditsToRedeem)
} else if n == Self::LockupInForce as i64 {
Some(Self::LockupInForce)
} else if n == Self::AlreadyDeactivated as i64 {
Some(Self::AlreadyDeactivated)
} else if n == Self::TooSoonToRedelegate as i64 {
Some(Self::TooSoonToRedelegate)
} else if n == Self::InsufficientStake as i64 {
Some(Self::InsufficientStake)
} else if n == Self::MergeTransientStake as i64 {
Some(Self::MergeTransientStake)
} else if n == Self::MergeMismatch as i64 {
Some(Self::MergeMismatch)
} else if n == Self::CustodianMissing as i64 {
Some(Self::CustodianMissing)
} else if n == Self::CustodianSignatureMissing as i64 {
Some(Self::CustodianSignatureMissing)
} else if n == Self::InsufficientReferenceVotes as i64 {
Some(Self::InsufficientReferenceVotes)
} else if n == Self::VoteAddressMismatch as i64 {
Some(Self::VoteAddressMismatch)
} else if n == Self::MinimumDelinquentEpochsForDeactivationNotMet as i64 {
Some(Self::MinimumDelinquentEpochsForDeactivationNotMet)
} else if n == Self::InsufficientDelegation as i64 {
Some(Self::InsufficientDelegation)
} else if n == Self::RedelegateTransientOrInactiveStake as i64 {
Some(Self::RedelegateTransientOrInactiveStake)
} else if n == Self::RedelegateToSameVoteAccount as i64 {
Some(Self::RedelegateToSameVoteAccount)
} else if n == Self::RedelegatedStakeMustFullyActivateBeforeDeactivationIsPermitted as i64 {
Some(Self::RedelegatedStakeMustFullyActivateBeforeDeactivationIsPermitted)
} else if n == Self::EpochRewardsActive as i64 {
Some(Self::EpochRewardsActive)
} else {
None
}
}
#[inline]
fn from_u64(n: u64) -> Option<Self> {
Self::from_i64(n as i64)
}
}

impl ToPrimitive for StakeError {
#[inline]
fn to_i64(&self) -> Option<i64> {
Some(match *self {
Self::NoCreditsToRedeem => Self::NoCreditsToRedeem as i64,
Self::LockupInForce => Self::LockupInForce as i64,
Self::AlreadyDeactivated => Self::AlreadyDeactivated as i64,
Self::TooSoonToRedelegate => Self::TooSoonToRedelegate as i64,
Self::InsufficientStake => Self::InsufficientStake as i64,
Self::MergeTransientStake => Self::MergeTransientStake as i64,
Self::MergeMismatch => Self::MergeMismatch as i64,
Self::CustodianMissing => Self::CustodianMissing as i64,
Self::CustodianSignatureMissing => Self::CustodianSignatureMissing as i64,
Self::InsufficientReferenceVotes => Self::InsufficientReferenceVotes as i64,
Self::VoteAddressMismatch => Self::VoteAddressMismatch as i64,
Self::MinimumDelinquentEpochsForDeactivationNotMet => {
Self::MinimumDelinquentEpochsForDeactivationNotMet as i64
}
Self::InsufficientDelegation => Self::InsufficientDelegation as i64,
Self::RedelegateTransientOrInactiveStake => {
Self::RedelegateTransientOrInactiveStake as i64
}
Self::RedelegateToSameVoteAccount => Self::RedelegateToSameVoteAccount as i64,
Self::RedelegatedStakeMustFullyActivateBeforeDeactivationIsPermitted => {
Self::RedelegatedStakeMustFullyActivateBeforeDeactivationIsPermitted as i64
}
Self::EpochRewardsActive => Self::EpochRewardsActive as i64,
})
}
#[inline]
fn to_u64(&self) -> Option<u64> {
self.to_i64().map(|x| x as u64)
}
}

impl std::error::Error for StakeError {}

impl core::fmt::Display for StakeError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
StakeError::NoCreditsToRedeem => f.write_str("not enough credits to redeem"),
StakeError::LockupInForce => f.write_str("lockup has not yet expired"),
StakeError::AlreadyDeactivated => f.write_str("stake already deactivated"),
StakeError::TooSoonToRedelegate => f.write_str("one re-delegation permitted per epoch"),
StakeError::InsufficientStake => f.write_str("split amount is more than is staked"),
StakeError::MergeTransientStake => {
f.write_str("stake account with transient stake cannot be merged")
}
StakeError::MergeMismatch => f.write_str(
"stake account merge failed due to different authority, lockups or state",
),
StakeError::CustodianMissing => f.write_str("custodian address not present"),
StakeError::CustodianSignatureMissing => f.write_str("custodian signature not present"),
StakeError::InsufficientReferenceVotes => {
f.write_str("insufficient voting activity in the reference vote account")
}
StakeError::VoteAddressMismatch => {
f.write_str("stake account is not delegated to the provided vote account")
}
StakeError::MinimumDelinquentEpochsForDeactivationNotMet => f.write_str(
"stake account has not been delinquent for the minimum epochs required for \
deactivation",
),
StakeError::InsufficientDelegation => {
f.write_str("delegation amount is less than the minimum")
}
StakeError::RedelegateTransientOrInactiveStake => {
f.write_str("stake account with transient or inactive stake cannot be redelegated")
}
StakeError::RedelegateToSameVoteAccount => {
f.write_str("stake redelegation to the same vote account is not permitted")
}
StakeError::RedelegatedStakeMustFullyActivateBeforeDeactivationIsPermitted => {
f.write_str("redelegated stake must be fully activated before deactivation")
}
StakeError::EpochRewardsActive => f.write_str(
"stake action is not permitted while the epoch rewards period is active",
),
}
}
}

impl<E> DecodeError<E> for StakeError {
fn type_of() -> &'static str {
"StakeError"
}
}

#[cfg(test)]
mod tests {
use {
super::StakeError, num_traits::FromPrimitive, solana_decode_error::DecodeError,
solana_instruction::error::InstructionError, strum::IntoEnumIterator,
};

#[test]
fn test_stake_error_from_primitive_exhaustive() {
for variant in StakeError::iter() {
let variant_i64 = variant.clone() as i64;
assert_eq!(
StakeError::from_repr(variant_i64 as usize),
StakeError::from_i64(variant_i64)
);
}
}

#[test]
fn test_custom_error_decode() {
use num_traits::FromPrimitive;
fn pretty_err<T>(err: InstructionError) -> String
where
T: 'static + std::error::Error + DecodeError<T> + FromPrimitive,
{
if let InstructionError::Custom(code) = err {
let specific_error: T = T::decode_custom_error_to_enum(code).unwrap();
format!(
"{:?}: {}::{:?} - {}",
err,
T::type_of(),
specific_error,
specific_error,
)
} else {
"".to_string()
}
}
assert_eq!(
"Custom(0): StakeError::NoCreditsToRedeem - not enough credits to redeem",
pretty_err::<StakeError>(StakeError::NoCreditsToRedeem.into())
)
}
}
Loading

0 comments on commit eef358e

Please sign in to comment.