Incomplete prototype

Things

WIP

Format

Work in progress

Work in progres

Work in progress

Work in progress

Work in progress

Work in progress

Work in progress

Work in progress

Work in progress

Work in progress

Work in progress

Work in progress

Work in progress

Work in progress

Work in progress
This commit is contained in:
Andrea Ciceri 2025-03-25 17:07:20 +01:00
commit 7a1e03ee7a
No known key found for this signature in database
19 changed files with 7242 additions and 0 deletions

66
onchain/README.md Normal file
View file

@ -0,0 +1,66 @@
## Foundry
**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**
Foundry consists of:
- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.
## Documentation
https://book.getfoundry.sh/
## Usage
### Build
```shell
$ forge build
```
### Test
```shell
$ forge test
```
### Format
```shell
$ forge fmt
```
### Gas Snapshots
```shell
$ forge snapshot
```
### Anvil
```shell
$ anvil
```
### Deploy
```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
```
### Cast
```shell
$ cast <subcommand>
```
### Help
```shell
$ forge --help
$ anvil --help
$ cast --help
```

6
onchain/foundry.toml Normal file
View file

@ -0,0 +1,6 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options

View file

@ -0,0 +1,89 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.28;
import {IUniswapV2Pair} from "./IUniswapV2Pair.sol";
import {IERC20} from "./IERC20.sol";
contract ArbitrageManager {
uint256 constant f = 997;
function sqrt(uint256 x)
public
pure
returns (uint128)
{
if (x == 0) return 0;
else{
uint256 xx = x;
uint256 r = 1;
if (xx >= 0x100000000000000000000000000000000) { xx >>= 128; r <<= 64; }
if (xx >= 0x10000000000000000) { xx >>= 64; r <<= 32; }
if (xx >= 0x100000000) { xx >>= 32; r <<= 16; }
if (xx >= 0x10000) { xx >>= 16; r <<= 8; }
if (xx >= 0x100) { xx >>= 8; r <<= 4; }
if (xx >= 0x10) { xx >>= 4; r <<= 2; }
if (xx >= 0x8) { r <<= 1; }
r = (r + x / r) >> 1;
r = (r + x / r) >> 1;
r = (r + x / r) >> 1;
r = (r + x / r) >> 1;
r = (r + x / r) >> 1;
r = (r + x / r) >> 1;
r = (r + x / r) >> 1;
uint256 r1 = x / r;
return uint128 (r < r1 ? r : r1);
}
}
function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut)
public
pure
returns (uint256 amountOut)
{
require(amountIn > 0, "UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT");
require(reserveIn > 0 && reserveOut > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY");
uint256 amountInWithFee = amountIn * 997;
uint256 numerator = amountInWithFee * reserveOut;
uint256 denominator = reserveIn * 1000 + amountInWithFee;
amountOut = numerator / denominator;
}
function optimalIn(uint256 X_A, uint256 Y_A, uint256 X_B, uint256 Y_B)
public
pure
returns(uint256)
{
uint256 k = f * X_B / 1000 + f ** 2 / 1000 * X_A / 1000;
uint256 phi = sqrt(f * X_A) * sqrt(Y_B * X_B / 1000 * Y_A);
uint256 psi = Y_A * X_B;
if (psi >= phi) return 0;
else return (phi - psi) / k;
}
function swap(address _pairA, address _pairB, uint256 amountIn, bool direction)
external
returns (uint256 amountOut)
{
IUniswapV2Pair pairA = IUniswapV2Pair(_pairA);
IUniswapV2Pair pairB = IUniswapV2Pair(_pairB);
IERC20 tokenA = direction ? IERC20(pairA.token0()) : IERC20(pairA.token1());
// Transfer the input tokens from the sender to pairA
tokenA.transferFrom(msg.sender, address(pairA), amountIn);
// Perform the first swap on pairA
(uint256 reserve0A, uint256 reserve1A,) = pairA.getReserves();
amountOut = getAmountOut(amountIn, direction ? reserve0A : reserve1A, direction ? reserve1A : reserve0A);
pairA.swap(direction ? 0 : amountOut, direction ? amountOut : 0, address(pairB), new bytes(0));
// Perform the second swap on pairB
(uint256 reserve0B, uint256 reserve1B,) = pairB.getReserves();
amountOut = getAmountOut(amountOut, direction ? reserve1B : reserve0B, direction ? reserve0B : reserve1B);
pairB.swap(direction ? amountOut : 0, direction ? 0 : amountOut, msg.sender, new bytes(0));
// Ensure that the arbitrage is profitable
require(amountOut > amountIn, "Arbitrage not profitable");
}
}

