use ethers::types::transaction::eip712::Eip712Error;
use ethers::types::{Signature, H160};
use ethers::{
signers::{LocalWallet, Signer},
utils::hex,
};
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use std::time::{SystemTime, UNIX_EPOCH};
use ethers::{types::U256, utils::keccak256};
use crate::models::SubaccountDto;
use crate::{domain_config::DOMAINS, enums::Environment};
use anyhow::Result;
pub fn to_scaled_e9(value: Decimal) -> Result<u128> {
Ok((value * dec!(1_000_000_000))
.to_u128()
.expect("Value too large to fit in u128"))
}
pub fn get_nonce() -> u64 {
let start = SystemTime::now();
let since_epoch = start
.duration_since(UNIX_EPOCH)
.expect("Time went backwards");
since_epoch.as_nanos() as u64
}
pub fn get_now() -> i64 {
let start = SystemTime::now();
let since_epoch = start
.duration_since(UNIX_EPOCH)
.expect("Time went backwards");
since_epoch.as_secs() as i64
}
pub fn get_domain_separator(env: Environment) -> [u8; 32] {
let domain_config = DOMAINS.get(env);
let domain_type_hash = keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)",
);
keccak256(ethers::abi::encode(&[
ethers::abi::Token::Uint(U256::from(domain_type_hash)),
ethers::abi::Token::Uint(U256::from(keccak256(domain_config.name))),
ethers::abi::Token::Uint(U256::from(keccak256(domain_config.version))),
ethers::abi::Token::Uint(U256::from(domain_config.chain_id)),
ethers::abi::Token::Address(domain_config.verifying_contract.parse().unwrap()),
]))
}
pub fn hex_to_bytes32(s: &str) -> Result<[u8; 32], Box<dyn std::error::Error>> {
let raw = s.trim_start_matches("0x");
let decoded = hex::decode(raw)?;
let mut out = [0u8; 32];
let n = decoded.len().min(32);
out[..n].copy_from_slice(&decoded[..n]);
Ok(out)
}
pub fn make_full_hash(domain_hash: &[u8; 32], message_hash: &[u8; 32]) -> [u8; 32] {
let mut full_hash = Vec::with_capacity(66);
full_hash.push(0x19);
full_hash.push(0x01);
full_hash.extend_from_slice(domain_hash);
full_hash.extend_from_slice(message_hash);
keccak256(&full_hash)
}
pub trait Eip712 {
fn type_hash() -> Result<[u8; 32], Eip712Error>;
fn struct_hash(&self) -> Result<[u8; 32], Eip712Error>;
fn sign(&self, env: Environment, wallet: &LocalWallet) -> Result<Signature, Eip712Error> {
let full_hash = self.encode_eip712(env)?;
let signature = wallet.sign_hash(full_hash.into());
Ok(signature.unwrap())
}
fn encode_eip712(&self, env: Environment) -> Result<[u8; 32], Eip712Error> {
let domain_separator = get_domain_separator(env);
let full_hash = make_full_hash(&domain_separator, &self.struct_hash()?);
Ok(full_hash)
}
}
#[derive(Clone)]
pub struct EipSigningFields {
pub sender: H160,
pub subaccount: [u8; 32],
pub nonce: u64,
pub signed_at: u64,
}
#[derive(Clone)]
pub struct DtoSigningFields {
pub sender: String,
pub subaccount: String,
pub nonce: String,
pub signed_at: i64,
}
pub struct SigningContext<'a> {
wallet: &'a LocalWallet,
subaccount: &'a SubaccountDto,
pub nonce: u64,
pub signed_at: u64,
}
impl<'a> SigningContext<'a> {
pub fn eip_signing_fields(&self) -> EipSigningFields {
EipSigningFields {
sender: self.wallet.address(),
subaccount: hex_to_bytes32(&self.subaccount.name).unwrap(),
nonce: self.nonce,
signed_at: self.signed_at,
}
}
pub fn dto_signing_fields(&self) -> DtoSigningFields {
DtoSigningFields {
sender: format!("{:?}", self.wallet.address()),
subaccount: self.subaccount.name.clone(),
nonce: self.nonce.to_string(),
signed_at: self.signed_at as i64,
}
}
pub fn new(wallet: &'a LocalWallet, subaccount: &'a SubaccountDto) -> Self {
SigningContext {
wallet,
subaccount,
nonce: get_nonce(),
signed_at: get_now() as u64,
}
}
}