use crate::contracts::erc20::Erc20Contract;
use crate::error::{CctpError, Result};
use alloy_network::Ethereum;
use alloy_primitives::{Address, U256};
use alloy_provider::Provider;
#[deprecated(since = "3.3.0", note = "use `batch_token_state` instead")]
pub async fn batch_token_checks<P>(
provider: &P,
token: Address,
owner: Address,
spender: Address,
) -> Result<(U256, U256)>
where
P: Provider<Ethereum> + Clone,
{
let state = batch_token_state(provider, token, owner, spender).await?;
Ok((state.allowance, state.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 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}")))?;
Ok(TokenState { balance, allowance })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_token_state_predicates() {
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)));
assert!(!state.needs_approval(U256::from(500)));
assert!(state.needs_approval(U256::from(501)));
assert!(state.has_sufficient_balance(U256::from(1000)));
assert!(!state.has_sufficient_balance(U256::from(1001)));
let no_allowance = TokenState {
balance: U256::from(1000),
allowance: U256::ZERO,
};
assert!(!no_allowance.can_transfer(U256::from(1)));
assert!(no_allowance.needs_approval(U256::from(1)));
assert!(no_allowance.has_sufficient_balance(U256::from(1000)));
}
}