use alloy_primitives::{Address, B256, U256, keccak256};
use alloy_sol_types::Eip712Domain;
use const_hex::{FromHex, FromHexError};
use std::fmt;
use std::str::FromStr;
#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct DomainSeparator(pub [u8; 32]);
impl DomainSeparator {
pub fn new(chain_id: u64, contract_address: Address) -> Self {
let domain = Eip712Domain {
name: Some("Gnosis Protocol".into()),
version: Some("v2".into()),
chain_id: Some(U256::from(chain_id)),
verifying_contract: Some(contract_address),
salt: None,
};
Self(domain.separator().into())
}
}
impl FromStr for DomainSeparator {
type Err = FromHexError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let body = s
.strip_prefix("0x")
.ok_or(FromHexError::InvalidStringLength)?;
Ok(Self(FromHex::from_hex(body)?))
}
}
impl fmt::Debug for DomainSeparator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&const_hex::encode(self.0))
}
}
pub fn hashed_eip712_message(domain_separator: &DomainSeparator, struct_hash: &[u8; 32]) -> B256 {
let mut message = [0u8; 66];
message[0..2].copy_from_slice(&[0x19, 0x01]);
message[2..34].copy_from_slice(&domain_separator.0);
message[34..66].copy_from_slice(struct_hash);
keccak256(message)
}
pub fn hashed_ethsign_message(domain_separator: &DomainSeparator, struct_hash: &[u8; 32]) -> B256 {
let mut message = [0u8; 60];
message[..28].copy_from_slice(b"\x19Ethereum Signed Message:\n32");
message[28..].copy_from_slice(hashed_eip712_message(domain_separator, struct_hash).as_slice());
keccak256(message)
}
#[cfg(test)]
mod tests {
use hex_literal::hex;
use super::*;
use crate::contracts::GPV2_SETTLEMENT;
#[test]
fn domain_separator_sepolia() {
let chain_id: u64 = 11_155_111;
let computed = DomainSeparator::new(chain_id, GPV2_SETTLEMENT);
let expected = DomainSeparator(hex!(
"daee378bd0eb30ddf479272accf91761e697bc00e067a268f95f1d2732ed230b"
));
assert_eq!(computed, expected);
}
#[test]
fn domain_separator_from_str_round_trips() {
let body = "9d7e07ef92761aa9453ae5ff25083a2b19764131b15295d3c7e89f1f1b8c67d9";
let prefixed = format!("0x{body}");
let parsed = DomainSeparator::from_str(&prefixed).unwrap();
assert_eq!(format!("{parsed:?}"), body);
}
#[test]
fn domain_separator_from_str_requires_0x_prefix() {
let bare = "9d7e07ef92761aa9453ae5ff25083a2b19764131b15295d3c7e89f1f1b8c67d9";
assert!(DomainSeparator::from_str(bare).is_err());
}
}