Skip to content

Commit 1619033

Browse files
authored
Merge pull request darkforestry#199 from darkforestry/feat/balancer
feat(balancer): balancer integration tracking
2 parents d711856 + 8af746e commit 1619033

23 files changed

+1389
-118
lines changed

Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ alloy = { version = "0.2", features = [
3333
"signer-local",
3434
] }
3535

36+
rug = "1.24.1"
37+
3638
[features]
3739
default = ["state-space"]
3840
state-space = ["arraydeque"]

benches/uniswapv2_simulate.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use alloy::primitives::{address, U256};
1+
use alloy::primitives::{address, Address, U256};
22
use criterion::{criterion_group, criterion_main, Criterion};
33

44
use amms::amm::uniswap_v2::UniswapV2Pool;
@@ -28,7 +28,9 @@ fn criterion_benchmark(c: &mut Criterion) {
2828
pool.reserve_0 = random_ether(1.0, 1000.0);
2929
pool.reserve_1 = random_ether(1.0, 1000.0);
3030
let swap_amount = U256::from(random_ether(1.0, 10.0));
31-
let _ = pool.simulate_swap(token_a, swap_amount).unwrap();
31+
let _ = pool
32+
.simulate_swap(token_a, Address::default(), swap_amount)
33+
.unwrap();
3234
})
3335
});
3436
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
//SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
interface IBPool {
5+
function getCurrentTokens() external returns (address[] memory);
6+
function getDenormalizedWeight(address token) external returns (uint);
7+
function getSwapFee() external returns (uint);
8+
function getBalance(address token) external returns (uint);
9+
}
10+
11+
interface IERC20 {
12+
function decimals() external view returns (uint8);
13+
}
14+
15+
/**
16+
* @dev This contract is not meant to be deployed. Instead, use a static call with the
17+
* deployment bytecode as payload.
18+
*/
19+
contract GetBalancerV2PoolDataBatchRequest {
20+
struct PoolData {
21+
address[] tokens;
22+
uint8[] decimals;
23+
uint256[] liquidity;
24+
uint256[] weights;
25+
uint32 fee;
26+
}
27+
28+
constructor(address[] memory pools) {
29+
PoolData[] memory allPoolData = new PoolData[](pools.length);
30+
31+
for (uint256 i = 0; i < pools.length; ++i) {
32+
address poolAddress = pools[i];
33+
34+
if (codeSizeIsZero(poolAddress)) continue;
35+
36+
PoolData memory poolData;
37+
38+
// Get the tokens
39+
address[] memory tokens = IBPool(poolAddress).getCurrentTokens();
40+
uint8[] memory decimals = new uint8[](tokens.length);
41+
uint256[] memory liquidity = new uint256[](tokens.length);
42+
uint256[] memory weights = new uint256[](tokens.length);
43+
44+
for (uint256 j = 0; j < tokens.length; ++j) {
45+
if (codeSizeIsZero(tokens[j])) {
46+
continue;
47+
}
48+
}
49+
50+
// Grab the decimals/liquidity
51+
for (uint256 j = 0; j < tokens.length; ++j) {
52+
uint8 tokenDecimals = getTokenDecimals(tokens[j]);
53+
if (tokenDecimals == 0) {
54+
continue;
55+
} else {
56+
decimals[j] = tokenDecimals;
57+
}
58+
weights[j] = IBPool(poolAddress).getDenormalizedWeight(
59+
tokens[j]
60+
);
61+
liquidity[j] = IBPool(poolAddress).getBalance(tokens[j]);
62+
}
63+
64+
// Grab the swap fee
65+
poolData.fee = uint32(IBPool(poolAddress).getSwapFee());
66+
poolData.tokens = tokens;
67+
poolData.decimals = decimals;
68+
poolData.liquidity = liquidity;
69+
poolData.weights = weights;
70+
allPoolData[i] = poolData;
71+
}
72+
73+
bytes memory _abiEncodedData = abi.encode(allPoolData);
74+
assembly {
75+
// Return from the start of the data (discarding the original data address)
76+
// up to the end of the memory used
77+
let dataStart := add(_abiEncodedData, 0x20)
78+
return(dataStart, sub(msize(), dataStart))
79+
}
80+
}
81+
82+
function getTokenDecimals(address token) internal returns (uint8) {
83+
(bool success, bytes memory data) = token.call(
84+
abi.encodeWithSignature("decimals()")
85+
);
86+
87+
if (success) {
88+
uint256 decimals;
89+
if (data.length == 32) {
90+
(decimals) = abi.decode(data, (uint256));
91+
if (decimals == 0 || decimals > 255) {
92+
return 0;
93+
} else {
94+
return uint8(decimals);
95+
}
96+
} else {
97+
return 0;
98+
}
99+
} else {
100+
return 0;
101+
}
102+
}
103+
104+
function codeSizeIsZero(address target) internal view returns (bool) {
105+
if (target.code.length == 0) {
106+
return true;
107+
} else {
108+
return false;
109+
}
110+
}
111+
}

