From af11059737a4748a389d804457cb2a697f8fa6c1 Mon Sep 17 00:00:00 2001 From: Zoomer <87513793+ethzoomer@users.noreply.github.com> Date: Wed, 6 Mar 2024 05:52:52 -0600 Subject: [PATCH] feat: add unstaked CL deposits to positions() (#51) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add unstaked CL deposits to positions() * LpSugar: optimize calls for CL positions. --------- Co-authored-by: Stas Co-authored-by: Stas SUȘCOV --- contracts/LpSugar.vy | 82 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 69 insertions(+), 13 deletions(-) diff --git a/contracts/LpSugar.vy b/contracts/LpSugar.vy index a1daabd..1b20133 100644 --- a/contracts/LpSugar.vy +++ b/contracts/LpSugar.vy @@ -218,6 +218,7 @@ interface ICLGauge: def rewardToken() -> address: view def feesVotingReward() -> address: view def stakedContains(_account: address, _position_id: uint256) -> bool: view + def stakedValues(_account: address) -> DynArray[uint256, MAX_POSITIONS]: view interface INFPositionManager: def positions(_position_id: uint256) -> PositionData: view @@ -633,7 +634,7 @@ def positions(_limit: uint256, _offset: uint256, _account: address)\ positions.append(pos) if self._is_cl_factory(factory.address): - # Assume there's no second CL factory :| + # fetch unstaked CL positions positions_count: uint256 = self.nfpm.balanceOf(_account) for pindex in range(0, MAX_POSITIONS): @@ -647,41 +648,95 @@ def positions(_limit: uint256, _offset: uint256, _account: address)\ else: pools_done += 1 - pos: Position = self._cl_position(pindex, _account, factory.address) + pos_id: uint256 = self.nfpm.tokenOfOwnerByIndex(_account, pindex) + pos: Position = self._cl_position( + pos_id, + _account, + empty(address), + empty(address), + factory.address + ) if pos.lp != empty(address): positions.append(pos) + # fetch staked CL positions + pools_count: uint256 = factory.allPoolsLength() + + for pindex in range(0, MAX_POOLS): + if pindex >= pools_count or pools_done >= _limit: + break + + # Basically skip calls for offset records... + if to_skip > 0: + to_skip -= 1 + continue + else: + pools_done += 1 + + pool_addr: address = factory.allPools(pindex) + gauge: ICLGauge = ICLGauge(self.voter.gauges(pool_addr)) + + if gauge.address == empty(address): + continue + + staked_position_ids: DynArray[uint256, MAX_POSITIONS] = gauge.stakedValues(_account) + + for sindex in range(0, MAX_POSITIONS): + if sindex >= len(staked_position_ids): + break + + pos: Position = self._cl_position( + staked_position_ids[sindex], + _account, + pool_addr, + gauge.address, + factory.address + ) + + positions.append(pos) + return positions @internal @view -def _cl_position(_index: uint256, _account: address, _factory: address) -> Position: +def _cl_position(_id: uint256, _account: address,\ + _pool:address, _gauge:address, _factory: address) -> Position: """ @notice Returns concentrated pool position data - @param _index The position index in the NF Position Manager + @param _id The token ID of the position @param _account The account to fetch positions for + @param _pool The pool address + @param _gauge The pool gauge address @param _factory The CL factory address @return A Position struct """ pos: Position = empty(Position) - pos.id = self.nfpm.tokenOfOwnerByIndex(_account, _index) + pos.id = _id + pos.lp = _pool data: PositionData = self.nfpm.positions(pos.id) - pos.lp = IPoolFactory(_factory).getPool( - data.token0, - data.token1, - convert(data.tickSpacing, int24) - ) + # Try to find the pool if we're fetching an unstaked position + if pos.lp == empty(address): + pos.lp = IPoolFactory(_factory).getPool( + data.token0, + data.token1, + convert(data.tickSpacing, int24) + ) if pos.lp == empty(address): return empty(Position) - staked: bool = False pool: IPool = IPool(pos.lp) - gauge: ICLGauge = ICLGauge(self.voter.gauges(pos.lp)) + gauge: ICLGauge = ICLGauge(_gauge) slot: Slot = pool.slot0() + # If the _gauge is present, it's because we're fetching a staked position + staked: bool = _gauge != empty(address) + + # Try to find the gauge if we're fetching an unstaked position + if _gauge == empty(address): + gauge = ICLGauge(self.voter.gauges(pos.lp)) amounts: Amounts = self.cl_helper.principal( self.nfpm.address, pos.id, slot.sqrtPriceX96 @@ -699,12 +754,13 @@ def _cl_position(_index: uint256, _account: address, _factory: address) -> Posit pos.unstaked_earned0 = convert(data.tokensOwed0, uint256) pos.unstaked_earned1 = convert(data.tokensOwed1, uint256) - if gauge.address != empty(address): + if staked == False and gauge.address != empty(address): staked = gauge.stakedContains(_account, pos.id) if staked: pos.emissions_earned = gauge.earned(_account, pos.id) + # Reverse the liquidity since a staked position uses full available liquidity if staked: pos.staked = pos.liquidity pos.staked0 = pos.amount0