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

88
Cargo.lock generated
View File

@@ -30,6 +30,15 @@ dependencies = [
"zerocopy", "zerocopy",
] ]
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "allocator-api2" name = "allocator-api2"
version = "0.2.21" version = "0.2.21"
@@ -49,6 +58,7 @@ dependencies = [
"alloy-network", "alloy-network",
"alloy-provider", "alloy-provider",
"alloy-rpc-client", "alloy-rpc-client",
"alloy-rpc-types",
"alloy-serde", "alloy-serde",
"alloy-transport", "alloy-transport",
"alloy-transport-http", "alloy-transport-http",
@@ -332,6 +342,18 @@ dependencies = [
"wasmtimer", "wasmtimer",
] ]
[[package]]
name = "alloy-rpc-types"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eea9bf1abdd506f985a53533f5ac01296bcd6102c5e139bbc5d40bc468d2c916"
dependencies = [
"alloy-primitives",
"alloy-rpc-types-eth",
"alloy-serde",
"serde",
]
[[package]] [[package]]
name = "alloy-rpc-types-eth" name = "alloy-rpc-types-eth"
version = "0.5.4" version = "0.5.4"
@@ -1247,6 +1269,12 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-timer"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.31" version = "0.3.31"
@@ -2260,12 +2288,41 @@ dependencies = [
"bitflags", "bitflags",
] ]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.8.5" version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "relative-path"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.12.12" version = "0.12.12"
@@ -2326,6 +2383,36 @@ dependencies = [
"rustc-hex", "rustc-hex",
] ]
[[package]]
name = "rstest"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03e905296805ab93e13c1ec3a03f4b6c4f35e9498a3d5fa96dc626d22c03cd89"
dependencies = [
"futures-timer",
"futures-util",
"rstest_macros",
"rustc_version 0.4.1",
]
[[package]]
name = "rstest_macros"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef0053bbffce09062bee4bcc499b0fbe7a57b879f1efe088d6d8d4c7adcdef9b"
dependencies = [
"cfg-if",
"glob",
"proc-macro-crate",
"proc-macro2",
"quote",
"regex",
"relative-path",
"rustc_version 0.4.1",
"syn 2.0.96",
"unicode-ident",
]
[[package]] [[package]]
name = "ruint" name = "ruint"
version = "1.12.4" version = "1.12.4"
@@ -3049,6 +3136,7 @@ dependencies = [
"lazy_static", "lazy_static",
"num-bigint", "num-bigint",
"num-traits", "num-traits",
"rstest",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 1.0.69", "thiserror 1.0.69",

View File

@@ -14,11 +14,14 @@ serde_json = "1.0.135"
thiserror = "1.0.69" thiserror = "1.0.69"
tokio = { version = "1.38.0", features = ["full"] } tokio = { version = "1.38.0", features = ["full"] }
alloy = { version = "0.5.4", features = ["providers"], optional = true } alloy = { version = "0.5.4", features = ["providers", "rpc-types-eth"], optional = true }
alloy-sol-types = { version = "0.8.14", optional = true } alloy-sol-types = { version = "0.8.14", optional = true }
alloy-primitives = { version = "0.8.9", optional = true } alloy-primitives = { version = "0.8.9", optional = true }
tycho-core = { git = "https://github.com/propeller-heads/tycho-indexer.git", package = "tycho-core", tag = "0.46.0" } tycho-core = { git = "https://github.com/propeller-heads/tycho-indexer.git", package = "tycho-core", tag = "0.46.0" }
[dev-dependencies]
rstest = "0.24.0"
[features] [features]
default = ["evm"] default = ["evm"]
evm = ["alloy", "alloy-sol-types", "alloy-primitives"] evm = ["alloy", "alloy-sol-types", "alloy-primitives"]

View File

@@ -1,49 +1,98 @@
use std::{env, sync::Arc}; use std::{env, sync::Arc};
use alloy::{ use alloy::{
providers::{ProviderBuilder, RootProvider}, providers::{Provider, ProviderBuilder, RootProvider},
rpc::types::{TransactionInput, TransactionRequest},
transports::BoxTransport, transports::BoxTransport,
}; };
use alloy_primitives::Address; use alloy_primitives::{Address, Bytes, TxKind, U256};
use alloy_sol_types::SolValue;
use dotenv::dotenv; use dotenv::dotenv;
use tokio::runtime::Runtime;
use crate::encoding::{errors::EncodingError, evm::utils::encode_input};
#[allow(dead_code)] #[allow(dead_code)]
pub struct ProtocolApprovalsManager { pub struct ProtocolApprovalsManager {
client: Arc<RootProvider<BoxTransport>>, client: Arc<RootProvider<BoxTransport>>,
runtime: Runtime,
} }
impl ProtocolApprovalsManager { impl ProtocolApprovalsManager {
pub fn new() -> Self { 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, &self,
_token: Address, token: Address,
_spender_address: Address, owner_address: Address,
_router_address: Address, spender_address: Address,
) -> bool { ) -> Result<bool, EncodingError> {
todo!() let args = (owner_address, spender_address);
// should be something like let data = encode_input("allowance(address,address)", args.abi_encode());
// let allowance = self let tx = TransactionRequest {
// .client to: Some(TxKind::from(token)),
// .call(token, "allowance(address,address)(uint256)", (router_address, input: TransactionInput { input: Some(Bytes::from(data)), data: None },
// spender_address)) .await; ..Default::default()
// };
// allowance == U256::ZERO // If allowance is 0, approval is needed
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(); dotenv().ok();
let eth_rpc_url = env::var("ETH_RPC_URL").expect("Missing ETH_RPC_URL in environment"); let eth_rpc_url = env::var("ETH_RPC_URL").expect("Missing ETH_RPC_URL in environment");
let runtime = tokio::runtime::Handle::try_current() let client = ProviderBuilder::new()
.is_err() .on_builtin(&eth_rpc_url)
.then(|| tokio::runtime::Runtime::new().unwrap()) .await
.unwrap(); .expect("Failed to build provider");
let client = runtime.block_on(async {
ProviderBuilder::new()
.on_builtin(&eth_rpc_url)
.await
.unwrap()
});
Arc::new(client) 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, encoding_context: EncodingContext,
) -> Result<Vec<u8>, EncodingError> { ) -> Result<Vec<u8>, EncodingError> {
let token_approvals_manager = ProtocolApprovalsManager::new(); 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 token = bytes_to_address(&swap.token_in)?;
let router_address = bytes_to_address(&encoding_context.address_for_approvals)?; let router_address = bytes_to_address(&encoding_context.address_for_approvals)?;
let approval_needed = runtime.block_on(async { let approval_needed =
token_approvals_manager token_approvals_manager.approval_needed(token, router_address, self.vault_address)?;
.approval_needed(token, self.vault_address, router_address)
.await
});
// should we return gas estimation here too?? if there is an approval needed, gas will be // should we return gas estimation here too?? if there is an approval needed, gas will be
// higher. // higher.
let args = ( let args = (