Add typst and tinymist to shell

Start writing notes

Notes on Uniswap V2's optimum

Fix error in formula

Test `computeAmountInt` using various deltas

Add `concurrency` to the default configuration file

Remove unused imports

Correctly propagate error

Allow dead code

Make the priority queue a real FIFO

Refactor: remove priority queue as stream and use channels

Increase buffer size

New `flashArbitrage` function

Comment with some ideas

Add pragma version

Refactor: decrease the amount of calls

Remove unused code

Re-enable tests

Remove comment

Process known pairs when started

Avoid re-allocating a new provider every time

Ignore `nixos.qcow2` file created by the VM

Add support for `aarch64-linux`

Add NixOS module and VM configuration

Add `itertools`

Add arbitrage opportunity detection

Implement `fallback` method for non standard callbacks

Add more logs

Fix sign error in optimum formula

Add deployment scripts and `agenix-shell` secrets

Bump cargo packages

Fix typo

Print out an error if processing a pair goes wrong

Add `actionlint` to formatters

Fix typo

Add TODO comment

Remove not relevant anymore comment

Big refactor

- process actions always in the correct order avoiding corner cases
- avoid using semaphores

New API key

Add `age` to dev shell

Used by Emacs' `agenix-mode` on my system

Fix parametric deploy scripts

Add `run-forge-tests` flake app

Remove fork URL from Solidity source

Remove `pairDir` argument

Add link to `ArbitrageManager`'s ABI

WIP
This commit is contained in:
Andrea Ciceri 2025-05-10 10:45:59 +02:00
parent 7a1e03ee7a
commit fb378c4931
No known key found for this signature in database
17 changed files with 1222 additions and 441 deletions

View file

@ -16,7 +16,6 @@ contract ArbitrageTest is Test {
IUniswapV2Pair sushiswapPair = IUniswapV2Pair(0x397FF1542f962076d0BFE58eA045FfA2d347ACa0);
function setUp() public {
mainnetFork = vm.createFork("https://eth-mainnet.g.alchemy.com/v2/kkDMaLVYpWQA0GsCYNFvAODnAxCCiamv"); // TODO use an env variable
vm.selectFork(mainnetFork);
vm.rollFork(22_147_269);
arbitrageManager = new ArbitrageManager();
@ -34,12 +33,13 @@ contract ArbitrageTest is Test {
n = 115792089237316195423570985008687907853269984665640564039457584007913129639935;
assertEq(340282366920938463463374607431768211456 - 1, arbitrageManager.sqrt(n)); // +-1 is an acceptable rounding error
}
function test_computeAmountIn() public {
function test_swapUsingOptimum() public {
(uint256 X_A, uint256 Y_A, ) = uniswapPair.getReserves(); // (USDT, WETH)
(uint256 X_B, uint256 Y_B, ) = sushiswapPair.getReserves(); // (USDT, WETH)
console.log("Uniswap pair reserves", X_A, Y_A);
console.log("Sushiswap pair reserves", X_B, Y_B);
console.log("Uniswap pair ratio", Y_A/X_A);
console.log("Sushiswap pair ratio", Y_B/X_B);
@ -53,6 +53,7 @@ contract ArbitrageTest is Test {
(X_A, Y_A, ) = uniswapPair.getReserves();
(X_B, Y_B, ) = sushiswapPair.getReserves();
console.log("Uniswap pair reserves", X_A, Y_A);
console.log("Sushiswap pair reserves", X_B, Y_B);
console.log("Uniswap pair ratio", Y_A/X_A);
console.log("Sushiswap pair ratio", Y_B/X_B);
@ -74,4 +75,47 @@ contract ArbitrageTest is Test {
console.log("Uniswap pair ratio", Y_A/X_A);
console.log("Sushiswap pair ratio", Y_B/X_B);
}
function computeGain(uint256 X_A, uint256 Y_A, uint256 X_B, uint256 Y_B, int256 delta)
internal view returns(uint256)
{
uint256 optimum = (delta > 0) ?
arbitrageManager.optimalIn(X_A, Y_A, X_B, Y_B) + uint256(delta)
: arbitrageManager.optimalIn(X_A, Y_A, X_B, Y_B) - uint256(-delta);
uint256 amountOut = arbitrageManager.getAmountOut(optimum, Y_A, X_A);
amountOut = arbitrageManager.getAmountOut(amountOut, X_B, Y_B);
return amountOut - optimum;
}
function test_computeOptimum() public view {
(uint256 X_A, uint256 Y_A, ) = uniswapPair.getReserves(); // (USDT, WETH)
(uint256 X_B, uint256 Y_B, ) = sushiswapPair.getReserves(); // (USDT, WETH)
Y_A -= Y_A / 5; // unbalancing the pair
// Using delta too low (~< 10**8) seems to produce a better gain,
// I *believe this has to do to some rounding, it should be neglibile
uint256[4] memory deltas = [uint256(0), uint256(10**8), uint256(10**9), uint256(10**10)];
uint256 gain = computeGain(X_A, Y_A, X_B, Y_B, 0);
for (uint256 i; i < deltas.length; i++) {
assertGe(gain, computeGain(X_A, Y_A, X_B, Y_B, int256(deltas[i])), "Computed optimum isnt't really optimal");
assertGe(gain, computeGain(X_A, Y_A, X_B, Y_B, -int256(deltas[i])), "Computed optimum isnt't really optimal");
}
}
function test_flashArbitrage () public {
uint256 initialWethBalance = weth.balanceOf(address(this));
console.log("initial weth balance", initialWethBalance);
(, uint256 Y_A, ) = uniswapPair.getReserves(); // (USDT, WETH)
uint256 unbalance = Y_A / 5;
vm.prank(address(uniswapPair)); // it works only for the next call
weth.transfer(address(0), unbalance);
uniswapPair.sync();
uint256 profit = arbitrageManager.flashArbitrage(address(uniswapPair), address(sushiswapPair), false);
console.log("profit", profit);
assertEq(initialWethBalance + profit, weth.balanceOf(address(this)), "There was no profit");
}
}