tycho_execution/encoding/evm/approvals/
protocol_approvals_manager.rs

1use std::sync::Arc;
2
3use alloy::{
4    primitives::{Address, Bytes, TxKind, U256},
5    providers::Provider,
6    rpc::types::{TransactionInput, TransactionRequest},
7    sol_types::SolValue,
8};
9use tokio::{
10    runtime::{Handle, Runtime},
11    task::block_in_place,
12};
13
14use crate::encoding::{
15    errors::EncodingError,
16    evm::{
17        encoding_utils::encode_input,
18        utils::{get_client, get_runtime, EVMProvider},
19    },
20};
21
22/// A manager for checking if an approval is needed for interacting with a certain spender.
23pub struct ProtocolApprovalsManager {
24    client: EVMProvider,
25    runtime_handle: Handle,
26    #[allow(dead_code)]
27    runtime: Option<Arc<Runtime>>,
28}
29impl ProtocolApprovalsManager {
30    pub fn new() -> Result<Self, EncodingError> {
31        let (handle, runtime) = get_runtime()?;
32        let client = block_in_place(|| handle.block_on(get_client()))?;
33        Ok(Self { client, runtime_handle: handle, runtime })
34    }
35
36    /// Checks the current allowance for the given token, owner, and spender, and returns true
37    /// if the current allowance is zero.
38    pub fn approval_needed(
39        &self,
40        token: Address,
41        owner_address: Address,
42        spender_address: Address,
43    ) -> Result<bool, EncodingError> {
44        let args = (owner_address, spender_address);
45        let data = encode_input("allowance(address,address)", args.abi_encode());
46        let tx = TransactionRequest {
47            to: Some(TxKind::from(token)),
48            input: TransactionInput { input: Some(Bytes::from(data)), data: None },
49            ..Default::default()
50        };
51
52        let output = block_in_place(|| {
53            self.runtime_handle
54                .block_on(async { self.client.call(tx).await })
55        });
56        match output {
57            Ok(response) => {
58                let allowance: U256 = U256::abi_decode(&response).map_err(|_| {
59                    EncodingError::FatalError("Failed to decode response for allowance".to_string())
60                })?;
61
62                Ok(allowance.is_zero())
63            }
64            Err(err) => Err(EncodingError::RecoverableError(format!(
65                "Allowance call failed with error: {err}"
66            ))),
67        }
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use std::str::FromStr;
74
75    use rstest::rstest;
76
77    use super::*;
78    #[rstest]
79    #[case::approval_not_needed(
80        "0xba12222222228d8ba445958a75a0704d566bf2c8",
81        "0x2c6a3cd97c6283b95ac8c5a4459ebb0d5fd404f4",
82        false
83    )]
84    #[case::approval_needed(
85        "0x2c6a3cd97c6283b95ac8c5a4459ebb0d5fd404f4",
86        "0xba12222222228d8ba445958a75a0704d566bf2c8",
87        true
88    )]
89    fn test_approval_needed(#[case] spender: &str, #[case] owner: &str, #[case] expected: bool) {
90        let manager = ProtocolApprovalsManager::new().unwrap();
91
92        let token = Address::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
93        let spender = Address::from_str(spender).unwrap();
94        let owner = Address::from_str(owner).unwrap();
95
96        let result = manager
97            .approval_needed(token, owner, spender)
98            .unwrap();
99        assert_eq!(result, expected);
100    }
101}