Skip to content

Commit

Permalink
add position manager [wip] (#141)
Browse files Browse the repository at this point in the history
* initial thoughts lock and batch

* update safecallback with constructor

* simple batch under lock

* oops

* misc version bump; will conflict but can resolve later

* defining types and different levels of abstractions

* merge in main; resolve conflicts

* wip

* misc fixes with main:latest

* basic mint

* begin moving tests to fuzz

* test for slippage

* burning

* decrease liquidity

* mint transfer burn, liquidityOf accounting

* wip

* refactor to use CurrencySettleTake

* basic fee collection

* wip

* misc fix

* fee collection for independent same-range parties

* LiquidityPosition -> LiquidityRange

* erc20 fee collection

* decrease liquidity with fee collection

* wip test decrease liquidity on same range

* reworked fuzzers; more testing on fee claims for liquidity decreasing

* forge fmt

* test fixes for flipped deltas

* wip

* test coverage for increase liquidity cases

* preliminary gas benchmarks

* Position manager refactor (#2)

* chore: update v4-core:latest (#105)

* update core

* rename lockAcquired to unlockCallback

* update core; temporary path hack in remappings

* update v4-core; remove remapping

* wip: fix compatibility

* update core; fix renaming of swap fee to lp fee

* update core; fix events

* update core; address liquidity salt and modify liquidity return values

* fix incorrect delta accounting when modifying liquidity

* fix todo, use CurrencySettleTake

* remove deadcode

* update core; use StateLibrary; update sqrtRatio to sqrtPrice

* fix beforeSwap return signatures

* forge fmt; remove commented out code

* update core (wow gas savings)

* update core

* update core

* update core; hook flags LSB

* update core

* update core

* chore: update v4 core (#115)

* Update v4-core

* CurrencySettleTake -> CurrencySettler

* Snapshots

* compiling but very broken

* replace PoolStateLibrary

* update currency settle take

* compiling

* wip

* use v4-core's forge-std

* test liquidity increase

* additional fixes for collection and liquidity decrease

* test migration

* replace old implementation with new

---------

Signed-off-by: saucepoint <[email protected]>
Co-authored-by: 0x57 <[email protected]>

* cleanup: TODOs and imports

* Position manager Consolidate (#3)

* wip: consolidation

* further consolidation

* consolidate to single file

* yay no more stack too deep

* some code comments

* use currency settler syntax

* use v4-core's gas snapshot

* use snapLastCall and isolate for posm benchmarks

* Update contracts/libraries/CurrencySettleTake.sol

Co-authored-by: 0x57 <[email protected]>

* use v4-core's solmate its more recent

* use v4-core's openzeppelin-contracts

* add ERC721Permit

* feedback: memory hookData

* initial refactor. stack too deep

* passing tests

* gutted LockAndBatchCall

* cleanup diff

* renaming vanilla functions

* sanitize

* change add liq accounting (#126)

* change add liq accounting

* remove rand comments

* fix exact fees

* use closeAllDeltas

* comments cleanup

* additional liquidity tests (#129)

* additional increase liquidity tests

* edge case of using cached fees for autocompound

* wip

* fix autocompound bug, use custodied and unclaimed fees in the autocompound

* fix tests and use BalanceDeltas (#130)

* fix some assertions

* use BalanceDeltas for arithmetic

* cleanest code in the game???

* additional cleaning

* typo lol

* autocompound gas benchmarks

* autocompound excess credit gas benchmark

* save 600 gas, cleaner code when moving caller delta to tokensOwed

---------

Co-authored-by: saucepoint <[email protected]>

* create compatibility with arbitrary execute handler

* being supporting batched ops on vanilla functions

* some initial tests to drive TDD

* gas with isolate

* mint to recipient

* refactor for external call and code reuse (#134)

* updated interface with unlockAndExecute

* update decrease (#133)

* update decrease

* update collect

* update decrease/collect

* remove delta function

* update burn

* fix bubbling different return types because of recursive calls

* all operations only return a BalanceDelta type (#136)

* temp-dev-update (#135)

* checkpointing

* move decrease and collect to transient storage

* remove returns since they are now saved to transient storage

* draft: delta closing

* wip

* Sra/edits (#137)

* consolidate using owner, update burn

* fix: accrue deltas to caller in increase

* Rip Out Vanilla (#138)

* rip out vanilla and benchmark

* fix gas benchmark

* check posm is the locker before allowing access to external functions

* restore execute tests

* posm takes as 6909; remove legacy deadcode

* restore tests

* move helpers to the same file

* fix: cleanup

---------

Co-authored-by: Sara Reynolds <[email protected]>
Co-authored-by: Sara Reynolds <[email protected]>

* using internal calls, first pass (#143)

* using internal calls, first pass

* fix gas tests

* fix execute test

* fix tests

* edit mint gas test

* fix mint test

* fix warnings

* dumb fix to test ci (#146)

* dumb fix to test ci

* memory limit

* update gas limit to pass tests

---------

Co-authored-by: gretzke <[email protected]>

* some more gas snapshots (#150)

* feat: posm, use salts for all positions & update permit (#148)

* use position salts

* use fees owed in some tests

* remove claims from increase,decrease

* increment token id before reading it

* Revert "increment token id before reading it"

This reverts commit d366d75.

* owner to alice

* add more mint gas tests

* update comment

* Owner-level ERC721 Permit (#153)

* checkpointing

* move decrease and collect to transient storage

* remove returns since they are now saved to transient storage

* draft: delta closing

* wip

* Sra/edits (#137)

* consolidate using owner, update burn

* fix: accrue deltas to caller in increase

* Rip Out Vanilla (#138)

* rip out vanilla and benchmark

* fix gas benchmark

* check posm is the locker before allowing access to external functions

* restore execute tests

* posm takes as 6909; remove legacy deadcode

* restore tests

* move helpers to the same file

* move operator to NFTposm; move nonce to ERC721Permit; owner-level nonce

* tests for operator/permit

* snapshots

* gas benchmarks for permit

* test fixes

* unordered nonces

* fix tests / cheatcode usage

* fix tests

---------

Co-authored-by: Sara Reynolds <[email protected]>

---------

Co-authored-by: gretzke <[email protected]>
Co-authored-by: saucepoint <[email protected]>

* Multicall & initialize (#154)

* add multicall and an external function for initialization, with tests

* test multicall contract

* gas snapshot multicall

* fix ci test

* fix tests

* forge fmt

* change how msg.value is used in multicall mock

---------

Co-authored-by: Sara Reynolds <[email protected]>

* prep shared actions (#158)

* update main (#162)

* ERC721Permit cleanup (#161)

* wip Solmate ERC721

* the queens dead, you may put down your arms. with this commit, i hereby lay BaseLiquidityManagement and the ideals of fee accounting finally to rest

* refactor tokenId => LiquidityRange; begin separating PoolKey

* move comment

---------

Co-authored-by: Sara Reynolds <[email protected]>

* remove old files, imports

* Update src/NonfungiblePositionManager.sol

Co-authored-by: saucepoint <[email protected]>

* pr comments

* pr comments

* refactor test helpers per feedback

* fix gas

* remove permit

* fix compiler warnings

* rename to PositionManager

* cache length

* skip take for 0

* fix tests

* Update src/interfaces/IPositionManager.sol

Co-authored-by: Alice <[email protected]>

* update multicall tests per feedback

* remove unused imports

* more unused imports

* improve assertion

* assert mint recipient is the payer and not the recipient

* pr feedback

* assert pool creation

* use poolManager

* remove liquidityrange imports

* remove version string

* pr comments, use base test setup

* fuzz sqrtPrice

* use fuzz, assert eq

* other final rand pr comment fixes

* fix off by 1

* use bound

* use nextTokenId

---------

Signed-off-by: saucepoint <[email protected]>
Co-authored-by: saucepoint <[email protected]>
Co-authored-by: saucepoint <[email protected]>
Co-authored-by: 0x57 <[email protected]>
Co-authored-by: 0x57 <[email protected]>
Co-authored-by: gretzke <[email protected]>
Co-authored-by: Alice <[email protected]>
  • Loading branch information
7 people authored Jul 23, 2024
1 parent a9cfd38 commit 1abe26d
Show file tree
Hide file tree
Showing 77 changed files with 2,192 additions and 194 deletions.
1 change: 0 additions & 1 deletion .forge-snapshots/FullOracleObserve0After5Seconds.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/FullOracleObserve200By13.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/FullOracleObserve200By13Plus5.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/FullOracleObserve5After5Seconds.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/FullOracleObserveOldest.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/FullOracleObserveZero.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/FullRangeAddInitialLiquidity.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/FullRangeAddLiquidity.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/FullRangeFirstSwap.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/FullRangeInitialize.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/FullRangeRemoveLiquidity.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/FullRangeSecondSwap.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/FullRangeSwap.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/OracleGrow10Slots.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/OracleGrow1Slot.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/OracleGrow1SlotCardinalityGreater.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/OracleInitialize.snap

This file was deleted.

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/OracleObserveCurrentTime.snap

This file was deleted.

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/OracleObserveLast20Seconds.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/OracleObserveLatestEqual.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/OracleObserveLatestTransform.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/OracleObserveMiddle.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/OracleObserveOldest.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/OracleObserveSinceMostRecent.snap

This file was deleted.

1 change: 1 addition & 0 deletions .forge-snapshots/PositionManager_collect.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
162386
1 change: 1 addition & 0 deletions .forge-snapshots/PositionManager_collect_sameRange.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
162386
1 change: 1 addition & 0 deletions .forge-snapshots/PositionManager_decreaseLiquidity.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
127764
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
140645
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
157182
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
148692
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
184956
1 change: 1 addition & 0 deletions .forge-snapshots/PositionManager_mint.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
418192
1 change: 1 addition & 0 deletions .forge-snapshots/PositionManager_mint_onSameTickLower.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
360874
1 change: 1 addition & 0 deletions .forge-snapshots/PositionManager_mint_onSameTickUpper.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
361516
1 change: 1 addition & 0 deletions .forge-snapshots/PositionManager_mint_sameRange.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
287098
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
366892
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
462111
1 change: 0 additions & 1 deletion .forge-snapshots/TWAMMSubmitOrder.snap

This file was deleted.

2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ jobs:
with:
version: nightly

- name: Run tests
- name: Check format
run: forge fmt --check
12 changes: 0 additions & 12 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,15 +1,3 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
[submodule "lib/forge-gas-snapshot"]
path = lib/forge-gas-snapshot
url = https://github.com/marktoda/forge-gas-snapshot
[submodule "lib/v4-core"]
path = lib/v4-core
url = https://github.com/Uniswap/v4-core
[submodule "lib/solmate"]
path = lib/solmate
url = https://github.com/transmissions11/solmate
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ contract CoolHook is BaseHook {
address,
IPoolManager.PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata params
) external override poolManagerOnly returns (bytes4) {
) external override onlyByManager returns (bytes4) {
// hook logic
return BaseHook.beforeAddLiquidity.selector;
}
Expand Down
1 change: 0 additions & 1 deletion lib/forge-gas-snapshot
Submodule forge-gas-snapshot deleted from 9161f7
1 change: 0 additions & 1 deletion lib/forge-std
Submodule forge-std deleted from 3d8086
1 change: 0 additions & 1 deletion lib/openzeppelin-contracts
Submodule openzeppelin-contracts deleted from 5ae630
1 change: 0 additions & 1 deletion lib/solmate
Submodule solmate deleted from bfc9c2
4 changes: 1 addition & 3 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
@uniswap/v4-core/=lib/v4-core/
solmate/=lib/solmate/src/
forge-std/=lib/forge-std/src/
@openzeppelin/=lib/openzeppelin-contracts/
@openzeppelin/=lib/v4-core/lib/openzeppelin-contracts/
221 changes: 221 additions & 0 deletions src/PositionManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol";

import {ERC721Permit} from "./base/ERC721Permit.sol";
import {IPositionManager, Actions} from "./interfaces/IPositionManager.sol";
import {SafeCallback} from "./base/SafeCallback.sol";
import {Multicall} from "./base/Multicall.sol";
import {PoolInitializer} from "./base/PoolInitializer.sol";
import {CurrencySettleTake} from "./libraries/CurrencySettleTake.sol";
import {LiquidityRange} from "./types/LiquidityRange.sol";

contract PositionManager is IPositionManager, ERC721Permit, PoolInitializer, Multicall, SafeCallback {
using CurrencyLibrary for Currency;
using CurrencySettleTake for Currency;
using PoolIdLibrary for PoolKey;
using StateLibrary for IPoolManager;
using TransientStateLibrary for IPoolManager;
using SafeCast for uint256;

/// @dev The ID of the next token that will be minted. Skips 0
uint256 public nextTokenId = 1;

// maps the ERC721 tokenId to its Range (poolKey, tick range)
mapping(uint256 tokenId => LiquidityRange range) public tokenRange;

constructor(IPoolManager _poolManager)
SafeCallback(_poolManager)
ERC721Permit("Uniswap V4 Positions NFT", "UNI-V4-POSM", "1")
{}

modifier checkDeadline(uint256 deadline) {
if (block.timestamp > deadline) revert DeadlinePassed();
_;
}

/// @param unlockData is an encoding of actions, params, and currencies
/// @return returnData is the endocing of each actions return information
function modifyLiquidities(bytes calldata unlockData, uint256 deadline)
external
checkDeadline(deadline)
returns (bytes[] memory)
{
// TODO: Edit the encoding/decoding.
return abi.decode(poolManager.unlock(abi.encode(unlockData, msg.sender)), (bytes[]));
}

function _unlockCallback(bytes calldata payload) internal override returns (bytes memory) {
// TODO: Fix double encode/decode
(bytes memory unlockData, address sender) = abi.decode(payload, (bytes, address));

(Actions[] memory actions, bytes[] memory params) = abi.decode(unlockData, (Actions[], bytes[]));

bytes[] memory returnData = _dispatch(actions, params, sender);

return abi.encode(returnData);
}

function _dispatch(Actions[] memory actions, bytes[] memory params, address sender)
internal
returns (bytes[] memory returnData)
{
uint256 length = actions.length;
if (length != params.length) revert MismatchedLengths();
returnData = new bytes[](length);
for (uint256 i; i < length; i++) {
if (actions[i] == Actions.INCREASE) {
returnData[i] = _increase(params[i], sender);
} else if (actions[i] == Actions.DECREASE) {
returnData[i] = _decrease(params[i], sender);
} else if (actions[i] == Actions.MINT) {
// TODO: Mint will be coupled with increase.
returnData[i] = _mint(params[i]);
} else if (actions[i] == Actions.CLOSE_CURRENCY) {
returnData[i] = _close(params[i], sender);
} else if (actions[i] == Actions.BURN) {
// TODO: Burn will be coupled with decrease.
(uint256 tokenId) = abi.decode(params[i], (uint256));
burn(tokenId, sender);
} else {
revert UnsupportedAction();
}
}
}

/// @param param is an encoding of uint256 tokenId, uint256 liquidity, bytes hookData
/// @param sender the msg.sender, set by the `modifyLiquidities` function before the `unlockCallback`. Using msg.sender directly inside
/// the _unlockCallback will be the pool manager.
/// @return returns an encoding of the BalanceDelta applied by this increase call, including credited fees.
/// @dev Calling increase with 0 liquidity will credit the caller with any underlying fees of the position
function _increase(bytes memory param, address sender) internal returns (bytes memory) {
(uint256 tokenId, uint256 liquidity, bytes memory hookData) = abi.decode(param, (uint256, uint256, bytes));

_requireApprovedOrOwner(tokenId, sender);

// Note: The tokenId is used as the salt for this position, so every minted liquidity has unique storage in the pool manager.
(BalanceDelta delta,) = _modifyLiquidity(tokenRange[tokenId], liquidity.toInt256(), bytes32(tokenId), hookData);
return abi.encode(delta);
}

/// @param params is an encoding of uint256 tokenId, uint256 liquidity, bytes hookData
/// @param sender the msg.sender, set by the `modifyLiquidities` function before the `unlockCallback`. Using msg.sender directly inside
/// the _unlockCallback will be the pool manager.
/// @return returns an encoding of the BalanceDelta applied by this increase call, including credited fees.
/// @dev Calling decrease with 0 liquidity will credit the caller with any underlying fees of the position
function _decrease(bytes memory params, address sender) internal returns (bytes memory) {
(uint256 tokenId, uint256 liquidity, bytes memory hookData) = abi.decode(params, (uint256, uint256, bytes));

_requireApprovedOrOwner(tokenId, sender);

// Note: the tokenId is used as the salt.
(BalanceDelta delta,) =
_modifyLiquidity(tokenRange[tokenId], -(liquidity.toInt256()), bytes32(tokenId), hookData);
return abi.encode(delta);
}

/// @param param is an encoding of LiquidityRange memory range, uint256 liquidity, address recipient, bytes hookData where recipient is the receiver / owner of the ERC721
/// @return returns an encoding of the BalanceDelta from the initial increase
function _mint(bytes memory param) internal returns (bytes memory) {
(LiquidityRange memory range, uint256 liquidity, address owner, bytes memory hookData) =
abi.decode(param, (LiquidityRange, uint256, address, bytes));

// mint receipt token
uint256 tokenId;
// tokenId is assigned to current nextTokenId before incrementing it
unchecked {
tokenId = nextTokenId++;
}
_mint(owner, tokenId);

(BalanceDelta delta,) = _modifyLiquidity(range, liquidity.toInt256(), bytes32(tokenId), hookData);

tokenRange[tokenId] = range;

return abi.encode(delta);
}

/// @param params is an encoding of the Currency to close
/// @param sender is the msg.sender encoded by the `modifyLiquidities` function before the `unlockCallback`.
/// @return an encoding of int256 the balance of the currency being settled by this call
function _close(bytes memory params, address sender) internal returns (bytes memory) {
(Currency currency) = abi.decode(params, (Currency));
// this address has applied all deltas on behalf of the user/owner
// it is safe to close this entire delta because of slippage checks throughout the batched calls.
int256 currencyDelta = poolManager.currencyDelta(address(this), currency);

// the sender is the payer or receiver
if (currencyDelta < 0) {
currency.settle(poolManager, sender, uint256(-int256(currencyDelta)), false);
} else if (currencyDelta > 0) {
currency.take(poolManager, sender, uint256(int256(currencyDelta)), false);
}

return abi.encode(currencyDelta);
}

function burn(uint256 tokenId, address sender) internal {
_requireApprovedOrOwner(tokenId, sender);

// Checks that the full position's liquidity has been removed and all tokens have been collected from tokensOwed.
_validateBurn(tokenId);

delete tokenRange[tokenId];
// Burn the token.
_burn(tokenId);
}

function _modifyLiquidity(LiquidityRange memory range, int256 liquidityChange, bytes32 salt, bytes memory hookData)
internal
returns (BalanceDelta liquidityDelta, BalanceDelta totalFeesAccrued)
{
(liquidityDelta, totalFeesAccrued) = poolManager.modifyLiquidity(
range.poolKey,
IPoolManager.ModifyLiquidityParams({
tickLower: range.tickLower,
tickUpper: range.tickUpper,
liquidityDelta: liquidityChange,
salt: salt
}),
hookData
);
}

// ensures liquidity of the position is empty before burning the token.
function _validateBurn(uint256 tokenId) internal view {
bytes32 positionId = getPositionIdFromTokenId(tokenId);
uint128 liquidity = poolManager.getPositionLiquidity(tokenRange[tokenId].poolKey.toId(), positionId);
if (liquidity > 0) revert PositionMustBeEmpty();
}

// TODO: Move this to a posm state-view library.
function getPositionIdFromTokenId(uint256 tokenId) public view returns (bytes32 positionId) {
LiquidityRange memory range = tokenRange[tokenId];
bytes32 salt = bytes32(tokenId);
int24 tickLower = range.tickLower;
int24 tickUpper = range.tickUpper;
address owner = address(this);

// positionId = keccak256(abi.encodePacked(owner, tickLower, tickUpper, salt))
assembly {
mstore(0x26, salt) // [0x26, 0x46)
mstore(0x06, tickUpper) // [0x23, 0x26)
mstore(0x03, tickLower) // [0x20, 0x23)
mstore(0, owner) // [0x0c, 0x20)
positionId := keccak256(0x0c, 0x3a) // len is 58 bytes
mstore(0x26, 0) // rewrite 0x26 to 0
}
}

function _requireApprovedOrOwner(uint256 tokenId, address sender) internal view {
if (!_isApprovedOrOwner(sender, tokenId)) revert NotApproved(sender);
}
}
Loading

0 comments on commit 1abe26d

Please sign in to comment.