0xkitsune / cfmms-rs Goto Github PK
View Code? Open in Web Editor NEWThe predecessor to `amms-rs`. CFMM lib built in Rust enabling pair syncing and swap simulation with pools on Ethereum.
The predecessor to `amms-rs`. CFMM lib built in Rust enabling pair syncing and swap simulation with pools on Ethereum.
Hey Guys
Great work on cfmm-rs
and uniswapv3_math-rs
!!
Just had a question... I noticed in uniswap_v3.rs
you are defining the swap event signature and writing code to decode swap logs, can't you instead just generate this code using abigen with ethers-rs
?
Something like:
let event = UniswapV3PoolEvents::decode_log(&RawLog {
topics: log.topics,
data: log.data.to_vec(),
});
Thanks
Q
Not really an issue but just wondering why the pool definitions use u128 ints for the reserves as reserves on chain are u256?
Using step_by()
when iterating over a block range will potentially cause blocks to be skipped. We need to update this to another approach.
On sync UniswapV2, when solidity batcher contract tries to batch pool 0xc0ca776fe52ec92b1d5603caadf148dbd8c22a80
, one of the tokens doesn't have decimals()
method. Because of that, raw call of contract deployer results with Error: (code: -32000, message: contract creation code storage out of gas, data: None)
.
I tries:
gas_limit = u64::MAX
and extended memory_limit
decimals()
). Maximum - 18 normal pools and 1 pool with token which doesn't have a decimals method. Also, its fine to call contract with a single [pool_address]
where pool_address
will be 0xc0ca776fe52ec92b1d5603caadf148dbd8c22a80
. If we have [pool_address, usdc_weth_pool_address]
there is also out of gas error.In Remix it seems fine to deploy, but there is kinda other simulation.
Currently calculate_price
casts reserve_0
and reserve_1
to f64. Change this to BigFloat for now.
In the future, we should add a function that returns the price as a Fixed point number and only uses U256 for the price calculation.
Implement constants for known DEXs like Uniswap V2/V3, Sushiswap, etc for all chains.
Some functions that call the node (ex. Syncing, getting pool data, etc) can use a multicall to complete much faster. We should implement this where applicable. There should be an option to use the function with and without multicall, probably by passing in a optional Chain
Enum (ex. Chain::Ethereum, Chain::Polygon, etc). If the value is None, it wont use the multicall. The mulicall address should be a constant value for each chain.
Addresses #51
BigFloat
is currently used to calculate virtual reserves in Univ3 pools as well as pool filtering. Strip out all places where BigFloat
is used.
Add a function to simulate an amount out from a route, as well as a function to simulate an amount out from a route while updating the values of the pool.
simulate_swap
and simulate_swap_mut
both require two external calls to the node to retrieve the Tick.Info
and the word corresponding to tickBitmap[currentTick]
during simulation. Two separate calls are made each time an initialized tick in crossed.
This can be optimized by batching the two calls into a single call to the node as both calls can happen in parallel. I think this will be most easily accomplished by abstracting the calls to the node outside of uniswap_v3_math
into cfmms-rs
.
Right now, when constructing a checkpoint, we are saving the Dex fee as a String, but it needs to be a number. This can be fixed very simply but just switching the existing code:
dex_map.insert(
String::from("fee"),
format!("{:?}", uniswap_v2_dex.fee).into(),
);
to this:
dex_map.insert(
String::from("fee"),
uniswap_v2_dex.fee.into(),
);
Implement a function within the dex
mod that gets all of the pools containing a target token address.
pub fn get_all_pools_containing_token(target_token: H160, dexes: Vec<Dex>, provider: Arc<Provider<P>>, ) -> Result<Vec<Pool>, CFFMError<P>>{
for dex in dexes{
//Get all pools starting from the dex creation block,
//Filter out pools that do not have the target token
//For all pools containing the target token, sync the pools
}
// --snip--
}
Implement swap functions for UniswapV2Pool
and UniswapV3Pool
that takes in swap arguments, a wallet and a provider. These functions should create a transaction, sign and send it to the network, returning a Result
with the Ok
variant containing the pending transaction hash.
Additionally, there should be a some_swap_function_calldata()
function for each swap function that generates the calldata for the transaction, but doesn't actually sign and send the transaction to the network.
A contant value should be used anywhere there is a U256 value that never changes.
For example:
//Set sqrt_price_limit_x_96 to the max or min sqrt price in the pool depending on zero_for_one
let sqrt_price_limit_x_96 = if zero_for_one {
U256::from("4295128739") + 1
} else {
U256::from("0xFFFD8963EFD1FC6A506488495D951D5263988D26") - 1
};
All of these values should be changed to match this format:
pub const MIN_SQRT_RATIO: U256 = U256([285968860985, 0, 0, 0]);
pub const MAX_SQRT_RATIO: U256 = U256([
9809463991923573570,
227557619515130776,
5049738529920590081,
1,
]);
Ideally all logs should not be manually parsed but should be handled with EventFilters generated through abigen. Currently new_from_event_log
is a function used for parsing, which does not handle EventFilters generated through an abigen.
UniswapV2 variants should have an allPairs
function that returns all pairs for the DEX.
I think adding liquidity provision helpers on the pools could be very appealing to liquidity providers monitoring and moving around large amounts of liquidity across CFMMs. This would allow liquidity providers to seamlessly automate their liquidity provision, and analytically optimize the profitability of their positions.
For Starters:
Mint Liquidity
function mint(
address recipient,
int24 tickLower,
int24 tickUpper,
uint128 amount,
bytes calldata data
) external returns (uint256 amount0, uint256 amount1);
Collect Fees
function collect(
address recipient,
int24 tickLower,
int24 tickUpper,
uint128 amount0Requested,
uint128 amount1Requested
) external returns (uint128 amount0, uint128 amount1);
Remove Liquidity from a position
function burn(
int24 tickLower,
int24 tickUpper,
uint128 amount
) external returns (uint256 amount0, uint256 amount1);
Ranged Liquidity or Time Weighted Average price
function observe(uint32[] calldata secondsAgos)
external
view
returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s);
Seconds per Liquidity & Seconds inside a tick range
function snapshotCumulativesInside(int24 tickLower, int24 tickUpper)
external
view
returns (
int56 tickCumulativeInside,
uint160 secondsPerLiquidityInsideX128,
uint32 secondsInside
);
The above function would also be helpful for calculating optimal JIT positions and amounts.
Write tests for the dex
mod.
Right now, the Pool
enum uses match statements in every function to determine which pool variant is being used.
Explore changing the design to utilize traits.
Right now, swap simulation uses a .3% fee, where not all Dexs that are a uniswap v2 variant have the same fee. Update swap logic so that the correct fee is used. Also, update Pool
creation as well as Dex
creation so that the fee can be dynamic (right now it is fixed to the .3% fee for these variants).
Update the functions to take middleware instead of a provider. This allows for greater flexibility like using the NonceManagerMiddleware
, FlashbotsMiddleware
or some custom Middlewear.
Hey.
Thanks for the amazing effort on this library!
When doing some comparisons to Swap
events outputted for an arbitrary pair (for this example I am using 500 fee tier WETH/USDC on Optimism), I notice a very small margin of error from the output of the simulation vs what the swap log shows.
The negative amount 0 representing the output of the swap from logs.
'Amount out from internal swap' representing the result of the simulated swap.
I thought it could be due to a difference in pool state after the swap log, as the batch tick data call would hit the chain AFTER the swap log. However, I switched to a completely pool 'state' which is initialised with tick data in either direction and altered the simulation to use that data, there is still a small error. See DIFF
. Is this due to just precision difference between EVM and Rust?
Am able to get 0 DIFF
when the output is USDC sometimes...
2023-06-06T08:19:50.046Z INFO [verdant] Found a swap event 0x68df429f0050417cac5ebb407bbe2adcbe6aab990d9d7f65d8bc2a93f08695fe. Running sims.
2023-06-06T08:19:50.046Z INFO [cfmms::pool::uniswap_v3] SQRT PRICE FROM LOG 3374780273765172741931233
2023-06-06T08:19:50.046Z INFO [cfmms::pool::uniswap_v3] AMOUNT 0 "-1472873957491611"
2023-06-06T08:19:50.046Z INFO [cfmms::pool::uniswap_v3] AMOUNT 1 "2673712"
2023-06-06T08:19:50.047Z INFO [cfmms::pool::uniswap_v3] Current tick -201286 false
2023-06-06T08:19:50.048Z INFO [cfmms::pool::uniswap_v3] AMOUNT OUT FROM INTERNAL SWAP "1472873826317980"
2023-06-06T08:19:50.049Z INFO [cfmms::pool::uniswap_v3] DIFF "0.00000008905964446774203"```
Right now, the current logic is getting the amount_0
and amount_1
from log_data[1]
. This is incorrect as the amount_0
should be retrieved from log_data[0]
. All logic used to sync pools from logs should be checked and updated. Additionally, logic decoding logs should ensure that indexed arguments are handled correctly and not assumed to be in log data.
Implement a filter for pools that have had less than n
transactions in t
period of time.
Opening this issue to start brainstorming around what the CFFM book might look like. I think the book should look something like the Foundry book, walking through the different mods in the lib and how to use them.
Remove num-bigfloat
crate from all price calculations because of unexpected panic in division.
Panic Example:
use num_bigfloat::BigFloat;
fn main() {
let x = BigFloat::from(1);
let y = BigFloat::from(2);
let z = x / y;
println!("{z:?}");
}
Addresses #7
This affects the calculate_price
functions in uniswap_v2.rs
& uniswap_v3.rs
For Uniswap V3 Swap simulation, the protocol needs to be incorporated.
// if the protocol fee is on, calculate how much is owed, decrement feeAmount, and increment protocolFee
if (cache.feeProtocol > 0) {
uint256 delta = step.feeAmount / cache.feeProtocol;
step.feeAmount -= delta;
state.protocolFee += uint128(delta);
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.