diff --git a/contracts/LpSugar.vy b/contracts/LpSugar.vy index 4037bdd..3655fe4 100644 --- a/contracts/LpSugar.vy +++ b/contracts/LpSugar.vy @@ -13,9 +13,10 @@ MAX_TOKENS: constant(uint256) = 2000 MAX_LPS: constant(uint256) = 500 MAX_EPOCHS: constant(uint256) = 200 MAX_REWARDS: constant(uint256) = 16 -MAX_POSITIONS: constant(uint256) = 10 +MAX_POSITIONS: constant(uint256) = 500 MAX_PRICES: constant(uint256) = 20 WEEK: constant(uint256) = 7 * 24 * 60 * 60 +Q96: constant(uint160) = 2**96 # Slot0 from CLPool.sol struct Slot: @@ -31,8 +32,7 @@ struct GaugeFees: token0: uint128 token1: uint128 -# Position Principal from Slipstream SugarHelper -struct PositionPrincipal: +struct Amounts: amount0: uint256 amount1: uint256 @@ -40,7 +40,9 @@ struct PositionPrincipal: struct PositionData: nonce: uint96 operator: address - poolId: uint80 + token0: address + token1: address + tickSpacing: uint24 tickLower: int24 tickUpper: int24 liquidity: uint128 @@ -64,6 +66,7 @@ struct TickInfo: struct Position: id: uint256 # NFT ID on v3, 0 on v2 + lp: address liquidity: uint256 # Liquidity amount on v3, amount of LP tokens on v2 staked: uint256 # liq amount staked on v3, amount of staked LP tokens on v2 amount0: uint256 # amount of unstaked token0 on both v2 and v3 @@ -131,8 +134,6 @@ struct Lp: token0_fees: uint256 token1_fees: uint256 - positions: DynArray[Position, MAX_POSITIONS] - struct LpEpochReward: token: address amount: uint256 @@ -170,7 +171,7 @@ interface IPoolFactory: def allPoolsLength() -> uint256: view def allPools(_index: uint256) -> address: view def getFee(_pool_addr: address, _stable: bool) -> uint256: view - def getPool(_token0: address, _token1: address, _fee: uint24) -> address: view + def getPool(_token0: address, _token1: address, _fee: int24) -> address: view interface IPool: def token0() -> address: view @@ -238,17 +239,17 @@ interface IReward: def earned(_token: address, _venft_id: uint256) -> uint256: view interface ISlipstreamHelper: - def getAmountsForLiquidity(_ratio: uint160, _ratioA: uint160, _ratioB: uint160, _liquidity: uint128) -> PositionPrincipal: view + def getAmountsForLiquidity(_ratio: uint160, _ratioA: uint160, _ratioB: uint160, _liquidity: uint128) -> Amounts: view def getSqrtRatioAtTick(_tick: int24) -> uint160: view - def principal(_nfpm: address, _position_id: uint256, _ratio: uint160) -> PositionPrincipal: view - def poolFees(_pool: address, _liquidity: uint128, _current_tick: int24, _lower_tick: int24, _upper_tick: int24) -> PositionPrincipal: view + def principal(_nfpm: address, _position_id: uint256, _ratio: uint160) -> Amounts: view + def poolFees(_pool: address, _liquidity: uint128, _current_tick: int24, _lower_tick: int24, _upper_tick: int24) -> Amounts: view # Vars registry: public(IFactoryRegistry) voter: public(IVoter) convertor: public(address) nfpm: public(INFPositionManager) -slipstream_helper: public(ISlipstreamHelper) +cl_helper: public(ISlipstreamHelper) # Methods @@ -262,7 +263,7 @@ def __init__(_voter: address, _registry: address, _convertor: address, \ self.registry = IFactoryRegistry(_registry) self.nfpm = INFPositionManager(_nfpm) self.convertor = _convertor - self.slipstream_helper = ISlipstreamHelper(_slipstream_helper) + self.cl_helper = ISlipstreamHelper(_slipstream_helper) @internal @view @@ -448,6 +449,23 @@ def _token(_address: address, _account: address) -> Token: listed: self.voter.isWhitelistedToken(_address) }) +@external +@view +def positions(_account: address) -> DynArray[Position, MAX_POSITIONS]: + """ + @notice Returns a collection of positions + @param _account The account to fetch positions for + @return Array for Lp structs + """ + positions: DynArray[Position, MAX_POSITIONS] = \ + empty(DynArray[Position, MAX_POSITIONS]) + + if _account == empty(address): + return positions + + # positions = self._cl_positions(_account, pool_data[0]) + return positions + @external @view def all(_limit: uint256, _offset: uint256, _account: address) \ @@ -495,7 +513,6 @@ def byIndex(_index: uint256, _account: address) -> Lp: offset = _index - 1 pools: DynArray[address[4], MAX_POOLS] = self._pools(1, offset) - pool_data: address[4] = pools[_index] pool: IPool = IPool(pool_data[1]) token0: address = pool.token0() @@ -579,6 +596,7 @@ def _byData(_data: address[4], _token0: address, _token1: address, \ positions.append( Position({ id: 0, + lp: pool.address, liquidity: acc_balance, staked: acc_staked, amount0: (acc_balance * reserve0) / pool_liquidity, @@ -628,10 +646,77 @@ def _byData(_data: address[4], _token0: address, _token1: address, \ unstaked_fee: 0, token0_fees: token0_fees, token1_fees: token1_fees, - - positions: positions }) +@internal +@view +def _cl_positions(_account: address, _factory: address) \ + -> DynArray[Position, MAX_POSITIONS]: + factory: IPoolFactory = IPoolFactory(_factory) + positions_count: uint256 = 0 + positions: DynArray[Position, MAX_POSITIONS] = \ + empty(DynArray[Position, MAX_POSITIONS]) + + if _account != empty(address): + positions_count = self.nfpm.balanceOf(_account) + + for index in range(0, MAX_POSITIONS): + if index >= positions_count: + break + + staked: bool = False + pos: Position = empty(Position) + pos.id = self.nfpm.tokenOfOwnerByIndex(_account, index) + + data: PositionData = self.nfpm.positions(pos.id) + + pos.liquidity = convert(data.liquidity, uint256) + pos.tick_lower = data.tickLower + pos.tick_upper = data.tickUpper + + pos.price_lower = self.cl_helper.getSqrtRatioAtTick(pos.tick_lower) + pos.price_upper = self.cl_helper.getSqrtRatioAtTick(pos.tick_upper) + pos.price_lower = (pos.price_lower / Q96)**2 + pos.price_upper = (pos.price_lower / Q96)**2 + + pos.unstaked_earned0 = convert(data.tokensOwed0, uint256) + pos.unstaked_earned1 = convert(data.tokensOwed1, uint256) + + pos.lp = factory.getPool( + data.token0, + data.token1, + convert(data.tickSpacing, int24) + ) + + if pos.lp == empty(address): + continue + + pool: IPool = IPool(pos.lp) + gauge: ICLGauge = ICLGauge(self.voter.gauges(pos.lp)) + slot: Slot = pool.slot0() + + amounts: Amounts = self.cl_helper.principal( + self.nfpm.address, pos.id, slot.sqrtPriceX96 + ) + pos.amount0 = amounts.amount0 + pos.amount1 = amounts.amount1 + + if gauge.address != empty(address): + pos.emissions_earned = gauge.earned(_account, pos.id) + staked = gauge.stakedContains(_account, pos.id) + + if staked: + pos.staked = pos.liquidity + pos.staked0 = pos.amount0 + pos.staked1 = pos.amount1 + pos.amount0 = 0 + pos.amount1 = 0 + pos.liquidity = 0 + + positions.append(pos) + + return positions + @internal @view def _byDataCL(_data: address[4], _token0: address, _token1: address, \ @@ -652,7 +737,6 @@ def _byDataCL(_data: address[4], _token0: address, _token1: address, \ emissions_token: address = empty(address) token0: IERC20 = IERC20(_token0) token1: IERC20 = IERC20(_token1) - positions_count: uint256 = 0 staked0: uint256 = 0 staked1: uint256 = 0 tick_spacing: int24 = pool.tickSpacing() @@ -662,78 +746,34 @@ def _byDataCL(_data: address[4], _token0: address, _token1: address, \ token1_fees: uint256 = 0 slot: Slot = pool.slot0() - positions: DynArray[Position, MAX_POSITIONS] = \ - empty(DynArray[Position, MAX_POSITIONS]) - - if _account != empty(address): - positions_count = self.nfpm.balanceOf(_account) if gauge.address == empty(address): - unstaked_fees: PositionPrincipal = self.slipstream_helper.poolFees(_data[1], pool_liquidity, slot.tick, slot.tick, slot.tick + tick_spacing) + unstaked_fees: Amounts = self.cl_helper.poolFees( + _data[1], pool_liquidity, slot.tick, slot.tick, slot.tick + tick_spacing + ) token0_fees = unstaked_fees.amount0 token1_fees = unstaked_fees.amount1 else: fee_voting_reward = gauge.feesVotingReward() emissions_token = gauge.rewardToken() - ratio_a: uint160 = self.slipstream_helper.getSqrtRatioAtTick(slot.tick) - ratio_b: uint160 = self.slipstream_helper.getSqrtRatioAtTick(slot.tick + tick_spacing) - tick_staked: PositionPrincipal = self.slipstream_helper.getAmountsForLiquidity(slot.sqrtPriceX96, ratio_a, ratio_b, gauge_liquidity) + ratio_a: uint160 = self.cl_helper.getSqrtRatioAtTick(slot.tick) + ratio_b: uint160 = self.cl_helper.getSqrtRatioAtTick(slot.tick + tick_spacing) + tick_staked: Amounts = self.cl_helper.getAmountsForLiquidity( + slot.sqrtPriceX96, ratio_a, ratio_b, gauge_liquidity + ) staked0 = tick_staked.amount0 staked1 = tick_staked.amount1 - staked_fees: PositionPrincipal = self.slipstream_helper.poolFees(_data[1], gauge_liquidity, slot.tick, slot.tick, slot.tick + tick_spacing) + staked_fees: Amounts = self.cl_helper.poolFees( + _data[1], gauge_liquidity, slot.tick, slot.tick, slot.tick + tick_spacing + ) token0_fees = staked_fees.amount0 token1_fees = staked_fees.amount1 if gauge_alive: emissions = gauge.rewardRate() - for index in range(0, MAX_POSITIONS): - if index >= positions_count: - break - - position_id: uint256 = self.nfpm.tokenOfOwnerByIndex(_account, index) - position_data: PositionData = self.nfpm.positions(position_id) - position_principal: PositionPrincipal = self.slipstream_helper.principal(self.nfpm.address, position_id, slot.sqrtPriceX96) - position_amount0: uint256 = 0 - position_amount1: uint256 = 0 - position_staked0: uint256 = 0 - position_staked1: uint256 = 0 - - emissions_earned: uint256 = 0 - staked: bool = False - - if gauge.address != empty(address): - emissions_earned = gauge.earned(_account, position_id) - staked = gauge.stakedContains(_account, position_id) - - if staked: - position_staked0 = position_principal.amount0 - position_staked1 = position_principal.amount1 - else: - position_amount0 = position_principal.amount0 - position_amount1 = position_principal.amount1 - - positions.append( - Position({ - id: position_id, - liquidity: convert(position_data.liquidity, uint256), - staked: convert(staked, uint256), - amount0: position_amount0, - amount1: position_amount1, - staked0: position_staked0, - staked1: position_staked1, - unstaked_earned0: convert(position_data.tokensOwed0, uint256), - unstaked_earned1: convert(position_data.tokensOwed1, uint256), - emissions_earned: emissions_earned, - tick_lower: position_data.tickLower, - tick_upper: position_data.tickUpper, - price_lower: self.slipstream_helper.getSqrtRatioAtTick(position_data.tickLower), - price_upper: self.slipstream_helper.getSqrtRatioAtTick(position_data.tickUpper), - }) - ) - return Lp({ lp: pool.address, symbol: "", @@ -767,8 +807,6 @@ def _byDataCL(_data: address[4], _token0: address, _token1: address, \ unstaked_fee: convert(pool.unstakedFee(), uint256), token0_fees: token0_fees, token1_fees: token1_fees, - - positions: positions }) @external