Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
242 changes: 173 additions & 69 deletions Contract/NArb.sol

Large diffs are not rendered by default.

138 changes: 71 additions & 67 deletions Contract/UniswapFlashQuery.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,7 @@ interface IUniswapV3Pool {
view
returns (
uint160 sqrtPriceX96,
int24 tick,
uint16 observationIndex,
uint16 observationCardinality,
uint16 observationCardinalityNext,
uint8 feeProtocol,
bool unlocked
int24 tick
);
function ticks(int24 tick)
external
Expand Down Expand Up @@ -60,6 +55,10 @@ interface ICarbonController {
function pairTradingFeePPM(address token0, address token1) external view returns (uint32);
}

error ArrayLengthMismatch();
error InvalidTickSpacing();
error InvalidRange();

// In order to quickly load up data from Uniswap-like market, this contract allows easy iteration with a single eth_call
contract FlashUniswapQueryV1 {
uint8 private constant V3_STARTUP_BITMAP_WORD_RADIUS = 2;
Expand Down Expand Up @@ -106,16 +105,18 @@ contract FlashUniswapQueryV1 {

function getReservesByPairs(IUniswapV2Pair[] calldata _pairs) external view returns (uint256[3][] memory) {
uint256[3][] memory result = new uint256[3][](_pairs.length);
for (uint256 i = 0; i < _pairs.length; i++) {
for (uint256 i; i < _pairs.length; ) {
(result[i][0], result[i][1], result[i][2]) = _pairs[i].getReserves();
unchecked { ++i; }
}
return result;
}

function getV3LiveStates(IUniswapV3Pool[] calldata _pools) external view returns (V3LiveState[] memory) {
V3LiveState[] memory result = new V3LiveState[](_pools.length);
for (uint256 i = 0; i < _pools.length; i++) {
for (uint256 i; i < _pools.length; ) {
result[i] = _getV3LiveState(_pools[i]);
unchecked { ++i; }
}
return result;
}
Expand All @@ -124,19 +125,19 @@ contract FlashUniswapQueryV1 {
IUniswapV3Pool[] calldata _pools,
int24[] calldata _tickSpacings
) external view returns (V3StartupState[] memory) {
require(_pools.length == _tickSpacings.length, "Array length mismatch");
if (_pools.length != _tickSpacings.length) revert ArrayLengthMismatch();

V3StartupState[] memory result = new V3StartupState[](_pools.length);
for (uint256 i = 0; i < _pools.length; i++) {
for (uint256 i; i < _pools.length; ) {
V3LiveState memory live = _getV3LiveState(_pools[i]);
int16[] memory wordPositions = _getV3StartupWordPositions(live.tick, _tickSpacings[i]);
V3BitmapData[] memory bitmaps = _getV3TickBitmaps(_pools[i], wordPositions);
V3BitmapData[] memory bitmaps = _getV3TickBitmaps(_pools[i], live.tick, _tickSpacings[i]);

result[i] = V3StartupState({
live: live,
bitmaps: bitmaps,
ticks: _getV3InitializedTicksFromBitmaps(_pools[i], _tickSpacings[i], bitmaps)
});
unchecked { ++i; }
}
return result;
}
Expand All @@ -146,7 +147,7 @@ contract FlashUniswapQueryV1 {
CarbonPairRequest[] calldata _requests
) external view returns (CarbonPairStrategies[] memory) {
CarbonPairStrategies[] memory result = new CarbonPairStrategies[](_requests.length);
for (uint256 i = 0; i < _requests.length; i++) {
for (uint256 i; i < _requests.length; ) {
CarbonPairRequest calldata request = _requests[i];
result[i] = CarbonPairStrategies({
token0: request.token0,
Expand All @@ -159,12 +160,13 @@ contract FlashUniswapQueryV1 {
request.endIndex
)
});
unchecked { ++i; }
}
return result;
}

function _getV3LiveState(IUniswapV3Pool _pool) internal view returns (V3LiveState memory) {
(uint160 sqrtPriceX96, int24 tick, , , , , ) = _pool.slot0();
(uint160 sqrtPriceX96, int24 tick) = _pool.slot0();
return V3LiveState({
pool: address(_pool),
sqrtPriceX96: sqrtPriceX96,
Expand All @@ -175,38 +177,25 @@ contract FlashUniswapQueryV1 {

function _getV3TickBitmaps(
IUniswapV3Pool _pool,
int16[] memory _wordPositions
) internal view returns (V3BitmapData[] memory) {
V3BitmapData[] memory result = new V3BitmapData[](_wordPositions.length);
for (uint256 i = 0; i < _wordPositions.length; i++) {
result[i] = V3BitmapData({
wordPosition: _wordPositions[i],
bitmap: _pool.tickBitmap(_wordPositions[i])
});
}
return result;
}

function _getV3StartupWordPositions(
int24 _tick,
int24 _tickSpacing
) internal pure returns (int16[] memory) {
require(_tickSpacing > 0, "Invalid tick spacing");
) internal view returns (V3BitmapData[] memory) {
if (_tickSpacing <= 0) revert InvalidTickSpacing();

int24 compressed = _tick / _tickSpacing;
if (_tick < 0 && _tick % _tickSpacing != 0) {
compressed--;
}
if (_tick < 0 && _tick % _tickSpacing != 0) compressed--;

int16 centerWord = int16(compressed >> 8);
uint256 wordCount = uint256(V3_STARTUP_BITMAP_WORD_RADIUS) * 2 + 1;
int16[] memory result = new int16[](wordCount);
int16 startWord = centerWord - int16(uint16(V3_STARTUP_BITMAP_WORD_RADIUS));

for (uint256 i = 0; i < wordCount; i++) {
result[i] = startWord + int16(uint16(i));
int16 startWord = int16(compressed >> 8) - int16(uint16(V3_STARTUP_BITMAP_WORD_RADIUS));
V3BitmapData[] memory result = new V3BitmapData[](wordCount);
for (uint256 i; i < wordCount; ) {
int16 wordPosition = startWord + int16(uint16(i));
result[i] = V3BitmapData({
wordPosition: wordPosition,
bitmap: _pool.tickBitmap(wordPosition)
});
unchecked { ++i; }
}

return result;
}

Expand All @@ -215,44 +204,56 @@ contract FlashUniswapQueryV1 {
int24 _tickSpacing,
V3BitmapData[] memory _bitmaps
) internal view returns (V3TickData[] memory) {
require(_tickSpacing > 0, "Invalid tick spacing");
if (_tickSpacing <= 0) revert InvalidTickSpacing();

V3TickData[] memory temp = new V3TickData[](V3_STARTUP_MAX_INITIALIZED_TICKS_PER_POOL);
V3TickData[] memory result = new V3TickData[](_initializedTickCapacity(_bitmaps));
uint256 tickCount = 0;

for (uint256 wordIndex = 0; wordIndex < _bitmaps.length; wordIndex++) {
for (uint256 wordIndex; wordIndex < _bitmaps.length; ) {
uint256 bitmap = _bitmaps[wordIndex].bitmap;
if (bitmap == 0) {
continue;
}

for (uint16 bit = 0; bit < 256 && tickCount < V3_STARTUP_MAX_INITIALIZED_TICKS_PER_POOL; bit++) {
if ((bitmap & (uint256(1) << bit)) == 0) {
continue;
if (bitmap != 0) {
for (uint16 byteIndex; byteIndex < 32 && tickCount < result.length; ) {
uint8 chunk = uint8(bitmap >> (byteIndex * 8));
if (chunk != 0) {
for (uint8 bitInByte; bitInByte < 8 && tickCount < result.length; ) {
if ((chunk & (uint8(1) << bitInByte)) != 0) {
uint256 bit = uint256(byteIndex) * 8 + bitInByte;
int24 tick = int24(
(int256(_bitmaps[wordIndex].wordPosition) * 256 + int256(bit)) *
int256(_tickSpacing)
);

result[tickCount] = _getV3Tick(_pool, tick);
unchecked { ++tickCount; }
}
unchecked { ++bitInByte; }
}
}
unchecked { ++byteIndex; }
}

int24 tick = int24(
(int256(_bitmaps[wordIndex].wordPosition) * 256 + int256(uint256(bit))) *
int256(_tickSpacing)
);

temp[tickCount] = _getV3Tick(_pool, tick);
tickCount++;
}

if (tickCount >= V3_STARTUP_MAX_INITIALIZED_TICKS_PER_POOL) {
if (tickCount == result.length) {
break;
}
}

V3TickData[] memory result = new V3TickData[](tickCount);
for (uint256 i = 0; i < tickCount; i++) {
result[i] = temp[i];
unchecked { ++wordIndex; }
}

return result;
}

function _initializedTickCapacity(V3BitmapData[] memory _bitmaps) private pure returns (uint256 count) {
for (uint256 i; i < _bitmaps.length; ) {
uint256 bitmap = _bitmaps[i].bitmap;
while (bitmap != 0 && count < V3_STARTUP_MAX_INITIALIZED_TICKS_PER_POOL) {
bitmap &= bitmap - 1;
unchecked { ++count; }
}
if (count == V3_STARTUP_MAX_INITIALIZED_TICKS_PER_POOL) return count;
unchecked { ++i; }
}
}

function _getV3Tick(
IUniswapV3Pool _pool,
int24 _tick
Expand Down Expand Up @@ -285,30 +286,33 @@ contract FlashUniswapQueryV1 {
if (_stop > _allPairsLength) {
_stop = _allPairsLength;
}
require(_stop >= _start, "start cannot be higher than stop");
if (_stop < _start) revert InvalidRange();
uint256 _qty = _stop - _start;
address[3][] memory result = new address[3][](_qty);
for (uint256 i = 0; i < _qty; i++) {
for (uint256 i; i < _qty; ) {
IUniswapV2Pair _uniswapPair = IUniswapV2Pair(_uniswapFactory.allPairs(_start + i));
result[i][0] = _uniswapPair.token0();
result[i][1] = _uniswapPair.token1();
result[i][2] = address(_uniswapPair);
unchecked { ++i; }
}
return result;
}

function filterVolatileHermesPairs(IBaseV1Pair[] calldata _pairs) external view returns (bool[] memory) {
bool[] memory result = new bool[](_pairs.length);
for (uint256 i = 0; i < _pairs.length; i++) {
for (uint256 i; i < _pairs.length; ) {
(, , , , result[i], , ) = _pairs[i].metadata();
unchecked { ++i; }
}
return result;
}

function getPairsLength(UniswapV2Factory[] calldata _factories) external view returns (uint256[] memory) {
uint256[] memory result = new uint256[](_factories.length);
for (uint256 i = 0; i < _factories.length; i++) {
for (uint256 i; i < _factories.length; ) {
result[i] = _factories[i].allPairsLength();
unchecked { ++i; }
}
return result;
}
Expand Down
15 changes: 1 addition & 14 deletions Contract/interfaces/IBaseV1Pair.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,4 @@ interface IBaseV1Pair {
address token0,
address token1
);

function getReserves()
external
view
returns (
uint112 reserve0,
uint112 reserve1,
uint32 blockTimestampLast
);

function token0() external view returns (address);

function token1() external view returns (address);
}
}
71 changes: 1 addition & 70 deletions Contract/interfaces/IERC20.sol
Original file line number Diff line number Diff line change
@@ -1,78 +1,9 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);

/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);

/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);

/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);

/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);

/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);

/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);

/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
}
Loading