Skip to main content

LendControllerView

The LendControllerView is a stateless view helper contract that the LendController delegates read-only computations to. It extends the base ControllerView module with lending-specific logic — primarily making max_borrowable and tokens_to_shrink aware of borrow caps and available balance.

Each lending market deploys its own LendControllerView instance alongside the controller.

LendControllerView.vy

The source code for the LendControllerView.vy contract can be found on GitHub. The base view module is ControllerView.vy. The contract is written in Vyper version 0.4.3.

Deployment addresses will be added once contracts are finalized.

Examples use Ethers v6 with an ABI generated from the pinned source linked in each panel. Replace every zero address and amount placeholder. Read-only calls use the verified Ethereum v2 reference market.


Lending-Specific Views

These functions override the base ControllerView to account for borrow caps and available liquidity.

max_borrowable

LendControllerView.max_borrowable(_d_collateral: uint256, _N: uint256, _user: address = empty(address)) -> uint256: view

Returns the maximum amount that can be borrowed for a given collateral and band count. Unlike the base implementation, this caps the result by the borrow cap and available balance in the vault.

InputTypeDescription
_d_collateraluint256Collateral amount
_Nuint256Number of bands
_useraddressBorrower address (for existing loan context)

Returns: max borrowable amount, capped by market limits (uint256).

<>Source code

Excerpt from lending/LendControllerView.vy at 0d726b1:

@external
@view
def max_borrowable(
_d_collateral: uint256,
_N: uint256,
_user: address = empty(address),
) -> uint256:
"""
@notice Natspec for this function is available in its controller contract
"""
user_state: uint256[4] = core._user_state(_user)
if user_state[1] > 0: # Can't borrow in soft-liquidation
return 0

N: uint256 = _N
if user_state[3] > 0: # The user has the position
N = user_state[3]

return crv_math.sub_or_zero(
core._max_borrowable(user_state[0] + _d_collateral, N, self._get_cap() + user_state[2] , _user),
user_state[2],
)


Example
const contract = new Contract('0xcda563f85388e621e7d810387e5afdac5d395e2b', LendControllerViewAbi, provider)
const result = await contract.max_borrowable(
/* _d_collateral: uint256 */ 0n,
/* _N: uint256 */ 0n,
/* _user: address */ "0x0000000000000000000000000000000000000000",
)

tokens_to_shrink

LendControllerView.tokens_to_shrink(_user: address, _d_collateral: uint256 = 0) -> uint256: view

Returns the amount of debt tokens needed to "shrink" a user's position by the given collateral amount. Accounts for lending market constraints.

InputTypeDescription
_useraddressBorrower address
_d_collateraluint256Collateral to remove

Returns: tokens needed (uint256).

<>Source code

Excerpt from lending/LendControllerView.vy at 0d726b1:

@external
@view
def tokens_to_shrink(_user: address, _d_collateral: uint256 = 0) -> uint256:
"""
@notice Natspec for this function is available in its controller contract
"""
return core._tokens_to_shrink(_user, self._get_cap(), _d_collateral)

Example
const contract = new Contract('0xcda563f85388e621e7d810387e5afdac5d395e2b', LendControllerViewAbi, provider)
const result = await contract.tokens_to_shrink(
/* _user: address */ "0x0000000000000000000000000000000000000000",
/* _d_collateral: uint256 */ 0n,
)

Health Previews

These functions are exported from the base ControllerView module and compute health predictions for various operations.

create_loan_health_preview

LendControllerView.create_loan_health_preview(_collateral: uint256, _debt: uint256, _N: uint256, _for: address, _full: bool) -> int256: view

Previews the health that would result from creating a loan with the given parameters.

InputTypeDescription
_collateraluint256Collateral amount
_debtuint256Debt amount
_Nuint256Number of bands
_foraddressBorrower address
_fullboolFull (pessimistic) health

Returns: predicted health (int256).

<>Source code

Excerpt from ControllerView.vy at 0d726b1:

