Borrower
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
Name | Type | Description |
---|---|---|
oracleSeed | uint40 | The 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 warn
ed 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
Name | Type | Description |
---|---|---|
callee | ILiquidator | A smart contract capable of swapping TOKEN0 for TOKEN1 and vice versa |
data | bytes | Encoded parameters that get forwarded to callee callbacks |
strain | uint256 | Almost 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. |
oracleSeed | uint40 | The 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
Name | Type | Description |
---|---|---|
callee | IManager | The smart contract that will get temporary control of this account |
data | bytes | Encoded parameters that get forwarded to callee |
oracleSeed | uint40 | The 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
Name | Type | Description |
---|---|---|
amount0 | uint256 | The amount of TOKEN0 owed to the UNISWAP_POOL |
amount1 | uint256 | The 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
Name | Type | Description |
---|---|---|
lower | int24 | The tick at the position's lower bound |
upper | int24 | The tick at the position's upper bound |
liquidity | uint128 | The amount of liquidity to add, in Uniswap's internal units |
Returns
Name | Type | Description |
---|---|---|
amount0 | uint256 | The precise amount of TOKEN0 that went into the Uniswap position |
amount1 | uint256 | The 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
Name | Type | Description |
---|---|---|
lower | int24 | The tick at the position's lower bound |
upper | int24 | The tick at the position's upper bound |
liquidity | uint128 | The amount of liquidity to remove, in Uniswap's internal units. Pass 0 to collect fees without burning any liquidity. |
recipient | address | Receives the tokens from Uniswap. Usually the address of this Borrower account. |
Returns
Name | Type | Description |
---|---|---|
burned0 | uint256 | The amount of TOKEN0 that was removed from the Uniswap position |
burned1 | uint256 | The amount of TOKEN1 that was removed from the Uniswap position |
collected0 | uint256 | Equal to burned0 plus any earned TOKEN0 fees that hadn't yet been claimed |
collected1 | uint256 | Equal 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
Name | Type | Description |
---|---|---|
amount0 | uint256 | The amount of TOKEN0 to transfer |
amount1 | uint256 | The amount of TOKEN1 to transfer |
recipient | address | Receives 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
Name | Type | Description |
---|---|---|
amount0 | uint256 | The amount of TOKEN0 to borrow |
amount1 | uint256 | The amount of TOKEN1 to borrow |
recipient | address | Receives 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
Name | Type | Description |
---|---|---|
amount0 | uint256 | The amount of TOKEN0 to repay |
amount1 | uint256 | The 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
Name | Type | Description |
---|---|---|
recipient | address payable | Receives 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 liquidate
d
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
}