Borrower

Git Source

Inherits: IUniswapV3MintCallback

Author: Aloe Labs, Inc.

"Test everything; hold fast what is good." - 1 Thessalonians 5:21

State Variables

FACTORY

The factory that created this contract

Factory public immutable FACTORY;

ORACLE

The oracle to use for prices and implied volatility

VolatilityOracle public immutable ORACLE;

UNISWAP_POOL

The Uniswap pair in which the vault will manage positions

IUniswapV3Pool public immutable UNISWAP_POOL;

TOKEN0

The first token of the Uniswap pair

ERC20 public immutable TOKEN0;

TOKEN1

The second token of the Uniswap pair

ERC20 public immutable TOKEN1;

LENDER0

The lender of TOKEN0

Lender public immutable LENDER0;

LENDER1

The lender of TOKEN1

Lender public immutable LENDER1;

slot0

Slot0 public slot0;

positions

int24[6] public positions;

Functions

constructor

constructor(VolatilityOracle oracle, IUniswapV3Pool pool, Lender lender0, Lender lender1);

receive

receive() external payable;

initialize

function initialize(address owner) external;

rescue

function rescue(ERC20 token) external;

warn

Warns the borrower that they're about to be liquidated. NOTE: Liquidators are only forced to call this in cases where the 5% swap bonus is up for grabs.

function warn(uint40 oracleSeed) external;

Parameters

NameTypeDescription
oracleSeeduint40The indices of UNISWAP_POOL.observations where we start our search for the 30-minute-old (lowest 16 bits) and 60-minute-old (next 16 bits) observations when getting TWAPs. If any of the highest 8 bits are set, we fallback to binary search.

liquidate

Liquidates the borrower, using all available assets to pay down liabilities. If some or all of the payment cannot be made in-kind, callee is expected to swap one asset for the other at a venue of their choosing. NOTE: Branches involving callbacks will fail until the borrower has been warned and the grace period has expired.

As a baseline, callee receives address(this).balance / strain ETH. This amount is intended to cover transaction fees. If the liquidation involves a swap callback, callee receives a 5% bonus denominated in the surplus token. In other words, if the two numeric callback arguments were denominated in the same asset, the first argument would be 5% larger.

function liquidate(ILiquidator callee, bytes calldata data, uint256 strain, uint40 oracleSeed) external;

Parameters

NameTypeDescription
calleeILiquidatorA smart contract capable of swapping TOKEN0 for TOKEN1 and vice versa
databytesEncoded parameters that get forwarded to callee callbacks
strainuint256Almost always set to 1 to pay off all debt and receive maximum reward. If liquidity is thin and swap price impact would be too large, you can use higher values to reduce swap size and make it easier for callee to do its job. 2 would be half swap size, 3 one third, and so on.
oracleSeeduint40The indices of UNISWAP_POOL.observations where we start our search for the 30-minute-old (lowest 16 bits) and 60-minute-old (next 16 bits) observations when getting TWAPs. If any of the highest 8 bits are set, we fallback to binary search.

modify

Allows the owner to manage their account by handing control to some callee. Inside the callback callee has access to all sub-commands (uniswapDeposit, uniswapWithdraw, transfer, borrow, repay, and withdrawAnte). Whatever callee does, the account MUST be healthy after the callback.

function modify(IManager callee, bytes calldata data, uint40 oracleSeed) external payable;

Parameters

NameTypeDescription
calleeIManagerThe smart contract that will get temporary control of this account
databytesEncoded parameters that get forwarded to callee
oracleSeeduint40The indices of UNISWAP_POOL.observations where we start our search for the 30-minute-old (lowest 16 bits) and 60-minute-old (next 16 bits) observations when getting TWAPs. If any of the highest 8 bits are set, we fallback to binary search.

uniswapV3MintCallback

Callback for Uniswap V3 pool; necessary for uniswapDeposit to work

function uniswapV3MintCallback(uint256 amount0, uint256 amount1, bytes calldata) external;

Parameters

NameTypeDescription
amount0uint256The amount of TOKEN0 owed to the UNISWAP_POOL
amount1uint256The amount of TOKEN1 owed to the UNISWAP_POOL
<none>bytes

uniswapDeposit

Allows the account owner to add liquidity to a Uniswap position (or create a new one). Only works within the modify callback.

The LiquidityAmounts library can help convert underlying amounts to units of liquidity. NOTE: Depending on your use-case, it may be more gas-efficient to call UNISWAP_POOL.mint in your own contract, instead of doing uniswapDeposit inside of modify's callback. As long as you set this Borrower as the recipient in UNISWAP_POOL.mint, the result is the same.

function uniswapDeposit(
    int24 lower,
    int24 upper,
    uint128 liquidity
) external returns (uint256 amount0, uint256 amount1);

Parameters

NameTypeDescription
lowerint24The tick at the position's lower bound
upperint24The tick at the position's upper bound
liquidityuint128The amount of liquidity to add, in Uniswap's internal units

Returns

NameTypeDescription
amount0uint256The precise amount of TOKEN0 that went into the Uniswap position
amount1uint256The precise amount of TOKEN1 that went into the Uniswap position