@external
@view
def create_loan_health_preview(
_collateral: uint256,
_debt: uint256,
_N: uint256,
_for: address,
_full: bool,
) -> int256:
"""
@notice Natspec for this function is available in its controller contract
"""
assert _debt > 0, "debt==0"
n1: int256 = staticcall CONTROLLER.calculate_debt_n1(_collateral, _debt, _N, _for)
ld: uint256 = self._liquidation_discount()

return self._calc_full_health(_collateral, _debt, _N, n1, ld, _full)


@internal
@view
def _add_collateral_borrow_health_preview(
_collateral: uint256,
_debt: uint256,
_for: address,
_full: bool,
_remove: bool,
) -> int256:
"""
@notice Natspec for this function is available in its controller contract
"""
debt: uint256 = self._debt(_for)
ns: int256[2] = staticcall AMM.read_user_tick_numbers(_for)
N: uint256 = convert(unsafe_add(unsafe_sub(ns[1], ns[0]), 1), uint256)
xy: uint256[2] = staticcall AMM.get_sum_xy(_for)

assert debt > 0, "debt==0"
assert xy[0] == 0, "Underwater"
# … the remaining implementation is linked above.
Example
const contract = new Contract('0xcda563f85388e621e7d810387e5afdac5d395e2b', LendControllerViewAbi, provider)
const result = await contract.create_loan_health_preview(
/* _collateral: uint256 */ 0n,
/* _debt: uint256 */ 0n,
/* _N: uint256 */ 0n,
/* _for: address */ "0x0000000000000000000000000000000000000000",
/* _full: bool */ false,
)

borrow_more_health_preview

LendControllerView.borrow_more_health_preview(_collateral: uint256, _debt: uint256, _for: address, _full: bool) -> int256: view

Previews health after borrowing more.

InputTypeDescription
_collateraluint256Additional collateral
_debtuint256Additional debt
_foraddressBorrower address
_fullboolFull (pessimistic) health

Returns: predicted health (int256).

<>Source code

Excerpt from ControllerView.vy at 0d726b1:

@external
@view
def borrow_more_health_preview(
_collateral: uint256,
_debt: uint256,
_for: address,
_full: bool,
) -> int256:
"""
@notice Natspec for this function is available in its controller contract
"""
if _debt == 0:
return staticcall CONTROLLER.health(_for, _full)
return self._add_collateral_borrow_health_preview(
_collateral, _debt, _for, _full, False
)


Example
const contract = new Contract('0xcda563f85388e621e7d810387e5afdac5d395e2b', LendControllerViewAbi, provider)
const result = await contract.borrow_more_health_preview(
/* _collateral: uint256 */ 0n,
/* _debt: uint256 */ 0n,
/* _for: address */ "0x0000000000000000000000000000000000000000",
/* _full: bool */ false,
)

add_collateral_health_preview

LendControllerView.add_collateral_health_preview(_collateral: uint256, _for: address, _full: bool) -> int256: view

Previews health after adding collateral.

InputTypeDescription
_collateraluint256Collateral to add
_foraddressBorrower address
_fullboolFull (pessimistic) health

Returns: predicted health (int256).

<>Source code

Excerpt from ControllerView.vy at 0d726b1:

@external
@view
def add_collateral_health_preview(
_collateral: uint256,
_for: address,
_full: bool,
) -> int256:
"""
@notice Natspec for this function is available in its controller contract
"""
if _collateral == 0:
return staticcall CONTROLLER.health(_for, _full)
return self._add_collateral_borrow_health_preview(_collateral, 0, _for, _full, False)


Example
const contract = new Contract('0xcda563f85388e621e7d810387e5afdac5d395e2b', LendControllerViewAbi, provider)
const result = await contract.add_collateral_health_preview(
/* _collateral: uint256 */ 0n,
/* _for: address */ "0x0000000000000000000000000000000000000000",
/* _full: bool */ false,
)

remove_collateral_health_preview

LendControllerView.remove_collateral_health_preview(_collateral: uint256, _for: address, _full: bool) -> int256: view

Previews health after removing collateral.

InputTypeDescription
_collateraluint256Collateral to remove
_foraddressBorrower address
_fullboolFull (pessimistic) health

Returns: predicted health (int256).

<>Source code

