use crate::contracts::erc20::Erc20Contract;
use crate::error::Result;
use alloy_network::Ethereum;
use alloy_primitives::{Address, U256};
use alloy_provider::Provider;
#[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, balance) =
tokio::join!(erc20.allowance(owner, spender), erc20.balance_of(owner));
Ok(TokenState {
balance: balance?,
allowance: 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)));
}
}