17
onchain/src/IERC20.sol Normal file
View file

@ -0,0 +1,17 @@
pragma solidity ^0.8.28;
interface IERC20 {
event Approval(address indexed owner, address indexed spender, uint256 value);
event Transfer(address indexed from, address indexed to, uint256 value);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint256);
function balanceOf(address owner) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transfer(address to, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
}

View file

@ -0,0 +1,53 @@
pragma solidity ^0.8.28;
interface IUniswapV2Pair {
event Approval(address indexed owner, address indexed spender, uint256 value);
event Transfer(address indexed from, address indexed to, uint256 value);
function name() external pure returns (string memory);
function symbol() external pure returns (string memory);
function decimals() external pure returns (uint8);
function totalSupply() external view returns (uint256);
function balanceOf(address owner) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transfer(address to, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function PERMIT_TYPEHASH() external pure returns (bytes32);
function nonces(address owner) external view returns (uint256);
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external;
event Mint(address indexed sender, uint256 amount0, uint256 amount1);
event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to);
event Swap(
address indexed sender,
uint256 amount0In,
uint256 amount1In,
uint256 amount0Out,
uint256 amount1Out,
address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);
function MINIMUM_LIQUIDITY() external pure returns (uint256);
function factory() external view returns (address);
function token0() external view returns (address);
function token1() external view returns (address);
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
function price0CumulativeLast() external view returns (uint256);
function price1CumulativeLast() external view returns (uint256);
function kLast() external view returns (uint256);
function mint(address to) external returns (uint256 liquidity);
function burn(address to) external returns (uint256 amount0, uint256 amount1);
function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external;
function skim(address to) external;
function sync() external;
function initialize(address, address) external;
}

View file

@ -0,0 +1,77 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.28;
import {Test, console} from "forge-std/Test.sol";
import {ArbitrageManager} from "../src/ArbitrageManager.sol";
import {IERC20} from "../src/IERC20.sol";
import {IUniswapV2Pair} from "../src/IUniswapV2Pair.sol";
contract ArbitrageTest is Test {
ArbitrageManager public arbitrageManager;
uint256 mainnetFork;
address constant vitalik = 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045;
IERC20 weth = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
IUniswapV2Pair uniswapPair = IUniswapV2Pair(0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc);
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();
}
function test_canSelectFork() public view {
assertEq(vm.activeFork(), mainnetFork);
assertEq(block.number, 22_147_269);
}
function test_sqrt() public view {
uint256 n = 344353442342354234324324324323;
assertEq(arbitrageManager.sqrt(n**2), n);
n = 115792089237316195423570985008687907853269984665640564039457584007913129639935;
assertEq(340282366920938463463374607431768211456 - 1, arbitrageManager.sqrt(n)); // +-1 is an acceptable rounding error
}
function test_computeAmountIn() 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("Uniswap pair ratio", Y_A/X_A);
console.log("Sushiswap pair ratio", Y_B/X_B);
uint256 unbalance = Y_A / 5;
console.log("Unbalance", unbalance);
vm.prank(address(uniswapPair)); // it works only for the next call
weth.transfer(address(0), unbalance);
uniswapPair.sync();
console.log("Arbitrage opportunity created");
(X_A, Y_A, ) = uniswapPair.getReserves();
(X_B, Y_B, ) = sushiswapPair.getReserves();
console.log("Uniswap pair reserves", X_A, Y_A);
console.log("Uniswap pair ratio", Y_A/X_A);
console.log("Sushiswap pair ratio", Y_B/X_B);
uint256 optimum = arbitrageManager.optimalIn(X_A, Y_A, X_B, Y_B);
console.log("The optimum is", optimum);
vm.prank(address(0)); // it works only for the next call
weth.transfer(address(uniswapPair), optimum);
uint256 amountOut = arbitrageManager.getAmountOut(optimum, Y_A, X_A);
console.log("First swap's amountOut", amountOut);
uniswapPair.swap(amountOut, 0, address(sushiswapPair), new bytes(0));
amountOut = arbitrageManager.getAmountOut(amountOut, X_B, Y_B);
console.log("Second swap's amountOut", amountOut);
sushiswapPair.swap(0, amountOut, address(this), new bytes(0));
(X_A, Y_A, ) = uniswapPair.getReserves();
(X_B, Y_B, ) = sushiswapPair.getReserves();
console.log("Uniswap pair reserves", X_A, Y_A);
console.log("Uniswap pair ratio", Y_A/X_A);
console.log("Sushiswap pair ratio", Y_B/X_B);
}
}