Excerpt from ControllerView.vy at 0d726b1:

@external
@view
def remove_collateral_health_preview(
_collateral: uint256,
_for: address,
_full: bool,
) -> int256:
"""
@notice Natspec for this function is available in its controller contract
"""
if _collateral == 0:
return staticcall CONTROLLER.health(_for, _full)
return self._add_collateral_borrow_health_preview(
_collateral, 0, _for, _full, True
)


Example
const contract = new Contract('0xcda563f85388e621e7d810387e5afdac5d395e2b', LendControllerViewAbi, provider)
const result = await contract.remove_collateral_health_preview(
/* _collateral: uint256 */ 0n,
/* _for: address */ "0x0000000000000000000000000000000000000000",
/* _full: bool */ false,
)

repay_health_preview

LendControllerView.repay_health_preview(_d_collateral: uint256, _d_debt: uint256, _for: address, _shrink: bool, _full: bool) -> int256: view

Previews health after repaying debt.

InputTypeDescription
_d_collateraluint256Collateral amount removed through the callback
_d_debtuint256Debt to repay
_foraddressBorrower address
_shrinkboolWhether the repayment shrinks a soft-liquidated position
_fullboolFull (pessimistic) health

Returns: predicted health (int256).

<>Source code

Excerpt from ControllerView.vy at 0d726b1:

@external
@view
def repay_health_preview(
_d_collateral: uint256,
_d_debt: uint256,
_for: address,
_shrink: bool,
_full: bool,
) -> int256:
"""
@notice Natspec for this function is available in its controller contract
"""
ns: int256[2] = staticcall AMM.read_user_tick_numbers(_for)
debt: uint256 = self._debt(_for)
active_band: int256 = staticcall AMM.active_band_with_skip()

assert debt > 0, "Loan doesn't exist"

if ns[0] > active_band or _shrink: # re-deposit
xy: uint256[2] = staticcall AMM.get_sum_xy(_for)
d_debt: uint256 = _d_debt + xy[0]
assert d_debt > 0, "No coins to repay"
assert debt > d_debt, "Repay amount is too high"
debt = unsafe_sub(debt, d_debt)

collateral: uint256 = xy[1]
assert collateral > _d_collateral, "Can't remove more collateral than user has"
collateral = unsafe_sub(collateral, _d_collateral)

if _shrink:
assert ns[1] >= active_band + MIN_TICKS, "Can't shrink"

N: uint256 = convert(unsafe_add(unsafe_sub(ns[1], max(ns[0], active_band + 1)), 1), uint256)
n1: int256 = staticcall CONTROLLER.calculate_debt_n1(collateral, debt, N, _for)

return self._calc_full_health(collateral, debt, N, n1, self._liquidation_discount(), _full)
else:
assert _d_debt > 0, "No coins to repay"
# … the remaining implementation is linked above.
Example
const contract = new Contract('0xcda563f85388e621e7d810387e5afdac5d395e2b', LendControllerViewAbi, provider)
const result = await contract.repay_health_preview(
/* _d_collateral: uint256 */ 0n,
/* _d_debt: uint256 */ 0n,
/* _for: address */ "0x0000000000000000000000000000000000000000",
/* _shrink: bool */ false,
/* _full: bool */ false,
)

liquidate_health_preview

LendControllerView.liquidate_health_preview(_user: address, _caller: address, _frac: uint256, _full: bool) -> int256: view

Previews health after a partial liquidation.

InputTypeDescription
_useraddressBorrower address
_calleraddressAddress that would submit the liquidation
_fracuint256Fraction of the position to liquidate, where 10**18 is 100%
_fullboolFull (pessimistic) health

Returns: predicted health (int256).

<>Source code

Excerpt from ControllerView.vy at 0d726b1:

@external
@view
def liquidate_health_preview(
_user: address,
_caller: address,
_frac: uint256,
_full: bool,
) -> int256:
"""
@notice Natspec for this function is available in its controller contract
"""
assert _frac < WAD, "frac >= 100%"
debt: uint256 = self._debt(_user)
ns: int256[2] = staticcall AMM.read_user_tick_numbers(_user)
active_band: int256 = staticcall AMM.active_band_with_skip()

