diff --git a/server/Cargo.lock b/server/Cargo.lock index e36c264..64de77b 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -719,6 +719,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.117", + "trybuild", ] [[package]] @@ -1965,6 +1966,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "dissimilar" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeda16ab4059c5fd2a83f2b9c9e9c981327b18aa8e3b313f7e6563799d4f093e" + [[package]] name = "downcast-rs" version = "2.0.2" @@ -3200,7 +3207,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36c791ecdf977c99f45f23280405d7723727470f6689a5e6dbf513ac547ae10d" dependencies = [ "serde", - "toml", + "toml 0.9.12+spec-1.1.0", ] [[package]] @@ -4972,6 +4979,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "target-triple" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "591ef38edfb78ca4771ee32cf494cb8771944bee237a9b91fc9c1424ac4b777b" + [[package]] name = "tempfile" version = "3.27.0" @@ -4985,6 +4998,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "terminal_size" version = "0.4.4" @@ -5207,6 +5229,21 @@ dependencies = [ "winnow 0.7.15", ] +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.2", +] + [[package]] name = "toml_datetime" version = "0.7.5+spec-1.1.0" @@ -5246,6 +5283,12 @@ dependencies = [ "winnow 1.0.2", ] +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + [[package]] name = "tonic" version = "0.14.5" @@ -5433,6 +5476,22 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "trybuild" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0710d4dfbeae4f9c390baa784c49858a7468fa433f3fe5d0ec5ebef651cf59f9" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 1.1.2+spec-1.1.0", +] + [[package]] name = "typenum" version = "1.20.0" diff --git a/server/Cargo.toml b/server/Cargo.toml index cd588a8..0f93ba1 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -129,7 +129,6 @@ rc_buffer = "warn" rc_mutex = "warn" redundant_test_prefix = "warn" redundant_type_annotations = "warn" -ref_patterns = "warn" renamed_function_params = "warn" rest_pat_in_fully_bound_structs = "warn" return_and_then = "warn" diff --git a/server/crates/arbiter-crypto/src/integrity.rs b/server/crates/arbiter-crypto/src/integrity.rs new file mode 100644 index 0000000..96efecf --- /dev/null +++ b/server/crates/arbiter-crypto/src/integrity.rs @@ -0,0 +1,49 @@ +use crate::hashing::Hashable; + +/// Marks a struct as a participant in the database integrity system. +/// +/// Implementors are protected by an HMAC-SHA256 MAC stored in the +/// `integrity_envelope` table. The MAC is computed over: +/// +/// ```text +/// HMAC-SHA256(key, len(KIND) || KIND || len(entity_id) || entity_id || VERSION || SHA256(Hashable)) +/// ``` +/// +/// Both `KIND` and `VERSION` act as domain separators — they prevent a valid +/// MAC for one entity type or schema version from being accepted for another. +/// +/// # Deriving +/// +/// Use `#[derive(Integrable)]` with the `#[integrable(kind = "...")]` attribute. +/// `VERSION` is computed automatically as an FNV-1a hash of the struct's field +/// names and types, so it changes whenever the schema changes without any manual +/// bookkeeping. +/// +/// ```rust,ignore +/// #[derive(Hashable, Integrable)] +/// #[integrable(kind = "operator_credentials")] +/// pub struct OperatorCredentials { +/// pub pubkey: PublicKey, +/// } +/// ``` +/// +/// # Upgrading schema +/// +/// When fields are added, removed, or reordered, `VERSION` changes automatically. +/// Existing MAC records in the database will return [`PayloadVersionMismatch`] on +/// verification — this is the signal to re-sign all rows for this `KIND` as part +/// of a migration. +/// +/// [`PayloadVersionMismatch`]: crate::integrity::Integrable +pub trait Integrable: Hashable { + /// Stable name of this entity type as stored in `integrity_envelope.entity_kind`. + /// + /// Must be a valid schema name: starts with a letter, contains only `[a-zA-Z0-9_]`, + /// and must be globally unique across all `Integrable` types in the system. + const KIND: &'static str; + + /// FNV-1a hash of the struct's field names and types at the time the derive + /// macro ran. Changes automatically when the schema changes, invalidating + /// existing MACs and signalling that a migration is required. + const VERSION: i32; +} diff --git a/server/crates/arbiter-crypto/src/lib.rs b/server/crates/arbiter-crypto/src/lib.rs index 8e1c9ce..4d0690f 100644 --- a/server/crates/arbiter-crypto/src/lib.rs +++ b/server/crates/arbiter-crypto/src/lib.rs @@ -1,6 +1,7 @@ #[cfg(feature = "authn")] pub mod authn; pub mod hashing; +pub mod integrity; #[cfg(feature = "safecell")] pub mod safecell; diff --git a/server/crates/arbiter-macros/Cargo.toml b/server/crates/arbiter-macros/Cargo.toml index 15a5070..d1787fb 100644 --- a/server/crates/arbiter-macros/Cargo.toml +++ b/server/crates/arbiter-macros/Cargo.toml @@ -14,6 +14,7 @@ syn = { version = "2.0", features = ["derive", "fold", "full", "visit-mut"] } [dev-dependencies] arbiter-crypto = { path = "../arbiter-crypto" } +trybuild = { version = "1.0", features = ["diff"] } [lints] workspace = true diff --git a/server/crates/arbiter-macros/src/hashable.rs b/server/crates/arbiter-macros/src/hashable.rs index bfaec3e..f8cf8b8 100644 --- a/server/crates/arbiter-macros/src/hashable.rs +++ b/server/crates/arbiter-macros/src/hashable.rs @@ -53,32 +53,16 @@ struct FieldAccess { fn collect_field_accesses(struct_data: &DataStruct) -> Vec { match &struct_data.fields { - Fields::Named(fields) => { - // Keep deterministic alphabetical order for named fields. - // Do not remove this sort, because it keeps hash output stable regardless of source order. - let mut named_fields = fields - .named - .iter() - .map(|field| { - let name = field - .ident - .as_ref() - .expect("Fields::Named(fields) must have names") - .clone(); - (name.to_string(), name) - }) - .collect::>(); - - named_fields.sort_by(|a, b| a.0.cmp(&b.0)); - - named_fields - .into_iter() - .map(|(_, name)| FieldAccess { + Fields::Named(fields) => crate::utils::sorted_named_fields(fields) + .into_iter() + .map(|field| { + let name = field.ident.as_ref().unwrap(); + FieldAccess { access: quote! { #name }, span: name.span(), - }) - .collect() - } + } + }) + .collect(), Fields::Unnamed(fields) => fields .unnamed .iter() diff --git a/server/crates/arbiter-macros/src/integrable.rs b/server/crates/arbiter-macros/src/integrable.rs new file mode 100644 index 0000000..ddc2b92 --- /dev/null +++ b/server/crates/arbiter-macros/src/integrable.rs @@ -0,0 +1,134 @@ +use crate::utils::INTEGRABLE_TRAIT_PATH; +use proc_macro2::TokenStream; +use quote::quote; +use syn::{DeriveInput, LitStr, spanned::Spanned as _}; + +struct IntegrableAttr { + kind: String, +} + +impl IntegrableAttr { + fn from_attrs( + attrs: &[syn::Attribute], + ident_span: proc_macro2::Span, + ) -> Result { + let mut kind: Option = None; + let mut found = false; + + for attr in attrs { + if !attr.path().is_ident("integrable") { + continue; + } + if found { + return Err(syn::Error::new(attr.span(), "duplicate #[integrable] attribute")); + } + found = true; + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("kind") { + let lit: LitStr = meta.value()?.parse()?; + let v = lit.value(); + if v.is_empty() { + return Err(syn::Error::new(lit.span(), "kind must not be empty")); + } + if !is_valid_kind(&v) { + return Err(syn::Error::new( + lit.span(), + "kind must be a valid schema name: start with a letter, contain only [a-zA-Z0-9_]", + )); + } + kind = Some(v); + } else { + return Err(meta.error("unknown key; expected `kind`")); + } + Ok(()) + })?; + } + + let kind = kind.ok_or_else(|| { + syn::Error::new(ident_span, "#[integrable(kind = \"...\")] is required") + })?; + + Ok(Self { kind }) + } +} + +fn is_valid_kind(s: &str) -> bool { + let mut chars = s.chars(); + matches!(chars.next(), Some(c) if c.is_ascii_alphabetic()) + && chars.all(|c| c.is_ascii_alphanumeric() || c == '_') +} + +fn fnv1a(data: &[u8], mut hash: u32) -> u32 { + const FNV_PRIME: u32 = 16_777_619; + for &b in data { + hash ^= u32::from(b); + hash = hash.wrapping_mul(FNV_PRIME); + } + hash +} + +// Hashes field names and types using the same alphabetical sort order as Hashable, +// so that source-level field reordering never changes VERSION. +fn compute_version(fields: &syn::Fields) -> i32 { + const FNV_OFFSET: u32 = 2_166_136_261; + let mut hash = FNV_OFFSET; + + match fields { + syn::Fields::Named(named) => { + for field in crate::utils::sorted_named_fields(named) { + let name = field.ident.as_ref().unwrap().to_string(); + let ty = &field.ty; + hash = fnv1a(name.as_bytes(), hash); + hash = fnv1a(quote!(#ty).to_string().as_bytes(), hash); + } + } + syn::Fields::Unnamed(unnamed) => { + for (i, field) in unnamed.unnamed.iter().enumerate() { + let ty = &field.ty; + hash = fnv1a(i.to_string().as_bytes(), hash); + hash = fnv1a(quote!(#ty).to_string().as_bytes(), hash); + } + } + syn::Fields::Unit => {} + } + + // Clear sign bit to guarantee a positive i32; substitute 0 → 1. + let v = (hash >> 1).cast_signed(); + if v == 0 { 1 } else { v } +} + +pub(crate) fn derive(input: &DeriveInput) -> TokenStream { + let syn::Data::Struct(ref data) = input.data else { + return syn::Error::new( + input.ident.span(), + "#[derive(Integrable)] is only supported on structs", + ) + .to_compile_error(); + }; + + let integrable_trait = INTEGRABLE_TRAIT_PATH.to_path(); + let hashable_trait = crate::utils::HASHABLE_TRAIT_PATH.to_path(); + let ident = &input.ident; + + let mut generics = input.generics.clone(); + for type_param in generics.type_params_mut() { + type_param.bounds.push(syn::parse_quote!(#hashable_trait)); + } + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let attr = match IntegrableAttr::from_attrs(&input.attrs, input.ident.span()) { + Ok(a) => a, + Err(e) => return e.to_compile_error(), + }; + + let kind = attr.kind; + let version = compute_version(&data.fields); + + quote! { + #[automatically_derived] + impl #impl_generics #integrable_trait for #ident #ty_generics #where_clause { + const KIND: &'static str = #kind; + const VERSION: i32 = #version; + } + } +} diff --git a/server/crates/arbiter-macros/src/lib.rs b/server/crates/arbiter-macros/src/lib.rs index 51b8f79..a814f16 100644 --- a/server/crates/arbiter-macros/src/lib.rs +++ b/server/crates/arbiter-macros/src/lib.rs @@ -1,6 +1,7 @@ use syn::{DeriveInput, parse_macro_input}; mod hashable; +mod integrable; mod utils; #[proc_macro_derive(Hashable)] @@ -8,3 +9,9 @@ pub fn derive_hashable(input: proc_macro::TokenStream) -> proc_macro::TokenStrea let input = parse_macro_input!(input as DeriveInput); hashable::derive(&input).into() } + +#[proc_macro_derive(Integrable, attributes(integrable))] +pub fn derive_integrable(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + integrable::derive(&input).into() +} diff --git a/server/crates/arbiter-macros/src/utils.rs b/server/crates/arbiter-macros/src/utils.rs index c1ccf2e..283c101 100644 --- a/server/crates/arbiter-macros/src/utils.rs +++ b/server/crates/arbiter-macros/src/utils.rs @@ -22,3 +22,14 @@ macro_rules! ensure_path { ensure_path!(::arbiter_crypto::hashing::Hashable as HASHABLE_TRAIT_PATH); ensure_path!(::arbiter_crypto::hashing::Digest as HMAC_DIGEST_PATH); +ensure_path!(::arbiter_crypto::integrity::Integrable as INTEGRABLE_TRAIT_PATH); + +/// Returns named struct fields sorted alphabetically by name. +/// Both `Hashable` and `Integrable` derive macros must iterate fields in the +/// same deterministic order so that source-level reordering never changes +/// either the runtime hash or the compile-time VERSION. +pub(crate) fn sorted_named_fields(fields: &syn::FieldsNamed) -> Vec<&syn::Field> { + let mut v: Vec<&syn::Field> = fields.named.iter().collect(); + v.sort_by_key(|f| f.ident.as_ref().unwrap().to_string()); + v +} diff --git a/server/crates/arbiter-macros/tests/integrable.rs b/server/crates/arbiter-macros/tests/integrable.rs new file mode 100644 index 0000000..8205969 --- /dev/null +++ b/server/crates/arbiter-macros/tests/integrable.rs @@ -0,0 +1,53 @@ +use arbiter_crypto::integrity::Integrable; + +#[derive(arbiter_macros::Hashable, arbiter_macros::Integrable)] +#[integrable(kind = "test_entity")] +struct TestEntity { + value: i32, +} + +#[derive(arbiter_macros::Hashable, arbiter_macros::Integrable)] +#[integrable(kind = "other_entity")] +struct OtherEntity { + label: String, + count: u64, +} + +#[test] +fn kind_is_set_correctly() { + assert_eq!(::KIND, "test_entity"); + assert_eq!(::KIND, "other_entity"); +} + +#[test] +fn version_is_positive() { + const { + assert!(::VERSION > 0); + assert!(::VERSION > 0); + } +} + +#[test] +fn different_field_layouts_produce_different_versions() { + assert_ne!( + ::VERSION, + ::VERSION, + ); +} + +#[derive(arbiter_macros::Hashable, arbiter_macros::Integrable)] +#[integrable(kind = "generic_entity")] +struct GenericEntity { + inner: T, +} + +#[test] +fn generic_struct_derives_integrable() { + assert_eq!( + as Integrable>::KIND, + "generic_entity" + ); + const { + assert!( as Integrable>::VERSION > 0); + } +} diff --git a/server/crates/arbiter-macros/tests/integrable_errors.rs b/server/crates/arbiter-macros/tests/integrable_errors.rs new file mode 100644 index 0000000..2a33f39 --- /dev/null +++ b/server/crates/arbiter-macros/tests/integrable_errors.rs @@ -0,0 +1,5 @@ +#[test] +fn integrable_compile_fail() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/integrable/*.rs"); +} diff --git a/server/crates/arbiter-macros/tests/ui/integrable/duplicate_attr.rs b/server/crates/arbiter-macros/tests/ui/integrable/duplicate_attr.rs new file mode 100644 index 0000000..4a46634 --- /dev/null +++ b/server/crates/arbiter-macros/tests/ui/integrable/duplicate_attr.rs @@ -0,0 +1,8 @@ +#[derive(arbiter_macros::Hashable, arbiter_macros::Integrable)] +#[integrable(kind = "entity_a")] +#[integrable(kind = "entity_b")] +struct DuplicateAttr { + value: i32, +} + +fn main() {} diff --git a/server/crates/arbiter-macros/tests/ui/integrable/duplicate_attr.stderr b/server/crates/arbiter-macros/tests/ui/integrable/duplicate_attr.stderr new file mode 100644 index 0000000..57b4cdc --- /dev/null +++ b/server/crates/arbiter-macros/tests/ui/integrable/duplicate_attr.stderr @@ -0,0 +1,5 @@ +error: duplicate #[integrable] attribute + --> tests/ui/integrable/duplicate_attr.rs:3:1 + | +3 | #[integrable(kind = "entity_b")] + | ^ diff --git a/server/crates/arbiter-macros/tests/ui/integrable/empty_kind.rs b/server/crates/arbiter-macros/tests/ui/integrable/empty_kind.rs new file mode 100644 index 0000000..03c74b1 --- /dev/null +++ b/server/crates/arbiter-macros/tests/ui/integrable/empty_kind.rs @@ -0,0 +1,7 @@ +#[derive(arbiter_macros::Hashable, arbiter_macros::Integrable)] +#[integrable(kind = "")] +struct EmptyKind { + value: i32, +} + +fn main() {} diff --git a/server/crates/arbiter-macros/tests/ui/integrable/empty_kind.stderr b/server/crates/arbiter-macros/tests/ui/integrable/empty_kind.stderr new file mode 100644 index 0000000..9c76314 --- /dev/null +++ b/server/crates/arbiter-macros/tests/ui/integrable/empty_kind.stderr @@ -0,0 +1,5 @@ +error: kind must not be empty + --> tests/ui/integrable/empty_kind.rs:2:21 + | +2 | #[integrable(kind = "")] + | ^^ diff --git a/server/crates/arbiter-macros/tests/ui/integrable/enum_not_supported.rs b/server/crates/arbiter-macros/tests/ui/integrable/enum_not_supported.rs new file mode 100644 index 0000000..f867a91 --- /dev/null +++ b/server/crates/arbiter-macros/tests/ui/integrable/enum_not_supported.rs @@ -0,0 +1,8 @@ +#[derive(arbiter_macros::Integrable)] +#[integrable(kind = "my_enum")] +enum MyEnum { + A, + B, +} + +fn main() {} diff --git a/server/crates/arbiter-macros/tests/ui/integrable/enum_not_supported.stderr b/server/crates/arbiter-macros/tests/ui/integrable/enum_not_supported.stderr new file mode 100644 index 0000000..aa71dbe --- /dev/null +++ b/server/crates/arbiter-macros/tests/ui/integrable/enum_not_supported.stderr @@ -0,0 +1,5 @@ +error: #[derive(Integrable)] is only supported on structs + --> tests/ui/integrable/enum_not_supported.rs:3:6 + | +3 | enum MyEnum { + | ^^^^^^ diff --git a/server/crates/arbiter-macros/tests/ui/integrable/invalid_kind.rs b/server/crates/arbiter-macros/tests/ui/integrable/invalid_kind.rs new file mode 100644 index 0000000..e185270 --- /dev/null +++ b/server/crates/arbiter-macros/tests/ui/integrable/invalid_kind.rs @@ -0,0 +1,7 @@ +#[derive(arbiter_macros::Hashable, arbiter_macros::Integrable)] +#[integrable(kind = "bad kind!")] +struct InvalidKind { + value: i32, +} + +fn main() {} diff --git a/server/crates/arbiter-macros/tests/ui/integrable/invalid_kind.stderr b/server/crates/arbiter-macros/tests/ui/integrable/invalid_kind.stderr new file mode 100644 index 0000000..1aea443 --- /dev/null +++ b/server/crates/arbiter-macros/tests/ui/integrable/invalid_kind.stderr @@ -0,0 +1,5 @@ +error: kind must be a valid schema name: start with a letter, contain only [a-zA-Z0-9_] + --> tests/ui/integrable/invalid_kind.rs:2:21 + | +2 | #[integrable(kind = "bad kind!")] + | ^^^^^^^^^^^ diff --git a/server/crates/arbiter-macros/tests/ui/integrable/missing_attr.rs b/server/crates/arbiter-macros/tests/ui/integrable/missing_attr.rs new file mode 100644 index 0000000..1617816 --- /dev/null +++ b/server/crates/arbiter-macros/tests/ui/integrable/missing_attr.rs @@ -0,0 +1,6 @@ +#[derive(arbiter_macros::Hashable, arbiter_macros::Integrable)] +struct MissingAttr { + value: i32, +} + +fn main() {} diff --git a/server/crates/arbiter-macros/tests/ui/integrable/missing_attr.stderr b/server/crates/arbiter-macros/tests/ui/integrable/missing_attr.stderr new file mode 100644 index 0000000..a20b6f4 --- /dev/null +++ b/server/crates/arbiter-macros/tests/ui/integrable/missing_attr.stderr @@ -0,0 +1,5 @@ +error: #[integrable(kind = "...")] is required + --> tests/ui/integrable/missing_attr.rs:2:8 + | +2 | struct MissingAttr { + | ^^^^^^^^^^^ diff --git a/server/crates/arbiter-server/src/crypto/integrity/v1.rs b/server/crates/arbiter-server/src/crypto/integrity/v1.rs index edb2274..3e9bf00 100644 --- a/server/crates/arbiter-server/src/crypto/integrity/v1.rs +++ b/server/crates/arbiter-server/src/crypto/integrity/v1.rs @@ -52,10 +52,7 @@ pub const INTEGRITY_SUBKEY_TAG: &[u8] = b"arbiter/db-integrity-key/v1"; pub type HmacSha256 = Hmac; -pub trait Integrable: Hashable { - const KIND: &'static str; - const VERSION: i32 = 1; -} +pub use arbiter_crypto::integrity::Integrable; fn payload_hash(payload: &impl Hashable) -> [u8; 32] { let mut hasher = Sha256::new(); @@ -217,15 +214,13 @@ mod tests { }; use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; - use super::{Error, Integrable, sign_entity, verify_entity}; - #[derive(Clone, arbiter_macros::Hashable)] + use super::{Error, sign_entity, verify_entity}; + #[derive(Clone, arbiter_macros::Hashable, arbiter_macros::Integrable)] + #[integrable(kind = "dummy_entity")] struct DummyEntity { payload_version: i32, payload: Vec, } - impl Integrable for DummyEntity { - const KIND: &'static str = "dummy_entity"; - } async fn bootstrapped_vault(db: &db::DatabasePool) -> ActorRef { let actor = Vault::spawn( diff --git a/server/crates/arbiter-server/src/evm/policies/ether_transfer/mod.rs b/server/crates/arbiter-server/src/evm/policies/ether_transfer/mod.rs index bff34a0..38d5155 100644 --- a/server/crates/arbiter-server/src/evm/policies/ether_transfer/mod.rs +++ b/server/crates/arbiter-server/src/evm/policies/ether_transfer/mod.rs @@ -1,6 +1,5 @@ use super::{DatabaseID, EvalContext, EvalViolation}; use crate::{ - crypto::integrity::v1::Integrable, db::models::{ EvmBasicGrant, EvmEtherTransferGrant, EvmEtherTransferGrantTarget, EvmEtherTransferLimit, NewEvmEtherTransferLimit, SqliteTimestamp, @@ -52,14 +51,12 @@ impl From for SpecificMeaning { } // A grant for ether transfers, which can be scoped to specific target addresses and volume limits -#[derive(Debug, Clone, arbiter_macros::Hashable)] +#[derive(Debug, Clone, arbiter_macros::Hashable, arbiter_macros::Integrable)] +#[integrable(kind = "EtherTransfer")] pub struct Settings { pub target: Vec
, pub limit: VolumeRateLimit, } -impl Integrable for Settings { - const KIND: &'static str = "EtherTransfer"; -} impl From for SpecificGrant { fn from(val: Settings) -> Self { diff --git a/server/crates/arbiter-server/src/evm/policies/token_transfers/mod.rs b/server/crates/arbiter-server/src/evm/policies/token_transfers/mod.rs index d91b942..cc4ef51 100644 --- a/server/crates/arbiter-server/src/evm/policies/token_transfers/mod.rs +++ b/server/crates/arbiter-server/src/evm/policies/token_transfers/mod.rs @@ -1,6 +1,5 @@ use super::{DatabaseID, EvalContext, EvalViolation}; use crate::{ - crypto::integrity::Integrable, db::models::{ EvmBasicGrant, EvmTokenTransferGrant, EvmTokenTransferVolumeLimit, NewEvmTokenTransferGrant, NewEvmTokenTransferLog, NewEvmTokenTransferVolumeLimit, @@ -63,15 +62,13 @@ impl From for SpecificMeaning { } // A grant for token transfers, which can be scoped to specific target addresses and volume limits -#[derive(Debug, Clone, arbiter_macros::Hashable)] +#[derive(Debug, Clone, arbiter_macros::Hashable, arbiter_macros::Integrable)] +#[integrable(kind = "TokenTransfer")] pub struct Settings { pub token_contract: Address, pub target: Option
, pub volume_limits: Vec, } -impl Integrable for Settings { - const KIND: &'static str = "TokenTransfer"; -} impl From for SpecificGrant { fn from(val: Settings) -> Self { diff --git a/server/crates/arbiter-server/src/peers/client/mod.rs b/server/crates/arbiter-server/src/peers/client/mod.rs index f142bb8..85c7697 100644 --- a/server/crates/arbiter-server/src/peers/client/mod.rs +++ b/server/crates/arbiter-server/src/peers/client/mod.rs @@ -1,5 +1,5 @@ use crate::{ - actors::GlobalActors, crypto::integrity::Integrable, db, peers::client::session::ClientSession, + actors::GlobalActors, db, peers::client::session::ClientSession, }; use arbiter_crypto::authn; use arbiter_macros::Hashable; @@ -14,15 +14,12 @@ pub struct ClientProfile { pub metadata: ClientMetadata, } -#[derive(Hashable)] +#[derive(Hashable, arbiter_macros::Integrable)] +#[integrable(kind = "client_credentials")] pub struct ClientCredentials { pub pubkey: authn::PublicKey, } -impl Integrable for ClientCredentials { - const KIND: &'static str = "client_credentials"; -} - pub struct ClientConnection { pub(crate) db: db::DatabasePool, pub(crate) actors: GlobalActors, diff --git a/server/crates/arbiter-server/src/peers/operator/mod.rs b/server/crates/arbiter-server/src/peers/operator/mod.rs index 0869d51..5727230 100644 --- a/server/crates/arbiter-server/src/peers/operator/mod.rs +++ b/server/crates/arbiter-server/src/peers/operator/mod.rs @@ -3,7 +3,7 @@ use crate::{ GlobalActors, vault::{GetState, Vault}, }, - crypto::integrity::{self, AttestationStatus, Integrable}, + crypto::integrity::{self, AttestationStatus}, db::{DatabaseError, DatabasePool}, peers::client::ClientProfile, }; @@ -23,16 +23,13 @@ pub mod auth; pub mod session; pub mod vault_gate; -#[derive(Debug, Clone, Hashable)] +#[derive(Debug, Clone, Hashable, arbiter_macros::Integrable)] +#[integrable(kind = "operator_credentials")] pub struct Credentials { pub id: i32, pub pubkey: authn::PublicKey, } -impl Integrable for Credentials { - const KIND: &'static str = "operator_credentials"; -} - // Messages, sent by operator to connection client without having a request #[derive(Debug)] pub enum OutOfBand {