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    // Store the runtime to prevent it from being dropped before use.
27    // This is required since tycho-execution does not have a pre-existing runtime.
28    // However, if the library is used in a context where a runtime already exists, it is not
29    // necessary to store it.
30    #[allow(dead_code)]
31    runtime: Option<Arc<Runtime>>,
32}
33impl ProtocolApprovalsManager {
34    pub fn new() -> Result<Self, EncodingError> {
35        let (handle, runtime) = get_runtime()?;
36        let client = block_in_place(|| handle.block_on(get_client()))?;
37        Ok(Self { client, runtime_handle: handle, runtime })
38    }
39
40    /// Checks the current allowance for the given token, owner, and spender, and returns true
41    /// if the current allowance is zero.
42    pub fn approval_needed(
43        &self,
44        token: Address,
45        owner_address: Address,
46        spender_address: Address,
47    ) -> Result<bool, EncodingError> {
48        let args = (owner_address, spender_address);
49        let data = encode_input("allowance(address,address)", args.abi_encode());
50        let tx = TransactionRequest {
51            to: Some(TxKind::from(token)),
52            input: TransactionInput { input: Some(Bytes::from(data)), data: None },
53            ..Default::default()
54        };
55
56        let output = block_in_place(|| {
57            self.runtime_handle
58                .block_on(async { self.client.call(tx).await })
59        });
60        match output {
61            Ok(response) => {
62                let allowance: U256 = U256::abi_decode(&response).map_err(|_| {
63                    EncodingError::FatalError("Failed to decode response for allowance".to_string())
64                })?;
65
66                Ok(allowance.is_zero())
67            }
68            Err(err) => Err(EncodingError::RecoverableError(format!(
69                "Allowance call failed with error: {err}"
70            ))),
71        }
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use std::str::FromStr;
78
79    use rstest::rstest;
80
81    use super::*;
82    #[rstest]
83    #[case::approval_not_needed(
84        "0xba12222222228d8ba445958a75a0704d566bf2c8",
85        "0x2c6a3cd97c6283b95ac8c5a4459ebb0d5fd404f4",
86        false
87    )]
88    #[case::approval_needed(
89        "0x2c6a3cd97c6283b95ac8c5a4459ebb0d5fd404f4",
90        "0xba12222222228d8ba445958a75a0704d566bf2c8",
91        true
92    )]
93    fn test_approval_needed(#[case] spender: &str, #[case] owner: &str, #[case] expected: bool) {
94        let manager = ProtocolApprovalsManager::new().unwrap();
95
96        let token = Address::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
97        let spender = Address::from_str(spender).unwrap();
98        let owner = Address::from_str(owner).unwrap();
99
100        let result = manager
101            .approval_needed(token, owner, spender)
102            .unwrap();
103        assert_eq!(result, expected);
104    }
105}