Skip to content

Commit

Permalink
spl discriminator crate (solana-labs#4556)
Browse files Browse the repository at this point in the history
* spl discriminator

* fixed tlv impls for spec

* resolved some comments (naming conventions)

* remove TLV discrims

* new_with_hash_input fn

* HasDiscriminator

* added len function

* SplDiscriminates

* fix dependency issues

* merge fix lockfile

* bugfix on fat-finger in tlv libs

* update ArrayDiscriminator

* switch to SplDiscriminate

* drop len()
  • Loading branch information
Joe C authored Jun 22, 2023
1 parent 7c3c753 commit 307d10e
Show file tree
Hide file tree
Showing 25 changed files with 616 additions and 261 deletions.
37 changes: 35 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ members = [
"governance/tools",
"governance/chat/program",
"instruction-padding/program",
"libraries/math",
"libraries/discriminator",
"libraries/concurrent-merkle-tree",
"libraries/math",
"libraries/merkle-tree-reference",
"libraries/program-error",
"libraries/tlv-account-resolution",
Expand Down
19 changes: 19 additions & 0 deletions libraries/discriminator/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "spl-discriminator"
version = "0.1.0"
description = "Solana Program Library 8-Byte Discriminator Management"
authors = ["Solana Labs Maintainers <[email protected]>"]
repository = "https://github.com/solana-labs/solana-program-library"
license = "Apache-2.0"
edition = "2021"

[dependencies]
bytemuck = { version = "1.13.1", features = ["derive"] }
solana-program = "1.14.12"
spl-discriminator-derive = { version = "0.1.0", path = "./derive" }

[lib]
crate-type = ["cdylib", "lib"]

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
57 changes: 57 additions & 0 deletions libraries/discriminator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# SPL Discriminator

This library allows for easy management of 8-byte discriminators.

### The `ArrayDiscriminator` Struct

With this crate, you can leverage the `ArrayDiscriminator` type to manage an 8-byte discriminator for generic purposes.

```rust
let my_discriminator = ArrayDiscriminator::new([8, 5, 1, 56, 10, 53, 9, 198]);
```

The `new(..)` function is also a **constant function**, so you can use `ArrayDiscriminator` in constants as well.

```rust
const MY_DISCRIMINATOR: ArrayDiscriminator = ArrayDiscriminator::new([8, 5, 1, 56, 10, 53, 9, 198]);
```

The `ArrayDiscriminator` struct also offers another constant function `as_slice(&self)`, so you can use `as_slice()` in constants as well.

```rust
const MY_DISCRIMINATOR_SLICE: &[u8] = MY_DISCRIMINATOR.as_slice();
```

### The `SplDiscriminate` Trait

A trait, `SplDiscriminate` is also available, which will give you the `ArrayDiscriminator` constant type and also a slice representation of the discriminator. This can be particularly handy with match statements.

```rust
/// A trait for managing 8-byte discriminators in a slab of bytes
pub trait SplDiscriminate {
/// The 8-byte discriminator as a `[u8; 8]`
const SPL_DISCRIMINATOR: ArrayDiscriminator;
/// The 8-byte discriminator as a slice (`&[u8]`)
const SPL_DISCRIMINATOR_SLICE: &'static [u8] = Self::SPL_DISCRIMINATOR.as_slice();
}
```

### The `SplDiscriminate` Derive Macro

The `SplDiscriminate` derive macro is a particularly useful tool for those who wish to derive their 8-byte discriminator from a particular string literal. Typically, you would have to run a hash function against the string literal, then copy the first 8 bytes, and then hard-code those bytes into a statement like the one above.

Instead, you can simply annotate a struct or enum with `SplDiscriminate` and provide a **namespace** via the `discriminator_namespace` attribute, and the macro will automatically derive the 8-byte discriminator for you!

```rust
#[derive(SplDiscriminate)] // Implements `SplDiscriminate` for your struct/enum using your declared string literal namespace
#[discriminator_namespace("some_discriminator_namespace")]
pub struct MyInstruction1 {
arg1: String,
arg2: u8,
}

let my_discriminator: ArrayDiscriminator = MyInstruction1::SPL_DISCRIMINATOR;
let my_discriminator_slice: &[u8] = MyInstruction1::SPL_DISCRIMINATOR_SLICE;
```

Note: the 8-byte discriminator derived using the macro is always the **first 8 bytes** of the resulting hashed bytes.
16 changes: 16 additions & 0 deletions libraries/discriminator/derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "spl-discriminator-derive"
version = "0.1.0"
description = "Derive macro library for the `spl-discriminator` library"
authors = ["Solana Labs Maintainers <[email protected]>"]
repository = "https://github.com/solana-labs/solana-program-library"
license = "Apache-2.0"
edition = "2021"

[lib]
proc-macro = true

[dependencies]
quote = "1.0"
spl-discriminator-syn = { version = "0.1.0", path = "../syn" }
syn = { version = "2.0", features = ["full"] }
20 changes: 20 additions & 0 deletions libraries/discriminator/derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//! Derive macro library for the `spl-discriminator` library
#![deny(missing_docs)]
#![cfg_attr(not(test), forbid(unsafe_code))]

extern crate proc_macro;

use proc_macro::TokenStream;
use quote::ToTokens;
use spl_discriminator_syn::SplDiscriminateBuilder;
use syn::parse_macro_input;

/// Derive macro library to implement the `SplDiscriminate` trait
/// on an enum or struct
#[proc_macro_derive(SplDiscriminate, attributes(discriminator_hash_input))]
pub fn spl_discriminator(input: TokenStream) -> TokenStream {
parse_macro_input!(input as SplDiscriminateBuilder)
.to_token_stream()
.into()
}
68 changes: 68 additions & 0 deletions libraries/discriminator/src/discriminator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//! The traits and types used to create a discriminator for a type
use {
bytemuck::{Pod, Zeroable},
solana_program::{hash, program_error::ProgramError},
};

/// A trait for managing 8-byte discriminators in a slab of bytes
pub trait SplDiscriminate {
/// The 8-byte discriminator as a `[u8; 8]`
const SPL_DISCRIMINATOR: ArrayDiscriminator;
/// The 8-byte discriminator as a slice (`&[u8]`)
const SPL_DISCRIMINATOR_SLICE: &'static [u8] = Self::SPL_DISCRIMINATOR.as_slice();
}

/// Array Discriminator type
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
#[repr(transparent)]
pub struct ArrayDiscriminator([u8; ArrayDiscriminator::LENGTH]);
impl ArrayDiscriminator {
/// Size for discriminator in account data
pub const LENGTH: usize = 8;
/// Uninitialized variant of a discriminator
pub const UNINITIALIZED: Self = Self::new([0; Self::LENGTH]);
/// Creates a discriminator from an array
pub const fn new(value: [u8; Self::LENGTH]) -> Self {
Self(value)
}
/// Get the array as a const slice
pub const fn as_slice(&self) -> &[u8] {
self.0.as_slice()
}
/// Creates a new `ArrayDiscriminator` from some hash input string literal
pub fn new_with_hash_input(hash_input: &str) -> Self {
let hash_bytes = hash::hashv(&[hash_input.as_bytes()]).to_bytes();
let mut discriminator_bytes = [0u8; 8];
discriminator_bytes.copy_from_slice(&hash_bytes[..8]);
Self(discriminator_bytes)
}
}
impl AsRef<[u8]> for ArrayDiscriminator {
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}
impl AsRef<[u8; ArrayDiscriminator::LENGTH]> for ArrayDiscriminator {
fn as_ref(&self) -> &[u8; ArrayDiscriminator::LENGTH] {
&self.0
}
}
impl From<u64> for ArrayDiscriminator {
fn from(from: u64) -> Self {
Self(from.to_le_bytes())
}
}
impl From<[u8; Self::LENGTH]> for ArrayDiscriminator {
fn from(from: [u8; Self::LENGTH]) -> Self {
Self(from)
}
}
impl TryFrom<&[u8]> for ArrayDiscriminator {
type Error = ProgramError;
fn try_from(a: &[u8]) -> Result<Self, Self::Error> {
<[u8; Self::LENGTH]>::try_from(a)
.map(Self::from)
.map_err(|_| ProgramError::InvalidAccountData)
}
}
69 changes: 69 additions & 0 deletions libraries/discriminator/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! Crate defining a discriminator type, which creates a set of bytes
//! meant to be unique for instructions or struct types
#![deny(missing_docs)]
#![cfg_attr(not(test), forbid(unsafe_code))]

extern crate self as spl_discriminator;

/// Exports the discriminator module
pub mod discriminator;

// Export for downstream
pub use {
discriminator::{ArrayDiscriminator, SplDiscriminate},
spl_discriminator_derive::SplDiscriminate,
};

#[cfg(test)]
mod tests {
use {super::*, crate::discriminator::ArrayDiscriminator};

#[allow(dead_code)]
#[derive(SplDiscriminate)]
#[discriminator_hash_input("some_discriminator_hash_input")]
pub struct MyInstruction1 {
arg1: String,
arg2: u8,
}

#[allow(dead_code)]
#[derive(SplDiscriminate)]
#[discriminator_hash_input("yet_another_discriminator_hash_input")]
pub struct MyInstruction2 {
arg1: u64,
}

#[allow(dead_code)]
#[derive(SplDiscriminate)]
#[discriminator_hash_input("global:my_instruction_3")]
pub enum MyInstruction3 {
One,
Two,
Three,
}

fn assert_discriminator<T: spl_discriminator::discriminator::SplDiscriminate>(
hash_input: &str,
) {
let discriminator = build_discriminator(hash_input);
assert_eq!(T::SPL_DISCRIMINATOR, discriminator);
assert_eq!(T::SPL_DISCRIMINATOR_SLICE, discriminator.as_slice());
}

fn build_discriminator(hash_input: &str) -> ArrayDiscriminator {
let preimage = solana_program::hash::hashv(&[hash_input.as_bytes()]);
let mut bytes = [0u8; 8];
bytes.copy_from_slice(&preimage.to_bytes()[..8]);
ArrayDiscriminator::new(bytes)
}

#[test]
fn test_discrminators() {
assert_discriminator::<MyInstruction1>("some_discriminator_hash_input");
assert_discriminator::<MyInstruction2>("yet_another_discriminator_hash_input");
assert_discriminator::<MyInstruction3>("global:my_instruction_3");
let runtime_discrim = ArrayDiscriminator::new_with_hash_input("my_new_hash_input");
assert_eq!(runtime_discrim, build_discriminator("my_new_hash_input"),);
}
}
15 changes: 15 additions & 0 deletions libraries/discriminator/syn/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "spl-discriminator-syn"
version = "0.1.0"
description = "Token parsing and generating library for the `spl-discriminator` library"
authors = ["Solana Labs Maintainers <[email protected]>"]
repository = "https://github.com/solana-labs/solana-program-library"
license = "Apache-2.0"
edition = "2021"

[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
solana-program = "1.14.12"
syn = { version = "2.0", features = ["full"] }
thiserror = "1.0"
12 changes: 12 additions & 0 deletions libraries/discriminator/syn/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//! Error types for the `hash_input` parser
/// Error types for the `hash_input` parser
#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
pub enum SplDiscriminateError {
/// Discriminator hash_input attribute not provided
#[error("Discriminator `hash_input` attribute not provided")]
HashInputAttributeNotProvided,
/// Error parsing discriminator hash_input attribute
#[error("Error parsing discriminator `hash_input` attribute")]
HashInputAttributeParseError,
}
Loading

0 comments on commit 307d10e

Please sign in to comment.