use alloy_primitives::{Address, U256};
use crate::Domain;
use crate::conversions::ProtoConversionError;
use crate::proto::v1;
pub trait ChainAddress:
Clone
+ Default
+ Into<v1::MultiChainAddress>
+ TryFrom<v1::MultiChainAddress, Error = ProtoConversionError>
{
}
impl ChainAddress for Address {}
pub struct SpendRequestBuilder<SrcAddressType, DstAddressType> {
pub(crate) src_domain: Domain,
pub(crate) src_token: SrcAddressType,
pub(crate) dst_domain: Domain,
pub(crate) dst_token: DstAddressType,
pub(crate) dst_recipient: DstAddressType,
pub(crate) amount: U256,
pub(crate) expiry_height: Option<u64>,
pub(crate) max_fee: Option<U256>,
pub(crate) caller: Option<DstAddressType>,
}
impl<SrcAddressType, DstAddressType> SpendRequestBuilder<SrcAddressType, DstAddressType> {
pub const fn src_domain(&self) -> Domain {
self.src_domain
}
pub const fn dst_domain(&self) -> Domain {
self.dst_domain
}
pub const fn max_fee(&self) -> Option<U256> {
self.max_fee
}
pub const fn expiry_height(&self) -> Option<u64> {
self.expiry_height
}
}
impl<SrcAddressType, DstAddressType> SpendRequestBuilder<SrcAddressType, DstAddressType>
where
SrcAddressType: ChainAddress,
DstAddressType: ChainAddress,
{
pub fn new(
src_domain: Domain,
src_token: SrcAddressType,
dst_domain: Domain,
dst_token: DstAddressType,
dst_recipient: DstAddressType,
amount: U256,
) -> Self {
Self {
src_domain,
src_token,
dst_domain,
dst_token,
dst_recipient,
amount,
expiry_height: None,
max_fee: None,
caller: None,
}
}
pub fn with_max_fee(mut self, max_fee: U256) -> Self {
self.max_fee = Some(max_fee);
self
}
pub fn with_expiry_height(mut self, expiry_height: u64) -> Self {
self.expiry_height = Some(expiry_height);
self
}
pub fn with_caller(mut self, caller: DstAddressType) -> Self {
self.caller = Some(caller);
self
}
pub(crate) fn build_fee_intent(&self) -> v1::SpendIntent
where
SrcAddressType: Clone,
DstAddressType: Clone,
{
let dst_caller = self
.caller
.clone()
.map_or_else(|| DstAddressType::default().into(), Into::into);
v1::SpendIntent {
spec: Some(v1::SpendSpec {
src_spender: Some(SrcAddressType::default().into()),
src_domain: self.src_domain,
src_token: Some(self.src_token.clone().into()),
src_deposit_contract: Some(SrcAddressType::default().into()),
dst_domain: self.dst_domain,
dst_token: Some(self.dst_token.clone().into()),
dst_delivery_contract: Some(DstAddressType::default().into()),
dst_recipient: Some(self.dst_recipient.clone().into()),
dst_caller: Some(dst_caller),
amount: Some(self.amount.into()),
salt: Some([0u8; 32].into()),
}),
max_fee: Some(U256::ZERO.into()),
expiry_height: self.expiry_height.unwrap_or(0),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::Address;
#[derive(Clone, Debug, PartialEq, Eq)]
struct SrcAddress(Address);
impl Default for SrcAddress {
fn default() -> Self {
Self(Address::from([0x11; 20]))
}
}
impl From<SrcAddress> for v1::MultiChainAddress {
fn from(value: SrcAddress) -> Self {
value.0.into()
}
}
impl TryFrom<v1::MultiChainAddress> for SrcAddress {
type Error = ProtoConversionError;
fn try_from(value: v1::MultiChainAddress) -> Result<Self, Self::Error> {
Address::try_from(value).map(Self)
}
}
impl ChainAddress for SrcAddress {}
#[derive(Clone, Debug, PartialEq, Eq)]
struct DstAddress(Address);
impl Default for DstAddress {
fn default() -> Self {
Self(Address::from([0x22; 20]))
}
}
impl From<DstAddress> for v1::MultiChainAddress {
fn from(value: DstAddress) -> Self {
value.0.into()
}
}
impl TryFrom<v1::MultiChainAddress> for DstAddress {
type Error = ProtoConversionError;
fn try_from(value: v1::MultiChainAddress) -> Result<Self, Self::Error> {
Address::try_from(value).map(Self)
}
}
impl ChainAddress for DstAddress {}
#[test]
fn chain_address_roundtrip_success() {
let address = Address::from([0x44; 20]);
let proto: v1::MultiChainAddress = address.into();
let decoded = Address::try_from(proto).unwrap();
assert_eq!(decoded, address);
}
#[test]
fn build_fee_intent_success_uses_source_default_for_src_spender() {
let request = SpendRequestBuilder::new(
1,
SrcAddress(Address::from([0x33; 20])),
2,
DstAddress(Address::from([0x44; 20])),
DstAddress(Address::from([0x55; 20])),
U256::from(100u64),
);
let intent = request.build_fee_intent();
let spec = intent.spec.expect("fee intent spec");
let src_spender = spec.src_spender.expect("src_spender");
assert_eq!(src_spender, SrcAddress::default().into());
}
}