Skip to content

Commit

Permalink
ALM v2 (#104)
Browse files Browse the repository at this point in the history
* LpSugar: add ALM estimation helper.

* LpSugar: alm v2.

* env: alm v2 releases.

* LpSugar: account for ALM temper strategy.

* tests: assert the root pool address.

* env: new LpSugar release.
  • Loading branch information
stas committed Jan 7, 2025
1 parent 2bd6b6b commit 05104af
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 24 deletions.
85 changes: 66 additions & 19 deletions contracts/LpSugar.vy
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# @notice Makes it nicer to work with the liquidity pools.

from modules import lp_shared
from snekmate.utils import math

initializes: lp_shared

Expand All @@ -16,6 +17,9 @@ MAX_LPS: public(constant(uint256)) = 500
MAX_POSITIONS: public(constant(uint256)) = 200
MAX_TOKEN_SYMBOL_LEN: public(constant(uint256)) = 32

ALM_SCALE: constant(uint256) = as_wei_value(1000, "ether")
MAX_UINT: constant(uint256) = max_value(uint256)

# Slot0 from CLPool.sol
struct Slot:
sqrtPriceX96: uint160
Expand Down Expand Up @@ -121,7 +125,7 @@ struct Lp:
root: address

# See:
# https://github.com/mellow-finance/mellow-alm-toolkit/blob/main/src/interfaces/ICore.sol#L12-L60
# https://github.com/mellow-finance/mellow-alm-toolkit/blob/main/src/interfaces/ICore.sol#L71-L120
struct AlmManagedPositionInfo:
slippageD9: uint32
property: uint24
Expand Down Expand Up @@ -195,15 +199,15 @@ interface ISlipstreamHelper:
def poolFees(_pool: address, _liquidity: uint128, _current_tick: int24, _lower_tick: int24, _upper_tick: int24) -> Amounts: view

interface IAlmFactory:
def poolToAddresses(pool: address) -> address[2]: view
def getImmutableParams() -> address[5]: view
def poolToWrapper(pool: address) -> address: view
def core() -> address: view

interface IAlmCore:
def managedPositionAt(_id: uint256) -> AlmManagedPositionInfo: view

interface IAlmLpWrapper:
def positionId() -> uint256: view
def totalSupply() -> uint256: view
def previewMint(scale: uint256) -> uint256[2]: view

# Vars
cl_helper: public(ISlipstreamHelper)
Expand Down Expand Up @@ -557,7 +561,7 @@ def _positions(

alm_core: IAlmCore = empty(IAlmCore)
if self.alm_factory != empty(IAlmFactory):
alm_core = IAlmCore((staticcall self.alm_factory.getImmutableParams())[0])
alm_core = IAlmCore(staticcall self.alm_factory.core())

for index: uint256 in range(0, lp_shared.MAX_FACTORIES):
if index >= factories_count:
Expand Down Expand Up @@ -669,11 +673,11 @@ def _positions(
if self.alm_factory == empty(IAlmFactory):
continue

alm_addresses: address[2] = staticcall self.alm_factory.poolToAddresses(pool_addr)
alm_staking: IGauge = IGauge(alm_addresses[0])
alm_vault: IAlmLpWrapper = IAlmLpWrapper(alm_addresses[1])
alm_staking: IGauge = IGauge(
staticcall self.alm_factory.poolToWrapper(pool_addr)
)

if alm_vault.address == empty(address):
if alm_staking.address == empty(address):
continue

alm_user_liq: uint256 = staticcall alm_staking.balanceOf(_account)
Expand All @@ -682,7 +686,7 @@ def _positions(
continue

alm_pos: AlmManagedPositionInfo = staticcall alm_core.managedPositionAt(
staticcall alm_vault.positionId()
staticcall IAlmLpWrapper(alm_staking.address).positionId()
)

if gauge.address != empty(address) and len(alm_pos.ammPositionIds) > 0:
Expand All @@ -700,22 +704,38 @@ def _positions(
nfpm.address
)

alm_liq: uint256 = staticcall alm_vault.totalSupply()
# For the Temper strategy we might have a second position to add up
if len(alm_pos.ammPositionIds) > 1:
pos2: Position = self._cl_position(
alm_pos.ammPositionIds[1],
# Account is the ALM Core contract here...
alm_core.address,
pool_addr,
gauge.address if staked else empty(address),
factory.address,
nfpm.address
)
pos.amount0 += pos2.amount0
pos.amount1 += pos2.amount1
pos.staked0 += pos2.staked0
pos.staked1 += pos2.staked1

alm_liq: uint256 = staticcall alm_staking.totalSupply()
# adjust user share of the vault...
pos.amount0 = (alm_user_liq * pos.amount0) // alm_liq
pos.amount1 = (alm_user_liq * pos.amount1) // alm_liq
pos.staked0 = (alm_user_liq * pos.staked0) // alm_liq
pos.staked1 = (alm_user_liq * pos.staked1) // alm_liq

pos.emissions_earned = staticcall alm_staking.earned(_account)
# ignore dust as the rebalancing might report "fees"
pos.unstaked_earned0 = 0
pos.unstaked_earned1 = 0

pos.liquidity = (alm_user_liq * pos.liquidity) // alm_liq
pos.staked = (alm_user_liq * pos.staked) // alm_liq

pos.alm = alm_vault.address
pos.emissions_earned = staticcall alm_staking.earned(_account)
# ALM liquidity is fully staked
pos.liquidity = 0
pos.staked = alm_user_liq
pos.alm = alm_staking.address

if len(positions) < MAX_POSITIONS:
positions.append(pos)
Expand Down Expand Up @@ -980,9 +1000,9 @@ def _cl_lp(_data: address[4], _token0: address, _token1: address) -> Lp:
if gauge_alive and staticcall gauge.periodFinish() > block.timestamp:
emissions = staticcall gauge.rewardRate()

alm_addresses: address[2] = [empty(address), empty(address)]
alm_wrapper: address = empty(address)
if self.alm_factory != empty(IAlmFactory):
alm_addresses = staticcall self.alm_factory.poolToAddresses(pool.address)
alm_wrapper = staticcall self.alm_factory.poolToWrapper(pool.address)

return Lp({
lp: pool.address,
Expand Down Expand Up @@ -1019,7 +1039,7 @@ def _cl_lp(_data: address[4], _token0: address, _token1: address) -> Lp:
token1_fees: token1_fees,

nfpm: _data[3],
alm: alm_addresses[1],
alm: alm_wrapper,

root: lp_shared._root_lp_address(_data[0], _token0, _token1, tick_spacing),
})
Expand Down Expand Up @@ -1121,3 +1141,30 @@ def _has_userPositions(_nfpm: address) -> bool:
)[1]

return len(response) > 0


@external
@view
def almEstimateAmounts(
_wrapper: address,
_amount0: uint256,
_amount1: uint256
) -> uint256[3]:
"""
@notice Estimates the ALM amounts and LP tokens for a deposit
@param _wrapper The LP Wrapper contract
@param _amount0 First token amount
@param _amount1 Second token amount
@return Returns an array of tokens and LP amounts
"""
targets: uint256[2] = staticcall IAlmLpWrapper(_wrapper).previewMint(ALM_SCALE)

lp_amount: uint256 = min(
MAX_UINT if (targets[0] == 0) else math._mul_div(_amount0, ALM_SCALE, targets[0], False),
MAX_UINT if (targets[1] == 0) else math._mul_div(_amount1, ALM_SCALE, targets[1], False)
)

max0: uint256 = 0 if (targets[0] == 0) else math._mul_div(targets[0], lp_amount, ALM_SCALE, True)
max1: uint256 = 0 if (targets[1] == 0) else math._mul_div(targets[1], lp_amount, ALM_SCALE, True)

return [max0, max1, lp_amount]
4 changes: 2 additions & 2 deletions deployments/base.env
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ VOTER_8453=0x16613524e02ad97eDfeF371bC883F2F5d6C480A5
REGISTRY_8453=0x5C3F18F06CC09CA1910767A34a20F771039E37C0
CONVERTOR_8453=0x1111111111111111111111111111111111111111
SLIPSTREAM_HELPER_8453=0x9c62ab10577fB3C20A22E231b7703Ed6D456CC7a
ALM_FACTORY_8453=0x5B1b1aaC71bDca9Ed1dCb2AA357f678584db4029
ALM_FACTORY_8453=0xb4eA34bDD77D75b97dF6b07DA0b2A3021B6D2227
DIST_8453=0x227f65131A261548b057215bB1D5Ab2997964C7d
RELAY_REGISTRY_ADDRESSES_8453=0x05e41604B9463e2224227053980dfF3f57fb6dB5,0xD308aBCe663302d3b86b36d332CEFd8A4F62C5Ed
GOVERNOR_8453=0x94C012A23A8A65A6f40608dA30534a46a433F410
Expand All @@ -14,7 +14,7 @@ TEST_FACTORY_ADDRESS_8453=0x5e7BB104d84c7CB9B682AaC2F3d509f5F406809A
TEST_ADDRESS_8453=0x892Ff98a46e5bd141E2D12618f4B2Fe6284debac
TEST_ALM_ADDRESS_8453=0x892Ff98a46e5bd141E2D12618f4B2Fe6284debac

LP_SUGAR_ADDRESS_8453=0xF117AFbE285f867c67960F4bfEDe3121Cf6d8F76
LP_SUGAR_ADDRESS_8453=0x8D1eaAFe47D6b2d560d69Ff44A7e0D48980ab69b
REWARDS_SUGAR_ADDRESS_8453=0xA44600F4DBA6683d8BD99270B1A6a143fB9F1C3B
VE_SUGAR_ADDRESS_8453=0x4c5d3925fe65DFeB5A079485136e4De09cb664A5
RELAY_SUGAR_ADDRESS_8453=0x8932B5FE23C07Df06533F8f09E43e7cca6a24143
2 changes: 1 addition & 1 deletion deployments/mode.env
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ CONVERTOR_34443=0x1111111111111111111111111111111111111111
SLIPSTREAM_HELPER_34443=0xD24a61656AB0d70994Ef5F42fE11AA95c0a1d329
ALM_FACTORY_34443=0x0000000000000000000000000000000000000000

LP_SUGAR_ADDRESS_34443=0x8A5e97184E8850064805fAc2427ce7728689De5B
LP_SUGAR_ADDRESS_34443=0x7f23Fa4D139CCD5b7a4e4EFfFe1d2e61b33BCD21
REWARDS_SUGAR_ADDRESS_34443=0xD5d3ABAcB8CF075636792658EE0be8B03AF517B8

TEST_ADDRESS_34443=0x892ff98a46e5bd141e2d12618f4b2fe6284debac
Expand Down
4 changes: 2 additions & 2 deletions deployments/optimism.env
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ VOTER_10=0x41C914ee0c7E1A5edCD0295623e6dC557B5aBf3C
REGISTRY_10=0xF4c67CdEAaB8360370F41514d06e32CcD8aA1d7B
CONVERTOR_10=0x585Af0b397AC42dbeF7f18395426BF878634f18D
SLIPSTREAM_HELPER_10=0xD45624bf2CB9f65ecbdF3067d21992b099b56202
ALM_FACTORY_10=0xeD8b81E3fF6c54951621715F5992CA52007D88bA
ALM_FACTORY_10=0xb4eA34bDD77D75b97dF6b07DA0b2A3021B6D2227
DIST_10=0x9D4736EC60715e71aFe72973f7885DCBC21EA99b
RELAY_REGISTRY_ADDRESSES_10=0xe9F00f2e61CB0c6fb00A2e457546aCbF0fC303C2,0x6b1253B116B5919932399295C75116d33F8EfF96
GOVERNOR_10=0x1F82e10D58aEf03DeA2e478029fB0387A1cbE989

TEST_ADDRESS_10=0x892ff98a46e5bd141e2d12618f4b2fe6284debac
TEST_ALM_ADDRESS_10=0x892ff98a46e5bd141e2d12618f4b2fe6284debac

LP_SUGAR_ADDRESS_10=0x191FF5F5EAC07b082ff877236E8e7537c2d9D2A7
LP_SUGAR_ADDRESS_10=0x7F6D3A4c8a1111DDbFe282794f4D608aB7Cb23A2
REWARDS_SUGAR_ADDRESS_10=0x62CCFB2496f49A80B0184AD720379B529E9152fB
VE_SUGAR_ADDRESS_10=0x94f913362b232e31daB49a1aFB775cfd25DaA6a1
RELAY_SUGAR_ADDRESS_10=0xb8307e5842B9aeE75C704183F0355076aa74b4e2
5 changes: 5 additions & 0 deletions tests/test_lp_sugar.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ def test_all(sugar_contract):
assert lp2.lp == second_lp.lp
assert lp2.gauge == second_lp.gauge

# check we calculate the root pool address
if int(CHAIN_ID) == 34443:
assert lp1.root == "0x2bb4CFF1FE3F56599b4D409B2498B96D3E3f6665"
assert lp2.root == "0x48a3Ed8552483ed31Fd87ECa1a7b2F94aa1Cc394"


@pytest.mark.skipif(int(CHAIN_ID) not in [10, 8453], reason="Only root chains")
def test_all_pagination(sugar_contract):
Expand Down

0 comments on commit 05104af

Please sign in to comment.