Skip to main content

tycho_execution/encoding/evm/approvals/
protocol_approvals_manager.rs

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