use crate::Result;
use alloy_primitives::{
eip191_hash_message, Address, ChainId, PrimitiveSignature as Signature, B256,
};
use async_trait::async_trait;
use auto_impl::auto_impl;
#[cfg(feature = "eip712")]
use alloy_dyn_abi::eip712::TypedData;
#[cfg(feature = "eip712")]
use alloy_sol_types::{Eip712Domain, SolStruct};
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[auto_impl(&mut, Box)]
pub trait Signer<Sig = Signature> {
async fn sign_hash(&self, hash: &B256) -> Result<Sig>;
#[inline]
async fn sign_message(&self, message: &[u8]) -> Result<Sig> {
self.sign_hash(&eip191_hash_message(message)).await
}
#[cfg(feature = "eip712")]
#[inline]
#[auto_impl(keep_default_for(&mut, Box))]
async fn sign_typed_data<T: SolStruct + Send + Sync>(
&self,
payload: &T,
domain: &Eip712Domain,
) -> Result<Sig>
where
Self: Sized,
{
self.sign_hash(&payload.eip712_signing_hash(domain)).await
}
#[cfg(feature = "eip712")]
#[inline]
async fn sign_dynamic_typed_data(&self, payload: &TypedData) -> Result<Sig> {
self.sign_hash(&payload.eip712_signing_hash()?).await
}
fn address(&self) -> Address;
fn chain_id(&self) -> Option<ChainId>;
fn set_chain_id(&mut self, chain_id: Option<ChainId>);
#[inline]
#[must_use]
#[auto_impl(keep_default_for(&mut, Box))]
fn with_chain_id(mut self, chain_id: Option<ChainId>) -> Self
where
Self: Sized,
{
self.set_chain_id(chain_id);
self
}
}
#[auto_impl(&, &mut, Box, Rc, Arc)]
pub trait SignerSync<Sig = Signature> {
fn sign_hash_sync(&self, hash: &B256) -> Result<Sig>;
#[inline]
fn sign_message_sync(&self, message: &[u8]) -> Result<Sig> {
self.sign_hash_sync(&eip191_hash_message(message))
}
#[cfg(feature = "eip712")]
#[inline]
#[auto_impl(keep_default_for(&, &mut, Box, Rc, Arc))]
fn sign_typed_data_sync<T: SolStruct>(&self, payload: &T, domain: &Eip712Domain) -> Result<Sig>
where
Self: Sized,
{
self.sign_hash_sync(&payload.eip712_signing_hash(domain))
}
#[cfg(feature = "eip712")]
#[inline]
fn sign_dynamic_typed_data_sync(&self, payload: &TypedData) -> Result<Sig> {
let hash = payload.eip712_signing_hash()?;
self.sign_hash_sync(&hash)
}
fn chain_id_sync(&self) -> Option<ChainId>;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Error, UnsupportedSignerOperation};
use assert_matches::assert_matches;
use std::sync::Arc;
struct _ObjectSafe(Box<dyn Signer>, Box<dyn SignerSync>);
#[tokio::test]
async fn unimplemented() {
#[cfg(feature = "eip712")]
alloy_sol_types::sol! {
#[derive(Default, serde::Serialize)]
struct Eip712Data {
uint64 a;
}
}
async fn test_unimplemented_signer<S: Signer + SignerSync + Send + Sync>(s: &S) {
test_unsized_unimplemented_signer(s).await;
test_unsized_unimplemented_signer_sync(s);
#[cfg(feature = "eip712")]
assert!(s
.sign_typed_data_sync(&Eip712Data::default(), &Eip712Domain::default())
.is_err());
#[cfg(feature = "eip712")]
assert!(s
.sign_typed_data(&Eip712Data::default(), &Eip712Domain::default())
.await
.is_err());
}
async fn test_unsized_unimplemented_signer<S: Signer + ?Sized + Send + Sync>(s: &S) {
assert_matches!(
s.sign_hash(&B256::ZERO).await,
Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash))
);
assert_matches!(
s.sign_message(&[]).await,
Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash))
);
#[cfg(feature = "eip712")]
assert_matches!(
s.sign_dynamic_typed_data(&TypedData::from_struct(&Eip712Data::default(), None))
.await,
Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash))
);
assert_eq!(s.chain_id(), None);
}
fn test_unsized_unimplemented_signer_sync<S: SignerSync + ?Sized>(s: &S) {
assert_matches!(
s.sign_hash_sync(&B256::ZERO),
Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash))
);
assert_matches!(
s.sign_message_sync(&[]),
Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash))
);
#[cfg(feature = "eip712")]
assert_matches!(
s.sign_dynamic_typed_data_sync(&TypedData::from_struct(
&Eip712Data::default(),
None
)),
Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash))
);
assert_eq!(s.chain_id_sync(), None);
}
struct UnimplementedSigner;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl Signer for UnimplementedSigner {
async fn sign_hash(&self, _hash: &B256) -> Result<Signature> {
Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash))
}
fn address(&self) -> Address {
Address::ZERO
}
fn chain_id(&self) -> Option<ChainId> {
None
}
fn set_chain_id(&mut self, _chain_id: Option<ChainId>) {}
}
impl SignerSync for UnimplementedSigner {
fn sign_hash_sync(&self, _hash: &B256) -> Result<Signature> {
Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash))
}
fn chain_id_sync(&self) -> Option<ChainId> {
None
}
}
test_unimplemented_signer(&UnimplementedSigner).await;
test_unsized_unimplemented_signer(&UnimplementedSigner as &(dyn Signer + Send + Sync))
.await;
test_unsized_unimplemented_signer_sync(
&UnimplementedSigner as &(dyn SignerSync + Send + Sync),
);
test_unsized_unimplemented_signer(
&(Box::new(UnimplementedSigner) as Box<dyn Signer + Send + Sync>),
)
.await;
test_unsized_unimplemented_signer_sync(
&(Box::new(UnimplementedSigner) as Box<dyn SignerSync + Send + Sync>),
);
test_unsized_unimplemented_signer_sync(
&(Arc::new(UnimplementedSigner) as Arc<dyn SignerSync + Send + Sync>),
);
}
}