Skip to content

Commit

Permalink
Merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
shuhuiluo committed Jul 30, 2024
2 parents f01a9c2 + a36e87d commit b144924
Show file tree
Hide file tree
Showing 19 changed files with 818 additions and 358 deletions.
312 changes: 169 additions & 143 deletions .gas-snapshot

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
env:
FOUNDRY_PROFILE: ci
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
BASE_RPC_URL: ${{ secrets.BASE_RPC_URL }}

jobs:
check:
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ To run the tests:
forge test
```

## Inline Assembly

The libraries in this repository make use of [in-line assembly](https://docs.soliditylang.org/en/latest/assembly.html) for fine-grained control and optimizations. Knowledge of ABI encoding is required to understand how to calculate calldata length and parameter offsets. Helpful links:

- [ABI Specification](https://docs.soliditylang.org/en/latest/abi-spec.html#formal-specification-of-the-encoding)
- [HashEx Online ABI Encoder Tool](https://abi.hashex.org)
- [Solidity Memory Layout](https://docs.soliditylang.org/en/latest/internals/layout_in_memory.html)

## Contributions

Contributions are welcome. Please ensure that any modifications pass all tests before submitting a pull request.
Expand Down
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ via_ir = false

[rpc_endpoints]
mainnet = "${MAINNET_RPC_URL}"
base = "${BASE_RPC_URL}"

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@aperture_finance/uni-v3-lib",
"description": "A suite of Solidity libraries that have been imported and rewritten from Uniswap's v3-core and v3-periphery",
"version": "2.1.1",
"version": "3.0.3",
"author": "Aperture Finance",
"homepage": "https://aperture.finance/",
"license": "GPL-2.0-or-later",
Expand Down
83 changes: 75 additions & 8 deletions src/NPMCaller.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
pragma solidity >=0.8.0;

import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol";
import {INonfungiblePositionManager as INPM, IERC721Permit, IPeripheryImmutableState} from "./interfaces/INonfungiblePositionManager.sol";
import {IUniswapV3NonfungiblePositionManager as IUniV3NPM, ICommonNonfungiblePositionManager as INPM, IERC721Permit, IPeripheryImmutableState} from "./interfaces/IUniswapV3NonfungiblePositionManager.sol";
import {ISlipStreamNonfungiblePositionManager as ISlipStreamNPM} from "./interfaces/ISlipStreamNonfungiblePositionManager.sol";

// details about the uniswap position
struct PositionFull {
Expand Down Expand Up @@ -39,6 +40,15 @@ struct Position {
uint128 liquidity;
}

struct SlipStreamPosition {
address token0;
address token1;
int24 tickSpacing;
int24 tickLower;
int24 tickUpper;
uint128 liquidity;
}

/// @title Uniswap v3 Nonfungible Position Manager Caller
/// @author Aperture Finance
/// @notice Gas efficient library to call `INonfungiblePositionManager` assuming it exists.
Expand Down Expand Up @@ -241,7 +251,7 @@ library NPMCaller {
/// @param npm Uniswap v3 Nonfungible Position Manager
/// @param tokenId The ID of the token that represents the position
function positionsFull(INPM npm, uint256 tokenId) internal view returns (PositionFull memory pos) {
bytes4 selector = INPM.positions.selector;
bytes4 selector = IUniV3NPM.positions.selector;
assembly ("memory-safe") {
// Write the abi-encoded calldata into memory.
mstore(0, selector)
Expand All @@ -255,11 +265,33 @@ library NPMCaller {
}
}

/// @dev Equivalent to `INonfungiblePositionManager.positions(tokenId)`
/// @dev Equivalent to `IUniswapV3NonfungiblePositionManager.positions(tokenId)`
/// @param npm Uniswap v3 Nonfungible Position Manager
/// @param tokenId The ID of the token that represents the position
function positions(INPM npm, uint256 tokenId) internal view returns (Position memory pos) {
bytes4 selector = INPM.positions.selector;
bytes4 selector = IUniV3NPM.positions.selector;
assembly ("memory-safe") {
// Write the abi-encoded calldata into memory.
mstore(0, selector)
mstore(4, tokenId)
// We use 36 because of the length of our calldata.
// We copy up to 256 bytes of return data at `pos` which is the free memory pointer.
if iszero(staticcall(gas(), npm, 0, 0x24, pos, 0x100)) {
// Bubble up the revert reason.
revert(pos, returndatasize())
}
// Move the free memory pointer to the end of the struct.
mstore(0x40, add(pos, 0x100))
// Skip the first two struct members.
pos := add(pos, 0x40)
}
}

/// @dev Equivalent to `ISlipStreamNonfungiblePositionManager.positions(tokenId)`
/// @param npm SlipStream Nonfungible Position Manager
/// @param tokenId The ID of the token that represents the position
function positionsSlipStream(INPM npm, uint256 tokenId) internal view returns (SlipStreamPosition memory pos) {
bytes4 selector = ISlipStreamNPM.positions.selector;
assembly ("memory-safe") {
// Write the abi-encoded calldata into memory.
mstore(0, selector)
Expand All @@ -277,14 +309,14 @@ library NPMCaller {
}
}

/// @dev Equivalent to `INonfungiblePositionManager.mint`
/// @dev Equivalent to `IUniswapV3NonfungiblePositionManager.mint`
/// @param npm Uniswap v3 Nonfungible Position Manager
/// @param params The parameters for minting a position
function mint(
INPM npm,
INPM.MintParams memory params
IUniV3NPM.MintParams memory params
) internal returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) {
uint32 selector = uint32(INPM.mint.selector);
uint32 selector = uint32(IUniV3NPM.mint.selector);
assembly ("memory-safe") {
// Cache the free memory pointer.
let fmp := mload(0x40)
Expand All @@ -293,7 +325,7 @@ library NPMCaller {
let wordBeforeParams := mload(memBeforeParams)
// Write the function selector 4 bytes before `params`.
mstore(memBeforeParams, selector)
// We use 356 because of the length of our calldata.
// We use 356 (0x164) because of the length of our calldata.
// We copy up to 128 bytes of return data at the free memory pointer.
if iszero(call(gas(), npm, 0, sub(params, 4), 0x164, 0, 0x80)) {
// Bubble up the revert reason.
Expand All @@ -312,6 +344,41 @@ library NPMCaller {
}
}

/// @dev Equivalent to `ISlipStreamUniswapV3NonfungiblePositionManager.mint`
/// @param npm SlipStream Nonfungible Position Manager
/// @param params The parameters for minting a position
function mint(
INPM npm,
ISlipStreamNPM.MintParams memory params
) internal returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) {
uint32 selector = uint32(ISlipStreamNPM.mint.selector);
assembly ("memory-safe") {
// Cache the free memory pointer.
let fmp := mload(0x40)
// Cache the memory word before `params`.
let memBeforeParams := sub(params, 0x20)
let wordBeforeParams := mload(memBeforeParams)
// Write the function selector 4 bytes before `params`.
mstore(memBeforeParams, selector)
// We use 388 (0x184) because of the length of our calldata.
// We copy up to 128 bytes of return data at the free memory pointer.
if iszero(call(gas(), npm, 0, sub(params, 4), 0x184, 0, 0x80)) {
// Bubble up the revert reason.
revert(0, returndatasize())
}
// Read the return data.
tokenId := mload(0)
liquidity := mload(0x20)
amount0 := mload(0x40)
amount1 := mload(0x60)
// Restore the free memory pointer, zero pointer and memory word before `params`.
// `memBeforeParams` >= 0x60 so restore it after `mload`.
mstore(memBeforeParams, wordBeforeParams)
mstore(0x40, fmp)
mstore(0x60, 0)
}
}

/// @dev Equivalent to `INonfungiblePositionManager.increaseLiquidity`
/// @param npm Uniswap v3 Nonfungible Position Manager
/// @param params The parameters for increasing liquidity in a position
Expand Down
65 changes: 33 additions & 32 deletions src/SqrtPriceMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import "./UnsafeMath.sol";
/// @author Modified from Uniswap (https://github.com/uniswap/v3-core/blob/main/contracts/libraries/SqrtPriceMath.sol)
/// @notice Contains the math that uses square root of price as a Q64.96 and liquidity to compute deltas
library SqrtPriceMath {
using UnsafeMath for *;
using SafeCast for uint256;

/// @notice Gets the next sqrt price given a delta of token0
Expand All @@ -36,38 +35,39 @@ library SqrtPriceMath {
// we short circuit amount == 0 because the result is otherwise not guaranteed to equal the input price
if (amount == 0) return sqrtPX96;
uint256 numerator1;
uint256 _sqrtPX96;
assembly {
numerator1 := shl(96, liquidity)
_sqrtPX96 := sqrtPX96
}

if (add) {
unchecked {
uint256 product = amount * sqrtPX96;
uint256 product = amount * _sqrtPX96;
// checks for overflow
if (product.div(amount) == sqrtPX96) {
// denominator = liquidity + amount * sqrtPX96
if (UnsafeMath.div(product, amount) == _sqrtPX96) {
uint256 denominator = numerator1 + product;
// checks for overflow
if (denominator >= numerator1)
if (denominator >= numerator1) {
// always fits in 160 bits
return uint160(FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator));
return uint160(FullMath.mulDivRoundingUp(numerator1, _sqrtPX96, denominator));
}
}
}

// liquidity / (liquidity / sqrtPX96 + amount)
return uint160(numerator1.divRoundingUp(numerator1.div(sqrtPX96) + amount));
// denominator is checked for overflow
return uint160(UnsafeMath.divRoundingUp(numerator1, UnsafeMath.div(numerator1, _sqrtPX96) + amount));
} else {
uint256 denominator;
assembly ("memory-safe") {
// if the product overflows, we know the denominator underflows
// in addition, we must check that the denominator does not underflow
let product := mul(amount, sqrtPX96)
if iszero(and(eq(div(product, amount), sqrtPX96), gt(numerator1, product))) {
let product := mul(amount, _sqrtPX96)
if iszero(and(eq(div(product, amount), _sqrtPX96), gt(numerator1, product))) {
revert(0, 0)
}
denominator := sub(numerator1, product)
}
return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160();
return FullMath.mulDivRoundingUp(numerator1, _sqrtPX96, denominator).toUint160();
}
}

Expand All @@ -87,25 +87,25 @@ library SqrtPriceMath {
uint256 amount,
bool add
) internal pure returns (uint160 nextSqrtPrice) {
uint256 liquidity256;
uint256 _liquidity;
assembly {
liquidity256 := liquidity
_liquidity := liquidity
}
// if we're adding (subtracting), rounding down requires rounding the quotient down (up)
// in both cases, avoid a mulDiv for most inputs
if (add) {
uint256 quotient = (
amount <= type(uint160).max
? (amount << FixedPoint96.RESOLUTION).div(liquidity256)
: FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity256)
amount >> 160 == 0
? UnsafeMath.div((amount << FixedPoint96.RESOLUTION), _liquidity)
: FullMath.mulDiv(amount, FixedPoint96.Q96, _liquidity)
);

nextSqrtPrice = (uint256(sqrtPX96) + quotient).toUint160();
} else {
uint256 quotient = (
amount <= type(uint160).max
? (amount << FixedPoint96.RESOLUTION).divRoundingUp(liquidity256)
: FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity256)
amount >> 160 == 0
? UnsafeMath.divRoundingUp(amount << FixedPoint96.RESOLUTION, _liquidity)
: FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, _liquidity)
);
assembly ("memory-safe") {
if iszero(gt(sqrtPX96, quotient)) {
Expand Down Expand Up @@ -227,9 +227,10 @@ library SqrtPriceMath {
) internal pure returns (uint256 amount1) {
uint256 numerator = TernaryLib.absDiffU160(sqrtRatioAX96, sqrtRatioBX96);
uint256 denominator = FixedPoint96.Q96;
uint256 liquidity256;
uint256 _liquidity;
assembly {
liquidity256 := liquidity
// avoid implicit upcasting
_liquidity := liquidity
}
/**
* Equivalent to:
Expand All @@ -238,9 +239,9 @@ library SqrtPriceMath {
* : FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96);
* Cannot overflow because `type(uint128).max * type(uint160).max >> 96 < (1 << 192)`.
*/
amount1 = FullMath.mulDivQ96(liquidity256, numerator);
amount1 = FullMath.mulDivQ96(_liquidity, numerator);
assembly {
amount1 := add(amount1, and(gt(mulmod(liquidity256, numerator, denominator), 0), roundUp))
amount1 := add(amount1, and(gt(mulmod(_liquidity, numerator, denominator), 0), roundUp))
}
}

Expand All @@ -260,19 +261,19 @@ library SqrtPriceMath {
* ? -getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256()
* : getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256();
*/
bool sign;
bool roundUp;
uint256 mask;
uint128 liquidityAbs;
assembly {
// mask = 0 if liquidity >= 0 else -1
mask := sar(255, liquidity)
// sign = 1 if liquidity >= 0 else 0
sign := iszero(mask)
// roundUp = 1 if liquidity >= 0 else 0
roundUp := iszero(mask)
liquidityAbs := xor(mask, add(mask, liquidity))
}
// amount0Abs = liquidity / sqrt(lower) - liquidity / sqrt(upper) < type(uint224).max
// always fits in 224 bits, no need for toInt256()
uint256 amount0Abs = getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, liquidityAbs, sign);
uint256 amount0Abs = getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, liquidityAbs, roundUp);
assembly {
// If liquidity >= 0, amount0 = |amount0| = 0 ^ |amount0|
// If liquidity < 0, amount0 = -|amount0| = ~|amount0| + 1 = (-1) ^ |amount0| - (-1)
Expand All @@ -296,19 +297,19 @@ library SqrtPriceMath {
* ? -getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256()
* : getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256();
*/
bool sign;
bool roundUp;
uint256 mask;
uint128 liquidityAbs;
assembly {
// mask = 0 if liquidity >= 0 else -1
mask := sar(255, liquidity)
// sign = 1 if liquidity >= 0 else 0
sign := iszero(mask)
// roundUp = 1 if liquidity >= 0 else 0
roundUp := iszero(mask)
liquidityAbs := xor(mask, add(mask, liquidity))
}
// amount1Abs = liquidity * (sqrt(upper) - sqrt(lower)) < type(uint192).max
// always fits in 192 bits, no need for toInt256()
uint256 amount1Abs = getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, liquidityAbs, sign);
uint256 amount1Abs = getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, liquidityAbs, roundUp);
assembly {
// If liquidity >= 0, amount1 = |amount1| = 0 ^ |amount1|
// If liquidity < 0, amount1 = -|amount1| = ~|amount1| + 1 = (-1) ^ |amount1| - (-1)
Expand Down
Loading

0 comments on commit b144924

Please sign in to comment.