Conversation
…HashDigest<S, N>` type in `primitives` Replace three separate `hash_newtype!` / `digest_newtype!` macros (in `primitives`, `foreign-chain-inspector`, and `contract/tee/measurements`) with a single generic `HashDigest<S, N>` struct and a `define_hash!` macro in `mpc-primitives`. This eliminates code duplication across crates while supporting arbitrary byte lengths and consistent serde/borsh/schema implementations. Addresses #2597
227d139 to
d3fada7
Compare
…hash_newtype-macros-across-primitives-and-foreign-chain-inspector
There was a problem hiding this comment.
Pull request overview
This PR consolidates multiple per-crate “hash newtype” macros into a single generic HashDigest<S, N> implementation in mpc-primitives, and migrates downstream crates to use the shared define_hash! macro.
Changes:
- Introduces generic
HashDigest<S, N>+ exporteddefine_hash!macro inmpc-primitivesand removes the oldhash_newtype!macro. - Replaces local hash-newtype macros/usages in
foreign-chain-inspector,foreign-chain-rpc-interfaces, andcontract/tee/measurementswithmpc_primitives::define_hash!. - Updates crate dependencies accordingly (adds
paste, removes now-unusedhex/serde_within some crates) and removes some unnecessary.clone()calls enabled byCopyhashes.
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/primitives/src/lib.rs | Adds hidden _macro_deps re-exports used by define_hash!. |
| crates/primitives/src/hash.rs | Implements HashDigest<S, N>, HashSpec, and the exported define_hash! macro; migrates concrete hash types. |
| crates/primitives/Cargo.toml | Adds paste dependency; removes serde_with. |
| crates/mpc-attestation/src/attestation.rs | Removes redundant clones when embedding hash types (now Copy). |
| crates/foreign-chain-rpc-interfaces/src/bitcoin.rs | Replaces local hash macro with mpc_primitives::define_hash! for transport hashes. |
| crates/foreign-chain-rpc-interfaces/Cargo.toml | Drops direct hex dependency; adds mpc-primitives. |
| crates/foreign-chain-inspector/src/starknet.rs | Switches Starknet hash newtypes to mpc_primitives::define_hash!. |
| crates/foreign-chain-inspector/src/lib.rs | Removes the now-unused internal hash module. |
| crates/foreign-chain-inspector/src/hash.rs | Deletes the duplicate hash helper/macro implementation. |
| crates/foreign-chain-inspector/src/bitcoin.rs | Switches Bitcoin hash newtypes to mpc_primitives::define_hash!. |
| crates/foreign-chain-inspector/src/abstract_chain.rs | Switches abstract-chain hash newtypes to mpc_primitives::define_hash!. |
| crates/foreign-chain-inspector/Cargo.toml | Removes hex/serde from main deps; keeps serde in dev-deps for tests. |
| crates/contract/src/tee/proposal.rs | Removes redundant clones enabled by Copy hash types. |
| crates/contract/src/tee/measurements.rs | Replaces local 48-byte digest macro with mpc_primitives::define_hash!. |
| crates/contract/src/lib.rs | Removes redundant clones when voting with hash types (now Copy). |
| Cargo.toml | Adds workspace paste dependency and reformats rustls entry. |
| Cargo.lock | Updates lockfile for dependency changes (paste, removed hex/serde_with in some crates). |
Comments suppressed due to low confidence (1)
crates/primitives/src/hash.rs:310
TestHashSpec/TestHash48SpecimplementHashSpec, butHashSpechas supertraitsborsh::BorshSerialize + borsh::BorshDeserialize, so these specs must also implement those borsh traits. As written, this test module will not compile. Either switch the tests back to usingdefine_hash!(which generates the needed impls), or add minimalBorshSerialize/BorshDeserializeimpls for the spec structs.
define_hash!(TestHash, 32);
define_hash!(TestHash48, 48);
#[test]
fn test_from_bytes_array() {
let bytes = [1u8; 32];
let hash: TestHash = bytes.into();
assert_eq!(*hash, bytes);
}
#[test]
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
crates/primitives/src/hash.rs
Outdated
|
|
||
| impl<S: HashSpec<N>, const N: usize> core::fmt::Debug for HashDigest<S, N> { | ||
| fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| write!(f, "{}({})", S::NAME, self.as_hex()) |
There was a problem hiding this comment.
Debug for HashDigest calls self.as_hex(), which allocates a String (via hex::encode) on every debug format. These hash types are logged with {:?} in contract code paths (e.g. voting), so this can add avoidable allocation/gas overhead. Consider implementing Debug without allocating (e.g., debug the raw byte array or write hex directly to the formatter without building an intermediate String).
| write!(f, "{}({})", S::NAME, self.as_hex()) | |
| // Avoid allocating a `String` on every debug format by encoding into a | |
| // stack buffer and writing the hex directly to the formatter. | |
| let mut buf = [0u8; 2 * N]; | |
| hex::encode_to_slice(self.bytes, &mut buf).map_err(|_| core::fmt::Error)?; | |
| let hex_str = core::str::from_utf8(&buf).map_err(|_| core::fmt::Error)?; | |
| write!(f, "{}({})", S::NAME, hex_str) |
| /// Marker trait binding a hash type name to a specific byte length. | ||
| /// | ||
| /// Each concrete hash type defines a zero-sized spec struct and implements this trait | ||
| /// for exactly one `N`. This prevents constructing a `HashDigest<S, WRONG_N>` — the | ||
| /// compiler rejects it because the trait bound `S: HashSpec<WRONG_N>` is not satisfied. | ||
| pub trait HashSpec<const N: usize>: borsh::BorshSerialize + borsh::BorshDeserialize { | ||
| const NAME: &'static str; | ||
| } |
There was a problem hiding this comment.
HashSpec is defined with borsh::BorshSerialize + borsh::BorshDeserialize supertraits, which forces every crate using HashDigest/define_hash! to pull in and compile borsh even if they only need serde (e.g. RPC/interface crates). This also conflicts with the PR description/issue acceptance criteria about not forcing borsh on off-chain crates. Consider removing the borsh supertraits from HashSpec (since HashDigest’s borsh impls don’t depend on S) and/or feature-gating the borsh impls/dependency in mpc-primitives.
crates/primitives/src/hash.rs
Outdated
| macro_rules! define_hash { | ||
| ($(#[$meta:meta])* $name:ident, $n:literal) => { | ||
| $crate::_macro_deps::paste::paste! { | ||
| #[doc = concat!("Spec for [`", stringify!($name), "`].")] |
There was a problem hiding this comment.
define_hash! generates a public <Name>Spec struct for every hash type. This adds extra public types to downstream crate APIs (e.g. TransportBitcoinBlockHashSpec) that are likely not meant for consumers. Consider marking the spec struct #[doc(hidden)] (or similar) so it doesn’t pollute generated docs while still satisfying Rust privacy for the public type alias.
| #[doc = concat!("Spec for [`", stringify!($name), "`].")] | |
| #[doc(hidden)] |
Code ReviewOverall: Clean deduplication refactor. The generic Issue 1: Test specs missing borsh impls — likely compile error
pub trait HashSpec<const N: usize>: borsh::BorshSerialize + borsh::BorshDeserialize { ... }But the manually-defined test specs at struct TestHashSpec;
impl HashSpec<32> for TestHashSpec { // ← should fail: TestHashSpec doesn't impl BorshSerialize
const NAME: &'static str = "TestHash";
}Fix options (in order of preference):
Issue 2: Spurious borsh bounds on
|
…neric `HashDigest<S, N>` type in `primitives`
…hash_newtype-macros-across-primitives-and-foreign-chain-inspector
gilcu3
left a comment
There was a problem hiding this comment.
Left some comments, we can continue when you are back
| .iter() | ||
| .map(|hash| *hash.clone()) | ||
| .map(|hash| **hash) |
There was a problem hiding this comment.
nit: is it possible to use into_iter here to avoid the double *?
| } | ||
| } | ||
|
|
||
| // -- Debug ------------------------------------------------------------------- |
There was a problem hiding this comment.
nit: lets remove these comments
There was a problem hiding this comment.
This turned out to be much harder than I thought. When you are back, could you mention why it requires this machinery?
Replace three separate
hash_newtype!/digest_newtype!macros (inprimitives,foreign-chain-inspector, andcontract/tee/measurements) with a single genericHashDigest<S, N>struct and adefine_hash!macro inmpc-primitives. This eliminates code duplication across crates while supporting arbitrary byte lengths and consistent serde/borsh/schema implementations.Addresses #2597