Skip to content

Commit

Permalink
thread: Change the reallocation calculation and remove name from thre…
Browse files Browse the repository at this point in the history
…ad state (#37)

* thread: Change the reallocation calculation

* fix fmt

* Fix thread calculation

* debug thread

* Update calculation

* Add static padding

* remove static padding

* Remove logs in thread create
  • Loading branch information
Aursen authored Jul 19, 2024
1 parent 1a19de0 commit cc67039
Show file tree
Hide file tree
Showing 15 changed files with 677 additions and 38 deletions.
12 changes: 12 additions & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,6 @@ regex = "1.7.1"
async-trait = "0.1.64"
rustc_version = "0.4.0"
bytemuck = "1.4.0"
syn = "2.0"
quote = "1.0"
proc-macro2 = "1.0"
4 changes: 0 additions & 4 deletions programs/thread/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ pub const SEED_THREAD: &[u8] = b"thread";
#[constant]
pub const THREAD_MINIMUM_FEE: u64 = 1000;

/// Static space for next_instruction field.
#[constant]
pub const NEXT_INSTRUCTION_SIZE: usize = 1232;

/// The ID of the pool workers must be a member of to collect fees.
#[constant]
pub const POOL_ID: u64 = 0;
Expand Down
12 changes: 1 addition & 11 deletions programs/thread/src/instructions/thread_create.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::mem::size_of;

use anchor_lang::{
prelude::*,
system_program::{transfer, Transfer},
Expand Down Expand Up @@ -33,14 +31,7 @@ pub struct ThreadCreate<'info> {
],
bump,
payer= payer,
space = [
8,
size_of::<Thread>(),
id.len(),
instructions.try_to_vec()?.len(),
trigger.try_to_vec()?.len(),
NEXT_INSTRUCTION_SIZE,
].iter().sum()
space = Thread::min_space(&instructions)?
)]
pub thread: Account<'info, Thread>,
}
Expand Down Expand Up @@ -69,7 +60,6 @@ pub fn handler(
thread.id = id;
thread.domain = domain;
thread.instructions = instructions;
thread.name = String::new();
thread.next_instruction = None;
thread.paused = false;
thread.rate_limit = u64::MAX;
Expand Down
47 changes: 29 additions & 18 deletions programs/thread/src/state/thread.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
use std::mem::size_of;

use anchor_lang::{prelude::*, AnchorDeserialize, AnchorSerialize};
use sablier_utils::{
account::AccountInfoExt,
thread::{ClockData, SerializableInstruction, Trigger},
MinSpace, Space,
};

use crate::constants::{NEXT_INSTRUCTION_SIZE, SEED_THREAD};
use crate::constants::SEED_THREAD;

/// Tracks the current state of a transaction thread on Solana.
// TODO Wait for the next version of Anchor to implement InitSpace macro
#[account]
#[derive(Debug)]
pub struct Thread {
Expand All @@ -28,8 +26,6 @@ pub struct Thread {
pub id: Vec<u8>,
/// The instructions to be executed.
pub instructions: Vec<SerializableInstruction>,
/// The name of the thread.
pub name: String,
/// The next instruction to be executed.
pub next_instruction: Option<SerializableInstruction>,
/// Whether or not the thread is currently paused.
Expand Down Expand Up @@ -73,30 +69,45 @@ pub trait ThreadAccount {
fn realloc_account(&mut self) -> Result<()>;
}

impl Thread {
pub fn min_space(instructions: &[SerializableInstruction]) -> Result<usize> {
let ins_number = instructions.len();
let ins_space = instructions.try_to_vec()?.len();

Ok(
8
+ Pubkey::MIN_SPACE // authority
+ u8::MIN_SPACE // bump
+ ClockData::MIN_SPACE // created_at
+ (1 + 4 + 6) // domain
+ <Option<ExecContext>>::MIN_SPACE // exec_context
+ u64::MIN_SPACE // fee
+ (4 + 32) // id
+ (4 + ins_space) // instructions
+ (1 + ins_space / ins_number) // next_instruction
+ bool::MIN_SPACE // paused
+ u64::MIN_SPACE // rate_limit
+ Trigger::MIN_SPACE, // trigger
)
}
}

impl ThreadAccount for Account<'_, Thread> {
fn pubkey(&self) -> Pubkey {
Thread::pubkey(self.authority, self.id.clone(), self.domain.clone())
}

fn realloc_account(&mut self) -> Result<()> {
// Realloc memory for the thread account
let data_len = [
8,
size_of::<Thread>(),
self.id.len(),
self.instructions.try_to_vec()?.len(),
self.trigger.try_to_vec()?.len(),
NEXT_INSTRUCTION_SIZE,
]
.iter()
.sum();
let data_len = 8 + self.try_to_vec()?.len();

self.realloc(data_len, false)?;
Ok(())
}
}

/// The execution context of a particular transaction thread.
#[derive(AnchorDeserialize, AnchorSerialize, InitSpace, Clone, Copy, Debug, PartialEq, Eq)]
#[derive(AnchorDeserialize, AnchorSerialize, MinSpace, Clone, Copy, Debug, PartialEq, Eq)]
pub struct ExecContext {
/// Index of the next instruction to be executed.
pub exec_index: u64,
Expand All @@ -116,7 +127,7 @@ pub struct ExecContext {
}

/// The event which allowed a particular transaction thread to be triggered.
#[derive(AnchorDeserialize, AnchorSerialize, InitSpace, Clone, Copy, Debug, PartialEq, Eq)]
#[derive(AnchorDeserialize, AnchorSerialize, MinSpace, Clone, Copy, Debug, PartialEq, Eq)]
pub enum TriggerContext {
/// A running hash of the observed account data.
Account {
Expand Down
1 change: 1 addition & 0 deletions utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ anchor-lang.workspace = true
base64.workspace = true
serde = { workspace = true, features = ["derive"] }
static-pubkey.workspace = true
sablier-macros = { path = "./macros" }
25 changes: 25 additions & 0 deletions utils/macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "sablier-macros"
version.workspace = true
description = "Tools for building blocks on Solana"
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
documentation.workspace = true
readme = "./README.md"
keywords.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
proc-macro = true

[dependencies]
syn = { workspace = true, features = ["full"] }
proc-macro2.workspace = true
quote.workspace = true

[dev-dependencies]
sablier-utils.workspace = true
solana-sdk.workspace = true
178 changes: 178 additions & 0 deletions utils/macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, quote_spanned};
use space::{Arg, Args};
use syn::{
parse_macro_input, parse_quote, spanned::Spanned, AngleBracketedGenericArguments, DataEnum,
DataStruct, DeriveInput, Expr, Field, Fields, GenericParam, Generics, PathArguments, Type,
TypeArray, TypePath, Variant,
};

mod space;

#[proc_macro_derive(MinSpace, attributes(max_len, raw_space, internal))]
pub fn derive_min_space(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);

let name = input.ident;
let generics = add_trait_bounds(input.generics);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

let token = match input.data {
syn::Data::Struct(DataStruct { fields, .. }) => {
let len_expr = get_len_expr_from_fields(fields);

quote! {
#[automatically_derived]
impl #impl_generics sablier_utils::Space for #name #ty_generics #where_clause {
const MIN_SPACE: usize = #len_expr;
}
}
}
syn::Data::Enum(DataEnum { variants, .. }) => {
let variants = variants
.into_iter()
.map(|Variant { fields, .. }| get_len_expr_from_fields(fields));
let max = gen_max(variants);

quote! {
#[automatically_derived]
impl sablier_utils::Space for #name {
const MIN_SPACE: usize = 1 + #max;
}
}
}
syn::Data::Union(_) => {
quote_spanned! { name.span() => compile_error!("Union non implemented.") }
}
};

TokenStream::from(token)
}

// Add a bound `T: Space` to every type parameter T.
fn add_trait_bounds(mut generics: Generics) -> Generics {
for param in &mut generics.params {
if let GenericParam::Type(ref mut type_param) = *param {
type_param.bounds.push(parse_quote!(sablier_utils::Space));
}
}
generics
}

fn gen_max<T: Iterator<Item = TokenStream2>>(mut iter: T) -> TokenStream2 {
if let Some(item) = iter.next() {
let next_item = gen_max(iter);
quote!(sablier_utils::space::max(#item, #next_item))
} else {
quote!(0)
}
}

fn get_len_expr_from_fields(fields: Fields) -> TokenStream2 {
let len = fields.into_iter().map(|f| match TyLen::try_from(f) {
Ok(TyLen(len)) => quote!(#len),
Err(err) => err.into_compile_error(),
});

quote!(0 #(+ #len)*)
}

fn expr_from_ty(value: Type, args: &mut Vec<Arg>) -> syn::Result<Expr> {
let current_arg = args.pop();

let arg = match current_arg {
Some(Arg { is_raw, ref value }) => {
if is_raw {
return Ok(parse_quote!(#value));
} else {
Some(value)
}
}
None => None,
};

match value {
Type::Array(TypeArray { elem, len, .. }) => {
let inner_ty = expr_from_ty(*elem, args)?;

Ok(parse_quote!((#len * #inner_ty)))
}
Type::Path(TypePath { ref path, .. }) => {
let Some(segment) = path.segments.last() else {
return Err(syn::Error::new(value.span(), "Invalid path type."));
};
let ident = &segment.ident;

match ident.to_string().as_str() {
"String" => {
let Some(arg_value) = arg else {
return Err(syn::Error::new(ident.span(), "No max_len specified."));
};

Ok(parse_quote!((4 + #arg_value)))
}
"Vec" => {
let Some(arg_value) = arg else {
return Err(syn::Error::new(ident.span(), "No max_len specified."));
};

let new_ty = parse_first_arg(&segment.arguments)?;
let new_len = expr_from_ty(new_ty, args)?;

Ok(parse_quote!((4 + #new_len * #arg_value)))
}
_ => Ok(parse_quote!(<#value as sablier_utils::Space>::MIN_SPACE)),
}
}
_ => Ok(parse_quote!(<#value as sablier_utils::Space>::MIN_SPACE)),
}
}

fn parse_first_arg(path_args: &PathArguments) -> syn::Result<Type> {
let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) = path_args
else {
return Err(syn::Error::new(
path_args.span(),
"Invalid type of arguments.",
));
};

match &args[0] {
syn::GenericArgument::Type(ty) => Ok(ty.to_owned()),
_ => Err(syn::Error::new(
path_args.span(),
"The first argument is not a type.",
)),
}
}

struct TyLen(Expr);

impl TryFrom<Field> for TyLen {
type Error = syn::Error;

fn try_from(value: Field) -> Result<Self, Self::Error> {
let Some(name) = value.ident else {
return Err(syn::Error::new(value.span(), "Tuple field is not allowed."));
};

let mut attr_args = value
.attrs
.into_iter()
.filter_map(|a| Args::try_from(a).ok());

let args = attr_args.by_ref().take(1).next();

if attr_args.next().is_some() {
return Err(syn::Error::new(
name.span(),
"max_len and raw_space cannot be used at the same time.",
));
}

let expr = expr_from_ty(value.ty, &mut args.unwrap_or_default())?;

Ok(TyLen(expr))
}
}
Loading

0 comments on commit cc67039

Please sign in to comment.