approval: bool = self._has_approval(_user, _caller)
health_limit: uint256 = 0
ld: uint256 = 0
if approval:
ld = self._liquidation_discount()
else:
assert staticcall CONTROLLER.health(_user, True) < 0, "Not enough rekt"
ld = self._liquidation_discounts(_user)
health_limit = ld
f_remove: uint256 = core._get_f_remove(_frac, health_limit)

x_eff: uint256 = staticcall AMM.get_x_down(_user) * (WAD - f_remove) // WAD
debt = debt * (WAD - _frac) // WAD
health: int256 = self._calc_health(x_eff, debt, ld)

if _full and ns[0] > active_band:
xy: uint256[2] = staticcall AMM.get_sum_xy(_user)
collateral: uint256 = xy[1] * (WAD - f_remove) // WAD
p0: uint256 = staticcall AMM.p_oracle_up(ns[0])
p_diff: uint256 = crv_math.sub_or_zero(staticcall AMM.price_oracle(), p0)

if p_diff > 0:
# … the remaining implementation is linked above.
Example
const contract = new Contract('0xcda563f85388e621e7d810387e5afdac5d395e2b', LendControllerViewAbi, provider)
const result = await contract.liquidate_health_preview(
/* _user: address */ "0x0000000000000000000000000000000000000000",
/* _caller: address */ "0x0000000000000000000000000000000000000000",
/* _frac: uint256 */ 0n,
/* _full: bool */ false,
)

Position Info

user_state

LendControllerView.user_state(_user: address) -> uint256[4]: view

Returns the full state of a user's loan: [collateral, borrowed, debt, N].

InputTypeDescription
_useraddressBorrower address

Returns: [collateral, borrowed, debt, N] (uint256[4]).

<>Source code

Excerpt from ControllerView.vy at 0d726b1:

@external
@view
def user_state(_user: address) -> uint256[4]:
"""
@notice Natspec for this function is available in its controller contract
"""
return self._user_state(_user)


