-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add lens contracts and Foundry tests
- Loading branch information
Showing
29 changed files
with
4,507 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
PoolLensTest:testFuzz_GetPositions(int24,int24) (runs: 16, μ: 971375, ~: 998911) | ||
PoolLensTest:test_GetPopulatedTicksInRange() (gas: 4109615) | ||
PoolLensTest:test_GetPositions() (gas: 712285) | ||
PoolLensTest:test_GetSlots() (gas: 3380493) | ||
PoolLensTest:test_GetTickBitmap() (gas: 3092280) | ||
PositionLensTest:testFuzz_GetPosition(uint256) (runs: 16, μ: 155236, ~: 155452) | ||
PositionLensTest:test_AllPositions() (gas: 874498) | ||
PositionLensTest:test_GetFeesOwed() (gas: 809236) | ||
PositionLensTest:test_GetPositions() (gas: 1015301) | ||
PositionLensTest:test_GetTotalAmounts() (gas: 825164) | ||
TickLensTest:test_GetPopulatedTicksInRange() (gas: 10239511) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
name: Foundry | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
pull_request: | ||
|
||
env: | ||
FOUNDRY_PROFILE: ci | ||
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }} | ||
|
||
jobs: | ||
check: | ||
name: Forge Tests | ||
runs-on: ubuntu-latest | ||
timeout-minutes: 30 | ||
steps: | ||
- uses: actions/checkout@v3 | ||
with: | ||
submodules: recursive | ||
|
||
- name: Use Node.js 18.x | ||
uses: actions/setup-node@v3 | ||
with: | ||
node-version: 18.x | ||
cache: "yarn" | ||
cache-dependency-path: "yarn.lock" | ||
|
||
- name: Install dependencies 📦 | ||
run: yarn install --frozen-lockfile | ||
|
||
- name: Install Foundry | ||
uses: foundry-rs/foundry-toolchain@v1 | ||
with: | ||
version: nightly | ||
|
||
- name: Run Forge build | ||
run: | | ||
forge --version | ||
forge build | ||
id: build | ||
|
||
- name: Run Forge tests | ||
run: | | ||
forge test -vvv | ||
id: test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
name: Node.js | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
pull_request: | ||
|
||
env: | ||
INFURA_API_KEY: ${{ secrets.INFURA_API_KEY }} | ||
|
||
jobs: # each workflow consists of 1+ jobs; by default, all jobs run in parallel | ||
test: # Run tests. | ||
runs-on: ubuntu-latest # host's operating system | ||
steps: # each job consists of 1+ steps | ||
- name: Checkout commit # download the code from triggering commit | ||
uses: actions/checkout@v3 | ||
|
||
- name: Use Node.js 18.x | ||
uses: actions/setup-node@v3 | ||
with: | ||
node-version: 18.x | ||
cache: "yarn" | ||
cache-dependency-path: "yarn.lock" | ||
|
||
- name: Install dependencies 📦 | ||
run: yarn install --frozen-lockfile | ||
|
||
- name: Install Foundry | ||
uses: foundry-rs/foundry-toolchain@v1 | ||
with: | ||
version: nightly | ||
|
||
- name: Run Forge build | ||
run: | | ||
forge --version | ||
forge build | ||
id: build | ||
|
||
- name: Generate Typechain types | ||
run: yarn typechain | ||
|
||
- name: Run hardhat tests | ||
run: yarn test:hardhat |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
name: Publish package | ||
on: | ||
release: | ||
types: [published] | ||
jobs: | ||
npm_publish: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: actions/setup-node@v3 | ||
with: | ||
node-version: '18.x' | ||
registry-url: 'https://registry.npmjs.org' | ||
scope: '@aperture_finance' | ||
- run: yarn install --frozen-lockfile | ||
- run: yarn build | ||
- run: npm publish --access public | ||
env: | ||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[submodule "lib/forge-std"] | ||
path = lib/forge-std | ||
url = https://github.com/foundry-rs/forge-std |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import "./PositionUtils.sol"; | ||
|
||
/// @notice A lens for Uniswap v3 that peeks into the current state of all positions by an owner without deployment | ||
/// @author Aperture Finance | ||
/// @dev The return data can be accessed externally by `eth_call` without a `to` address or internally by catching the | ||
/// revert data, and decoded by `abi.decode(data, (PositionState[]))` | ||
contract EphemeralAllPositionsByOwner is PositionUtils { | ||
constructor(INPM npm, address owner) payable { | ||
PositionState[] memory positions = allPositions(npm, owner); | ||
bytes memory returnData = abi.encode(positions); | ||
assembly ("memory-safe") { | ||
// The return data in a constructor will be written to code, which may exceed the contract size limit. | ||
revert(add(returnData, 0x20), mload(returnData)) | ||
} | ||
} | ||
|
||
/// @dev Public function to expose the abi for easier decoding using TypeChain | ||
/// @param npm Nonfungible position manager | ||
/// @param owner The address that owns the NFTs | ||
function allPositions(INPM npm, address owner) public payable returns (PositionState[] memory positions) { | ||
uint256 balance = NPMCaller.balanceOf(npm, owner); | ||
positions = new PositionState[](balance); | ||
unchecked { | ||
for (uint256 i; i < balance; ++i) { | ||
uint256 tokenId = NPMCaller.tokenOfOwnerByIndex(npm, owner, i); | ||
PositionState memory state = positions[i]; | ||
state.owner = owner; | ||
positionInPlace(npm, tokenId, state.position); | ||
peek(npm, tokenId, state); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
pragma solidity ^0.8.0; | ||
|
||
import "./PoolUtils.sol"; | ||
|
||
/// @notice A lens that fetches chunks of tick data in a range for a Uniswap v3 pool without deployment | ||
/// @author Aperture Finance | ||
/// @dev The return data can be accessed externally by `eth_call` without a `to` address or internally by catching the | ||
/// revert data, and decoded by `abi.decode(data, (PopulatedTick[]))` | ||
contract EphemeralGetPopulatedTicksInRange is PoolUtils { | ||
constructor(V3PoolCallee pool, int24 tickLower, int24 tickUpper) payable { | ||
PopulatedTick[] memory populatedTicks = getPopulatedTicksInRange(pool, tickLower, tickUpper); | ||
bytes memory returnData = abi.encode(populatedTicks); | ||
assembly ("memory-safe") { | ||
revert(add(returnData, 0x20), mload(returnData)) | ||
} | ||
} | ||
|
||
/// @notice Get all the tick data for the populated ticks from tickLower to tickUpper | ||
/// @param pool The address of the pool for which to fetch populated tick data | ||
/// @param tickLower The lower tick boundary of the populated ticks to fetch | ||
/// @param tickUpper The upper tick boundary of the populated ticks to fetch | ||
/// @return populatedTicks An array of tick data for the given word in the tick bitmap | ||
function getPopulatedTicksInRange( | ||
V3PoolCallee pool, | ||
int24 tickLower, | ||
int24 tickUpper | ||
) public payable returns (PopulatedTick[] memory populatedTicks) { | ||
require(tickLower <= tickUpper); | ||
// checks that the pool exists | ||
int24 tickSpacing = IUniswapV3Pool(V3PoolCallee.unwrap(pool)).tickSpacing(); | ||
(int16 wordPosLower, int16 wordPosUpper) = getWordPositions(tickLower, tickUpper, tickSpacing); | ||
unchecked { | ||
(uint256[] memory tickBitmap, uint256 count) = getTickBitmapAndCount(pool, wordPosLower, wordPosUpper); | ||
// fetch populated tick data | ||
populatedTicks = new PopulatedTick[](count); | ||
uint256 idx; | ||
for (int16 wordPos = wordPosLower; wordPos <= wordPosUpper; ++wordPos) { | ||
idx = populateTicksInWord( | ||
pool, | ||
wordPos, | ||
tickSpacing, | ||
tickBitmap[uint16(wordPos - wordPosLower)], | ||
populatedTicks, | ||
idx | ||
); | ||
} | ||
} | ||
} | ||
|
||
/// @notice Get the tick data for all populated ticks in a word of the tick bitmap | ||
function populateTicksInWord( | ||
V3PoolCallee pool, | ||
int16 wordPos, | ||
int24 tickSpacing, | ||
uint256 bitmap, | ||
PopulatedTick[] memory populatedTicks, | ||
uint256 idx | ||
) internal view returns (uint256) { | ||
unchecked { | ||
for (uint256 bitPos; bitPos < 256; ++bitPos) { | ||
//slither-disable-next-line incorrect-shift | ||
if (bitmap & (1 << bitPos) != 0) { | ||
int24 tick; | ||
assembly { | ||
tick := mul(tickSpacing, add(shl(8, wordPos), bitPos)) | ||
} | ||
populateTick(pool, tick, populatedTicks[idx++]); | ||
} | ||
} | ||
return idx; | ||
} | ||
} | ||
|
||
function populateTick(V3PoolCallee pool, int24 tick, PopulatedTick memory populatedTick) internal view { | ||
PoolCaller.TickInfo memory info = pool.ticks(tick); | ||
populatedTick.tick = tick; | ||
populatedTick.liquidityNet = info.liquidityNet; | ||
populatedTick.liquidityGross = info.liquidityGross; | ||
populatedTick.feeGrowthOutside0X128 = info.feeGrowthOutside0X128; | ||
populatedTick.feeGrowthOutside1X128 = info.feeGrowthOutside1X128; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import "./PositionUtils.sol"; | ||
|
||
/// @notice A lens for Uniswap v3 that peeks into the current state of position and pool info without deployment | ||
/// @author Aperture Finance | ||
/// @dev The return data can be accessed externally by `eth_call` without a `to` address or internally by catching the | ||
/// revert data, and decoded by `abi.decode(data, (PositionState))` | ||
contract EphemeralGetPosition is PositionUtils { | ||
constructor(INPM npm, uint256 tokenId) payable { | ||
PositionState memory pos = getPosition(npm, tokenId); | ||
bytes memory returnData = abi.encode(pos); | ||
assembly ("memory-safe") { | ||
revert(add(returnData, 0x20), mload(returnData)) | ||
} | ||
} | ||
|
||
/// @dev Public function to expose the abi for easier decoding using TypeChain | ||
/// @param npm Nonfungible position manager | ||
/// @param tokenId Token ID of the position | ||
function getPosition(INPM npm, uint256 tokenId) public payable returns (PositionState memory state) { | ||
state.owner = NPMCaller.ownerOf(npm, tokenId); | ||
positionInPlace(npm, tokenId, state.position); | ||
peek(npm, tokenId, state); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import "./PositionUtils.sol"; | ||
|
||
/// @notice A lens for Uniswap v3 that peeks into the current state of positions and pool info without deployment | ||
/// @author Aperture Finance | ||
/// @dev The return data can be accessed externally by `eth_call` without a `to` address or internally by catching the | ||
/// revert data, and decoded by `abi.decode(data, (PositionState[]))` | ||
contract EphemeralGetPositions is PositionUtils { | ||
constructor(INPM npm, uint256[] memory tokenIds) payable { | ||
PositionState[] memory positions = getPositions(npm, tokenIds); | ||
bytes memory returnData = abi.encode(positions); | ||
assembly ("memory-safe") { | ||
revert(add(returnData, 0x20), mload(returnData)) | ||
} | ||
} | ||
|
||
/// @dev Public function to expose the abi for easier decoding using TypeChain | ||
/// @param npm Nonfungible position manager | ||
/// @param tokenIds Token IDs of the positions | ||
function getPositions( | ||
INPM npm, | ||
uint256[] memory tokenIds | ||
) public payable returns (PositionState[] memory positions) { | ||
unchecked { | ||
uint256 length = tokenIds.length; | ||
positions = new PositionState[](length); | ||
uint256 i; | ||
for (uint256 j; j < length; ++j) { | ||
uint256 tokenId = tokenIds[j]; | ||
PositionState memory state = positions[i]; | ||
if (positionInPlace(npm, tokenId, state.position)) { | ||
++i; | ||
state.owner = NPMCaller.ownerOf(npm, tokenId); | ||
peek(npm, tokenId, state); | ||
} | ||
} | ||
assembly ("memory-safe") { | ||
mstore(positions, i) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import "./PoolUtils.sol"; | ||
|
||
/// @notice A lens that batches fetching of the `positions` mapping for a Uniswap v3 pool without deployment | ||
/// @author Aperture Finance | ||
/// @dev The return data can be accessed externally by `eth_call` without a `to` address or internally by catching the | ||
/// revert data, and decoded by `abi.decode(data, (Slot[]))` | ||
contract EphemeralPoolPositions is PoolUtils { | ||
constructor(V3PoolCallee pool, PositionKey[] memory keys) payable { | ||
Slot[] memory slots = getPositions(pool, keys); | ||
bytes memory returnData = abi.encode(slots); | ||
assembly ("memory-safe") { | ||
revert(add(returnData, 0x20), mload(returnData)) | ||
} | ||
} | ||
|
||
/// @notice Get liquidity positions in a pool | ||
/// @dev Public function to expose the abi for easier decoding using TypeChain | ||
/// @param pool The address of the pool for which to fetch the tick bitmap | ||
/// @param keys The position keys to fetch | ||
/// @return slots An array of storage slots and their raw data | ||
function getPositions(V3PoolCallee pool, PositionKey[] memory keys) public payable returns (Slot[] memory slots) { | ||
unchecked { | ||
uint256 length = keys.length; | ||
// each position occupies 4 storage slots | ||
slots = new Slot[](length << 2); | ||
uint256 j; | ||
for (uint256 i; i < length; ++i) { | ||
// calculate the storage slot corresponding to the position key | ||
// the slot of positions[key] is keccak256(abi.encode(key, positions.slot)) | ||
bytes32 key = getPositionKey(keys[i]); | ||
uint256 slot; | ||
assembly ("memory-safe") { | ||
mstore(0, key) | ||
mstore(0x20, POSITIONS_SLOT) | ||
slot := keccak256(0, 0x40) | ||
} | ||
PoolCaller.PositionInfo memory info = pool.positions(key); | ||
slots[j++] = Slot(slot++, info.liquidity); | ||
slots[j++] = Slot(slot++, info.feeGrowthInside0LastX128); | ||
slots[j++] = Slot(slot++, info.feeGrowthInside1LastX128); | ||
uint256 data; | ||
assembly { | ||
data := or(shl(128, mload(add(info, 0x80))), mload(add(info, 0x60))) | ||
} | ||
slots[j++] = Slot(slot, data); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.