Merge pull request #12 from propeller-heads/encoding/dc/ENG-4064-protocol-approvals

feat: Implement ProtocolApprovalsManager
This commit is contained in:
dianacarvalho1
2025-01-20 17:22:26 +00:00
committed by GitHub
4 changed files with 171 additions and 37 deletions

88
Cargo.lock generated
View File

@@ -30,6 +30,15 @@ dependencies = [
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
@@ -49,6 +58,7 @@ dependencies = [
"alloy-network",
"alloy-provider",
"alloy-rpc-client",
"alloy-rpc-types",
"alloy-serde",
"alloy-transport",
"alloy-transport-http",
@@ -332,6 +342,18 @@ dependencies = [
"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]]
name = "alloy-rpc-types-eth"
version = "0.5.4"
@@ -1247,6 +1269,12 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-timer"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
[[package]]
name = "futures-util"
version = "0.3.31"
@@ -2260,12 +2288,41 @@ dependencies = [
"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]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "relative-path"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
[[package]]
name = "reqwest"
version = "0.12.12"
@@ -2326,6 +2383,36 @@ dependencies = [
"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]]
name = "ruint"
version = "1.12.4"
@@ -3049,6 +3136,7 @@ dependencies = [
"lazy_static",
"num-bigint",
"num-traits",
"rstest",
"serde",
"serde_json",
"thiserror 1.0.69",

View File

@@ -14,11 +14,14 @@ serde_json = "1.0.135"
thiserror = "1.0.69"
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-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" }
[dev-dependencies]
rstest = "0.24.0"
[features]
default = ["evm"]
evm = ["alloy", "alloy-sol-types", "alloy-primitives"]

View File

@@ -1,49 +1,99 @@
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!(
"Allowance 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 = (