use alloy::dyn_abi::Eip712Domain;
use alloy::primitives::{Address, B256, Signature};
use alloy::signers::SignerSync;
use alloy::sol_types::SolStruct;
use decdn_protocol::StreamResponseBody;
mod sol_types {
alloy::sol! {
#[allow(non_snake_case, missing_debug_implementations)]
struct StreamResponse {
bytes32 hash;
bool ok;
uint64 ratePerMb;
uint64 totalBytes;
bytes32 poolId;
uint64 timestampUs;
}
}
}
use sol_types::StreamResponse as StreamResponseSol;
pub const STREAM_RESPONSE_TYPEHASH: B256 =
alloy::primitives::b256!("0xc6d9e65c3527b52698988cae3aea61d5d8df64b415de933cc7e9762bc600eb3e");
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StreamSlashData {
pub hash: B256,
pub ok: bool,
pub rate_per_mb: u64,
pub total_bytes: u64,
pub pool_id: B256,
pub timestamp_us: u64,
}
impl StreamSlashData {
#[must_use]
pub fn from_response_body(body: &StreamResponseBody) -> Self {
Self {
hash: B256::from(body.hash),
ok: body.ok,
rate_per_mb: body.rate_per_mb,
total_bytes: body.total_bytes,
pool_id: B256::from(body.pool_id),
timestamp_us: body.timestamp_us,
}
}
const fn to_sol(self) -> StreamResponseSol {
StreamResponseSol {
hash: self.hash,
ok: self.ok,
ratePerMb: self.rate_per_mb,
totalBytes: self.total_bytes,
poolId: self.pool_id,
timestampUs: self.timestamp_us,
}
}
#[must_use]
pub fn signing_hash(&self, domain: &Eip712Domain) -> B256 {
(*self).to_sol().eip712_signing_hash(domain)
}
#[must_use]
pub fn struct_hash(&self) -> B256 {
(*self).to_sol().eip712_hash_struct()
}
pub fn sign<S: SignerSync>(
&self,
signer: &S,
domain: &Eip712Domain,
) -> Result<Signature, alloy::signers::Error> {
let hash = self.signing_hash(domain);
signer.sign_hash_sync(&hash)
}
pub fn recover_signer(
&self,
signature: &Signature,
domain: &Eip712Domain,
) -> Result<Address, StreamSlashError> {
if crate::sig_canon::is_high_s(signature) {
return Err(StreamSlashError::InvalidSignature);
}
let hash = self.signing_hash(domain);
signature
.recover_address_from_prehash(&hash)
.map_err(|_| StreamSlashError::InvalidSignature)
}
pub fn verify_signer(
&self,
signature: &Signature,
expected: Address,
domain: &Eip712Domain,
) -> Result<(), StreamSlashError> {
let recovered = self.recover_signer(signature, domain)?;
if recovered == expected {
Ok(())
} else {
Err(StreamSlashError::WrongSigner {
expected,
recovered,
})
}
}
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum StreamSlashError {
#[error("stream slash_sig is malformed")]
InvalidSignature,
#[error("stream slash_sig signed by {recovered}, expected {expected}")]
WrongSigner {
expected: Address,
recovered: Address,
},
}
#[cfg(test)]
#[allow(clippy::similar_names)] mod tests;