feat: Implement ProtocolApprovalsManager

I decided to make ProtocolApprovalsManager public method sync not to propagate asynceness early on onto the rest of the code. If later we decide that it is better to have this async, it should be easier to make that change than the opposite one.

--- don't change below this line ---
ENG-4064 Took 24 minutes
This commit is contained in:
Diana Carvalho
2025-01-20 13:12:31 +00:00
parent 8a709b3033
commit cbf2b4de5a
4 changed files with 170 additions and 37 deletions

View File

@@ -1,49 +1,98 @@
use std::{env, sync::Arc};
use alloy::{
providers::{ProviderBuilder, RootProvider},
providers::{Provider, ProviderBuilder, RootProvider},
rpc::types::{TransactionInput, TransactionRequest},
transports::BoxTransport,
};
use alloy_primitives::Address;
use alloy_primitives::{Address, Bytes, TxKind, U256};
use alloy_sol_types::SolValue;
use dotenv::dotenv;
use tokio::runtime::Runtime;
use crate::encoding::{errors::EncodingError, evm::utils::encode_input};
#[allow(dead_code)]
pub struct ProtocolApprovalsManager {
client: Arc<RootProvider<BoxTransport>>,
runtime: Runtime,
}
impl ProtocolApprovalsManager {
pub fn new() -> Self {
Self { client: get_client() }
let runtime = Runtime::new().expect("Failed to create runtime");
let client = runtime.block_on(get_client());
Self { client, runtime }
}
pub async fn approval_needed(
pub fn approval_needed(
&self,
_token: Address,
_spender_address: Address,
_router_address: Address,
) -> bool {
todo!()
// should be something like
// let allowance = self
// .client
// .call(token, "allowance(address,address)(uint256)", (router_address,
// spender_address)) .await;
//
// allowance == U256::ZERO // If allowance is 0, approval is needed
token: Address,
owner_address: Address,
spender_address: Address,
) -> Result<bool, EncodingError> {
let args = (owner_address, spender_address);
let data = encode_input("allowance(address,address)", args.abi_encode());
let tx = TransactionRequest {
to: Some(TxKind::from(token)),
input: TransactionInput { input: Some(Bytes::from(data)), data: None },
..Default::default()
};
let output = self
.runtime
.block_on(async { self.client.call(&tx).await });
match output {
Ok(response) => {
let allowance: U256 = U256::abi_decode(&response, true).map_err(|_| {
EncodingError::FatalError("Failed to decode response for allowance".to_string())
})?;
Ok(allowance.is_zero())
}
Err(err) => {
Err(EncodingError::RecoverableError(format!("Call failed with error: {:?}", err)))
}
}
}
}
pub fn get_client() -> Arc<RootProvider<BoxTransport>> {
pub async fn get_client() -> Arc<RootProvider<BoxTransport>> {
dotenv().ok();
let eth_rpc_url = env::var("ETH_RPC_URL").expect("Missing ETH_RPC_URL in environment");
let runtime = tokio::runtime::Handle::try_current()
.is_err()
.then(|| tokio::runtime::Runtime::new().unwrap())
.unwrap();
let client = runtime.block_on(async {
ProviderBuilder::new()
.on_builtin(&eth_rpc_url)
.await
.unwrap()
});
let client = ProviderBuilder::new()
.on_builtin(&eth_rpc_url)
.await
.expect("Failed to build provider");
Arc::new(client)
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use rstest::rstest;
use super::*;
#[rstest]
#[case::approval_not_needed(
"0xba12222222228d8ba445958a75a0704d566bf2c8",
"0x2c6a3cd97c6283b95ac8c5a4459ebb0d5fd404f4",
false
)]
#[case::approval_needed(
"0x2c6a3cd97c6283b95ac8c5a4459ebb0d5fd404f4",
"0xba12222222228d8ba445958a75a0704d566bf2c8",
true
)]
fn test_approval_needed(#[case] spender: &str, #[case] owner: &str, #[case] expected: bool) {
let manager = ProtocolApprovalsManager::new();
let token = Address::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let spender = Address::from_str(spender).unwrap();
let owner = Address::from_str(owner).unwrap();
let result = manager
.approval_needed(token, owner, spender)
.unwrap();
assert_eq!(result, expected);
}
}

View File

@@ -53,17 +53,10 @@ impl SwapEncoder for BalancerV2SwapEncoder {
encoding_context: EncodingContext,
) -> Result<Vec<u8>, EncodingError> {
let token_approvals_manager = ProtocolApprovalsManager::new();
let runtime = tokio::runtime::Handle::try_current()
.is_err()
.then(|| tokio::runtime::Runtime::new().unwrap())
.unwrap();
let token = bytes_to_address(&swap.token_in)?;
let router_address = bytes_to_address(&encoding_context.address_for_approvals)?;
let approval_needed = runtime.block_on(async {
token_approvals_manager
.approval_needed(token, self.vault_address, router_address)
.await
});
let approval_needed =
token_approvals_manager.approval_needed(token, router_address, self.vault_address)?;
// should we return gas estimation here too?? if there is an approval needed, gas will be
// higher.
let args = (