Contract Name:
FrontendManager
Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
/**
* @author OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.0/contracts/proxy/Clones.sol)
* @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
* deploying minimal proxy contracts, also known as "clones".
*
* > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
* > a minimal bytecode implementation that delegates all calls to a known, fixed address.
*
* The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
* (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
* deterministic method.
*
* _Available since v3.4._
*/
library Clones {
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create opcode, which should never revert.
*/
function clone(address implementation) internal returns (address instance) {
assembly ("memory-safe") {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create(0, 0x09, 0x37)
}
require(instance != address(0), "ERC1167: create failed");
}
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy
* the clone. Using the same `implementation` and `salt` multiple time will revert, since
* the clones cannot be deployed twice at the same address.
*/
function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
assembly ("memory-safe") {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create2(0, 0x09, 0x37, salt)
}
require(instance != address(0), "ERC1167: create2 failed");
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt
) internal view returns (address predicted) {
return predictDeterministicAddress(implementation, salt, address(this));
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(add(ptr, 0x38), deployer)
mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
mstore(add(ptr, 0x14), implementation)
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
mstore(add(ptr, 0x58), salt)
mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
predicted := keccak256(add(ptr, 0x43), 0x55)
}
}
}
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.15;
import {Create2} from "./Create2.sol";
/// @title ClonesWithImmutableArgs
/// @author wighawag, zefram.eth, Saw-mon & Natalie, wminshew
/// @notice Enables creating clone contracts with immutable args
library ClonesWithImmutableArgs {
// abi.encodeWithSignature("CreateFail()")
uint256 private constant _CREATE_FAIL_ERROR_SIG =
0xebfef18800000000000000000000000000000000000000000000000000000000;
// abi.encodeWithSignature("IdentityPrecompileFailure()")
uint256 private constant _IDENTITY_PRECOMPILE_ERROR_SIG =
0x3a008ffa00000000000000000000000000000000000000000000000000000000;
uint256 private constant _CUSTOM_ERROR_SIG_PTR = 0x0;
uint256 private constant _CUSTOM_ERROR_LENGTH = 0x4;
uint256 private constant _BOOTSTRAP_LENGTH = 0x3f; // 63 (43 instructions + 20 for implementation address)
/// @notice Creates a clone proxy of the implementation contract with immutable args
/// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
/// @param implementation The implementation contract to clone
/// @param data Encoded immutable args
/// @return instance The address of the created clone
function clone(address implementation, bytes memory data) internal returns (address instance) {
(uint256 creationPtr, uint256 creationSize) = _getCreationCode(implementation, data);
assembly ("memory-safe") {
instance := create(0, creationPtr, creationSize)
// if the create failed, the instance address won't be set
if iszero(instance) {
mstore(_CUSTOM_ERROR_SIG_PTR, _CREATE_FAIL_ERROR_SIG)
revert(_CUSTOM_ERROR_SIG_PTR, _CUSTOM_ERROR_LENGTH)
}
}
}
/// @notice Creates a clone proxy of the implementation contract with immutable args
/// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
/// @param implementation The implementation contract to clone
/// @param salt The salt for create2
/// @param data Encoded immutable args
/// @return instance The address of the created clone
function cloneDeterministic(
address implementation,
bytes32 salt,
bytes memory data
) internal returns (address payable instance) {
(uint256 creationPtr, uint256 creationSize) = _getCreationCode(implementation, data);
assembly ("memory-safe") {
instance := create2(0, creationPtr, creationSize, salt)
// if the create failed, the instance address won't be set
if iszero(instance) {
mstore(_CUSTOM_ERROR_SIG_PTR, _CREATE_FAIL_ERROR_SIG)
revert(_CUSTOM_ERROR_SIG_PTR, _CUSTOM_ERROR_LENGTH)
}
}
}
/// @notice Predicts the address where a deterministic clone of implementation will be deployed
/// @param implementation The implementation contract to clone
/// @param salt The salt for create2
/// @param data Encoded immutable args
/// @return predicted The predicted address of the created clone exists
function predictDeterministicAddress(
address implementation,
bytes32 salt,
address deployer,
bytes memory data
) internal view returns (address predicted) {
(uint256 creationPtr, uint256 creationSize) = _getCreationCode(implementation, data);
bytes32 bytecodeHash;
assembly ("memory-safe") {
bytecodeHash := keccak256(creationPtr, creationSize)
}
predicted = Create2.computeAddress(salt, bytecodeHash, deployer);
}
/// @notice Predicts the address where a deterministic clone of implementation will be deployed
/// @param implementation The implementation contract to clone
/// @param salt The salt for create2
/// @param data Encoded immutable args
/// @return predicted The predicted address of the created clone exists
function predictDeterministicAddress(
address implementation,
bytes32 salt,
bytes memory data
) internal view returns (address predicted) {
predicted = predictDeterministicAddress(implementation, salt, address(this), data);
}
/// @notice Computes the creation code for a clone with immutable args
/// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
/// @param implementation The implementation contract to clone
/// @param data Encoded immutable args
/// @return ptr The ptr to the clone's bytecode
/// @return creationSize The size of the clone to be created
function _getCreationCode(
address implementation,
bytes memory data
) private view returns (uint256 ptr, uint256 creationSize) {
// unrealistic for memory ptr or data length to exceed 256 bits
assembly ("memory-safe") {
let extraLength := add(mload(data), 2) // +2 bytes for telling how much data there is appended to the call
creationSize := add(extraLength, _BOOTSTRAP_LENGTH)
let runSize := sub(creationSize, 0x0a)
// free memory pointer
ptr := mload(0x40)
// -------------------------------------------------------------------------------------------------------------
// CREATION (10 bytes)
// -------------------------------------------------------------------------------------------------------------
// 61 runtime | PUSH2 runtime (r) | r | –
// 3d | RETURNDATASIZE | 0 r | –
// 81 | DUP2 | r 0 r | –
// 60 offset | PUSH1 offset (o) | o r 0 r | –
// 3d | RETURNDATASIZE | 0 o r 0 r | –
// 39 | CODECOPY | 0 r | [0 - runSize): runtime code
// f3 | RETURN | | [0 - runSize): runtime code
// -------------------------------------------------------------------------------------------------------------
// RUNTIME (53 bytes + extraLength)
// -------------------------------------------------------------------------------------------------------------
// --- copy calldata to memmory ---
// 36 | CALLDATASIZE | cds | –
// 3d | RETURNDATASIZE | 0 cds | –
// 3d | RETURNDATASIZE | 0 0 cds | –
// 37 | CALLDATACOPY | | [0 - cds): calldata
// --- keep some values in stack ---
// 3d | RETURNDATASIZE | 0 | [0 - cds): calldata
// 3d | RETURNDATASIZE | 0 0 | [0 - cds): calldata
// 3d | RETURNDATASIZE | 0 0 0 | [0 - cds): calldata
// 3d | RETURNDATASIZE | 0 0 0 0 | [0 - cds): calldata
// 61 extra | PUSH2 extra (e) | e 0 0 0 0 | [0 - cds): calldata
// --- copy extra data to memory ---
// 80 | DUP1 | e e 0 0 0 0 | [0 - cds): calldata
// 60 0x35 | PUSH1 0x35 | 0x35 e e 0 0 0 0 | [0 - cds): calldata
// 36 | CALLDATASIZE | cds 0x35 e e 0 0 0 0 | [0 - cds): calldata
// 39 | CODECOPY | e 0 0 0 0 | [0 - cds): calldata, [cds - cds + e): extraData
// --- delegate call to the implementation contract ---
// 36 | CALLDATASIZE | cds e 0 0 0 0 | [0 - cds): calldata, [cds - cds + e): extraData
// 01 | ADD | cds+e 0 0 0 0 | [0 - cds): calldata, [cds - cds + e): extraData
// 3d | RETURNDATASIZE | 0 cds+e 0 0 0 0 | [0 - cds): calldata, [cds - cds + e): extraData
// 73 addr | PUSH20 addr | addr 0 cds+e 0 0 0 0 | [0 - cds): calldata, [cds - cds + e): extraData
// 5a | GAS | gas addr 0 cds+e 0 0 0 0| [0 - cds): calldata, [cds - cds + e): extraData
// f4 | DELEGATECALL | success 0 0 | [0 - cds): calldata, [cds - cds + e): extraData
// --- copy return data to memory ---
// 3d | RETURNDATASIZE | rds success 0 0 | [0 - cds): calldata, [cds - cds + e): extraData
// 3d | RETURNDATASIZE | rds rds success 0 0 | [0 - cds): calldata, [cds - cds + e): extraData
// 93 | SWAP4 | 0 rds success 0 rds | [0 - cds): calldata, [cds - cds + e): extraData
// 80 | DUP1 | 0 0 rds success 0 rds | [0 - cds): calldata, [cds - cds + e): extraData
// 3e | RETURNDATACOPY | success 0 rds | [0 - rds): returndata, ... the rest might be dirty
// 60 0x33 | PUSH1 0x33 | 0x33 success 0 rds | [0 - rds): returndata, ... the rest might be dirty
// 57 | JUMPI | 0 rds | [0 - rds): returndata, ... the rest might be dirty
// --- revert ---
// fd | REVERT | | [0 - rds): returndata, ... the rest might be dirty
// --- return ---
// 5b | JUMPDEST | 0 rds | [0 - rds): returndata, ... the rest might be dirty
// f3 | RETURN | | [0 - rds): returndata, ... the rest might be dirty
mstore(
ptr,
or(
// ⎬ ♠︎♠︎♠︎♠︎ ♣︎♣︎ ⎨ - ♥︎♥︎♥︎♥︎- ♦︎♦︎ - >
hex"610000_3d_81_600a_3d_39_f3_36_3d_3d_37_3d_3d_3d_3d_610000_80_6035_36_39_36_01_3d_73", // 30 bytes
or(shl(0xe8, runSize), shl(0x58, extraLength)) // ♠︎=runSize, ♥︎=extraLength
)
)
mstore(add(ptr, 0x1e), shl(0x60, implementation)) // 20 bytes
// > - ☼☼ - |
mstore(add(ptr, 0x32), hex"5a_f4_3d_3d_93_80_3e_6033_57_fd_5b_f3") // 13 bytes
// -------------------------------------------------------------------------------------------------------------
// APPENDED DATA (Accessible from extcodecopy)
// (but also send as appended data to the delegatecall)
// -------------------------------------------------------------------------------------------------------------
extraLength := sub(extraLength, 2)
if iszero(
staticcall(
gas(),
0x04, // identity precompile
add(data, 0x20), // copy source
extraLength,
add(ptr, _BOOTSTRAP_LENGTH), // copy destination
extraLength
)
) {
mstore(_CUSTOM_ERROR_SIG_PTR, _IDENTITY_PRECOMPILE_ERROR_SIG)
revert(_CUSTOM_ERROR_SIG_PTR, _CUSTOM_ERROR_LENGTH)
}
mstore(add(add(ptr, _BOOTSTRAP_LENGTH), extraLength), shl(0xf0, add(extraLength, 2)))
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
/**
* @author OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.0/contracts/utils/Create2.sol)
* @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
* `CREATE2` can be used to compute in advance the address where a smart
* contract will be deployed, which allows for interesting new mechanisms known
* as 'counterfactual interactions'.
*
* See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
* information.
*/
library Create2 {
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
* `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
assembly ("memory-safe") {
let ptr := mload(0x40) // Get free memory pointer
// | | ↓ ptr ... ↓ ptr + 0x0B (start) ... ↓ ptr + 0x20 ... ↓ ptr + 0x40 ... |
// |-------------------|---------------------------------------------------------------------------|
// | bytecodeHash | CCCCCCCCCCCCC...CC |
// | salt | BBBBBBBBBBBBB...BB |
// | deployer | 000000...0000AAAAAAAAAAAAAAAAAAA...AA |
// | 0xFF | FF |
// |-------------------|---------------------------------------------------------------------------|
// | memory | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
// | keccak(start, 85) | ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |
mstore(add(ptr, 0x40), bytecodeHash)
mstore(add(ptr, 0x20), salt)
mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
mstore8(start, 0xff)
addr := keccak256(start, 85)
}
}
}
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.15;
/// @title ImmutableArgs
/// @author zefram.eth, Saw-mon & Natalie
/// @notice Provides helper functions for reading immutable args from calldata
library ImmutableArgs {
function addr() internal pure returns (address arg) {
assembly {
arg := shr(0x60, calldataload(sub(calldatasize(), 22)))
}
}
/// @notice Reads an immutable arg with type address
/// @param offset The offset of the arg in the packed data
/// @return arg The arg value
function addressAt(uint256 offset) internal pure returns (address arg) {
uint256 start = _startOfImmutableArgs();
assembly {
arg := shr(0x60, calldataload(add(start, offset)))
}
}
/// @notice Reads an immutable arg with type uint256
/// @param offset The offset of the arg in the packed data
/// @return arg The arg value
function uint256At(uint256 offset) internal pure returns (uint256 arg) {
uint256 start = _startOfImmutableArgs();
assembly {
arg := calldataload(add(start, offset))
}
}
function all() internal pure returns (bytes memory args) {
uint256 start = _startOfImmutableArgs();
unchecked {
args = msg.data[start:msg.data.length - 2];
}
}
/// @return offset The offset of the packed immutable args in calldata
function _startOfImmutableArgs() private pure returns (uint256 offset) {
assembly {
// read final 2 bytes of calldata, i.e. `extraLength`
offset := sub(calldatasize(), shr(0xf0, calldataload(sub(calldatasize(), 2))))
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1);
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator,
Rounding rounding
) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10**64) {
value /= 10**64;
result += 64;
}
if (value >= 10**32) {
value /= 10**32;
result += 32;
}
if (value >= 10**16) {
value /= 10**16;
result += 16;
}
if (value >= 10**8) {
value /= 10**8;
result += 8;
}
if (value >= 10**4) {
value /= 10**4;
result += 4;
}
if (value >= 10**2) {
value /= 10**2;
result += 2;
}
if (value >= 10**1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
}
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
/*//////////////////////////////////////////////////////////////
SIMPLIFIED FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
uint256 internal constant MAX_UINT256 = 2**256 - 1;
uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
}
function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
}
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}
/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
function mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// Divide x * y by the denominator.
z := div(mul(x, y), denominator)
}
}
function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// If x * y modulo the denominator is strictly greater than 0,
// 1 is added to round up the division of x * y by the denominator.
z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
}
}
function rpow(
uint256 x,
uint256 n,
uint256 scalar
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
switch x
case 0 {
switch n
case 0 {
// 0 ** 0 = 1
z := scalar
}
default {
// 0 ** n = 0
z := 0
}
}
default {
switch mod(n, 2)
case 0 {
// If n is even, store scalar in z for now.
z := scalar
}
default {
// If n is odd, store x in z for now.
z := x
}
// Shifting right by 1 is like dividing by 2.
let half := shr(1, scalar)
for {
// Shift n right by 1 before looping to halve it.
n := shr(1, n)
} n {
// Shift n right by 1 each iteration to halve it.
n := shr(1, n)
} {
// Revert immediately if x ** 2 would overflow.
// Equivalent to iszero(eq(div(xx, x), x)) here.
if shr(128, x) {
revert(0, 0)
}
// Store x squared.
let xx := mul(x, x)
// Round to the nearest number.
let xxRound := add(xx, half)
// Revert if xx + half overflowed.
if lt(xxRound, xx) {
revert(0, 0)
}
// Set x to scaled xxRound.
x := div(xxRound, scalar)
// If n is even:
if mod(n, 2) {
// Compute z * x.
let zx := mul(z, x)
// If z * x overflowed:
if iszero(eq(div(zx, x), z)) {
// Revert if x is non-zero.
if iszero(iszero(x)) {
revert(0, 0)
}
}
// Round to the nearest number.
let zxRound := add(zx, half)
// Revert if zx + half overflowed.
if lt(zxRound, zx) {
revert(0, 0)
}
// Return properly scaled zxRound.
z := div(zxRound, scalar)
}
}
}
}
}
/*//////////////////////////////////////////////////////////////
GENERAL NUMBER UTILITIES
//////////////////////////////////////////////////////////////*/
function sqrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
let y := x // We start y at x, which will help us make our initial estimate.
z := 181 // The "correct" value is 1, but this saves a multiplication later.
// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
// We check y >= 2^(k + 8) but shift right by k bits
// each branch to ensure that if x >= 256, then y >= 256.
if iszero(lt(y, 0x10000000000000000000000000000000000)) {
y := shr(128, y)
z := shl(64, z)
}
if iszero(lt(y, 0x1000000000000000000)) {
y := shr(64, y)
z := shl(32, z)
}
if iszero(lt(y, 0x10000000000)) {
y := shr(32, y)
z := shl(16, z)
}
if iszero(lt(y, 0x1000000)) {
y := shr(16, y)
z := shl(8, z)
}
// Goal was to get z*z*y within a small factor of x. More iterations could
// get y in a tighter range. Currently, we will have y in [256, 256*2^16).
// We ensured y >= 256 so that the relative difference between y and y+1 is small.
// That's not possible if x < 256 but we can just verify those cases exhaustively.
// Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
// Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
// Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.
// For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
// (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.
// Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
// sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.
// There is no overflow risk here since y < 2^136 after the first branch above.
z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.
// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
// If x+1 is a perfect square, the Babylonian method cycles between
// floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
// Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
// If you don't care whether the floor or ceil square root is returned, you can remove this statement.
z := sub(z, lt(div(x, z), z))
}
}
function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Mod x by y. Note this will return
// 0 instead of reverting if y is zero.
z := mod(x, y)
}
}
function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
// Divide x by y. Note this will return
// 0 instead of reverting if y is zero.
r := div(x, y)
}
}
function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Add 1 to x * y if x % y > 0. Note this will
// return 0 instead of reverting if y is zero.
z := add(gt(mod(x, y), 0), div(x, y))
}
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
import './pool/IUniswapV3PoolImmutables.sol';
import './pool/IUniswapV3PoolState.sol';
import './pool/IUniswapV3PoolDerivedState.sol';
import './pool/IUniswapV3PoolActions.sol';
import './pool/IUniswapV3PoolOwnerActions.sol';
import './pool/IUniswapV3PoolEvents.sol';
/// @title The interface for a Uniswap V3 Pool
/// @notice A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform
/// to the ERC20 specification
/// @dev The pool interface is broken up into many smaller pieces
interface IUniswapV3Pool is
IUniswapV3PoolImmutables,
IUniswapV3PoolState,
IUniswapV3PoolDerivedState,
IUniswapV3PoolActions,
IUniswapV3PoolOwnerActions,
IUniswapV3PoolEvents
{
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Callback for IUniswapV3PoolActions#mint
/// @notice Any contract that calls IUniswapV3PoolActions#mint must implement this interface
interface IUniswapV3MintCallback {
/// @notice Called to `msg.sender` after minting liquidity to a position from IUniswapV3Pool#mint.
/// @dev In the implementation you must pay the pool tokens owed for the minted liquidity.
/// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
/// @param amount0Owed The amount of token0 due to the pool for the minted liquidity
/// @param amount1Owed The amount of token1 due to the pool for the minted liquidity
/// @param data Any data passed through by the caller via the IUniswapV3PoolActions#mint call
function uniswapV3MintCallback(
uint256 amount0Owed,
uint256 amount1Owed,
bytes calldata data
) external;
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Callback for IUniswapV3PoolActions#swap
/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
interface IUniswapV3SwapCallback {
/// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
/// @dev In the implementation you must pay the pool tokens owed for the swap.
/// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
/// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
/// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
/// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
/// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
/// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
/// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes calldata data
) external;
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Permissionless pool actions
/// @notice Contains pool methods that can be called by anyone
interface IUniswapV3PoolActions {
/// @notice Sets the initial price for the pool
/// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value
/// @param sqrtPriceX96 the initial sqrt price of the pool as a Q64.96
function initialize(uint160 sqrtPriceX96) external;
/// @notice Adds liquidity for the given recipient/tickLower/tickUpper position
/// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback
/// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends
/// on tickLower, tickUpper, the amount of liquidity, and the current price.
/// @param recipient The address for which the liquidity will be created
/// @param tickLower The lower tick of the position in which to add liquidity
/// @param tickUpper The upper tick of the position in which to add liquidity
/// @param amount The amount of liquidity to mint
/// @param data Any data that should be passed through to the callback
/// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback
/// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback
function mint(
address recipient,
int24 tickLower,
int24 tickUpper,
uint128 amount,
bytes calldata data
) external returns (uint256 amount0, uint256 amount1);
/// @notice Collects tokens owed to a position
/// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity.
/// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or
/// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the
/// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity.
/// @param recipient The address which should receive the fees collected
/// @param tickLower The lower tick of the position for which to collect fees
/// @param tickUpper The upper tick of the position for which to collect fees
/// @param amount0Requested How much token0 should be withdrawn from the fees owed
/// @param amount1Requested How much token1 should be withdrawn from the fees owed
/// @return amount0 The amount of fees collected in token0
/// @return amount1 The amount of fees collected in token1
function collect(
address recipient,
int24 tickLower,
int24 tickUpper,
uint128 amount0Requested,
uint128 amount1Requested
) external returns (uint128 amount0, uint128 amount1);
/// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position
/// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0
/// @dev Fees must be collected separately via a call to #collect
/// @param tickLower The lower tick of the position for which to burn liquidity
/// @param tickUpper The upper tick of the position for which to burn liquidity
/// @param amount How much liquidity to burn
/// @return amount0 The amount of token0 sent to the recipient
/// @return amount1 The amount of token1 sent to the recipient
function burn(
int24 tickLower,
int24 tickUpper,
uint128 amount
) external returns (uint256 amount0, uint256 amount1);
/// @notice Swap token0 for token1, or token1 for token0
/// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback
/// @param recipient The address to receive the output of the swap
/// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0
/// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative)
/// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this
/// value after the swap. If one for zero, the price cannot be greater than this value after the swap
/// @param data Any data to be passed through to the callback
/// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive
/// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive
function swap(
address recipient,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata data
) external returns (int256 amount0, int256 amount1);
/// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback
/// @dev The caller of this method receives a callback in the form of IUniswapV3FlashCallback#uniswapV3FlashCallback
/// @dev Can be used to donate underlying tokens pro-rata to currently in-range liquidity providers by calling
/// with 0 amount{0,1} and sending the donation amount(s) from the callback
/// @param recipient The address which will receive the token0 and token1 amounts
/// @param amount0 The amount of token0 to send
/// @param amount1 The amount of token1 to send
/// @param data Any data to be passed through to the callback
function flash(
address recipient,
uint256 amount0,
uint256 amount1,
bytes calldata data
) external;
/// @notice Increase the maximum number of price and liquidity observations that this pool will store
/// @dev This method is no-op if the pool already has an observationCardinalityNext greater than or equal to
/// the input observationCardinalityNext.
/// @param observationCardinalityNext The desired minimum number of observations for the pool to store
function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external;
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Pool state that is not stored
/// @notice Contains view functions to provide information about the pool that is computed rather than stored on the
/// blockchain. The functions here may have variable gas costs.
interface IUniswapV3PoolDerivedState {
/// @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp
/// @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing
/// the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick,
/// you must call it with secondsAgos = [3600, 0].
/// @dev The time weighted average tick represents the geometric time weighted average price of the pool, in
/// log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio.
/// @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned
/// @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp
/// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity-in-range value as of each `secondsAgos` from the current block
/// timestamp
function observe(uint32[] calldata secondsAgos)
external
view
returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s);
/// @notice Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range
/// @dev Snapshots must only be compared to other snapshots, taken over a period for which a position existed.
/// I.e., snapshots cannot be compared if a position is not held for the entire period between when the first
/// snapshot is taken and the second snapshot is taken.
/// @param tickLower The lower tick of the range
/// @param tickUpper The upper tick of the range
/// @return tickCumulativeInside The snapshot of the tick accumulator for the range
/// @return secondsPerLiquidityInsideX128 The snapshot of seconds per liquidity for the range
/// @return secondsInside The snapshot of seconds per liquidity for the range
function snapshotCumulativesInside(int24 tickLower, int24 tickUpper)
external
view
returns (
int56 tickCumulativeInside,
uint160 secondsPerLiquidityInsideX128,
uint32 secondsInside
);
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Events emitted by a pool
/// @notice Contains all events emitted by the pool
interface IUniswapV3PoolEvents {
/// @notice Emitted exactly once by a pool when #initialize is first called on the pool
/// @dev Mint/Burn/Swap cannot be emitted by the pool before Initialize
/// @param sqrtPriceX96 The initial sqrt price of the pool, as a Q64.96
/// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool
event Initialize(uint160 sqrtPriceX96, int24 tick);
/// @notice Emitted when liquidity is minted for a given position
/// @param sender The address that minted the liquidity
/// @param owner The owner of the position and recipient of any minted liquidity
/// @param tickLower The lower tick of the position
/// @param tickUpper The upper tick of the position
/// @param amount The amount of liquidity minted to the position range
/// @param amount0 How much token0 was required for the minted liquidity
/// @param amount1 How much token1 was required for the minted liquidity
event Mint(
address sender,
address indexed owner,
int24 indexed tickLower,
int24 indexed tickUpper,
uint128 amount,
uint256 amount0,
uint256 amount1
);
/// @notice Emitted when fees are collected by the owner of a position
/// @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees
/// @param owner The owner of the position for which fees are collected
/// @param tickLower The lower tick of the position
/// @param tickUpper The upper tick of the position
/// @param amount0 The amount of token0 fees collected
/// @param amount1 The amount of token1 fees collected
event Collect(
address indexed owner,
address recipient,
int24 indexed tickLower,
int24 indexed tickUpper,
uint128 amount0,
uint128 amount1
);
/// @notice Emitted when a position's liquidity is removed
/// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect
/// @param owner The owner of the position for which liquidity is removed
/// @param tickLower The lower tick of the position
/// @param tickUpper The upper tick of the position
/// @param amount The amount of liquidity to remove
/// @param amount0 The amount of token0 withdrawn
/// @param amount1 The amount of token1 withdrawn
event Burn(
address indexed owner,
int24 indexed tickLower,
int24 indexed tickUpper,
uint128 amount,
uint256 amount0,
uint256 amount1
);
/// @notice Emitted by the pool for any swaps between token0 and token1
/// @param sender The address that initiated the swap call, and that received the callback
/// @param recipient The address that received the output of the swap
/// @param amount0 The delta of the token0 balance of the pool
/// @param amount1 The delta of the token1 balance of the pool
/// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96
/// @param liquidity The liquidity of the pool after the swap
/// @param tick The log base 1.0001 of price of the pool after the swap
event Swap(
address indexed sender,
address indexed recipient,
int256 amount0,
int256 amount1,
uint160 sqrtPriceX96,
uint128 liquidity,
int24 tick
);
/// @notice Emitted by the pool for any flashes of token0/token1
/// @param sender The address that initiated the swap call, and that received the callback
/// @param recipient The address that received the tokens from flash
/// @param amount0 The amount of token0 that was flashed
/// @param amount1 The amount of token1 that was flashed
/// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee
/// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee
event Flash(
address indexed sender,
address indexed recipient,
uint256 amount0,
uint256 amount1,
uint256 paid0,
uint256 paid1
);
/// @notice Emitted by the pool for increases to the number of observations that can be stored
/// @dev observationCardinalityNext is not the observation cardinality until an observation is written at the index
/// just before a mint/swap/burn.
/// @param observationCardinalityNextOld The previous value of the next observation cardinality
/// @param observationCardinalityNextNew The updated value of the next observation cardinality
event IncreaseObservationCardinalityNext(
uint16 observationCardinalityNextOld,
uint16 observationCardinalityNextNew
);
/// @notice Emitted when the protocol fee is changed by the pool
/// @param feeProtocol0Old The previous value of the token0 protocol fee
/// @param feeProtocol1Old The previous value of the token1 protocol fee
/// @param feeProtocol0New The updated value of the token0 protocol fee
/// @param feeProtocol1New The updated value of the token1 protocol fee
event SetFeeProtocol(uint8 feeProtocol0Old, uint8 feeProtocol1Old, uint8 feeProtocol0New, uint8 feeProtocol1New);
/// @notice Emitted when the collected protocol fees are withdrawn by the factory owner
/// @param sender The address that collects the protocol fees
/// @param recipient The address that receives the collected protocol fees
/// @param amount0 The amount of token0 protocol fees that is withdrawn
/// @param amount0 The amount of token1 protocol fees that is withdrawn
event CollectProtocol(address indexed sender, address indexed recipient, uint128 amount0, uint128 amount1);
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Pool state that never changes
/// @notice These parameters are fixed for a pool forever, i.e., the methods will always return the same values
interface IUniswapV3PoolImmutables {
/// @notice The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface
/// @return The contract address
function factory() external view returns (address);
/// @notice The first of the two tokens of the pool, sorted by address
/// @return The token contract address
function token0() external view returns (address);
/// @notice The second of the two tokens of the pool, sorted by address
/// @return The token contract address
function token1() external view returns (address);
/// @notice The pool's fee in hundredths of a bip, i.e. 1e-6
/// @return The fee
function fee() external view returns (uint24);
/// @notice The pool tick spacing
/// @dev Ticks can only be used at multiples of this value, minimum of 1 and always positive
/// e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ...
/// This value is an int24 to avoid casting even though it is always positive.
/// @return The tick spacing
function tickSpacing() external view returns (int24);
/// @notice The maximum amount of position liquidity that can use any tick in the range
/// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and
/// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool
/// @return The max amount of liquidity per tick
function maxLiquidityPerTick() external view returns (uint128);
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Permissioned pool actions
/// @notice Contains pool methods that may only be called by the factory owner
interface IUniswapV3PoolOwnerActions {
/// @notice Set the denominator of the protocol's % share of the fees
/// @param feeProtocol0 new protocol fee for token0 of the pool
/// @param feeProtocol1 new protocol fee for token1 of the pool
function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external;
/// @notice Collect the protocol fee accrued to the pool
/// @param recipient The address to which collected protocol fees should be sent
/// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1
/// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0
/// @return amount0 The protocol fee collected in token0
/// @return amount1 The protocol fee collected in token1
function collectProtocol(
address recipient,
uint128 amount0Requested,
uint128 amount1Requested
) external returns (uint128 amount0, uint128 amount1);
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Pool state that can change
/// @notice These methods compose the pool's state, and can change with any frequency including multiple times
/// per transaction
interface IUniswapV3PoolState {
/// @notice The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas
/// when accessed externally.
/// @return sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value
/// tick The current tick of the pool, i.e. according to the last tick transition that was run.
/// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick
/// boundary.
/// observationIndex The index of the last oracle observation that was written,
/// observationCardinality The current maximum number of observations stored in the pool,
/// observationCardinalityNext The next maximum number of observations, to be updated when the observation.
/// feeProtocol The protocol fee for both tokens of the pool.
/// Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0
/// is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee.
/// unlocked Whether the pool is currently locked to reentrancy
function slot0()
external
view
returns (
uint160 sqrtPriceX96,
int24 tick,
uint16 observationIndex,
uint16 observationCardinality,
uint16 observationCardinalityNext,
uint8 feeProtocol,
bool unlocked
);
/// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool
/// @dev This value can overflow the uint256
function feeGrowthGlobal0X128() external view returns (uint256);
/// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool
/// @dev This value can overflow the uint256
function feeGrowthGlobal1X128() external view returns (uint256);
/// @notice The amounts of token0 and token1 that are owed to the protocol
/// @dev Protocol fees will never exceed uint128 max in either token
function protocolFees() external view returns (uint128 token0, uint128 token1);
/// @notice The currently in range liquidity available to the pool
/// @dev This value has no relationship to the total liquidity across all ticks
function liquidity() external view returns (uint128);
/// @notice Look up information about a specific tick in the pool
/// @param tick The tick to look up
/// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or
/// tick upper,
/// liquidityNet how much liquidity changes when the pool price crosses the tick,
/// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0,
/// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1,
/// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick
/// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick,
/// secondsOutside the seconds spent on the other side of the tick from the current tick,
/// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise equal to false.
/// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0.
/// In addition, these values are only relative and must be used only in comparison to previous snapshots for
/// a specific position.
function ticks(int24 tick)
external
view
returns (
uint128 liquidityGross,
int128 liquidityNet,
uint256 feeGrowthOutside0X128,
uint256 feeGrowthOutside1X128,
int56 tickCumulativeOutside,
uint160 secondsPerLiquidityOutsideX128,
uint32 secondsOutside,
bool initialized
);
/// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information
function tickBitmap(int16 wordPosition) external view returns (uint256);
/// @notice Returns the information about a position by the position's key
/// @param key The position's key is a hash of a preimage composed by the owner, tickLower and tickUpper
/// @return _liquidity The amount of liquidity in the position,
/// Returns feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke,
/// Returns feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke,
/// Returns tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke,
/// Returns tokensOwed1 the computed amount of token1 owed to the position as of the last mint/burn/poke
function positions(bytes32 key)
external
view
returns (
uint128 _liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128,
uint128 tokensOwed0,
uint128 tokensOwed1
);
/// @notice Returns data about a specific observation index
/// @param index The element of the observations array to fetch
/// @dev You most likely want to use #observe() instead of this method to get an observation as of some amount of time
/// ago, rather than at a specific index in the array.
/// @return blockTimestamp The timestamp of the observation,
/// Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp,
/// Returns secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp,
/// Returns initialized whether the observation has been initialized and the values are safe to use
function observations(uint256 index)
external
view
returns (
uint32 blockTimestamp,
int56 tickCumulative,
uint160 secondsPerLiquidityCumulativeX128,
bool initialized
);
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;
import {Math} from "openzeppelin-contracts/utils/math/Math.sol";
import {ERC20, SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {IUniswapV3MintCallback} from "v3-core/contracts/interfaces/callback/IUniswapV3MintCallback.sol";
import {IUniswapV3Pool} from "v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import {LIQUIDATION_GRACE_PERIOD} from "./libraries/constants/Constants.sol";
import {Q96} from "./libraries/constants/Q.sol";
import {BalanceSheet, Assets, Prices} from "./libraries/BalanceSheet.sol";
import {LiquidityAmounts} from "./libraries/LiquidityAmounts.sol";
import {Positions} from "./libraries/Positions.sol";
import {TickMath} from "./libraries/TickMath.sol";
import {Lender} from "./Lender.sol";
import {VolatilityOracle} from "./VolatilityOracle.sol";
interface ILiquidator {
receive() external payable;
function swap1For0(bytes calldata data, uint256 received1, uint256 expected0) external;
function swap0For1(bytes calldata data, uint256 received0, uint256 expected1) external;
}
interface IManager {
function callback(bytes calldata data) external returns (uint144 positions);
}
/// @title Borrower
/// @author Aloe Labs, Inc.
/// @dev "Test everything; hold fast what is good." - 1 Thessalonians 5:21
contract Borrower is IUniswapV3MintCallback {
using SafeTransferLib for ERC20;
using Positions for int24[6];
event Warn();
event Liquidate(uint256 repay0, uint256 repay1, uint256 incentive1, uint256 priceX96);
uint8 public constant B = 5;
uint256 public constant ANTE = 0.001 ether;
/// @notice The oracle to use for prices and implied volatility
VolatilityOracle public immutable ORACLE;
/// @notice The Uniswap pair in which the vault will manage positions
IUniswapV3Pool public immutable UNISWAP_POOL;
/// @notice The first token of the Uniswap pair
ERC20 public immutable TOKEN0;
/// @notice The second token of the Uniswap pair
ERC20 public immutable TOKEN1;
/// @notice The lender of `TOKEN0`
Lender public immutable LENDER0;
/// @notice The lender of `TOKEN1`
Lender public immutable LENDER1;
enum State {
Ready,
Locked,
InModifyCallback
}
struct Slot0 {
address owner;
uint88 unleashLiquidationTime;
State state;
}
Slot0 public slot0;
int24[6] public positions;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR & INITIALIZER
//////////////////////////////////////////////////////////////*/
constructor(VolatilityOracle oracle, IUniswapV3Pool pool, Lender lender0, Lender lender1) {
ORACLE = oracle;
UNISWAP_POOL = pool;
LENDER0 = lender0;
LENDER1 = lender1;
TOKEN0 = lender0.asset();
TOKEN1 = lender1.asset();
require(pool.token0() == address(TOKEN0));
require(pool.token1() == address(TOKEN1));
}
function initialize(address owner_) external {
require(slot0.owner == address(0));
slot0.owner = owner_;
}
/*//////////////////////////////////////////////////////////////
MAIN ENTRY POINTS
//////////////////////////////////////////////////////////////*/
/**
* @notice 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() external {
// Load `slot0` from storage. We don't use `_loadSlot0` here because the `require` is different
uint256 slot0_;
assembly ("memory-safe") {
slot0_ := sload(slot0.slot)
}
// Equivalent to `slot0.state == State.Ready && slot0.unleashLiquidationTime == 0`
require(slot0_ >> 160 == 0);
{
// Fetch prices from oracle
Prices memory prices = getPrices();
// Withdraw Uniswap positions while tallying assets
Assets memory assets = _getAssets(positions.read(), prices, false);
// Fetch liabilities from lenders
(uint256 liabilities0, uint256 liabilities1) = _getLiabilities();
// Ensure only unhealthy accounts get warned
require(!BalanceSheet.isHealthy(prices, assets, liabilities0, liabilities1), "Aloe: healthy");
}
unchecked {
_saveSlot0(slot0_, (block.timestamp + LIQUIDATION_GRACE_PERIOD) << 160);
}
emit Warn();
}
/**
* @notice 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.
* @dev 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.
* @param callee A smart contract capable of swapping `TOKEN0` for `TOKEN1` and vice versa
* @param data Encoded parameters that get forwarded to `callee` callbacks
* @param strain 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.
*/
function liquidate(ILiquidator callee, bytes calldata data, uint256 strain) external {
uint256 slot0_ = _loadSlot0();
_saveSlot0(slot0_, _formatted(State.Locked));
// Fetch prices from oracle
Prices memory prices = getPrices();
uint256 liabilities0;
uint256 liabilities1;
uint256 incentive1;
uint256 priceX96;
{
// Withdraw Uniswap positions while tallying assets
Assets memory assets = _getAssets(positions.read(), prices, true);
// Fetch liabilities from lenders
(liabilities0, liabilities1) = _getLiabilities();
// Calculate liquidation incentive
(incentive1, priceX96) = BalanceSheet.computeLiquidationIncentive(
assets.fixed0 + assets.fluid0C, // total assets0 at `prices.c` (the TWAP)
assets.fixed1 + assets.fluid1C, // total assets1 at `prices.c` (the TWAP)
liabilities0,
liabilities1,
prices.c
);
// Ensure only unhealthy accounts can be liquidated
require(!BalanceSheet.isHealthy(prices, assets, liabilities0, liabilities1, incentive1), "Aloe: healthy");
}
// NOTE: The health check values assets at the TWAP and is difficult to manipulate. However,
// the instantaneous price does impact what tokens we receive when burning Uniswap positions.
// As such, additional calls to `TOKEN0.balanceOf` and `TOKEN1.balanceOf` are required for
// precise inventory, and we take care not to change `incentive1`.
unchecked {
// Figure out what portion of liabilities can be repaid using existing assets
uint256 repayable0 = Math.min(liabilities0, TOKEN0.balanceOf(address(this)));
uint256 repayable1 = Math.min(liabilities1, TOKEN1.balanceOf(address(this)));
// See what remains (similar to "shortfall" in BalanceSheet)
liabilities0 -= repayable0;
liabilities1 -= repayable1;
if (liabilities0 + liabilities1 == 0 || (liabilities0 > 0 && liabilities1 > 0)) {
// If both are zero or neither is zero, there's nothing more to do.
// Callbacks/swaps won't help.
incentive1 = 0;
} else if (liabilities0 > 0) {
uint256 unleashTime = slot0_ >> 160;
require(0 < unleashTime && unleashTime < block.timestamp, "Aloe: grace");
liabilities0 /= strain;
incentive1 /= strain;
// NOTE: This value is not constrained to `TOKEN1.balanceOf(address(this))`, so liquidators
// are responsible for setting `strain` such that the transfer doesn't revert. This shouldn't
// be an issue unless the borrower has already started accruing bad debt.
uint256 available1 = Math.mulDiv(liabilities0, priceX96, Q96) + incentive1;
TOKEN1.safeTransfer(address(callee), available1);
callee.swap1For0(data, available1, liabilities0);
repayable0 += liabilities0;
} else {
uint256 unleashTime = slot0_ >> 160;
require(0 < unleashTime && unleashTime < block.timestamp, "Aloe: grace");
liabilities1 /= strain;
incentive1 /= strain;
// NOTE: This value is not constrained to `TOKEN0.balanceOf(address(this))`, so liquidators
// are responsible for setting `strain` such that the transfer doesn't revert. This shouldn't
// be an issue unless the borrower has already started accruing bad debt.
uint256 available0 = Math.mulDiv(liabilities1 + incentive1, Q96, priceX96);
TOKEN0.safeTransfer(address(callee), available0);
callee.swap0For1(data, available0, liabilities1);
repayable1 += liabilities1;
}
_repay(repayable0, repayable1);
payable(callee).transfer(address(this).balance / strain);
_saveSlot0(slot0_ % (1 << 160), _formatted(State.Ready));
emit Liquidate(repayable0, repayable1, incentive1, priceX96);
}
}
/**
* @notice 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`, `borrow`,
* and `repay`) and if `allowances` are set, it also has permission to transfer ERC20s. Whatever
* `callee` does, the account MUST be healthy after the callback.
* @param callee The smart contract that will get temporary control of this account
* @param data Encoded parameters that get forwarded to `callee`
* @param allowances Whether to approve `callee` to transfer ERC20s. The first entry is for `TOKEN0`,
* and the 2nd is for `TOKEN1`.
*/
function modify(IManager callee, bytes calldata data, bool[2] calldata allowances) external payable {
require(_loadSlot0() % (1 << 160) == uint160(msg.sender), "Aloe: only owner");
if (allowances[0]) TOKEN0.safeApprove(address(callee), type(uint256).max);
if (allowances[1]) TOKEN1.safeApprove(address(callee), type(uint256).max);
_saveSlot0(uint160(msg.sender), _formatted(State.InModifyCallback));
int24[] memory positions_ = positions.write(callee.callback(data));
_saveSlot0(uint160(msg.sender), _formatted(State.Ready));
if (allowances[0]) TOKEN0.safeApprove(address(callee), 1);
if (allowances[1]) TOKEN1.safeApprove(address(callee), 1);
Prices memory prices = getPrices();
Assets memory assets = _getAssets(positions_, prices, false);
(uint256 liabilities0, uint256 liabilities1) = _getLiabilities();
require(BalanceSheet.isHealthy(prices, assets, liabilities0, liabilities1), "Aloe: unhealthy");
unchecked {
if (liabilities0 + liabilities1 > 0) require(address(this).balance > ANTE, "Aloe: missing ante");
}
}
/*//////////////////////////////////////////////////////////////
SUB-COMMANDS
//////////////////////////////////////////////////////////////*/
/**
* @notice Callback for Uniswap V3 pool; necessary for `uniswapDeposit` to work
* @param amount0 The amount of `TOKEN0` owed to the `UNISWAP_POOL`
* @param amount1 The amount of `TOKEN1` owed to the `UNISWAP_POOL`
*/
function uniswapV3MintCallback(uint256 amount0, uint256 amount1, bytes calldata) external {
require(msg.sender == address(UNISWAP_POOL));
if (amount0 > 0) TOKEN0.safeTransfer(msg.sender, amount0);
if (amount1 > 0) TOKEN1.safeTransfer(msg.sender, amount1);
}
/**
* @notice Allows the account owner to add liquidity to a Uniswap position (or create a new one).
* Only works within the `modify` callback.
* @dev The `LiquidityAmounts` library can help convert underlying amounts to units of `liquidity`
* @param lower The tick at the position's lower bound
* @param upper The tick at the position's upper bound
* @param liquidity The amount of liquidity to add, in Uniswap's internal units
* @return amount0 The precise amount of `TOKEN0` that went into the Uniswap position
* @return amount1 The precise amount of `TOKEN1` that went into the Uniswap position
*/
function uniswapDeposit(
int24 lower,
int24 upper,
uint128 liquidity
) external returns (uint256 amount0, uint256 amount1) {
require(slot0.state == State.InModifyCallback);
(amount0, amount1) = UNISWAP_POOL.mint(address(this), lower, upper, liquidity, "");
}
/**
* @notice Allows the account owner to withdraw liquidity from one of their Uniswap positions. Only
* works within the `modify` callback.
* @dev The `LiquidityAmounts` library can help convert underlying amounts to units of `liquidity`
* @param lower The tick at the position's lower bound
* @param upper The tick at the position's upper bound
* @param liquidity The amount of liquidity to remove, in Uniswap's internal units
* @return burned0 The amount of `TOKEN0` that was removed from the Uniswap position
* @return burned1 The amount of `TOKEN1` that was removed from the Uniswap position
* @return collected0 Equal to `burned0` plus any earned `TOKEN0` fees that hadn't yet been claimed
* @return collected1 Equal to `burned1` plus any earned `TOKEN1` fees that hadn't yet been claimed
*/
function uniswapWithdraw(
int24 lower,
int24 upper,
uint128 liquidity
) external returns (uint256 burned0, uint256 burned1, uint256 collected0, uint256 collected1) {
require(slot0.state == State.InModifyCallback);
(burned0, burned1, collected0, collected1) = _uniswapWithdraw(lower, upper, liquidity);
}
/**
* @notice Allows the account owner to borrow funds from `LENDER0` and `LENDER1`. Only works within
* the `modify` callback.
* @dev 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`.
* @param amount0 The amount of `TOKEN0` to borrow
* @param amount1 The amount of `TOKEN1` to borrow
* @param recipient Receives the borrowed tokens. Usually the address of this `Borrower` account.
*/
function borrow(uint256 amount0, uint256 amount1, address recipient) external {
require(slot0.state == State.InModifyCallback);
if (amount0 > 0) LENDER0.borrow(amount0, recipient);
if (amount1 > 0) LENDER1.borrow(amount1, recipient);
}
/**
* @notice Allows the account owner to repay debts to `LENDER0` and `LENDER1`. Only works within the
* `modify` callback.
* @dev This is technically unnecessary since you could call `Lender.repay` directly, specifying this
* contract as the `beneficiary` and using `modify`'s `allowances` array to give yourself full control
* of assets. We include it because it's convenient and gas-efficient for common use-cases.
* @param amount0 The amount of `TOKEN0` to repay
* @param amount1 The amount of `TOKEN1` to repay
*/
function repay(uint256 amount0, uint256 amount1) external {
require(slot0.state == State.InModifyCallback);
_repay(amount0, amount1);
}
/*//////////////////////////////////////////////////////////////
BALANCE SHEET
//////////////////////////////////////////////////////////////*/
function getUniswapPositions() external view returns (int24[] memory) {
return positions.read();
}
function getPrices() public view returns (Prices memory prices) {
(uint160 sqrtMeanPriceX96, uint256 sigma) = ORACLE.consult(UNISWAP_POOL);
// compute prices at which solvency will be checked
(uint160 a, uint160 b) = BalanceSheet.computeProbePrices(sqrtMeanPriceX96, sigma, B);
prices = Prices(a, b, sqrtMeanPriceX96);
}
function _getAssets(
int24[] memory positions_,
Prices memory prices,
bool withdraw
) private returns (Assets memory assets) {
assets.fixed0 = TOKEN0.balanceOf(address(this));
assets.fixed1 = TOKEN1.balanceOf(address(this));
uint256 count = positions_.length;
unchecked {
for (uint256 i; i < count; i += 2) {
// Load lower and upper ticks from the `positions_` array
int24 l = positions_[i];
int24 u = positions_[i + 1];
// Fetch amount of `liquidity` in the position
(uint128 liquidity, , , , ) = UNISWAP_POOL.positions(keccak256(abi.encodePacked(address(this), l, u)));
if (liquidity == 0) continue;
// Compute lower and upper sqrt ratios
uint160 L = TickMath.getSqrtRatioAtTick(l);
uint160 U = TickMath.getSqrtRatioAtTick(u);
// Compute the value of `liquidity` (in terms of token1) at both probe prices
assets.fluid1A += LiquidityAmounts.getValueOfLiquidity(prices.a, L, U, liquidity);
assets.fluid1B += LiquidityAmounts.getValueOfLiquidity(prices.b, L, U, liquidity);
// Compute what amounts underlie `liquidity` at the current TWAP
(uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity(prices.c, L, U, liquidity);
assets.fluid0C += amount0;
assets.fluid1C += amount1;
if (!withdraw) continue;
// Withdraw all `liquidity` from the position, adding earned fees as fixed assets
_uniswapWithdraw(l, u, liquidity);
}
}
}
function _getLiabilities() private view returns (uint256 amount0, uint256 amount1) {
amount0 = LENDER0.borrowBalanceStored(address(this));
amount1 = LENDER1.borrowBalanceStored(address(this));
}
/*//////////////////////////////////////////////////////////////
HELPERS
//////////////////////////////////////////////////////////////*/
function _uniswapWithdraw(
int24 lower,
int24 upper,
uint128 liquidity
) private returns (uint256 burned0, uint256 burned1, uint256 collected0, uint256 collected1) {
(burned0, burned1) = UNISWAP_POOL.burn(lower, upper, liquidity);
(collected0, collected1) = UNISWAP_POOL.collect(
address(this),
lower,
upper,
type(uint128).max,
type(uint128).max
);
}
function _repay(uint256 amount0, uint256 amount1) private {
if (amount0 > 0) {
TOKEN0.safeTransfer(address(LENDER0), amount0);
LENDER0.repay(amount0, address(this));
}
if (amount1 > 0) {
TOKEN1.safeTransfer(address(LENDER1), amount1);
LENDER1.repay(amount1, address(this));
}
}
/// @dev The name of this function impacts the optimizer's in-lining behavior. DO NOT CHANGE!
function _saveSlot0(uint256 slot0_, uint256 addend) private {
assembly ("memory-safe") {
sstore(slot0.slot, add(slot0_, addend))
}
}
function _loadSlot0() private view returns (uint256 slot0_) {
assembly ("memory-safe") {
slot0_ := sload(slot0.slot)
}
// Equivalent to `slot0.state == State.Ready`
require(slot0_ >> 248 == uint256(State.Ready));
}
function _formatted(State state) private pure returns (uint256) {
return uint256(state) << 248;
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;
import {Clones} from "clones-with-immutable-args/Clones.sol";
import {ClonesWithImmutableArgs} from "clones-with-immutable-args/ClonesWithImmutableArgs.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {IUniswapV3Pool} from "v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import {Borrower} from "./Borrower.sol";
import {Lender} from "./Lender.sol";
import {RateModel} from "./RateModel.sol";
import {VolatilityOracle} from "./VolatilityOracle.sol";
/// @title Factory
/// @author Aloe Labs, Inc.
/// @dev "Test everything; hold fast what is good." - 1 Thessalonians 5:21
contract Factory {
using ClonesWithImmutableArgs for address;
event CreateMarket(IUniswapV3Pool indexed pool, Lender lender0, Lender lender1);
event CreateBorrower(IUniswapV3Pool indexed pool, address indexed owner, address account);
struct Market {
Lender lender0;
Lender lender1;
Borrower borrowerImplementation;
}
VolatilityOracle public immutable ORACLE;
RateModel public immutable RATE_MODEL;
address public immutable lenderImplementation;
mapping(IUniswapV3Pool => Market) public getMarket;
mapping(address => bool) public isBorrower;
constructor(VolatilityOracle oracle, RateModel rateModel) {
ORACLE = oracle;
RATE_MODEL = rateModel;
lenderImplementation = address(new Lender(address(this)));
}
function createMarket(IUniswapV3Pool _pool) external {
ORACLE.prepare(_pool);
address asset0 = _pool.token0();
address asset1 = _pool.token1();
bytes32 salt = keccak256(abi.encode(_pool));
Lender lender0 = Lender(lenderImplementation.cloneDeterministic({salt: salt, data: abi.encodePacked(asset0)}));
Lender lender1 = Lender(lenderImplementation.cloneDeterministic({salt: salt, data: abi.encodePacked(asset1)}));
lender0.initialize(RATE_MODEL, 8);
lender1.initialize(RATE_MODEL, 8);
Borrower borrowerImplementation = new Borrower(ORACLE, _pool, lender0, lender1);
getMarket[_pool] = Market(lender0, lender1, borrowerImplementation);
emit CreateMarket(_pool, lender0, lender1);
}
function createBorrower(IUniswapV3Pool _pool, address _owner) external returns (address account) {
Market memory market = getMarket[_pool];
account = Clones.clone(address(market.borrowerImplementation));
Borrower(account).initialize(_owner);
isBorrower[account] = true;
market.lender0.whitelist(account);
market.lender1.whitelist(account);
emit CreateBorrower(_pool, _owner, account);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;
import {ImmutableArgs} from "clones-with-immutable-args/ImmutableArgs.sol";
import {Math} from "openzeppelin-contracts/utils/math/Math.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {BORROWS_SCALER, ONE} from "./libraries/constants/Constants.sol";
import {Q112} from "./libraries/constants/Q.sol";
import {RateModel} from "./RateModel.sol";
contract Ledger {
using FixedPointMathLib for uint256;
address public immutable FACTORY;
address public immutable RESERVE;
struct Cache {
uint256 totalSupply;
uint256 lastBalance;
uint256 lastAccrualTime;
uint256 borrowBase;
uint256 borrowIndex;
}
/*//////////////////////////////////////////////////////////////
LENDER STORAGE
//////////////////////////////////////////////////////////////*/
uint112 public totalSupply;
uint112 public lastBalance;
uint32 public lastAccrualTime;
uint184 public borrowBase;
uint72 public borrowIndex;
mapping(address => uint256) public borrows;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
/// @dev Highest 32 bits are the referral code, next 112 are the principle, lowest 112 are the shares.
mapping(address => uint256) public balances;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
ERC2612 STORAGE
//////////////////////////////////////////////////////////////*/
bytes32 internal initialDomainSeparator;
uint256 internal initialChainId;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
INCENTIVE STORAGE
//////////////////////////////////////////////////////////////*/
struct Courier {
address wallet;
uint16 cut;
}
mapping(uint32 => Courier) public couriers;
/*//////////////////////////////////////////////////////////////
GOVERNABLE PARAMETERS
//////////////////////////////////////////////////////////////*/
RateModel public rateModel;
uint8 public reserveFactor;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(address reserve) {
FACTORY = msg.sender;
RESERVE = reserve;
}
/// @notice The name of the banknote.
function name() external view returns (string memory) {
return string.concat("Aloe II ", asset().name());
}
/// @notice The symbol of the banknote.
function symbol() external view returns (string memory) {
return string.concat(asset().symbol(), "+");
}
/// @notice The number of decimals the banknote uses. Matches the underlying token.
function decimals() external view returns (uint8) {
return asset().decimals();
}
/// @notice The address of the underlying token.
function asset() public pure returns (ERC20) {
return ERC20(ImmutableArgs.addr());
}
function DOMAIN_SEPARATOR() public view returns (bytes32) {
return block.chainid == initialChainId ? initialDomainSeparator : _computeDomainSeparator();
}
/**
* @notice Gets basic lending statistics as if `accrueInterest` were just called.
* @return The updated `borrowIndex`
* @return The sum of all banknote balances, in underlying units
* @return The sum of all outstanding debts, in underlying units
* @return The sum of all banknote balances. Will differ from `totalSupply()` due to reserves inflation
*/
function stats() external view returns (uint72, uint256, uint256, uint256) {
(Cache memory cache, uint256 inventory, uint256 newTotalSupply) = _previewInterest(_getCache());
unchecked {
return (
uint72(cache.borrowIndex),
inventory,
(cache.borrowBase * cache.borrowIndex) / BORROWS_SCALER,
newTotalSupply
);
}
}
function courierOf(address account) external view returns (uint32) {
return uint32(balances[account] >> 224);
}
function principleOf(address account) external view returns (uint256) {
return (balances[account] >> 112) % Q112;
}
/// @notice The number of shares held by `account`
function balanceOf(address account) external view returns (uint256) {
return balances[account] % Q112;
}
function underlyingBalance(address account) external view returns (uint256) {
(, uint256 inventory, uint256 newTotalSupply) = _previewInterest(_getCache());
return _nominalAssets(account, inventory, newTotalSupply);
}
function underlyingBalanceStored(address account) external view returns (uint256) {
unchecked {
return
_nominalAssets({
account: account,
inventory: lastBalance + (uint256(borrowBase) * borrowIndex) / BORROWS_SCALER,
totalSupply_: totalSupply
});
}
}
function borrowBalance(address account) external view returns (uint256) {
uint256 b = borrows[account];
if (b == 0) return 0;
(Cache memory cache, , ) = _previewInterest(_getCache());
unchecked {
return ((b - 1) * cache.borrowIndex) / BORROWS_SCALER;
}
}
function borrowBalanceStored(address account) external view returns (uint256) {
uint256 b = borrows[account];
if (b == 0) return 0;
unchecked {
return ((b - 1) * borrowIndex) / BORROWS_SCALER;
}
}
/*//////////////////////////////////////////////////////////////
ERC4626 ACCOUNTING
//////////////////////////////////////////////////////////////*/
function totalAssets() external view returns (uint256) {
(, uint256 inventory, ) = _previewInterest(_getCache());
return inventory;
}
function convertToShares(uint256 assets) public view returns (uint256) {
(, uint256 inventory, uint256 newTotalSupply) = _previewInterest(_getCache());
return _convertToShares(assets, inventory, newTotalSupply, /* roundUp: */ false);
}
function convertToAssets(uint256 shares) public view returns (uint256) {
(, uint256 inventory, uint256 newTotalSupply) = _previewInterest(_getCache());
return _convertToAssets(shares, inventory, newTotalSupply, /* roundUp: */ false);
}
function previewDeposit(uint256 assets) public view returns (uint256) {
return convertToShares(assets);
}
function previewRedeem(uint256 shares) public view returns (uint256) {
return convertToAssets(shares);
}
function previewMint(uint256 shares) public view returns (uint256) {
(, uint256 inventory, uint256 newTotalSupply) = _previewInterest(_getCache());
return _convertToAssets(shares, inventory, newTotalSupply, /* roundUp: */ true);
}
function previewWithdraw(uint256 assets) public view returns (uint256) {
(, uint256 inventory, uint256 newTotalSupply) = _previewInterest(_getCache());
return _convertToShares(assets, inventory, newTotalSupply, /* roundUp: */ true);
}
/*//////////////////////////////////////////////////////////////
ERC4626 DEPOSIT/WITHDRAWAL LIMITS
//////////////////////////////////////////////////////////////*/
/**
* @notice Returns a conservative estimate of the maximum amount of `asset()` that can be deposited into the
* Vault for `receiver`, through a deposit call.
* @return The maximum amount of `asset()` that can be deposited
*
* @dev Should return the *precise* maximum. In this case that'd be on the order of 2**112 with weird constraints
* coming from both `lastBalance` and `totalSupply`, which changes during interest accrual. Instead of doing
* complicated math, we provide a constant conservative estimate of 2**96.
*
* - MUST return a limited value if receiver is subject to some deposit limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
* - MUST NOT revert.
*/
function maxDeposit(address) external pure returns (uint256) {
return 1 << 96;
}
/**
* @notice Returns a conservative estimate of the maximum number of Vault shares that can be minted for `receiver`,
* through a mint call.
* @return The maximum number of Vault shares that can be minted
*
* @dev Should return the *precise* maximum. In this case that'd be on the order of 2**112 with weird constraints
* coming from both `lastBalance` and `totalSupply`, which changes during interest accrual. Instead of doing
* complicated math, we provide a constant conservative estimate of 2**96.
*
* - MUST return a limited value if receiver is subject to some mint limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum number of shares that may be minted.
* - MUST NOT revert.
*/
function maxMint(address) external pure returns (uint256) {
return 1 << 96;
}
/**
* @notice Returns the maximum amount of `asset()` that can be withdrawn from the Vault by `owner`, through a
* withdraw call.
* @param owner The address that would burn Vault shares when withdrawing
* @return The maximum amount of `asset()` that can be withdrawn
*
* @dev
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxWithdraw(address owner) external view returns (uint256) {
(Cache memory cache, uint256 inventory, uint256 newTotalSupply) = _previewInterest(_getCache());
uint256 a = _nominalAssets(owner, inventory, newTotalSupply);
uint256 b = cache.lastBalance;
return a < b ? a : b;
}
/**
* @notice Returns the maximum number of Vault shares that can be redeemed in the Vault by `owner`, through a
* redeem call.
* @param owner The address that would burn Vault shares when redeeming
* @return The maximum number of Vault shares that can be redeemed
*
* @dev
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxRedeem(address owner) external view returns (uint256) {
(Cache memory cache, uint256 inventory, uint256 newTotalSupply) = _previewInterest(_getCache());
uint256 a = _nominalShares(owner, inventory, newTotalSupply);
uint256 b = _convertToShares(cache.lastBalance, inventory, newTotalSupply, false);
return a < b ? a : b;
}
/*//////////////////////////////////////////////////////////////
HELPERS
//////////////////////////////////////////////////////////////*/
function _computeDomainSeparator() internal view returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string version,uint256 chainId,address verifyingContract)"),
keccak256("1"),
block.chainid,
address(this)
)
);
}
function _previewInterest(Cache memory cache) internal view returns (Cache memory, uint256, uint256) {
unchecked {
uint256 oldBorrows = (cache.borrowBase * cache.borrowIndex) / BORROWS_SCALER;
uint256 oldInventory = cache.lastBalance + oldBorrows;
if (cache.lastAccrualTime == block.timestamp || oldBorrows == 0) {
return (cache, oldInventory, cache.totalSupply);
}
uint8 rf = reserveFactor;
uint256 accrualFactor = rateModel.getAccrualFactor({
elapsedTime: block.timestamp - cache.lastAccrualTime,
utilization: Math.mulDiv(1e18, oldBorrows, oldInventory)
});
cache.borrowIndex = (cache.borrowIndex * accrualFactor) / ONE;
cache.lastAccrualTime = 0; // 0 in storage means locked to reentrancy; 0 in `cache` means `borrowIndex` was updated
uint256 newInventory = cache.lastBalance + (cache.borrowBase * cache.borrowIndex) / BORROWS_SCALER;
uint256 newTotalSupply = Math.mulDiv(
cache.totalSupply,
newInventory,
newInventory - (newInventory - oldInventory) / rf
);
return (cache, newInventory, newTotalSupply);
}
}
function _convertToShares(
uint256 assets,
uint256 inventory,
uint256 totalSupply_,
bool roundUp
) internal pure returns (uint256) {
if (totalSupply_ == 0) return assets;
return roundUp ? assets.mulDivUp(totalSupply_, inventory) : assets.mulDivDown(totalSupply_, inventory);
}
function _convertToAssets(
uint256 shares,
uint256 inventory,
uint256 totalSupply_,
bool roundUp
) internal pure returns (uint256) {
if (totalSupply_ == 0) return shares;
return roundUp ? shares.mulDivUp(inventory, totalSupply_) : shares.mulDivDown(inventory, totalSupply_);
}
function _nominalShares(
address account,
uint256 inventory,
uint256 totalSupply_
) private view returns (uint256 shares) {
unchecked {
uint256 data = balances[account];
shares = data % Q112;
uint32 id = uint32(data >> 224);
if (id != 0) {
uint256 principle = _convertToShares((data >> 112) % Q112, inventory, totalSupply_, true);
if (shares > principle) {
shares -= ((shares - principle) * couriers[id].cut) / 10_000;
}
}
}
}
function _nominalAssets(
address account,
uint256 inventory,
uint256 totalSupply_
) private view returns (uint256 assets) {
unchecked {
uint256 data = balances[account];
assets = _convertToAssets(data % Q112, inventory, totalSupply_, false);
uint32 id = uint32(data >> 224);
if (id != 0) {
uint256 principle = (data >> 112) % Q112;
if (assets > principle) {
assets -= ((assets - principle) * couriers[id].cut) / 10_000;
}
}
}
}
function _getCache() private view returns (Cache memory) {
return Cache(totalSupply, lastBalance, lastAccrualTime, borrowBase, borrowIndex);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {ERC20, SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {BORROWS_SCALER, ONE, MIN_RESERVE_FACTOR, MAX_RESERVE_FACTOR} from "./libraries/constants/Constants.sol";
import {Q112} from "./libraries/constants/Q.sol";
import {SafeCastLib} from "./libraries/SafeCastLib.sol";
import {Ledger} from "./Ledger.sol";
import {RateModel} from "./RateModel.sol";
interface IFlashBorrower {
function onFlashLoan(address initiator, uint256 amount, bytes calldata data) external;
}
/// @title Lender
/// @author Aloe Labs, Inc.
/// @dev "Test everything; hold fast what is good." - 1 Thessalonians 5:21
contract Lender is Ledger {
using FixedPointMathLib for uint256;
using SafeCastLib for uint256;
using SafeTransferLib for ERC20;
event Approval(address indexed owner, address indexed spender, uint256 amount);
event Transfer(address indexed from, address indexed to, uint256 amount);
event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed caller,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
event Borrow(address indexed caller, address indexed recipient, uint256 amount, uint256 units);
event Repay(address indexed caller, address indexed beneficiary, uint256 amount, uint256 units);
event EnrollCourier(uint32 indexed id, address indexed wallet, uint16 cut);
event CreditCourier(uint32 indexed id, address indexed account);
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR & INITIALIZER
//////////////////////////////////////////////////////////////*/
constructor(address reserve) Ledger(reserve) {}
function initialize(RateModel rateModel_, uint8 reserveFactor_) external {
require(borrowIndex == 0);
borrowIndex = uint72(ONE);
lastAccrualTime = uint32(block.timestamp);
initialDomainSeparator = _computeDomainSeparator();
initialChainId = block.chainid;
rateModel = rateModel_;
require(MIN_RESERVE_FACTOR <= reserveFactor_ && reserveFactor_ <= MAX_RESERVE_FACTOR);
reserveFactor = reserveFactor_;
}
function whitelist(address borrower) external {
// Requirements:
// - `msg.sender == FACTORY` so that only the factory can whitelist borrowers
// - `borrows[borrower] == 0` ensures we don't accidentally erase debt
require(msg.sender == FACTORY && borrows[borrower] == 0);
// `borrow` and `repay` have to read the `borrows` mapping anyway, so setting this to 1
// allows them to efficiently check whether a given borrower is whitelisted. This extra
// unit of debt won't accrue interest or impact solvency calculations.
borrows[borrower] = 1;
}
function enrollCourier(uint32 id, address wallet, uint16 cut) external {
// Requirements:
// - `id != 0` because 0 is reserved as the no-courier case
// - `cut != 0 && cut < 10_000` just means between 0 and 100%
require(id != 0 && cut != 0 && cut < 10_000);
// Once an `id` has been enrolled, its info can't be changed
require(couriers[id].cut == 0);
couriers[id] = Courier(wallet, cut);
emit EnrollCourier(id, wallet, cut);
}
function creditCourier(uint32 id, address account) external {
// Callers are free to set their own courier, but they need permission to mess with others'
require(msg.sender == account || allowance[account][msg.sender] != 0);
// Prevent `RESERVE` from having a courier, since its principle wouldn't be tracked properly
require(account != RESERVE);
// Payout logic can't handle self-reference, so don't let accounts credit themselves
Courier memory courier = couriers[id];
require(courier.cut != 0 && courier.wallet != account);
// Only set courier if account balance is 0. Otherwise a previous courier may
// be cheated out of their fees.
require(balances[account] % Q112 == 0);
balances[account] = uint256(id) << 224;
emit CreditCourier(id, account);
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LOGIC
//////////////////////////////////////////////////////////////*/
function deposit(uint256 amount, address beneficiary) external returns (uint256 shares) {
// Guard against reentrancy, accrue interest, and update reserves
(Cache memory cache, uint256 inventory) = _load();
shares = _convertToShares(amount, inventory, cache.totalSupply, /* roundUp: */ false);
require(shares != 0, "Aloe: zero impact");
// Ensure tokens were transferred
cache.lastBalance += amount;
require(cache.lastBalance <= asset().balanceOf(address(this)), "Aloe: insufficient pre-pay");
// Mint shares and (if applicable) handle courier accounting
_unsafeMint(beneficiary, shares, amount);
cache.totalSupply += shares;
// Save state to storage (thus far, only mappings have been updated, so we must address everything else)
_save(cache, /* didChangeBorrowBase: */ false);
emit Deposit(msg.sender, beneficiary, amount, shares);
}
function redeem(uint256 shares, address recipient, address owner) external returns (uint256 amount) {
// Guard against reentrancy, accrue interest, and update reserves
(Cache memory cache, uint256 inventory) = _load();
amount = _convertToAssets(shares, inventory, cache.totalSupply, /* roundUp: */ false);
require(amount != 0, "Aloe: zero impact");
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender];
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
// Burn shares and (if applicable) handle courier accounting
_unsafeBurn(owner, shares, inventory, cache.totalSupply);
unchecked {
cache.totalSupply -= shares;
}
// Transfer tokens
cache.lastBalance -= amount;
asset().safeTransfer(recipient, amount);
// Save state to storage (thus far, only mappings have been updated, so we must address everything else)
_save(cache, /* didChangeBorrowBase: */ false);
emit Withdraw(msg.sender, recipient, owner, amount, shares);
}
/*//////////////////////////////////////////////////////////////
BORROW/REPAY LOGIC
//////////////////////////////////////////////////////////////*/
function borrow(uint256 amount, address recipient) external returns (uint256 units) {
uint256 b = borrows[msg.sender];
require(b != 0, "Aloe: not a borrower");
// Guard against reentrancy, accrue interest, and update reserves
(Cache memory cache, ) = _load();
units = amount.mulDivUp(BORROWS_SCALER, cache.borrowIndex);
cache.borrowBase += units;
borrows[msg.sender] = b + units;
// Transfer tokens
cache.lastBalance -= amount;
asset().safeTransfer(recipient, amount);
// Save state to storage (thus far, only mappings have been updated, so we must address everything else)
_save(cache, /* didChangeBorrowBase: */ true);
emit Borrow(msg.sender, recipient, amount, units);
}
function repay(uint256 amount, address beneficiary) external returns (uint256 units) {
uint256 b = borrows[beneficiary];
// Guard against reentrancy, accrue interest, and update reserves
(Cache memory cache, ) = _load();
unchecked {
units = (amount * BORROWS_SCALER) / cache.borrowIndex;
require(units < b, "Aloe: repay too much");
borrows[beneficiary] = b - units;
cache.borrowBase -= units;
}
// Ensure tokens were transferred
cache.lastBalance += amount;
require(cache.lastBalance <= asset().balanceOf(address(this)), "Aloe: insufficient pre-pay");
// Save state to storage (thus far, only mappings have been updated, so we must address everything else)
_save(cache, /* didChangeBorrowBase: */ true);
emit Repay(msg.sender, beneficiary, amount, units);
}
function accrueInterest() external returns (uint72) {
(Cache memory cache, ) = _load();
_save(cache, /* didChangeBorrowBase: */ false);
return uint72(cache.borrowIndex);
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 shares) external returns (bool) {
allowance[msg.sender][spender] = shares;
emit Approval(msg.sender, spender, shares);
return true;
}
function transfer(address to, uint256 shares) external returns (bool) {
_transfer(msg.sender, to, shares);
return true;
}
function transferFrom(address from, address to, uint256 shares) external returns (bool) {
uint256 allowed = allowance[from][msg.sender];
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - shares;
_transfer(from, to, shares);
return true;
}
/*//////////////////////////////////////////////////////////////
ERC2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
require(deadline >= block.timestamp, "Aloe: permit expired");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "Aloe: permit invalid");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
/*//////////////////////////////////////////////////////////////
HELPERS
//////////////////////////////////////////////////////////////*/
function _transfer(address from, address to, uint256 shares) private {
unchecked {
// From most to least significant...
// -------------------------------
// | courier id | 32 bits |
// | user's principle | 112 bits |
// | user's balance | 112 bits |
// -------------------------------
uint256 data;
data = balances[from];
require(data >> 224 == 0 && shares <= data % Q112);
balances[from] = data - shares;
data = balances[to];
require(data >> 224 == 0);
balances[to] = data + shares;
}
emit Transfer(from, to, shares);
}
/// @dev You must do `totalSupply += shares` separately. Do so in a checked context.
function _unsafeMint(address to, uint256 shares, uint256 amount) private {
unchecked {
// From most to least significant...
// -------------------------------
// | courier id | 32 bits |
// | user's principle | 112 bits |
// | user's balance | 112 bits |
// -------------------------------
uint256 data = balances[to];
if (data >> 224 != 0) {
// Keep track of principle iff courier deserves credit
require(amount + ((data >> 112) % Q112) < Q112);
data += amount << 112;
}
// Keep track of balance regardless of courier.
// Since `totalSupply` fits in uint112, the user's balance will too. No need to check here.
balances[to] = data + shares;
}
emit Transfer(address(0), to, shares);
}
/// @dev You must do `totalSupply -= shares` separately. Do so in an unchecked context.
function _unsafeBurn(address from, uint256 shares, uint256 inventory, uint256 totalSupply_) private {
unchecked {
// From most to least significant...
// -------------------------------
// | courier id | 32 bits |
// | user's principle | 112 bits |
// | user's balance | 112 bits |
// -------------------------------
uint256 data = balances[from];
uint256 balance = data % Q112;
uint32 id = uint32(data >> 224);
if (id != 0) {
uint256 principleAssets = (data >> 112) % Q112;
uint256 principleShares = principleAssets.mulDivUp(totalSupply_, inventory);
if (balance > principleShares) {
Courier memory courier = couriers[id];
// Compute total fee owed to courier. Take it out of balance so that
// comparison is correct (`shares <= balance`)
uint256 fee = ((balance - principleShares) * courier.cut) / 10_000;
balance -= fee;
// Compute portion of fee to pay out during this burn.
fee = (fee * shares) / balance;
// Send `fee` from `from` to `courier.wallet`. NOTE: We skip principle
// update on courier, so if couriers credit each other, 100% of `fee`
// is treated as profit.
data -= fee;
balances[courier.wallet] += fee;
emit Transfer(from, courier.wallet, fee);
}
// Update principle
data -= ((principleAssets * shares) / balance) << 112;
}
require(shares <= balance);
balances[from] = data - shares;
}
emit Transfer(from, address(0), shares);
}
function _load() private returns (Cache memory cache, uint256 inventory) {
cache = Cache(totalSupply, lastBalance, lastAccrualTime, borrowBase, borrowIndex);
// Guard against reentrancy
require(cache.lastAccrualTime != 0, "Aloe: locked");
lastAccrualTime = 0;
// Accrue interest (only in memory)
uint256 newTotalSupply;
(cache, inventory, newTotalSupply) = _previewInterest(cache);
// Update reserves (new `totalSupply` is only in memory, but `balanceOf` is updated in storage)
if (newTotalSupply > cache.totalSupply) {
_unsafeMint(RESERVE, newTotalSupply - cache.totalSupply, 0);
cache.totalSupply = newTotalSupply;
}
}
function _save(Cache memory cache, bool didChangeBorrowBase) private {
if (cache.lastAccrualTime == 0) {
// `cache.lastAccrualTime == 0` implies that `cache.borrowIndex` was updated.
// `cache.borrowBase` MAY also have been updated, so we store both components of the slot.
borrowBase = cache.borrowBase.safeCastTo184();
borrowIndex = cache.borrowIndex.safeCastTo72();
// Now that we've read the flag, we can update `cache.lastAccrualTime` to the real, appropriate value
cache.lastAccrualTime = block.timestamp;
} else if (didChangeBorrowBase) {
// Here, `cache.lastAccrualTime` is a real timestamp (could be `block.timestamp` or older). We can infer
// that `cache.borrowIndex` was *not* updated. So we only have to store `cache.borrowBase`.
borrowBase = cache.borrowBase.safeCastTo184();
}
totalSupply = cache.totalSupply.safeCastTo112();
lastBalance = cache.lastBalance.safeCastTo112();
lastAccrualTime = cache.lastAccrualTime.safeCastTo32(); // Disables reentrancy guard
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
/// @title RateModel
/// @author Aloe Labs, Inc.
/// @dev "Test everything; hold fast what is good." - 1 Thessalonians 5:21
contract RateModel {
uint256 private constant A = 6.1010463348e20;
uint256 private constant B = 1e12 - A / 1e18;
function getAccrualFactor(uint256 elapsedTime, uint256 utilization) external pure returns (uint256) {
unchecked {
uint256 rate = computeYieldPerSecond(utilization);
if (elapsedTime > 1 weeks) elapsedTime = 1 weeks;
return FixedPointMathLib.rpow(rate, elapsedTime, 1e12);
}
}
function computeYieldPerSecond(uint256 utilization) public pure returns (uint256) {
unchecked {
return (utilization < 0.99e18) ? B + A / (1e18 - utilization) : 1000000060400;
}
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;
import {IUniswapV3Pool} from "v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import {MAX_SIGMA, IV_SCALE, IV_CHANGE_PER_SECOND, ORACLE_LOOKBACK, FEE_GROWTH_GLOBALS_SAMPLE_PERIOD} from "./libraries/constants/Constants.sol";
import {Oracle} from "./libraries/Oracle.sol";
import {Volatility} from "./libraries/Volatility.sol";
/// @title VolatilityOracle
/// @author Aloe Labs, Inc.
/// @dev "Test everything; hold fast what is good." - 1 Thessalonians 5:21
contract VolatilityOracle {
event Update(IUniswapV3Pool indexed pool, uint160 sqrtMeanPriceX96, uint256 iv);
struct LastWrite {
uint8 index;
uint32 time;
uint216 iv;
}
mapping(IUniswapV3Pool => Volatility.PoolMetadata) public cachedMetadata;
mapping(IUniswapV3Pool => Volatility.FeeGrowthGlobals[60]) public feeGrowthGlobals;
mapping(IUniswapV3Pool => LastWrite) public lastWrites;
function prepare(IUniswapV3Pool pool) external {
cachedMetadata[pool] = _getPoolMetadata(pool);
if (lastWrites[pool].time == 0) {
feeGrowthGlobals[pool][0] = _getFeeGrowthGlobalsNow(pool);
lastWrites[pool] = LastWrite({index: 0, time: uint32(block.timestamp), iv: uint216(MAX_SIGMA)});
}
}
function update(IUniswapV3Pool pool) external returns (uint160, uint256) {
unchecked {
// Read `lastWrite` info from storage
LastWrite memory lastWrite = lastWrites[pool];
// If fewer than `VOLATILITY_SAMPLE_PERIOD` seconds have elapsed, return early. We
// still fetch the latest TWAP, but we do not sample feeGrowthGlobals or update IV
uint256 timeSinceLastWrite = block.timestamp - lastWrite.time;
if (timeSinceLastWrite < FEE_GROWTH_GLOBALS_SAMPLE_PERIOD) {
(uint160 sqrtMeanPriceX96, ) = Oracle.consult(pool, ORACLE_LOOKBACK);
return (sqrtMeanPriceX96, lastWrite.iv);
}
// Prepare to call all getters
Volatility.FeeGrowthGlobals[60] storage arr = feeGrowthGlobals[pool];
// Call all getters
Volatility.PoolData memory data = _getPoolData(pool);
Volatility.FeeGrowthGlobals memory a = _getFeeGrowthGlobalsOld(arr, lastWrite.index);
Volatility.FeeGrowthGlobals memory b = _getFeeGrowthGlobalsNow(pool);
// Default to using the existing IV
uint256 iv = lastWrite.iv;
// Only update IV if the feeGrowthGlobals samples are approximately 1 hour apart
if (
isInInterval({
min: 1 hours - 5 minutes, // NOTE: Keeping constants in-line because they're related to arr.length
x: b.timestamp - a.timestamp,
max: 1 hours + 5 minutes
})
) {
// Estimate, then clamp so it lies within [previous - maxChange, previous + maxChange]
iv = Volatility.estimate(cachedMetadata[pool], data, a, b, IV_SCALE);
uint256 maxChange = timeSinceLastWrite * IV_CHANGE_PER_SECOND;
if (iv > lastWrite.iv + maxChange) iv = lastWrite.iv + maxChange;
else if (iv + maxChange < lastWrite.iv) iv = lastWrite.iv - maxChange;
}
// Store the new feeGrowthGlobals sample and update `lastWrites`
uint8 next = uint8((lastWrite.index + 1) % 60);
arr[next] = b;
lastWrites[pool] = LastWrite(next, uint32(block.timestamp), uint216(iv));
emit Update(pool, data.sqrtMeanPriceX96, iv);
return (data.sqrtMeanPriceX96, iv);
}
}
function consult(IUniswapV3Pool pool) external view returns (uint160, uint256) {
(uint160 sqrtMeanPriceX96, ) = Oracle.consult(pool, ORACLE_LOOKBACK);
return (sqrtMeanPriceX96, lastWrites[pool].iv);
}
function _getPoolMetadata(IUniswapV3Pool pool) private view returns (Volatility.PoolMetadata memory metadata) {
(, , uint16 observationIndex, uint16 observationCardinality, , uint8 feeProtocol, ) = pool.slot0();
// If block times are inconsistent, `maxSecondsAgo` from oracle may be inflated. Divide by 2 to be extra safe.
metadata.maxSecondsAgo = Oracle.getMaxSecondsAgo(pool, observationIndex, observationCardinality) / 2;
require(metadata.maxSecondsAgo > ORACLE_LOOKBACK, "Aloe: cardinality");
uint24 fee = pool.fee();
metadata.gamma0 = fee;
metadata.gamma1 = fee;
unchecked {
if (feeProtocol % 16 != 0) metadata.gamma0 -= fee / (feeProtocol % 16);
if (feeProtocol >> 4 != 0) metadata.gamma1 -= fee / (feeProtocol >> 4);
}
metadata.tickSpacing = pool.tickSpacing();
}
function _getPoolData(IUniswapV3Pool pool) private view returns (Volatility.PoolData memory data) {
(uint160 sqrtPriceX96, int24 currentTick, , , , , ) = pool.slot0();
(uint160 sqrtMeanPriceX96, uint160 secondsPerLiquidityX128) = Oracle.consult(pool, ORACLE_LOOKBACK);
data = Volatility.PoolData(
sqrtPriceX96,
currentTick,
sqrtMeanPriceX96,
secondsPerLiquidityX128,
ORACLE_LOOKBACK,
pool.liquidity()
);
}
function _getFeeGrowthGlobalsNow(IUniswapV3Pool pool) private view returns (Volatility.FeeGrowthGlobals memory) {
return
Volatility.FeeGrowthGlobals(
pool.feeGrowthGlobal0X128(),
pool.feeGrowthGlobal1X128(),
uint32(block.timestamp)
);
}
function _getFeeGrowthGlobalsOld(
Volatility.FeeGrowthGlobals[60] storage arr,
uint256 index
) private view returns (Volatility.FeeGrowthGlobals memory) {
uint256 target = block.timestamp - 1 hours;
// See if the newest sample is nearest to `target`
Volatility.FeeGrowthGlobals memory sample = arr[index];
if (sample.timestamp <= target) return sample;
// See if the oldest sample is nearest to `target`
uint256 next = (index + 1) % 60;
sample = arr[next];
if (sample.timestamp >= target) return sample;
// Now that we've checked the edges, we know the best sample lies somewhere within the array.
return _binarySearch(arr, next, target);
}
function _binarySearch(
Volatility.FeeGrowthGlobals[60] storage arr,
uint256 l,
uint256 target
) private view returns (Volatility.FeeGrowthGlobals memory) {
Volatility.FeeGrowthGlobals memory beforeOrAt;
Volatility.FeeGrowthGlobals memory atOrAfter;
unchecked {
uint256 r = l + 59;
uint256 i;
while (true) {
i = (l + r) / 2;
beforeOrAt = arr[i % 60];
atOrAfter = arr[(i + 1) % 60];
if (isInInterval(beforeOrAt.timestamp, target, atOrAfter.timestamp)) break;
if (beforeOrAt.timestamp <= target) {
l = i + 1;
} else {
r = i - 1;
}
}
uint256 errorA = target - beforeOrAt.timestamp;
uint256 errorB = atOrAfter.timestamp - target;
return errorB < errorA ? atOrAfter : beforeOrAt;
}
}
function isInInterval(uint256 min, uint256 x, uint256 max) private pure returns (bool) {
return min <= x && x <= max;
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;
import {Math} from "openzeppelin-contracts/utils/math/Math.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {MIN_SIGMA, MAX_SIGMA, MAX_LEVERAGE, LIQUIDATION_INCENTIVE} from "./constants/Constants.sol";
import {Q96} from "./constants/Q.sol";
import {SafeCastLib} from "./SafeCastLib.sol";
struct Assets {
uint256 fixed0;
uint256 fixed1;
uint256 fluid1A;
uint256 fluid1B;
uint256 fluid0C;
uint256 fluid1C;
}
struct Prices {
uint160 a;
uint160 b;
uint160 c;
}
/// @title BalanceSheet
/// @notice Provides functions for computing a `Borrower`'s health
/// @author Aloe Labs, Inc.
library BalanceSheet {
function isHealthy(
Prices memory prices,
Assets memory mem,
uint256 liabilities0,
uint256 liabilities1
) internal pure returns (bool) {
(uint256 incentive1, ) = computeLiquidationIncentive(
mem.fixed0 + mem.fluid0C, // total assets0 at `prices.c` (the TWAP)
mem.fixed1 + mem.fluid1C, // total assets1 at `prices.c` (the TWAP)
liabilities0,
liabilities1,
prices.c
);
return isHealthy(prices, mem, liabilities0, liabilities1, incentive1);
}
function isHealthy(
Prices memory prices,
Assets memory mem,
uint256 liabilities0,
uint256 liabilities1,
uint256 incentive1
) internal pure returns (bool) {
// The liquidation incentive is added to `liabilities1` because it's a potential liability, and we
// don't want to re-evaluate it at the probe prices (as would happen if we added it to `liabilities0`).
unchecked {
liabilities0 += liabilities0 / MAX_LEVERAGE;
liabilities1 += liabilities1 / MAX_LEVERAGE + incentive1;
}
// combine
uint224 priceX96;
uint256 liabilities;
uint256 assets;
priceX96 = uint224(Math.mulDiv(prices.a, prices.a, Q96));
liabilities = liabilities1 + Math.mulDiv(liabilities0, priceX96, Q96);
assets = mem.fluid1A + mem.fixed1 + Math.mulDiv(mem.fixed0, priceX96, Q96);
if (liabilities > assets) return false;
priceX96 = uint224(Math.mulDiv(prices.b, prices.b, Q96));
liabilities = liabilities1 + Math.mulDiv(liabilities0, priceX96, Q96);
assets = mem.fluid1B + mem.fixed1 + Math.mulDiv(mem.fixed0, priceX96, Q96);
if (liabilities > assets) return false;
return true;
}
function computeProbePrices(
uint160 sqrtMeanPriceX96,
uint256 sigma,
uint256 n
) internal pure returns (uint160 a, uint160 b) {
unchecked {
if (sigma < MIN_SIGMA) sigma = MIN_SIGMA;
else if (sigma > MAX_SIGMA) sigma = MAX_SIGMA;
sigma *= n;
a = uint160((sqrtMeanPriceX96 * FixedPointMathLib.sqrt(1e18 - sigma)) / 1e9);
b = SafeCastLib.safeCastTo160((sqrtMeanPriceX96 * FixedPointMathLib.sqrt(1e18 + sigma)) / 1e9);
}
}
function computeLiquidationIncentive(
uint256 assets0,
uint256 assets1,
uint256 liabilities0,
uint256 liabilities1,
uint160 sqrtMeanPriceX96
) internal pure returns (uint256 incentive1, uint224 meanPriceX96) {
unchecked {
meanPriceX96 = uint224(Math.mulDiv(sqrtMeanPriceX96, sqrtMeanPriceX96, Q96));
if (liabilities0 > assets0) {
// shortfall is the amount that cannot be directly repaid using Borrower assets at this price
uint256 shortfall = liabilities0 - assets0;
// to cover it, a liquidator may have to use their own assets, taking on inventory risk.
// to compensate them for this risk, they're allowed to seize some of the surplus asset.
incentive1 += Math.mulDiv(shortfall / LIQUIDATION_INCENTIVE, meanPriceX96, Q96);
}
if (liabilities1 > assets1) {
// shortfall is the amount that cannot be directly repaid using Borrower assets at this price
uint256 shortfall = liabilities1 - assets1;
// to cover it, a liquidator may have to use their own assets, taking on inventory risk.
// to compensate them for this risk, they're allowed to seize some of the surplus asset.
incentive1 += shortfall / LIQUIDATION_INCENTIVE;
}
}
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.17;
import {Math} from "openzeppelin-contracts/utils/math/Math.sol";
import {Q96} from "./constants/Q.sol";
import {SafeCastLib} from "./SafeCastLib.sol";
/// @title Liquidity amount functions
/// @notice Provides functions for computing liquidity amounts from token amounts and prices
/// @author Aloe Labs, Inc.
/// @author Modified from Uniswap (https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/LiquidityAmounts.sol)
library LiquidityAmounts {
using SafeCastLib for uint256;
/// @notice Computes the amount of liquidity received for a given amount of token0 and price range
/// @dev Calculates amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower))
/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param amount0 The amount0 being sent in
/// @return liquidity The amount of returned liquidity
function getLiquidityForAmount0(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint256 amount0
) internal pure returns (uint128 liquidity) {
assert(sqrtRatioAX96 < sqrtRatioBX96);
uint256 intermediate = Math.mulDiv(sqrtRatioAX96, sqrtRatioBX96, Q96);
liquidity = Math.mulDiv(amount0, intermediate, sqrtRatioBX96 - sqrtRatioAX96).safeCastTo128();
}
/// @notice Computes the amount of liquidity received for a given amount of token1 and price range
/// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)).
/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param amount1 The amount1 being sent in
/// @return liquidity The amount of returned liquidity
function getLiquidityForAmount1(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint256 amount1
) internal pure returns (uint128 liquidity) {
assert(sqrtRatioAX96 < sqrtRatioBX96);
liquidity = Math.mulDiv(amount1, Q96, sqrtRatioBX96 - sqrtRatioAX96).safeCastTo128();
}
/// @notice Computes the maximum amount of liquidity received for a given amount of token0, token1, the current
/// pool prices and the prices at the tick boundaries
/// @param sqrtRatioX96 A sqrt price representing the current pool prices
/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param amount0 The amount of token0 being sent in
/// @param amount1 The amount of token1 being sent in
/// @return liquidity The maximum amount of liquidity received
function getLiquidityForAmounts(
uint160 sqrtRatioX96,
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint256 amount0,
uint256 amount1
) internal pure returns (uint128 liquidity) {
assert(sqrtRatioAX96 < sqrtRatioBX96);
if (sqrtRatioX96 <= sqrtRatioAX96) {
liquidity = getLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0);
} else if (sqrtRatioX96 < sqrtRatioBX96) {
uint128 liquidity0 = getLiquidityForAmount0(sqrtRatioX96, sqrtRatioBX96, amount0);
uint128 liquidity1 = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioX96, amount1);
liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;
} else {
liquidity = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1);
}
}
/// @notice Computes the amount of token0 for a given amount of liquidity and a price range
/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param liquidity The liquidity being valued
/// @return amount0 The amount of token0. Will fit in a uint224 if you need it to
function getAmount0ForLiquidity(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity
) internal pure returns (uint256 amount0) {
assert(sqrtRatioAX96 <= sqrtRatioBX96);
amount0 = Math.mulDiv(uint256(liquidity) << 96, sqrtRatioBX96 - sqrtRatioAX96, sqrtRatioBX96) / sqrtRatioAX96;
}
/// @notice Computes the amount of token1 for a given amount of liquidity and a price range
/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param liquidity The liquidity being valued
/// @return amount1 The amount of token1. Will fit in a uint192 if you need it to
function getAmount1ForLiquidity(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity
) internal pure returns (uint256 amount1) {
assert(sqrtRatioAX96 <= sqrtRatioBX96);
amount1 = Math.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, Q96);
}
/// @notice Computes the token0 and token1 value for a given amount of liquidity, the current
/// pool prices and the prices at the tick boundaries
/// @param sqrtRatioX96 A sqrt price representing the current pool prices
/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param liquidity The liquidity being valued
/// @return amount0 The amount of token0
/// @return amount1 The amount of token1
function getAmountsForLiquidity(
uint160 sqrtRatioX96,
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity
) internal pure returns (uint256 amount0, uint256 amount1) {
assert(sqrtRatioAX96 <= sqrtRatioBX96);
if (sqrtRatioX96 <= sqrtRatioAX96) {
amount0 = getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
} else if (sqrtRatioX96 < sqrtRatioBX96) {
amount0 = getAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity);
amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity);
} else {
amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
}
}
/// @notice Computes the value of each portion of the liquidity in terms of token1
/// @dev Each return value can fit in a uint192 if necessary
/// @param sqrtRatioX96 A sqrt price representing the current pool prices
/// @param sqrtRatioAX96 A sqrt price representing the lower tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the upper tick boundary
/// @param liquidity The liquidity being valued
/// @return value0 The value of amount0 underlying `liquidity`, in terms of token1
/// @return value1 The amount of token1
function getValuesOfLiquidity(
uint160 sqrtRatioX96,
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity
) internal pure returns (uint256 value0, uint256 value1) {
assert(sqrtRatioAX96 <= sqrtRatioBX96);
unchecked {
if (sqrtRatioX96 <= sqrtRatioAX96) {
uint256 priceX96 = Math.mulDiv(sqrtRatioX96, sqrtRatioX96, Q96);
value0 = Math.mulDiv(
priceX96,
Math.mulDiv(uint256(liquidity) << 96, sqrtRatioBX96 - sqrtRatioAX96, sqrtRatioBX96),
uint256(sqrtRatioAX96) << 96
);
} else if (sqrtRatioX96 < sqrtRatioBX96) {
uint256 numerator = Math.mulDiv(sqrtRatioX96, sqrtRatioBX96 - sqrtRatioX96, Q96);
value0 = Math.mulDiv(liquidity, numerator, sqrtRatioBX96);
value1 = Math.mulDiv(liquidity, sqrtRatioX96 - sqrtRatioAX96, Q96);
} else {
value1 = Math.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, Q96);
}
}
}
/// @notice Computes the value of the liquidity in terms of token1
/// @dev The return value can fit in a uint192 if necessary
/// @param sqrtRatioX96 A sqrt price representing the current pool prices
/// @param sqrtRatioAX96 A sqrt price representing the lower tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the upper tick boundary
/// @param liquidity The liquidity being valued
/// @return The value of the underlying `liquidity`, in terms of token1
function getValueOfLiquidity(
uint160 sqrtRatioX96,
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity
) internal pure returns (uint256) {
(uint256 value0, uint256 value1) = getValuesOfLiquidity(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, liquidity);
unchecked {
return value0 + value1;
}
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.17;
import {IUniswapV3Pool} from "v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import {TickMath} from "./TickMath.sol";
/// @title Oracle
/// @notice Provides functions to integrate with V3 pool oracle
/// @author Aloe Labs, Inc.
/// @author Modified from Uniswap (https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/OracleLibrary.sol)
library Oracle {
/**
* @notice Calculates time-weighted means of tick and liquidity for a given Uniswap V3 pool
* @param pool Address of the pool that we want to observe
* @param secondsAgo Number of seconds in the past from which to calculate the time-weighted means
* @return sqrtMeanPriceX96 The sqrt(geometricMeanPrice) from (block.timestamp - secondsAgo) to block.timestamp
* @return secondsPerLiquidityX128 The change in seconds per liquidity from (block.timestamp - secondsAgo)
* to block.timestamp
* @dev `secondsAgo` MUST NOT be 0
*/
function consult(
IUniswapV3Pool pool,
uint32 secondsAgo
) internal view returns (uint160 sqrtMeanPriceX96, uint160 secondsPerLiquidityX128) {
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = secondsAgo;
secondsAgos[1] = 0;
(int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = pool.observe(
secondsAgos
);
unchecked {
int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
int24 arithmeticMeanTick = int24(tickCumulativesDelta / int32(secondsAgo));
// Always round to negative infinity
if (tickCumulativesDelta < 0 && (tickCumulativesDelta % int32(secondsAgo) != 0)) arithmeticMeanTick--;
sqrtMeanPriceX96 = TickMath.getSqrtRatioAtTick(arithmeticMeanTick);
secondsPerLiquidityX128 = secondsPerLiquidityCumulativeX128s[1] - secondsPerLiquidityCumulativeX128s[0];
}
}
/**
* @notice Given a pool, returns the number of seconds ago of the oldest stored observation
* @param pool Address of Uniswap V3 pool that we want to observe
* @param observationIndex The observation index from pool.slot0()
* @param observationCardinality The observationCardinality from pool.slot0()
* @dev (, , uint16 observationIndex, uint16 observationCardinality, , , ) = pool.slot0();
* @return secondsAgo The number of seconds ago that the oldest observation was stored
*/
function getMaxSecondsAgo(
IUniswapV3Pool pool,
uint16 observationIndex,
uint16 observationCardinality
) internal view returns (uint32 secondsAgo) {
require(observationCardinality != 0, "NI");
unchecked {
(uint32 observationTimestamp, , , bool initialized) = pool.observations(
(observationIndex + 1) % observationCardinality
);
// The next index might not be initialized if the cardinality is in the process of increasing
// In this case the oldest observation is always in index 0
if (!initialized) {
(observationTimestamp, , , ) = pool.observations(0);
}
secondsAgo = uint32(block.timestamp) - observationTimestamp;
}
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;
import {Q24} from "./constants/Q.sol";
/**
* @notice Compresses `positions` into `zipped`. Useful for creating the return value of `IManager.callback`
* @param positions A flattened array of ticks, each consecutive pair of indices representing one Uniswap position
* @param zipped Encoded Uniswap positions
*/
function zip(int24[6] memory positions) pure returns (uint144 zipped) {
assembly ("memory-safe") {
zipped := mod(mload(positions), Q24)
zipped := add(zipped, shl(24, mod(mload(add(positions, 32)), Q24)))
zipped := add(zipped, shl(48, mod(mload(add(positions, 64)), Q24)))
zipped := add(zipped, shl(72, mod(mload(add(positions, 96)), Q24)))
zipped := add(zipped, shl(96, mod(mload(add(positions, 128)), Q24)))
zipped := add(zipped, shl(120, mod(mload(add(positions, 160)), Q24)))
}
}
/**
* @notice Extracts up to three Uniswap positions from `zipped`. Each position consists of an `int24 lower` and
* `int24 upper`, and will be included in the output array iff `lower != upper`. The output array is flattened
* such that lower and upper ticks are next to each other, e.g. one position may be at indices 0 & 1, and another
* at indices 2 & 3.
* @dev The output array's length will be one of {0, 2, 4, 6}. We do *not* validate that `lower < upper`, nor do
* we check whether positions actually hold liquidity. Also note that this function will happily return duplicate
* positions like [-100, 100, -100, 100].
* @param zipped Encoded Uniswap positions. Equivalent to the layout of `int24[6] storage yourPositions`
* @return positionsOfNonZeroWidth Flattened array of Uniswap positions that may or may not hold liquidity
*/
function extract(uint256 zipped) pure returns (int24[] memory positionsOfNonZeroWidth) {
assembly ("memory-safe") {
// zipped:
// --> xl + (xu << 24) + (yl << 48) + (yu << 72) + (zl << 96) + (zu << 120)
// --> |-------|-----|----|----|----|----|----|
// | shift | 120 | 96 | 72 | 48 | 24 | 0 |
// | value | zu | zl | yu | yl | xu | xl |
// |-------|-----|----|----|----|----|----|
positionsOfNonZeroWidth := mload(0x40)
let offset := 32
// if xl != xu
let l := mod(zipped, Q24)
let u := mod(shr(24, zipped), Q24)
if iszero(eq(l, u)) {
mstore(add(positionsOfNonZeroWidth, 32), l)
mstore(add(positionsOfNonZeroWidth, 64), u)
offset := 96
}
// if yl != yu
l := mod(shr(48, zipped), Q24)
u := mod(shr(72, zipped), Q24)
if iszero(eq(l, u)) {
mstore(add(positionsOfNonZeroWidth, offset), l)
mstore(add(positionsOfNonZeroWidth, add(offset, 32)), u)
offset := add(offset, 64)
}
// if zl != zu
l := mod(shr(96, zipped), Q24)
u := shr(120, zipped)
if iszero(eq(l, u)) {
mstore(add(positionsOfNonZeroWidth, offset), l)
mstore(add(positionsOfNonZeroWidth, add(offset, 32)), u)
offset := add(offset, 64)
}
mstore(positionsOfNonZeroWidth, shr(5, sub(offset, 32)))
mstore(0x40, add(positionsOfNonZeroWidth, offset))
}
}
/// @title Positions
/// @notice Provides functions for handling Uniswap positions in `Borrower`'s storage
/// @author Aloe Labs, Inc.
library Positions {
function write(int24[6] storage positions, uint256 update) internal returns (int24[] memory) {
// `update == 0` implies that the caller *does not* want to modify their positions, so we
// read the existing ones and return early.
if (update == 0) return read(positions);
// Optimistically copy the `update`d positions to storage.
// Need assembly to bypass Solidity's type-checking.
assembly ("memory-safe") {
sstore(positions.slot, update)
}
// Extract the updated positions from `update`. This is the array that will be used for
// solvency checks (at least until the next `write`), so we need to verify that all
// positions are unique (no duplicates / double-counting).
int24[] memory positions_ = extract(update);
uint256 count = positions_.length;
if (count == 4) {
require(positions_[0] != positions_[2] || positions_[1] != positions_[3]);
} else if (count == 6) {
// prettier-ignore
require(
(positions_[0] != positions_[2] || positions_[1] != positions_[3]) &&
(positions_[2] != positions_[4] || positions_[3] != positions_[5]) &&
(positions_[4] != positions_[0] || positions_[5] != positions_[1])
);
}
// NOTE: we still haven't checked that each `lower < upper`, or that the ticks align
// with tickSpacing. Uniswap will do that for us.
return positions_;
}
function read(int24[6] storage positions) internal view returns (int24[] memory positions_) {
uint144 zipped;
assembly ("memory-safe") {
zipped := sload(positions.slot)
}
positions_ = extract(zipped);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;
/// @title SafeCastLib
/// @notice Safe unsigned integer casting library that reverts on overflow.
/// @author Aloe Labs, Inc.
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeCastLib.sol)
library SafeCastLib {
function safeCastTo248(uint256 x) internal pure returns (uint248 y) {
require(x < 1 << 248);
y = uint248(x);
}
function safeCastTo224(uint256 x) internal pure returns (uint224 y) {
require(x < 1 << 224);
y = uint224(x);
}
function safeCastTo192(uint256 x) internal pure returns (uint192 y) {
require(x < 1 << 192);
y = uint192(x);
}
function safeCastTo184(uint256 x) internal pure returns (uint184 y) {
require(x < 1 << 184);
y = uint184(x);
}
function safeCastTo160(uint256 x) internal pure returns (uint160 y) {
require(x < 1 << 160);
y = uint160(x);
}
function safeCastTo128(uint256 x) internal pure returns (uint128 y) {
require(x < 1 << 128);
y = uint128(x);
}
function safeCastTo112(uint256 x) internal pure returns (uint112 y) {
require(x < 1 << 112);
y = uint112(x);
}
function safeCastTo96(uint256 x) internal pure returns (uint96 y) {
require(x < 1 << 96);
y = uint96(x);
}
function safeCastTo72(uint256 x) internal pure returns (uint72 y) {
require(x < 1 << 72);
y = uint72(x);
}
function safeCastTo64(uint256 x) internal pure returns (uint64 y) {
require(x < 1 << 64);
y = uint64(x);
}
function safeCastTo32(uint256 x) internal pure returns (uint32 y) {
require(x < 1 << 32);
y = uint32(x);
}
function safeCastTo24(uint256 x) internal pure returns (uint24 y) {
require(x < 1 << 24);
y = uint24(x);
}
function safeCastTo16(uint256 x) internal pure returns (uint16 y) {
require(x < 1 << 16);
y = uint16(x);
}
function safeCastTo8(uint256 x) internal pure returns (uint8 y) {
require(x < 1 << 8);
y = uint8(x);
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.17;
/// @title Math library for computing sqrt prices from ticks and vice versa
/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports
/// prices between 2**-128 and 2**128
/// @author Aloe Labs, Inc.
/// @author Modified from Uniswap (https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/TickMath.sol)
library TickMath {
/// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128
int24 internal constant MIN_TICK = -887272;
/// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128
int24 internal constant MAX_TICK = -MIN_TICK;
/// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)
uint160 internal constant MIN_SQRT_RATIO = 4295128739;
/// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)
uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;
/* solhint-disable code-complexity */
/// @notice Calculates sqrt(1.0001^tick) * 2^96
/// @dev Throws if |tick| > max tick
/// @param tick The input tick for the above formula
/// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0)
/// at the given tick
function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
require(absTick <= uint256(uint24(MAX_TICK)), "T");
uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
unchecked {
if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;
if (tick > 0) ratio = type(uint256).max / ratio;
// this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
// we then downcast because we know the result always fits within 160 bits due to our tick input constraint
// we round up in the division so getTickAtSqrtRatio of the output price is always consistent
sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
}
}
/* solhint-enable code-complexity */
/// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio
/// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may
/// ever return.
/// @param sqrtPriceX96 The sqrt ratio for which to compute the tick as a Q64.96
/// @return tick The greatest tick for which the ratio is less than or equal to the input ratio
function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) {
// second inequality must be < because the price can never reach the price at the max tick
require(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO, "R");
uint256 ratio = uint256(sqrtPriceX96) << 32;
uint256 r = ratio;
uint256 msb = 0;
assembly {
let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(5, gt(r, 0xFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(4, gt(r, 0xFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(3, gt(r, 0xFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(2, gt(r, 0xF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(1, gt(r, 0x3))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := gt(r, 0x1)
msb := or(msb, f)
}
if (msb >= 128) r = ratio >> (msb - 127);
else r = ratio << (127 - msb);
int256 log_2 = (int256(msb) - 128) << 64;
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(63, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(62, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(61, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(60, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(59, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(58, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(57, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(56, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(55, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(54, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(53, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(52, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(51, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(50, f))
}
int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number
int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);
int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);
tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;
}
/// @notice Rounds down to the nearest tick where tick % tickSpacing == 0
/// @param tick The tick to round
/// @param tickSpacing The tick spacing to round to
/// @return the floored tick
/// @dev Ensure tick +/- tickSpacing does not overflow or underflow int24
function floor(int24 tick, int24 tickSpacing) internal pure returns (int24) {
int24 mod = tick % tickSpacing;
unchecked {
if (mod >= 0) return tick - mod;
return tick - mod - tickSpacing;
}
}
/// @notice Rounds up to the nearest tick where tick % tickSpacing == 0
/// @param tick The tick to round
/// @param tickSpacing The tick spacing to round to
/// @return the ceiled tick
/// @dev Ensure tick +/- tickSpacing does not overflow or underflow int24
function ceil(int24 tick, int24 tickSpacing) internal pure returns (int24) {
int24 mod = tick % tickSpacing;
unchecked {
if (mod > 0) return tick - mod + tickSpacing;
return tick - mod;
}
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;
import {Math} from "openzeppelin-contracts/utils/math/Math.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {Q96} from "./constants/Q.sol";
import {TickMath} from "./TickMath.sol";
/// @title Volatility
/// @notice Provides functions that use Uniswap v3 to compute price volatility
/// @author Aloe Labs, Inc.
library Volatility {
struct PoolMetadata {
// the oldest oracle observation that's been populated by the pool
uint32 maxSecondsAgo;
// the overall fee minus the protocol fee for token0, times 1e6
uint24 gamma0;
// the overall fee minus the protocol fee for token1, times 1e6
uint24 gamma1;
// the pool tick spacing
int24 tickSpacing;
}
struct PoolData {
// the current price (from pool.slot0())
uint160 sqrtPriceX96;
// the current tick (from pool.slot0())
int24 currentTick;
// the mean sqrt(price) over some period (OracleLibrary.consult() to get arithmeticMeanTick, then use TickMath)
uint160 sqrtMeanPriceX96;
// the mean liquidity over some period (OracleLibrary.consult())
uint160 secondsPerLiquidityX128;
// the number of seconds to look back when getting mean tick & mean liquidity
uint32 oracleLookback;
// the liquidity depth at currentTick (from pool.liquidity())
uint128 tickLiquidity;
}
struct FeeGrowthGlobals {
// the fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool
uint256 feeGrowthGlobal0X128;
// the fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool
uint256 feeGrowthGlobal1X128;
// the block timestamp at which feeGrowthGlobal0X128 and feeGrowthGlobal1X128 were last updated
uint32 timestamp;
}
/**
* @notice Estimates implied volatility using https://lambert-guillaume.medium.com/on-chain-volatility-and-uniswap-v3-d031b98143d1
* @param metadata The pool's metadata (may be cached)
* @param data A summary of the pool's state from `pool.slot0` `pool.observe` and `pool.liquidity`
* @param a The pool's cumulative feeGrowthGlobals some time in the past
* @param b The pool's cumulative feeGrowthGlobals as of the current block
* @param scale The timescale (in seconds) in which IV should be reported, e.g. hourly, daily, annualized
* @return An estimate of the implied volatility scaled by 1e18
*/
function estimate(
PoolMetadata memory metadata,
PoolData memory data,
FeeGrowthGlobals memory a,
FeeGrowthGlobals memory b,
uint256 scale
) internal pure returns (uint256) {
unchecked {
if (data.secondsPerLiquidityX128 == 0 || b.timestamp - a.timestamp == 0) return 0;
uint128 revenue0Gamma1 = computeRevenueGamma(
a.feeGrowthGlobal0X128,
b.feeGrowthGlobal0X128,
data.secondsPerLiquidityX128,
data.oracleLookback,
metadata.gamma1
);
uint128 revenue1Gamma0 = computeRevenueGamma(
a.feeGrowthGlobal1X128,
b.feeGrowthGlobal1X128,
data.secondsPerLiquidityX128,
data.oracleLookback,
metadata.gamma0
);
// This is an approximation. Ideally the fees earned during each swap would be multiplied by the price
// *at that swap*. But for prices simulated with GBM and swap sizes either normally or uniformly distributed,
// the error you get from using geometric mean price is <1% even with high drift and volatility.
uint256 volumeGamma0Gamma1 = revenue1Gamma0 + amount0ToAmount1(revenue0Gamma1, data.sqrtMeanPriceX96);
// Fits in uint128
uint256 sqrtTickTVLX32 = FixedPointMathLib.sqrt(
computeTickTVLX64(metadata.tickSpacing, data.currentTick, data.sqrtPriceX96, data.tickLiquidity)
);
if (sqrtTickTVLX32 == 0) return 0;
// Fits in uint48
uint256 timeAdjustmentX32 = FixedPointMathLib.sqrt((scale << 64) / (b.timestamp - a.timestamp));
return (uint256(2e18) * timeAdjustmentX32 * FixedPointMathLib.sqrt(volumeGamma0Gamma1)) / sqrtTickTVLX32;
}
}
/**
* @notice Computes an `amount1` that (at `tick`) is equivalent in worth to the provided `amount0`
* @param amount0 The amount of token0 to convert
* @param sqrtPriceX96 The sqrt(price) at which the conversion should hold true
* @return amount1 An equivalent amount of token1
*/
function amount0ToAmount1(uint128 amount0, uint160 sqrtPriceX96) internal pure returns (uint256 amount1) {
uint224 priceX96 = uint224(Math.mulDiv(sqrtPriceX96, sqrtPriceX96, Q96));
amount1 = Math.mulDiv(amount0, priceX96, Q96);
}
/**
* @notice Computes pool revenue using feeGrowthGlobal accumulators, then scales it down by a factor of gamma
* @param feeGrowthGlobalAX128 The value of feeGrowthGlobal (either 0 or 1) at time A
* @param feeGrowthGlobalBX128 The value of feeGrowthGlobal (either 0 or 1, but matching) at time B (B > A)
* @param secondsPerLiquidityX128 The difference in the secondsPerLiquidity accumulator from `secondsAgo` seconds ago until now
* @param secondsAgo The oracle lookback period that was used to find `secondsPerLiquidityX128`
* @param gamma The fee factor to scale by
* @return Revenue over the period from `block.timestamp - secondsAgo` to `block.timestamp`, scaled down by a factor of gamma
*/
function computeRevenueGamma(
uint256 feeGrowthGlobalAX128,
uint256 feeGrowthGlobalBX128,
uint160 secondsPerLiquidityX128,
uint32 secondsAgo,
uint24 gamma
) internal pure returns (uint128) {
unchecked {
uint256 temp;
if (feeGrowthGlobalBX128 >= feeGrowthGlobalAX128) {
// feeGrowthGlobal has increased from time A to time B
temp = feeGrowthGlobalBX128 - feeGrowthGlobalAX128;
} else {
// feeGrowthGlobal has overflowed between time A and time B
temp = type(uint256).max - feeGrowthGlobalAX128 + feeGrowthGlobalBX128;
}
temp = Math.mulDiv(temp, secondsAgo * gamma, secondsPerLiquidityX128 * 1e6);
return temp > type(uint128).max ? type(uint128).max : uint128(temp);
}
}
/**
* @notice Computes the value of liquidity available at the current tick, denominated in token1
* @param tickSpacing The pool tick spacing (from pool.tickSpacing())
* @param tick The current tick (from pool.slot0())
* @param sqrtPriceX96 The current price (from pool.slot0())
* @param liquidity The liquidity depth at currentTick (from pool.liquidity())
*/
function computeTickTVLX64(
int24 tickSpacing,
int24 tick,
uint160 sqrtPriceX96,
uint128 liquidity
) internal pure returns (uint256 tickTVL) {
tick = TickMath.floor(tick, tickSpacing);
// both value0 and value1 fit in uint192
(uint256 value0, uint256 value1) = _getValueOfLiquidity(
sqrtPriceX96,
TickMath.getSqrtRatioAtTick(tick),
TickMath.getSqrtRatioAtTick(tick + tickSpacing),
liquidity
);
tickTVL = (value0 + value1) << 64;
}
/**
* @notice Computes the value of the liquidity in terms of token1
* @dev Each return value can fit in a uint192 if necessary
* @param sqrtRatioX96 A sqrt price representing the current pool prices
* @param sqrtRatioAX96 A sqrt price representing the lower tick boundary
* @param sqrtRatioBX96 A sqrt price representing the upper tick boundary
* @param liquidity The liquidity being valued
* @return value0 The value of amount0 underlying `liquidity`, in terms of token1
* @return value1 The amount of token1
*/
function _getValueOfLiquidity(
uint160 sqrtRatioX96,
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity
) private pure returns (uint256 value0, uint256 value1) {
assert(sqrtRatioAX96 <= sqrtRatioX96 && sqrtRatioX96 <= sqrtRatioBX96);
unchecked {
uint256 numerator = Math.mulDiv(sqrtRatioX96, sqrtRatioBX96 - sqrtRatioX96, Q96);
value0 = Math.mulDiv(liquidity, numerator, sqrtRatioBX96);
value1 = Math.mulDiv(liquidity, sqrtRatioX96 - sqrtRatioAX96, Q96);
}
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;
uint256 constant ONE = 1e12;
uint256 constant BORROWS_SCALER = type(uint72).max * ONE; // uint72 is from the type of borrowIndex in `Ledger`
uint256 constant MIN_SIGMA = 0.01e18;
// To avoid underflow in `BalanceSheet.computeProbePrices`, ensure that `MAX_SIGMA * Borrower.B <= 1e18`
uint256 constant MAX_SIGMA = 0.18e18;
uint256 constant MIN_RESERVE_FACTOR = 4; // Expressed as reciprocal, e.g. 4 --> 25%
uint256 constant MAX_RESERVE_FACTOR = 20; // Expressed as reciprocal, e.g. 20 --> 5%
// 1 + 1 / MAX_LEVERAGE should correspond to the maximum feasible single-block accrualFactor so that liquidators have time to respond to interest updates
uint256 constant MAX_LEVERAGE = 200;
uint256 constant LIQUIDATION_INCENTIVE = 20; // Expressed as reciprocal, e.g. 20 --> 5%
uint256 constant LIQUIDATION_GRACE_PERIOD = 2 minutes;
uint256 constant IV_SCALE = 24 hours;
uint256 constant IV_CHANGE_PER_SECOND = 5e12;
uint256 constant FEE_GROWTH_GLOBALS_SAMPLE_PERIOD = 1 minutes;
uint32 constant ORACLE_LOOKBACK = 20 minutes;
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;
uint256 constant Q8 = 1 << 8;
uint256 constant Q16 = 1 << 16;
uint256 constant Q24 = 1 << 24;
uint256 constant Q32 = 1 << 32;
uint256 constant Q40 = 1 << 40;
uint256 constant Q48 = 1 << 48;
uint256 constant Q56 = 1 << 56;
uint256 constant Q64 = 1 << 64;
uint256 constant Q72 = 1 << 72;
uint256 constant Q80 = 1 << 80;
uint256 constant Q88 = 1 << 88;
uint256 constant Q96 = 1 << 96;
uint256 constant Q104 = 1 << 104;
uint256 constant Q112 = 1 << 112;
uint256 constant Q120 = 1 << 120;
uint256 constant Q128 = 1 << 128;
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;
import {ERC20, SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {IUniswapV3SwapCallback} from "v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol";
import {IUniswapV3Pool} from "v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import {TickMath} from "aloe-ii-core/libraries/TickMath.sol";
import {Borrower, IManager} from "aloe-ii-core/Borrower.sol";
import {Factory} from "aloe-ii-core/Factory.sol";
import {Lender} from "aloe-ii-core/Lender.sol";
contract FrontendManager is IManager, IUniswapV3SwapCallback {
using SafeTransferLib for ERC20;
event Modify(address indexed owner, int24 tick);
Factory public immutable FACTORY;
constructor(Factory _factory) {
FACTORY = _factory;
}
/// @dev This is safe because though this contract will have many approvals, its assets are always transient.
// As long as we do `safeTransfer` and not `safeTransferFrom`, we're fine.
function uniswapV3SwapCallback(int256 amount0, int256 amount1, bytes calldata) external {
if (amount0 > 0) ERC20(IUniswapV3Pool(msg.sender).token0()).safeTransfer(msg.sender, uint256(amount0));
if (amount1 > 0) ERC20(IUniswapV3Pool(msg.sender).token1()).safeTransfer(msg.sender, uint256(amount1));
}
/* solhint-disable code-complexity */
function callback(bytes calldata data) external returns (uint144 positions) {
// We cast `msg.sender` as a `Borrower`, but it could really be anything. DO NOT TRUST!
Borrower account = Borrower(msg.sender);
// Same goes for `owner` -- we don't yet know if it's really the owner
(address owner, , ) = account.slot0();
// Decoding `data` can't hurt
uint8[] memory actions;
bytes[] memory args;
(actions, args, positions) = abi.decode(data, (uint8[], bytes[], uint144));
// Keep track of this so we don't have to read it from `FACTORY` twice
bool isBorrower;
for (uint256 i; i < actions.length; i++) {
uint8 action = actions[i];
// transfer in
if (action == 0) {
(address asset, uint256 amount) = abi.decode(args[i], (address, uint256));
// NOTE: Users will be approving this contract, so before making *any* call to `transferFrom`,
// we must ensure that `msg.sender` should be allowed to take `owner`'s funds. If `msg.sender`
// *is not* a `Borrower`, `owner` could be anything -- so revert. But if `msg.sender` *is* a
// `Borrower`, then we know that `owner` has explicitly called `modify` and handed execution
// flow to this contract (there's no other way for `Borrower` to have called this).
if (!isBorrower) {
isBorrower = FACTORY.isBorrower(msg.sender);
require(isBorrower);
}
ERC20(asset).safeTransferFrom(owner, msg.sender, amount);
continue;
}
// transfer out
if (action == 1) {
(address asset, uint256 amount) = abi.decode(args[i], (address, uint256));
ERC20(asset).safeTransferFrom(msg.sender, owner, amount);
continue;
}
// borrow
if (action == 2) {
(uint256 amount0, uint256 amount1) = abi.decode(args[i], (uint256, uint256));
account.borrow(amount0, amount1, msg.sender);
continue;
}
// repay
if (action == 3) {
(uint256 amount0, uint256 amount1) = abi.decode(args[i], (uint256, uint256));
account.repay(amount0, amount1);
continue;
}
// add liquidity
if (action == 4) {
(int24 lower, int24 upper, uint128 liquidity) = abi.decode(args[i], (int24, int24, uint128));
account.uniswapDeposit(lower, upper, liquidity);
continue;
}
// remove liquidity
if (action == 5) {
(int24 lower, int24 upper, uint128 liquidity) = abi.decode(args[i], (int24, int24, uint128));
account.uniswapWithdraw(lower, upper, liquidity);
continue;
}
// swap
if (action == 6) {
(address assetIn, int256 amount0, int256 amount1) = abi.decode(args[i], (address, int256, int256));
int256 amountIn;
int256 amountOut;
uint160 sqrtPriceLimitX96;
bool zeroForOne = amount0 < 0;
if (zeroForOne) {
// make sure `amountIn` is positive so that we're doing "exact amount in"
amountIn = -amount0;
amountOut = amount1;
sqrtPriceLimitX96 = TickMath.MIN_SQRT_RATIO + 1;
} else {
// make sure `amountIn` is positive so that we're doing "exact amount in"
amountIn = -amount1;
amountOut = amount0;
sqrtPriceLimitX96 = TickMath.MAX_SQRT_RATIO - 1;
}
// Pull in required funds from `account`
ERC20(assetIn).safeTransferFrom(msg.sender, address(this), uint256(amountIn));
(int256 received0, int256 received1) = account.UNISWAP_POOL().swap({
recipient: msg.sender,
zeroForOne: zeroForOne,
amountSpecified: amountIn,
sqrtPriceLimitX96: sqrtPriceLimitX96,
data: bytes("")
});
if (zeroForOne) require(-received1 >= amountOut, "slippage");
else require(-received0 >= amountOut, "slippage");
}
}
(, int24 currentTick, , , , , ) = account.UNISWAP_POOL().slot0();
emit Modify(owner, currentTick);
}
/* solhint-enable code-complexity */
}