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:
88
Cargo.lock
generated
88
Cargo.lock
generated
@@ -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",
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -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()
|
|
||||||
.then(|| tokio::runtime::Runtime::new().unwrap())
|
|
||||||
.unwrap();
|
|
||||||
let client = runtime.block_on(async {
|
|
||||||
ProviderBuilder::new()
|
|
||||||
.on_builtin(ð_rpc_url)
|
.on_builtin(ð_rpc_url)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.expect("Failed to build provider");
|
||||||
});
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 = (
|
||||||
|
|||||||
Reference in New Issue
Block a user