use crate::contracts::erc20::Erc20Contract;
use crate::error::{CctpError, Result};
use alloy_network::Ethereum;
use alloy_primitives::{Address, U256};
use alloy_provider::Provider;
use tracing::{debug, info};
pub async fn batch_token_checks<P>(
provider: &P,
token: Address,
owner: Address,
spender: Address,
) -> Result<(U256, U256)>
where
P: Provider<Ethereum> + Clone,
{
debug!(
token = %token,
owner = %owner,
spender = %spender,
event = "batch_token_checks_started"
);
let erc20 = Erc20Contract::new(token, provider.clone());
let (allowance_result, balance_result) =
tokio::join!(erc20.allowance(owner, spender), erc20.balance_of(owner));
let allowance = allowance_result
.map_err(|e| CctpError::ContractCall(format!("Failed to get allowance: {e}")))?;
let balance = balance_result
.map_err(|e| CctpError::ContractCall(format!("Failed to get balance: {e}")))?;
info!(
token = %token,
owner = %owner,
spender = %spender,
allowance = %allowance,
balance = %balance,
event = "batch_token_checks_completed"
);
Ok((allowance, balance))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TokenState {
pub balance: U256,
pub allowance: U256,
}
impl TokenState {
pub fn can_transfer(&self, amount: U256) -> bool {
self.balance >= amount && self.allowance >= amount
}
pub fn needs_approval(&self, amount: U256) -> bool {
self.allowance < amount
}
pub fn has_sufficient_balance(&self, amount: U256) -> bool {
self.balance >= amount
}
}
pub async fn batch_token_state<P>(
provider: &P,
token: Address,
owner: Address,
spender: Address,
) -> Result<TokenState>
where
P: Provider<Ethereum> + Clone,
{
let (allowance, balance) = batch_token_checks(provider, token, owner, spender).await?;
Ok(TokenState { balance, allowance })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_token_state_can_transfer() {
let state = TokenState {
balance: U256::from(1000),
allowance: U256::from(500),
};
assert!(state.can_transfer(U256::from(500)));
assert!(state.can_transfer(U256::from(100)));
assert!(!state.can_transfer(U256::from(501))); assert!(!state.can_transfer(U256::from(1001))); }
#[test]
fn test_token_state_needs_approval() {
let state = TokenState {
balance: U256::from(1000),
allowance: U256::from(500),
};
assert!(!state.needs_approval(U256::from(500)));
assert!(!state.needs_approval(U256::from(100)));
assert!(state.needs_approval(U256::from(501)));
assert!(state.needs_approval(U256::from(1000)));
}
#[test]
fn test_token_state_has_sufficient_balance() {
let state = TokenState {
balance: U256::from(1000),
allowance: U256::from(500),
};
assert!(state.has_sufficient_balance(U256::from(1000)));
assert!(state.has_sufficient_balance(U256::from(100)));
assert!(!state.has_sufficient_balance(U256::from(1001)));
}
#[test]
fn test_token_state_zero_allowance() {
let state = TokenState {
balance: U256::from(1000),
allowance: U256::ZERO,
};
assert!(!state.can_transfer(U256::from(1)));
assert!(state.needs_approval(U256::from(1)));
assert!(state.has_sufficient_balance(U256::from(1000)));
}
}