@internal
@view
def _max_p_base(_amm: IAMM, _logn_a_ration: int256) -> uint256:
"""
@notice Calculate max base price including skipping bands
"""
p_oracle: uint256 = staticcall _amm.price_oracle()
# Should be correct unless price changes suddenly by MAX_P_BASE_BANDS+ bands
n1: int256 = math._wad_ln(
convert(staticcall _amm.get_base_price() * WAD // p_oracle, int256)
)
if n1 < 0:
n1 -= (
_logn_a_ration - 1
) # This is to deal with vyper's rounding of negative numbers
n1 = unsafe_div(n1, _logn_a_ration) + MAX_P_BASE_BANDS
n_min: int256 = staticcall _amm.active_band_with_skip()
n1 = max(n1, n_min + 1)
p_base: uint256 = staticcall _amm.p_oracle_up(n1)

for _: uint256 in range(MAX_SKIP_TICKS + 1):
n1 -= 1
if n1 <= n_min:
break
p_base_prev: uint256 = p_base
p_base = staticcall _amm.p_oracle_up(n1)
if p_base > p_oracle:
return p_base_prev
return p_base
# … the remaining implementation is linked above.
Example
const contract = new Contract('0xcda563f85388e621e7d810387e5afdac5d395e2b', LendControllerViewAbi, provider)
const result = await contract.user_state(
/* _user: address */ "0x0000000000000000000000000000000000000000",
)

user_prices

LendControllerView.user_prices(_user: address) -> uint256[2]: view

Returns the upper and lower price bounds of a user's collateral bands.

InputTypeDescription
_useraddressBorrower address

Returns: [price_upper, price_lower] (uint256[2]).

<>Source code

Excerpt from ControllerView.vy at 0d726b1:

@external
@view
def user_prices(_user: address) -> uint256[2]: # Upper, lower
"""
@notice Natspec for this function is available in its controller contract
"""
assert staticcall AMM.has_liquidity(_user)
ns: int256[2] = staticcall AMM.read_user_tick_numbers(_user) # ns[1] > ns[0]
return [
staticcall AMM.p_oracle_up(ns[0]), staticcall AMM.p_oracle_down(ns[1])
]


@internal
@view
def _user_state(_user: address) -> uint256[4]:
"""
@notice Natspec for this function is available in its controller contract
"""
xy: uint256[2] = empty(uint256[2])
N: uint256 = 0
debt: uint256 = self._debt(_user)
if debt > 0:
xy = staticcall AMM.get_sum_xy(_user)
ns: int256[2] = staticcall AMM.read_user_tick_numbers(_user) # ns[1] > ns[0]
N = convert(unsafe_add(unsafe_sub(ns[1], ns[0]), 1), uint256)

return [xy[1], xy[0], debt, N]


Example
const contract = new Contract('0xcda563f85388e621e7d810387e5afdac5d395e2b', LendControllerViewAbi, provider)
const result = await contract.user_prices(
/* _user: address */ "0x0000000000000000000000000000000000000000",
)

users_to_liquidate

LendControllerView.users_to_liquidate(_from: uint256 = 0, _limit: uint256 = 0) -> IController.Position[]: view

Returns a paginated list of liquidatable positions.

InputTypeDescription
_fromuint256Starting index
_limituint256Maximum positions to return

Returns: array of liquidatable positions (IController.Position[]).

<>Source code

Excerpt from ControllerView.vy at 0d726b1:

@external
@view
def users_to_liquidate(
_from: uint256 = 0, _limit: uint256 = 0
) -> DynArray[IController.Position, 1000]:
"""
@notice Natspec for this function is available in its controller contract
"""
return self.users_with_health(
CONTROLLER,
_from,
_limit,
0,
False,
empty(address),
True,
)


Example
const contract = new Contract('0xcda563f85388e621e7d810387e5afdac5d395e2b', LendControllerViewAbi, provider)
const result = await contract.users_to_liquidate(
/* _from: uint256 */ 0n,
/* _limit: uint256 */ 0n,
)

min_collateral

LendControllerView.min_collateral(_debt: uint256, _N: uint256, _user: address = empty(address)) -> uint256: view

Returns the minimum collateral required for a given debt amount and number of bands.

InputTypeDescription
_debtuint256Desired debt
_Nuint256Number of bands
_useraddressUser address, needed only when the user has nonzero extra health

Returns: minimum collateral (uint256).

<>Source code

Excerpt from ControllerView.vy at 0d726b1:

@external
@view
def min_collateral(
_debt: uint256, _N: uint256, _user: address = empty(address)
) -> uint256:
"""
@notice Natspec for this function is available in its controller contract
"""
# Add N**2 to account for precision loss in multiple bands, e.g. N / (y/N) = N**2 / y
assert _N <= MAX_TICKS_UINT and _N >= MIN_TICKS_UINT
return unsafe_div(
unsafe_div(
_debt
* unsafe_mul(WAD, BORROWED_PRECISION) // self._max_p_base(AMM, LOGN_A_RATIO)
* WAD // self._get_y_effective(
WAD, _N, self._loan_discount() + self._extra_health(_user)
)
+ unsafe_add(
unsafe_mul(_N, unsafe_add(_N, 2 * DEAD_SHARES)),
unsafe_sub(COLLATERAL_PRECISION, 1),
),
COLLATERAL_PRECISION,
)
* WAD,
WAD - 10**14,
)


@internal
@view
def _tokens_to_shrink(_user: address, _cap: uint256, _d_collateral: uint256) -> uint256:
active_band: int256 = staticcall AMM.active_band_with_skip()
ns: int256[2] = staticcall AMM.read_user_tick_numbers(_user)

if ns[0] > active_band:
return 0

assert ns[1] >= active_band + MIN_TICKS, "Can't shrink"
# … the remaining implementation is linked above.
Example
const contract = new Contract('0xcda563f85388e621e7d810387e5afdac5d395e2b', LendControllerViewAbi, provider)
const result = await contract.min_collateral(
/* _debt: uint256 */ 0n,
/* _N: uint256 */ 0n,
/* _user: address */ "0x0000000000000000000000000000000000000000",
)