uniswapWithdraw

Allows the account owner to withdraw liquidity from one of their Uniswap positions. Only works within the modify callback.

The LiquidityAmounts library can help convert underlying amounts to units of liquidity

function uniswapWithdraw(
    int24 lower,
    int24 upper,
    uint128 liquidity,
    address recipient
) external returns (uint256 burned0, uint256 burned1, uint256 collected0, uint256 collected1);

Parameters

NameTypeDescription
lowerint24The tick at the position's lower bound
upperint24The tick at the position's upper bound
liquidityuint128The amount of liquidity to remove, in Uniswap's internal units. Pass 0 to collect fees without burning any liquidity.
recipientaddressReceives the tokens from Uniswap. Usually the address of this Borrower account.

Returns

NameTypeDescription
burned0uint256The amount of TOKEN0 that was removed from the Uniswap position
burned1uint256The amount of TOKEN1 that was removed from the Uniswap position
collected0uint256Equal to burned0 plus any earned TOKEN0 fees that hadn't yet been claimed
collected1uint256Equal to burned1 plus any earned TOKEN1 fees that hadn't yet been claimed

transfer

The most flexible sub-command. Allows the account owner to transfer amounts of TOKEN0 and TOKEN1 to any recipient they want. Only works within the modify callback.

function transfer(uint256 amount0, uint256 amount1, address recipient) external;

Parameters

NameTypeDescription
amount0uint256The amount of TOKEN0 to transfer
amount1uint256The amount of TOKEN1 to transfer
recipientaddressReceives the transferred tokens

borrow

Allows the account owner to borrow funds from LENDER0 and LENDER1. Only works within the modify callback.

If amount0 > 0 and interest hasn't yet accrued in this block for LENDER0, it will accrue prior to processing your new borrow. Same goes for amount1 > 0 and LENDER1.

function borrow(uint256 amount0, uint256 amount1, address recipient) external;

Parameters

NameTypeDescription
amount0uint256The amount of TOKEN0 to borrow
amount1uint256The amount of TOKEN1 to borrow
recipientaddressReceives the borrowed tokens. Usually the address of this Borrower account.

repay

Allows the account owner to repay debts to LENDER0 and LENDER1. Only works within the modify callback.

This is technically unnecessary since you could call Lender.repay directly, specifying this contract as the beneficiary and using the transfer sub-command to make payments. We include it because it's convenient and gas-efficient for common use-cases.

function repay(uint256 amount0, uint256 amount1) external;

Parameters

NameTypeDescription
amount0uint256The amount of TOKEN0 to repay
amount1uint256The amount of TOKEN1 to repay

withdrawAnte

Allows the account owner to withdraw their ante. Only works within the modify callback.

function withdrawAnte(address payable recipient) external;

Parameters

NameTypeDescription
recipientaddress payableReceives the ante (as Ether)

getUniswapPositions

function getUniswapPositions() external view returns (int24[] memory);

getPrices

function getPrices(uint40 oracleSeed) public view returns (Prices memory prices, bool seemsLegit);

_getPrices

function _getPrices(
    uint40 oracleSeed,
    uint8 nSigma,
    uint8 manipulationThresholdDivisor
) private view returns (Prices memory prices, bool seemsLegit);

_getAssets

function _getAssets(
    int24[] memory positions_,
    Prices memory prices,
    bool withdraw
) private returns (Assets memory assets);

_getLiabilities

function _getLiabilities() private view returns (uint256 amount0, uint256 amount1);

_uniswapWithdraw

function _uniswapWithdraw(
    int24 lower,
    int24 upper,
    uint128 liquidity,
    address recipient
) private returns (uint256 burned0, uint256 burned1, uint256 collected0, uint256 collected1);

_repay

function _repay(uint256 amount0, uint256 amount1) private;

_saveSlot0

function _saveSlot0(uint256 slot0_, uint256 addend) private;

_loadSlot0

function _loadSlot0() private view returns (uint256 slot0_);

_formatted

function _formatted(State state) private pure returns (uint256);

Events

Warn

Most liquidations involve swapping one asset for another. To incentivize such swaps (even in volatile markets) liquidators are rewarded with a 5% bonus. To avoid paying that bonus to liquidators, the account owner can listen for this event. Once it's emitted, they have 2 minutes to bring the account back to health. If they fail, the liquidation will proceed.

Fortuitous price movements and/or direct Lender.repay can bring the account back to health and nullify the immediate liquidation threat, but they will not clear the warning. This means that next time the account is unhealthy, liquidators might skip warn and liquidate right away. To clear the warning and return to a "clean" state, make sure to call modify -- even if the callback is a no-op.

The deadline for regaining health (avoiding liquidation) is given by slot0.unleashLiquidationTime. If this value is 0, the account is in the aforementioned "clean" state.

event Warn();

Liquidate

Emitted when the account gets liquidated

event Liquidate(uint256 repay0, uint256 repay1, uint256 incentive1, uint256 priceX128);

Structs

Slot0

struct Slot0 {
    address owner;
    uint88 unleashLiquidationTime;
    State state;
}

Enums

State

enum State {
    Ready,
    Locked,
    InModifyCallback
}