examples/filter-value.rs

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ async fn main() -> eyre::Result<()> {
4747

4848
// Filter out pools below usd threshold
4949
let weth_address = address!("0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270");
50+
let usdc = address!("3c499c542cEF5E3811e1192ce70d8cC03d5c3359");
5051
let usd_weth_pair_address = address!("cd353F79d9FADe311fC3119B841e1f456b54e858");
5152
let usd_weth_pool = AMM::UniswapV2Pool(
5253
UniswapV2Pool::new_from_address(usd_weth_pair_address, 300, provider.clone()).await?,
@@ -61,6 +62,7 @@ async fn main() -> eyre::Result<()> {
6162
usd_weth_pool,
6263
15000.00, //Setting usd_threshold to 15000 filters out any pool that contains less than $15000.00 USD value
6364
weth_address,
65+
usdc,
6466
weth_value_in_token_to_weth_pool_threshold,
6567
200,
6668
provider.clone(),

examples/simulate-swap.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::sync::Arc;
22

33
use alloy::{
4-
primitives::{address, U256},
4+
primitives::{address, Address, U256},
55
providers::ProviderBuilder,
66
};
77

@@ -20,7 +20,11 @@ async fn main() -> eyre::Result<()> {
2020

2121
// Simulate a swap
2222
let token_in = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
23-
let amount_out = pool.simulate_swap(token_in, U256::from(1000000000000000000_u128))?;
23+
let amount_out = pool.simulate_swap(
24+
token_in,
25+
Address::default(),
26+
U256::from(1000000000000000000_u128),
27+
)?;
2428

2529
println!("Amount out: {amount_out}");
2630

src/amm/balancer_v2/batch_request/GetBalancerV2PoolDataBatchRequest.json

+87
Large diffs are not rendered by default.
+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
use std::sync::Arc;
2+
3+
use alloy::{
4+
dyn_abi::{DynSolType, DynSolValue},
5+
network::Network,
6+
providers::Provider,
7+
sol,
8+
transports::Transport,
9+
};
10+
11+
use crate::{
12+
amm::{AutomatedMarketMaker, AMM},
13+
errors::AMMError,
14+
};
15+
16+
use super::BalancerV2Pool;
17+
18+
sol! {
19+
#[allow(missing_docs)]
20+
#[sol(rpc)]
21+
IGetBalancerV2PoolDataBatchRequest,
22+
"src/amm/balancer_v2/batch_request/GetBalancerV2PoolDataBatchRequest.json"
23+
}
24+
25+
#[inline]
26+
fn populate_pool_data_from_tokens(pool: &mut BalancerV2Pool, tokens: &[DynSolValue]) {
27+
// TODO: Add error handling
28+
pool.tokens = tokens[0]
29+
.as_array()
30+
.expect("Expected array")
31+
.iter()
32+
.map(|t| t.as_address().expect("Expected address"))
33+
.collect();
34+
pool.decimals = tokens[1]
35+
.as_array()
36+
.expect("Expected array")
37+
.iter()
38+
.map(|t| t.as_uint().expect("Expected uint").0.to::<u8>())
39+
.collect();
40+
pool.liquidity = tokens[2]
41+
.as_array()
42+
.expect("Expected array")
43+
.iter()
44+
.map(|t| t.as_uint().expect("Expected uint").0)
45+
.collect();
46+
pool.weights = tokens[3]
47+
.as_array()
48+
.expect("Expected array")
49+
.iter()
50+
.map(|t| t.as_uint().expect("Expected uint").0)
51+
.collect();
52+
pool.fee = tokens[4].as_uint().expect("Expected uint").0.to::<u32>();
53+
}
54+
55+
pub async fn get_balancer_v2_pool_data_batch_request<T, N, P>(
56+
pool: &mut BalancerV2Pool,
57+
block_number: Option<u64>,
58+
provider: Arc<P>,
59+
) -> Result<(), AMMError>
60+
where
61+
T: Transport + Clone,
62+
N: Network,
63+
P: Provider<T, N>,
64+
{
65+
let deployer = IGetBalancerV2PoolDataBatchRequest::deploy_builder(provider, vec![pool.address]);
66+
let res = if let Some(block_number) = block_number {
67+
deployer.block(block_number.into()).call_raw().await?
68+
} else {
69+
deployer.call_raw().await?
70+
};
71+
72+
let constructor_return = DynSolType::Array(Box::new(DynSolType::Tuple(vec![
73+
DynSolType::Array(Box::new(DynSolType::Address)),
74+
DynSolType::Array(Box::new(DynSolType::Uint(8))),
75+
DynSolType::Array(Box::new(DynSolType::Uint(256))),
76+
DynSolType::Array(Box::new(DynSolType::Uint(256))),
77+
DynSolType::Uint(32),
78+
])));
79+
80+
let return_data_tokens = constructor_return.abi_decode_sequence(&res)?;
81+
82+
if let Some(tokens_arr) = return_data_tokens.as_array() {
83+
for token in tokens_arr {
84+
let pool_data = token
85+
.as_tuple()
86+
.ok_or(AMMError::BatchRequestError(pool.address))?;
87+
88+
populate_pool_data_from_tokens(pool, pool_data);
89+
}
90+
}
91+
92+
Ok(())
93+
}
94+
95+
pub async fn get_amm_data_batch_request<T, N, P>(
96+
amms: &mut [AMM],
97+
provider: Arc<P>,
98+
) -> Result<(), AMMError>
99+
where
100+
T: Transport + Clone,
101+
N: Network,
102+
P: Provider<T, N>,
103+
{
104+
let deployer = IGetBalancerV2PoolDataBatchRequest::deploy_builder(
105+
provider,
106+
amms.iter().map(|amm| amm.address()).collect(),
107+
);
108+
let res = deployer.call_raw().await?;
109+
110+
let constructor_return = DynSolType::Array(Box::new(DynSolType::Tuple(vec![
111+
DynSolType::Array(Box::new(DynSolType::Address)),
112+
DynSolType::Array(Box::new(DynSolType::Uint(8))),
113+
DynSolType::Array(Box::new(DynSolType::Uint(256))),
114+
DynSolType::Array(Box::new(DynSolType::Uint(256))),
115+
DynSolType::Uint(32),
116+
])));
117+
118+
let return_data_tokens = constructor_return.abi_decode_sequence(&res)?;
119+
120+
if let Some(tokens_arr) = return_data_tokens.as_array() {
121+
for (i, token) in tokens_arr.iter().enumerate() {
122+
let pool_data = token
123+
.as_tuple()
124+
.ok_or(AMMError::BatchRequestError(amms[i].address()))?;
125+
if let AMM::BalancerV2Pool(pool) = &mut amms[i] {
126+
populate_pool_data_from_tokens(pool, pool_data);
127+
}
128+
}
129+
}
130+
Ok(())
131+
}

0 commit comments

Comments
 (0)