// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
// Common.sol
//
// Common mathematical functions needed by both SD59x18 and UD60x18. Note that these global functions do not
// always operate with SD59x18 and UD60x18 numbers.
/*//////////////////////////////////////////////////////////////////////////
CUSTOM ERRORS
//////////////////////////////////////////////////////////////////////////*/
/// @notice Thrown when the resultant value in {mulDiv} overflows uint256.
error PRBMath_MulDiv_Overflow(uint256 x, uint256 y, uint256 denominator);
/// @notice Thrown when the resultant value in {mulDiv18} overflows uint256.
error PRBMath_MulDiv18_Overflow(uint256 x, uint256 y);
/// @notice Thrown when one of the inputs passed to {mulDivSigned} is `type(int256).min`.
error PRBMath_MulDivSigned_InputTooSmall();
/// @notice Thrown when the resultant value in {mulDivSigned} overflows int256.
error PRBMath_MulDivSigned_Overflow(int256 x, int256 y);
/*//////////////////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////////////////*/
/// @dev The maximum value a uint128 number can have.
uint128 constant MAX_UINT128 = type(uint128).max;
/// @dev The maximum value a uint40 number can have.
uint40 constant MAX_UINT40 = type(uint40).max;
/// @dev The unit number, which the decimal precision of the fixed-point types.
uint256 constant UNIT = 1e18;
/// @dev The unit number inverted mod 2^256.
uint256 constant UNIT_INVERSE = 78156646155174841979727994598816262306175212592076161876661_508869554232690281;
/// @dev The the largest power of two that divides the decimal value of `UNIT`. The logarithm of this value is the least significant
/// bit in the binary representation of `UNIT`.
uint256 constant UNIT_LPOTD = 262144;
/*//////////////////////////////////////////////////////////////////////////
FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @notice Calculates the binary exponent of x using the binary fraction method.
/// @dev Has to use 192.64-bit fixed-point numbers. See https://ethereum.stackexchange.com/a/96594/24693.
/// @param x The exponent as an unsigned 192.64-bit fixed-point number.
/// @return result The result as an unsigned 60.18-decimal fixed-point number.
/// @custom:smtchecker abstract-function-nondet
function exp2(uint256 x) pure returns (uint256 result) {
unchecked {
// Start from 0.5 in the 192.64-bit fixed-point format.
result = 0x800000000000000000000000000000000000000000000000;
// The following logic multiplies the result by $\sqrt{2^{-i}}$ when the bit at position i is 1. Key points:
//
// 1. Intermediate results will not overflow, as the starting point is 2^191 and all magic factors are under 2^65.
// 2. The rationale for organizing the if statements into groups of 8 is gas savings. If the result of performing
// a bitwise AND operation between x and any value in the array [0x80; 0x40; 0x20; 0x10; 0x08; 0x04; 0x02; 0x01] is 1,
// we know that `x & 0xFF` is also 1.
if (x & 0xFF00000000000000 > 0) {
if (x & 0x8000000000000000 > 0) {
result = (result * 0x16A09E667F3BCC909) >> 64;
}
if (x & 0x4000000000000000 > 0) {
result = (result * 0x1306FE0A31B7152DF) >> 64;
}
if (x & 0x2000000000000000 > 0) {
result = (result * 0x1172B83C7D517ADCE) >> 64;
}
if (x & 0x1000000000000000 > 0) {
result = (result * 0x10B5586CF9890F62A) >> 64;
}
if (x & 0x800000000000000 > 0) {
result = (result * 0x1059B0D31585743AE) >> 64;
}
if (x & 0x400000000000000 > 0) {
result = (result * 0x102C9A3E778060EE7) >> 64;
}
if (x & 0x200000000000000 > 0) {
result = (result * 0x10163DA9FB33356D8) >> 64;
}
if (x & 0x100000000000000 > 0) {
result = (result * 0x100B1AFA5ABCBED61) >> 64;
}
}
if (x & 0xFF000000000000 > 0) {
if (x & 0x80000000000000 > 0) {
result = (result * 0x10058C86DA1C09EA2) >> 64;
}
if (x & 0x40000000000000 > 0) {
result = (result * 0x1002C605E2E8CEC50) >> 64;
}
if (x & 0x20000000000000 > 0) {
result = (result * 0x100162F3904051FA1) >> 64;
}
if (x & 0x10000000000000 > 0) {
result = (result * 0x1000B175EFFDC76BA) >> 64;
}
if (x & 0x8000000000000 > 0) {
result = (result * 0x100058BA01FB9F96D) >> 64;
}
if (x & 0x4000000000000 > 0) {
result = (result * 0x10002C5CC37DA9492) >> 64;
}
if (x & 0x2000000000000 > 0) {
result = (result * 0x1000162E525EE0547) >> 64;
}
if (x & 0x1000000000000 > 0) {
result = (result * 0x10000B17255775C04) >> 64;
}
}
if (x & 0xFF0000000000 > 0) {
if (x & 0x800000000000 > 0) {
result = (result * 0x1000058B91B5BC9AE) >> 64;
}
if (x & 0x400000000000 > 0) {
result = (result * 0x100002C5C89D5EC6D) >> 64;
}
if (x & 0x200000000000 > 0) {
result = (result * 0x10000162E43F4F831) >> 64;
}
if (x & 0x100000000000 > 0) {
result = (result * 0x100000B1721BCFC9A) >> 64;
}
if (x & 0x80000000000 > 0) {
result = (result * 0x10000058B90CF1E6E) >> 64;
}
if (x & 0x40000000000 > 0) {
result = (result * 0x1000002C5C863B73F) >> 64;
}
if (x & 0x20000000000 > 0) {
result = (result * 0x100000162E430E5A2) >> 64;
}
if (x & 0x10000000000 > 0) {
result = (result * 0x1000000B172183551) >> 64;
}
}
if (x & 0xFF00000000 > 0) {
if (x & 0x8000000000 > 0) {
result = (result * 0x100000058B90C0B49) >> 64;
}
if (x & 0x4000000000 > 0) {
result = (result * 0x10000002C5C8601CC) >> 64;
}
if (x & 0x2000000000 > 0) {
result = (result * 0x1000000162E42FFF0) >> 64;
}
if (x & 0x1000000000 > 0) {
result = (result * 0x10000000B17217FBB) >> 64;
}
if (x & 0x800000000 > 0) {
result = (result * 0x1000000058B90BFCE) >> 64;
}
if (x & 0x400000000 > 0) {
result = (result * 0x100000002C5C85FE3) >> 64;
}
if (x & 0x200000000 > 0) {
result = (result * 0x10000000162E42FF1) >> 64;
}
if (x & 0x100000000 > 0) {
result = (result * 0x100000000B17217F8) >> 64;
}
}
if (x & 0xFF000000 > 0) {
if (x & 0x80000000 > 0) {
result = (result * 0x10000000058B90BFC) >> 64;
}
if (x & 0x40000000 > 0) {
result = (result * 0x1000000002C5C85FE) >> 64;
}
if (x & 0x20000000 > 0) {
result = (result * 0x100000000162E42FF) >> 64;
}
if (x & 0x10000000 > 0) {
result = (result * 0x1000000000B17217F) >> 64;
}
if (x & 0x8000000 > 0) {
result = (result * 0x100000000058B90C0) >> 64;
}
if (x & 0x4000000 > 0) {
result = (result * 0x10000000002C5C860) >> 64;
}
if (x & 0x2000000 > 0) {
result = (result * 0x1000000000162E430) >> 64;
}
if (x & 0x1000000 > 0) {
result = (result * 0x10000000000B17218) >> 64;
}
}
if (x & 0xFF0000 > 0) {
if (x & 0x800000 > 0) {
result = (result * 0x1000000000058B90C) >> 64;
}
if (x & 0x400000 > 0) {
result = (result * 0x100000000002C5C86) >> 64;
}
if (x & 0x200000 > 0) {
result = (result * 0x10000000000162E43) >> 64;
}
if (x & 0x100000 > 0) {
result = (result * 0x100000000000B1721) >> 64;
}
if (x & 0x80000 > 0) {
result = (result * 0x10000000000058B91) >> 64;
}
if (x & 0x40000 > 0) {
result = (result * 0x1000000000002C5C8) >> 64;
}
if (x & 0x20000 > 0) {
result = (result * 0x100000000000162E4) >> 64;
}
if (x & 0x10000 > 0) {
result = (result * 0x1000000000000B172) >> 64;
}
}
if (x & 0xFF00 > 0) {
if (x & 0x8000 > 0) {
result = (result * 0x100000000000058B9) >> 64;
}
if (x & 0x4000 > 0) {
result = (result * 0x10000000000002C5D) >> 64;
}
if (x & 0x2000 > 0) {
result = (result * 0x1000000000000162E) >> 64;
}
if (x & 0x1000 > 0) {
result = (result * 0x10000000000000B17) >> 64;
}
if (x & 0x800 > 0) {
result = (result * 0x1000000000000058C) >> 64;
}
if (x & 0x400 > 0) {
result = (result * 0x100000000000002C6) >> 64;
}
if (x & 0x200 > 0) {
result = (result * 0x10000000000000163) >> 64;
}
if (x & 0x100 > 0) {
result = (result * 0x100000000000000B1) >> 64;
}
}
if (x & 0xFF > 0) {
if (x & 0x80 > 0) {
result = (result * 0x10000000000000059) >> 64;
}
if (x & 0x40 > 0) {
result = (result * 0x1000000000000002C) >> 64;
}
if (x & 0x20 > 0) {
result = (result * 0x10000000000000016) >> 64;
}
if (x & 0x10 > 0) {
result = (result * 0x1000000000000000B) >> 64;
}
if (x & 0x8 > 0) {
result = (result * 0x10000000000000006) >> 64;
}
if (x & 0x4 > 0) {
result = (result * 0x10000000000000003) >> 64;
}
if (x & 0x2 > 0) {
result = (result * 0x10000000000000001) >> 64;
}
if (x & 0x1 > 0) {
result = (result * 0x10000000000000001) >> 64;
}
}
// In the code snippet below, two operations are executed simultaneously:
//
// 1. The result is multiplied by $(2^n + 1)$, where $2^n$ represents the integer part, and the additional 1
// accounts for the initial guess of 0.5. This is achieved by subtracting from 191 instead of 192.
// 2. The result is then converted to an unsigned 60.18-decimal fixed-point format.
//
// The underlying logic is based on the relationship $2^{191-ip} = 2^{ip} / 2^{191}$, where $ip$ denotes the,
// integer part, $2^n$.
result *= UNIT;
result >>= (191 - (x >> 64));
}
}
/// @notice Finds the zero-based index of the first 1 in the binary representation of x.
///
/// @dev See the note on "msb" in this Wikipedia article: https://en.wikipedia.org/wiki/Find_first_set
///
/// Each step in this implementation is equivalent to this high-level code:
///
/// ```solidity
/// if (x >= 2 ** 128) {
/// x >>= 128;
/// result += 128;
/// }
/// ```
///
/// Where 128 is replaced with each respective power of two factor. See the full high-level implementation here:
/// https://gist.github.com/PaulRBerg/f932f8693f2733e30c4d479e8e980948
///
/// The Yul instructions used below are:
///
/// - "gt" is "greater than"
/// - "or" is the OR bitwise operator
/// - "shl" is "shift left"
/// - "shr" is "shift right"
///
/// @param x The uint256 number for which to find the index of the most significant bit.
/// @return result The index of the most significant bit as a uint256.
/// @custom:smtchecker abstract-function-nondet
function msb(uint256 x) pure returns (uint256 result) {
// 2^128
assembly ("memory-safe") {
let factor := shl(7, gt(x, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
x := shr(factor, x)
result := or(result, factor)
}
// 2^64
assembly ("memory-safe") {
let factor := shl(6, gt(x, 0xFFFFFFFFFFFFFFFF))
x := shr(factor, x)
result := or(result, factor)
}
// 2^32
assembly ("memory-safe") {
let factor := shl(5, gt(x, 0xFFFFFFFF))
x := shr(factor, x)
result := or(result, factor)
}
// 2^16
assembly ("memory-safe") {
let factor := shl(4, gt(x, 0xFFFF))
x := shr(factor, x)
result := or(result, factor)
}
// 2^8
assembly ("memory-safe") {
let factor := shl(3, gt(x, 0xFF))
x := shr(factor, x)
result := or(result, factor)
}
// 2^4
assembly ("memory-safe") {
let factor := shl(2, gt(x, 0xF))
x := shr(factor, x)
result := or(result, factor)
}
// 2^2
assembly ("memory-safe") {
let factor := shl(1, gt(x, 0x3))
x := shr(factor, x)
result := or(result, factor)
}
// 2^1
// No need to shift x any more.
assembly ("memory-safe") {
let factor := gt(x, 0x1)
result := or(result, factor)
}
}
/// @notice Calculates x*y÷denominator with 512-bit precision.
///
/// @dev Credits to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv.
///
/// Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - The denominator must not be zero.
/// - The result must fit in uint256.
///
/// @param x The multiplicand as a uint256.
/// @param y The multiplier as a uint256.
/// @param denominator The divisor as a uint256.
/// @return result The result as a uint256.
/// @custom:smtchecker abstract-function-nondet
function mulDiv(uint256 x, uint256 y, uint256 denominator) pure returns (uint256 result) {
// 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 ("memory-safe") {
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) {
unchecked {
return prod0 / denominator;
}
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
if (prod1 >= denominator) {
revert PRBMath_MulDiv_Overflow(x, y, denominator);
}
////////////////////////////////////////////////////////////////////////////
// 512 by 256 division
////////////////////////////////////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly ("memory-safe") {
// Compute remainder using the mulmod Yul instruction.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512-bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
unchecked {
// Calculate the largest power of two divisor of the denominator using the unary operator ~. This operation cannot overflow
// because the denominator cannot be zero at this point in the function execution. The result is always >= 1.
// For more detail, see https://cs.stackexchange.com/q/138556/92363.
uint256 lpotdod = denominator & (~denominator + 1);
uint256 flippedLpotdod;
assembly ("memory-safe") {
// Factor powers of two out of denominator.
denominator := div(denominator, lpotdod)
// Divide [prod1 prod0] by lpotdod.
prod0 := div(prod0, lpotdod)
// Get the flipped value `2^256 / lpotdod`. If the `lpotdod` is zero, the flipped value is one.
// `sub(0, lpotdod)` produces the two's complement version of `lpotdod`, which is equivalent to flipping all the bits.
// However, `div` interprets this value as an unsigned value: https://ethereum.stackexchange.com/q/147168/24693
flippedLpotdod := add(div(sub(0, lpotdod), lpotdod), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * flippedLpotdod;
// 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;
}
}
/// @notice Calculates x*y÷1e18 with 512-bit precision.
///
/// @dev A variant of {mulDiv} with constant folding, i.e. in which the denominator is hard coded to 1e18.
///
/// Notes:
/// - The body is purposely left uncommented; to understand how this works, see the documentation in {mulDiv}.
/// - The result is rounded toward zero.
/// - We take as an axiom that the result cannot be `MAX_UINT256` when x and y solve the following system of equations:
///
/// $$
/// \begin{cases}
/// x * y = MAX\_UINT256 * UNIT \\
/// (x * y) \% UNIT \geq \frac{UNIT}{2}
/// \end{cases}
/// $$
///
/// Requirements:
/// - Refer to the requirements in {mulDiv}.
/// - The result must fit in uint256.
///
/// @param x The multiplicand as an unsigned 60.18-decimal fixed-point number.
/// @param y The multiplier as an unsigned 60.18-decimal fixed-point number.
/// @return result The result as an unsigned 60.18-decimal fixed-point number.
/// @custom:smtchecker abstract-function-nondet
function mulDiv18(uint256 x, uint256 y) pure returns (uint256 result) {
uint256 prod0;
uint256 prod1;
assembly ("memory-safe") {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
if (prod1 == 0) {
unchecked {
return prod0 / UNIT;
}
}
if (prod1 >= UNIT) {
revert PRBMath_MulDiv18_Overflow(x, y);
}
uint256 remainder;
assembly ("memory-safe") {
remainder := mulmod(x, y, UNIT)
result :=
mul(
or(
div(sub(prod0, remainder), UNIT_LPOTD),
mul(sub(prod1, gt(remainder, prod0)), add(div(sub(0, UNIT_LPOTD), UNIT_LPOTD), 1))
),
UNIT_INVERSE
)
}
}
/// @notice Calculates x*y÷denominator with 512-bit precision.
///
/// @dev This is an extension of {mulDiv} for signed numbers, which works by computing the signs and the absolute values separately.
///
/// Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - Refer to the requirements in {mulDiv}.
/// - None of the inputs can be `type(int256).min`.
/// - The result must fit in int256.
///
/// @param x The multiplicand as an int256.
/// @param y The multiplier as an int256.
/// @param denominator The divisor as an int256.
/// @return result The result as an int256.
/// @custom:smtchecker abstract-function-nondet
function mulDivSigned(int256 x, int256 y, int256 denominator) pure returns (int256 result) {
if (x == type(int256).min || y == type(int256).min || denominator == type(int256).min) {
revert PRBMath_MulDivSigned_InputTooSmall();
}
// Get hold of the absolute values of x, y and the denominator.
uint256 xAbs;
uint256 yAbs;
uint256 dAbs;
unchecked {
xAbs = x < 0 ? uint256(-x) : uint256(x);
yAbs = y < 0 ? uint256(-y) : uint256(y);
dAbs = denominator < 0 ? uint256(-denominator) : uint256(denominator);
}
// Compute the absolute value of x*y÷denominator. The result must fit in int256.
uint256 resultAbs = mulDiv(xAbs, yAbs, dAbs);
if (resultAbs > uint256(type(int256).max)) {
revert PRBMath_MulDivSigned_Overflow(x, y);
}
// Get the signs of x, y and the denominator.
uint256 sx;
uint256 sy;
uint256 sd;
assembly ("memory-safe") {
// "sgt" is the "signed greater than" assembly instruction and "sub(0,1)" is -1 in two's complement.
sx := sgt(x, sub(0, 1))
sy := sgt(y, sub(0, 1))
sd := sgt(denominator, sub(0, 1))
}
// XOR over sx, sy and sd. What this does is to check whether there are 1 or 3 negative signs in the inputs.
// If there are, the result should be negative. Otherwise, it should be positive.
unchecked {
result = sx ^ sy ^ sd == 0 ? -int256(resultAbs) : int256(resultAbs);
}
}
/// @notice Calculates the square root of x using the Babylonian method.
///
/// @dev See https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method.
///
/// Notes:
/// - If x is not a perfect square, the result is rounded down.
/// - Credits to OpenZeppelin for the explanations in comments below.
///
/// @param x The uint256 number for which to calculate the square root.
/// @return result The result as a uint256.
/// @custom:smtchecker abstract-function-nondet
function sqrt(uint256 x) pure returns (uint256 result) {
if (x == 0) {
return 0;
}
// For our first guess, we calculate the biggest power of 2 which is smaller than the square root of x.
//
// We know that the "msb" (most significant bit) of x is a power of 2 such that we have:
//
// $$
// msb(x) <= x <= 2*msb(x)$
// $$
//
// We write $msb(x)$ as $2^k$, and we get:
//
// $$
// k = log_2(x)
// $$
//
// Thus, we can write the initial inequality as:
//
// $$
// 2^{log_2(x)} <= x <= 2*2^{log_2(x)+1} \\
// sqrt(2^k) <= sqrt(x) < sqrt(2^{k+1}) \\
// 2^{k/2} <= sqrt(x) < 2^{(k+1)/2} <= 2^{(k/2)+1}
// $$
//
// Consequently, $2^{log_2(x) /2} is a good first approximation of sqrt(x) with at least one correct bit.
uint256 xAux = uint256(x);
result = 1;
if (xAux >= 2 ** 128) {
xAux >>= 128;
result <<= 64;
}
if (xAux >= 2 ** 64) {
xAux >>= 64;
result <<= 32;
}
if (xAux >= 2 ** 32) {
xAux >>= 32;
result <<= 16;
}
if (xAux >= 2 ** 16) {
xAux >>= 16;
result <<= 8;
}
if (xAux >= 2 ** 8) {
xAux >>= 8;
result <<= 4;
}
if (xAux >= 2 ** 4) {
xAux >>= 4;
result <<= 2;
}
if (xAux >= 2 ** 2) {
result <<= 1;
}
// At this point, `result` is an estimation with at least one bit of precision. We know the true value has at
// most 128 bits, 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 + x / result) >> 1;
result = (result + x / result) >> 1;
result = (result + x / result) >> 1;
result = (result + x / result) >> 1;
result = (result + x / result) >> 1;
result = (result + x / result) >> 1;
result = (result + x / result) >> 1;
// If x is not a perfect square, round the result toward zero.
uint256 roundedResult = x / result;
if (result >= roundedResult) {
result = roundedResult;
}
}
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (libraries/CommonEventsAndErrors.sol)
/// @notice A collection of common events and errors thrown within the Origami contracts
library CommonEventsAndErrors {
error InsufficientBalance(address token, uint256 required, uint256 balance);
error InvalidToken(address token);
error InvalidParam();
error InvalidAddress(address addr);
error InvalidAmount(address token, uint256 amount);
error ExpectedNonZero();
error Slippage(uint256 minAmountExpected, uint256 actualAmount);
error IsPaused();
error UnknownExecuteError(bytes returndata);
error InvalidAccess();
error BreachedMaxTotalSupply(uint256 totalSupply, uint256 maxTotalSupply);
event TokenRecovered(address indexed to, address indexed token, uint256 amount);
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (libraries/DynamicFees.sol)
import { IOrigamiOracle } from "contracts/interfaces/common/oracle/IOrigamiOracle.sol";
import { OrigamiMath } from "contracts/libraries/OrigamiMath.sol";
import { CommonEventsAndErrors } from "contracts/libraries/CommonEventsAndErrors.sol";
/**
* @notice A helper to calculate dynamic entry and exit fees based off the difference
* between an oracle historic vs spot price
*/
library DynamicFees {
using OrigamiMath for uint256;
enum FeeType {
DEPOSIT_FEE,
EXIT_FEE
}
/**
* @notice The current deposit or exit fee based on market conditions.
* Fees are applied to the portion of lovToken shares the depositor
* would have received. Instead that fee portion isn't minted (benefiting remaining users)
* Ignoring the minFeeBps, deposit vs exit fees are symmetric:
* - A 0.004 cent increase in price (away from expected historic) should result a deposit fee of X bps
* - A 0.004 cent decrease in price (away from expected historic) should result an exit fee, also of X bps
* ie X is the same in both cases.
* @dev feeLeverageFactor has 4dp precision
*/
function dynamicFeeBps(
FeeType feeType,
IOrigamiOracle oracle,
address expectedBaseAsset,
uint64 minFeeBps,
uint256 feeLeverageFactor
) internal view returns (uint256) {
// Pull the spot and expected historic price from the oracle.
// Round up for both to be consistent no matter if the oracle is in expected quoted order or not.
(uint256 _spotPrice, uint256 _histPrice, address _baseAsset, address _quoteAsset) = oracle.latestPrices(
IOrigamiOracle.PriceType.SPOT_PRICE,
OrigamiMath.Rounding.ROUND_UP,
IOrigamiOracle.PriceType.HISTORIC_PRICE,
OrigamiMath.Rounding.ROUND_UP
);
// Whether the expected 'base' asset of the oracle is indeed the base asset.
// If not, then the delta and denominator is switched
bool _inQuotedOrder;
if (_baseAsset == expectedBaseAsset) {
_inQuotedOrder = true;
} else if (_quoteAsset != expectedBaseAsset) {
revert CommonEventsAndErrors.InvalidToken(expectedBaseAsset);
}
uint256 _delta;
uint256 _denominator;
if (feeType == FeeType.DEPOSIT_FEE) {
// If spot price is > than the expected historic, then they are exiting
// at a price better than expected. The exit fee is based off the relative
// difference of the expected spotPrice - historicPrice.
// Or opposite if the oracle order is inverted
unchecked {
if (_inQuotedOrder) {
if (_spotPrice < _histPrice) {
(_delta, _denominator) = (_histPrice - _spotPrice, _histPrice);
}
} else {
if (_spotPrice > _histPrice) {
(_delta, _denominator) = (_spotPrice - _histPrice, _spotPrice);
}
}
}
} else {
// If spot price is > than the expected historic, then they are exiting
// at a price better than expected. The exit fee is based off the relative
// difference of the expected spotPrice - historicPrice.
// Or opposite if the oracle order is inverted
unchecked {
if (_inQuotedOrder) {
if (_spotPrice > _histPrice) {
(_delta, _denominator) = (_spotPrice - _histPrice, _histPrice);
}
} else {
if (_spotPrice < _histPrice) {
(_delta, _denominator) = (_histPrice - _spotPrice, _spotPrice);
}
}
}
}
// If no delta, just return the min fee
if (_delta == 0) {
return minFeeBps;
}
// Relative diff multiply by a leverage factor to match the worst case lovToken
// effective exposure
// Result is in basis points, since `feeLeverageFactor` has 4dp precision
uint256 _fee = _delta.mulDiv(
feeLeverageFactor,
_denominator,
OrigamiMath.Rounding.ROUND_UP
);
// Use the maximum of the calculated fee and a pre-set minimum.
return minFeeBps > _fee ? minFeeBps : _fee;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (interfaces/common/borrowAndLend/IOrigamiBorrowAndLend.sol)
/**
* @notice An Origami abstraction over a borrow/lend money market for
* a single `supplyToken` and a single `borrowToken`, for a given `positionOwner`
*/
interface IOrigamiBorrowAndLend {
event PositionOwnerSet(address indexed account);
event SurplusDebtReclaimed(uint256 amount, address indexed recipient);
/**
* @notice Set the position owner who can borrow/lend via this contract
*/
function setPositionOwner(address account) external;
/**
* @notice Supply tokens as collateral
*/
function supply(
uint256 supplyAmount
) external;
/**
* @notice Withdraw collateral tokens to recipient
* @dev Set `withdrawAmount` to type(uint256).max in order to withdraw the whole balance
*/
function withdraw(
uint256 withdrawAmount,
address recipient
) external returns (uint256 amountWithdrawn);
/**
* @notice Borrow tokens and send to recipient
*/
function borrow(
uint256 borrowAmount,
address recipient
) external;
/**
* @notice Repay debt.
* @dev If `repayAmount` is set higher than the actual outstanding debt balance, it will be capped
* to that outstanding debt balance
* `debtRepaidAmount` return parameter will be capped to the outstanding debt balance.
* Any surplus debtTokens (if debt fully repaid) will remain in this contract
*/
function repay(
uint256 repayAmount
) external returns (uint256 debtRepaidAmount);
/**
* @notice Repay debt and withdraw collateral in one step
* @dev If `repayAmount` is set higher than the actual outstanding debt balance, it will be capped
* to that outstanding debt balance
* Set `withdrawAmount` to type(uint256).max in order to withdraw the whole balance
* `debtRepaidAmount` return parameter will be capped to the outstanding debt amount.
* Any surplus debtTokens (if debt fully repaid) will remain in this contract
*/
function repayAndWithdraw(
uint256 repayAmount,
uint256 withdrawAmount,
address recipient
) external returns (
uint256 debtRepaidAmount,
uint256 withdrawnAmount
);
/**
* @notice Supply collateral and borrow in one step
*/
function supplyAndBorrow(
uint256 supplyAmount,
uint256 borrowAmount,
address recipient
) external;
/**
* @notice The approved owner of the borrow/lend position
*/
function positionOwner() external view returns (address);
/**
* @notice The token supplied as collateral
*/
function supplyToken() external view returns (address);
/**
* @notice The token which is borrowed
*/
function borrowToken() external view returns (address);
/**
* @notice The current (manually tracked) balance of tokens supplied
*/
function suppliedBalance() external view returns (uint256);
/**
* @notice The current debt balance of tokens borrowed
*/
function debtBalance() external view returns (uint256);
/**
* @notice Whether a given Assets/Liabilities Ratio is safe, given the upstream
* money market parameters
*/
function isSafeAlRatio(uint256 alRatio) external view returns (bool);
/**
* @notice How many `supplyToken` are available to withdraw from collateral
* from the entire protocol, assuming this contract has fully paid down its debt
*/
function availableToWithdraw() external view returns (uint256);
/**
* @notice How much more capacity is available to supply
*/
function availableToSupply() external view returns (
uint256 supplyCap,
uint256 available
);
/**
* @notice How many `borrowToken` are available to borrow
* from the entire protocol
*/
function availableToBorrow() external view returns (uint256);
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (interfaces/common/access/IOrigamiElevatedAccess.sol)
/**
* @notice Inherit to add Owner roles for DAO elevated access.
*/
interface IOrigamiElevatedAccess {
event ExplicitAccessSet(address indexed account, bytes4 indexed fnSelector, bool indexed value);
event NewOwnerProposed(address indexed oldOwner, address indexed oldProposedOwner, address indexed newProposedOwner);
event NewOwnerAccepted(address indexed oldOwner, address indexed newOwner);
struct ExplicitAccess {
bytes4 fnSelector;
bool allowed;
}
/**
* @notice The address of the current owner.
*/
function owner() external returns (address);
/**
* @notice Explicit approval for an address to execute a function.
* allowedCaller => function selector => true/false
*/
function explicitFunctionAccess(address contractAddr, bytes4 functionSelector) external returns (bool);
/**
* @notice Proposes a new Owner.
* Can only be called by the current owner
*/
function proposeNewOwner(address account) external;
/**
* @notice Caller accepts the role as new Owner.
* Can only be called by the proposed owner
*/
function acceptOwner() external;
/**
* @notice Grant `allowedCaller` the rights to call the function selectors in the access list.
* @dev fnSelector == bytes4(keccak256("fn(argType1,argType2,...)"))
*/
function setExplicitAccess(address allowedCaller, ExplicitAccess[] calldata access) external;
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (interfaces/common/flashLoan/IOrigamiFlashLoanProvider.sol)
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @notice An Origami abstraction over FlashLoan providers
*/
interface IOrigamiFlashLoanProvider {
/**
* @notice Initiate a flashloan for a single token
* The caller must implement the `IOrigamiFlashLoanReceiver()` interface.
* @param token The ERC20 token to borrow
* @param amount The amount to borrow
* @param params Client specific abi encoded params which are passed through from the msg.sender
* and into the `flashLoanCallback()` call
*/
function flashLoan(
IERC20 token,
uint256 amount,
bytes memory params
) external;
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (interfaces/common/flashLoan/IOrigamiFlashLoanReceiver.sol)
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @notice Handle Flash Loan callback's originated from a `IOrigamiFlashLoanProvider`
*/
interface IOrigamiFlashLoanReceiver {
/**
* @notice Invoked from IOrigamiFlashLoanProvider once a flash loan is successfully
* received, to the msg.sender of `flashLoan()`
* @dev Must return false (or revert) if handling within the callback failed.
* @param token The ERC20 token which has been borrowed
* @param amount The amount which has been borrowed
* @param fee The flashloan fee amount (in the same token)
* @param params Client specific abi encoded params which are passed through from the original `flashLoan()` call
*/
function flashLoanCallback(
IERC20 token,
uint256 amount,
uint256 fee,
bytes calldata params
) external returns (bool success);
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (interfaces/investments/IOrigamiInvestment.sol)
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol";
/**
* @title Origami Investment
* @notice Users invest in the underlying protocol and receive a number of this Origami investment in return.
* Origami will apply the accepted investment token into the underlying protocol in the most optimal way.
*/
interface IOrigamiInvestment is IERC20Metadata, IERC20Permit {
event TokenPricesSet(address indexed _tokenPrices);
event ManagerSet(address indexed manager);
event PerformanceFeeSet(uint256 fee);
/**
* @notice Track the depoyed version of this contract.
*/
function apiVersion() external pure returns (string memory);
/**
* @notice The underlying token this investment wraps.
* @dev For informational purposes only, eg integrations/FE
* If the investment wraps a protocol without an ERC20 (eg a non-liquid staked position)
* then this may be 0x0
*/
function baseToken() external view returns (address);
/**
* @notice Emitted when a user makes a new investment
* @param user The user who made the investment
* @param fromTokenAmount The number of `fromToken` used to invest
* @param fromToken The token used to invest, one of `acceptedInvestTokens()`
* @param investmentAmount The number of investment tokens received, after fees
**/
event Invested(address indexed user, uint256 fromTokenAmount, address indexed fromToken, uint256 investmentAmount);
/**
* @notice Emitted when a user exists a position in an investment
* @param user The user who exited the investment
* @param investmentAmount The number of Origami investment tokens sold
* @param toToken The token the user exited into
* @param toTokenAmount The number of `toToken` received, after fees
* @param recipient The receipient address of the `toToken`s
**/
event Exited(address indexed user, uint256 investmentAmount, address indexed toToken, uint256 toTokenAmount, address indexed recipient);
/// @notice Errors for unsupported functions - for example if native chain ETH/AVAX/etc isn't a vaild investment
error Unsupported();
/**
* @notice The set of accepted tokens which can be used to invest.
* If the native chain ETH/AVAX is accepted, 0x0 will also be included in this list.
*/
function acceptedInvestTokens() external view returns (address[] memory);
/**
* @notice The set of accepted tokens which can be used to exit into.
* If the native chain ETH/AVAX is accepted, 0x0 will also be included in this list.
*/
function acceptedExitTokens() external view returns (address[] memory);
/**
* @notice Whether new investments are paused.
*/
function areInvestmentsPaused() external view returns (bool);
/**
* @notice Whether exits are temporarily paused.
*/
function areExitsPaused() external view returns (bool);
/**
* @notice Quote data required when entering into this investment.
*/
struct InvestQuoteData {
/// @notice The token used to invest, which must be one of `acceptedInvestTokens()`
address fromToken;
/// @notice The quantity of `fromToken` to invest with
uint256 fromTokenAmount;
/// @notice The maximum acceptable slippage of the `expectedInvestmentAmount`
uint256 maxSlippageBps;
/// @notice The maximum deadline to execute the transaction.
uint256 deadline;
/// @notice The expected amount of this Origami Investment token to receive in return
uint256 expectedInvestmentAmount;
/// @notice The minimum amount of this Origami Investment Token to receive after
/// slippage has been applied.
uint256 minInvestmentAmount;
/// @notice Any extra quote parameters required by the underlying investment
bytes underlyingInvestmentQuoteData;
}
/**
* @notice Quote data required when exoomg this investment.
*/
struct ExitQuoteData {
/// @notice The amount of this investment to sell
uint256 investmentTokenAmount;
/// @notice The token to sell into, which must be one of `acceptedExitTokens()`
address toToken;
/// @notice The maximum acceptable slippage of the `expectedToTokenAmount`
uint256 maxSlippageBps;
/// @notice The maximum deadline to execute the transaction.
uint256 deadline;
/// @notice The expected amount of `toToken` to receive in return
/// @dev Note slippage is applied to this when calling `invest()`
uint256 expectedToTokenAmount;
/// @notice The minimum amount of `toToken` to receive after
/// slippage has been applied.
uint256 minToTokenAmount;
/// @notice Any extra quote parameters required by the underlying investment
bytes underlyingInvestmentQuoteData;
}
/**
* @notice Get a quote to buy this Origami investment using one of the accepted tokens.
* @dev The 0x0 address can be used for native chain ETH/AVAX
* @param fromTokenAmount How much of `fromToken` to invest with
* @param fromToken What ERC20 token to purchase with. This must be one of `acceptedInvestTokens`
* @param maxSlippageBps The maximum acceptable slippage of the received investment amount
* @param deadline The maximum deadline to execute the exit.
* @return quoteData The quote data, including any params required for the underlying investment type.
* @return investFeeBps Any fees expected when investing with the given token, either from Origami or from the underlying investment.
*/
function investQuote(
uint256 fromTokenAmount,
address fromToken,
uint256 maxSlippageBps,
uint256 deadline
) external view returns (
InvestQuoteData memory quoteData,
uint256[] memory investFeeBps
);
/**
* @notice User buys this Origami investment with an amount of one of the approved ERC20 tokens.
* @param quoteData The quote data received from investQuote()
* @return investmentAmount The actual number of this Origami investment tokens received.
*/
function investWithToken(
InvestQuoteData calldata quoteData
) external returns (
uint256 investmentAmount
);
/**
* @notice User buys this Origami investment with an amount of native chain token (ETH/AVAX)
* @param quoteData The quote data received from investQuote()
* @return investmentAmount The actual number of this Origami investment tokens received.
*/
function investWithNative(
InvestQuoteData calldata quoteData
) external payable returns (
uint256 investmentAmount
);
/**
* @notice Get a quote to sell this Origami investment to receive one of the accepted tokens.
* @dev The 0x0 address can be used for native chain ETH/AVAX
* @param investmentAmount The number of Origami investment tokens to sell
* @param toToken The token to receive when selling. This must be one of `acceptedExitTokens`
* @param maxSlippageBps The maximum acceptable slippage of the received `toToken`
* @param deadline The maximum deadline to execute the exit.
* @return quoteData The quote data, including any params required for the underlying investment type.
* @return exitFeeBps Any fees expected when exiting the investment to the nominated token, either from Origami or from the underlying investment.
*/
function exitQuote(
uint256 investmentAmount,
address toToken,
uint256 maxSlippageBps,
uint256 deadline
) external view returns (
ExitQuoteData memory quoteData,
uint256[] memory exitFeeBps
);
/**
* @notice Sell this Origami investment to receive one of the accepted tokens.
* @param quoteData The quote data received from exitQuote()
* @param recipient The receiving address of the `toToken`
* @return toTokenAmount The number of `toToken` tokens received upon selling the Origami investment tokens.
*/
function exitToToken(
ExitQuoteData calldata quoteData,
address recipient
) external returns (
uint256 toTokenAmount
);
/**
* @notice Sell this Origami investment to native ETH/AVAX.
* @param quoteData The quote data received from exitQuote()
* @param recipient The receiving address of the native chain token.
* @return nativeAmount The number of native chain ETH/AVAX/etc tokens received upon selling the Origami investment tokens.
*/
function exitToNative(
ExitQuoteData calldata quoteData,
address payable recipient
) external returns (
uint256 nativeAmount
);
/**
* @notice The maximum amount of fromToken's that can be deposited
* taking any other underlying protocol constraints into consideration
*/
function maxInvest(address fromToken) external view returns (uint256 amount);
/**
* @notice The maximum amount of tokens that can be exited into the toToken
* taking any other underlying protocol constraints into consideration
*/
function maxExit(address toToken) external view returns (uint256 amount);
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (interfaces/investments/lovToken/IOrigamiLovToken.sol)
import { IOrigamiOTokenManager } from "contracts/interfaces/investments/IOrigamiOTokenManager.sol";
import { IOrigamiInvestment } from "contracts/interfaces/investments/IOrigamiInvestment.sol";
/**
* @title Origami lovToken
*
* @notice Users deposit with an accepted token and are minted lovTokens
* Origami will rebalance to lever up on the underlying reserve token, targetting a
* specific A/L (assets / liabilities) range
*
* @dev The logic on how to handle the specific deposits/exits for each lovToken is delegated
* to a manager contract
*/
interface IOrigamiLovToken is IOrigamiInvestment {
event PerformanceFeesCollected(address indexed feeCollector, uint256 mintAmount);
event FeeCollectorSet(address indexed feeCollector);
event MaxTotalSupplySet(uint256 maxTotalSupply);
/**
* @notice The token used to track reserves for this investment
*/
function reserveToken() external view returns (address);
/**
* @notice The Origami contract managing the deposits/exits and the application of
* the deposit tokens into the underlying protocol
*/
function manager() external view returns (IOrigamiOTokenManager);
/**
* @notice Set the Origami lovToken Manager.
*/
function setManager(address _manager) external;
/**
* @notice Set the vault performance fee
* @dev Represented in basis points
*/
function setAnnualPerformanceFee(uint48 _annualPerformanceFeeBps) external;
/**
* @notice Set the max total supply allowed for investments into this lovToken
*/
function setMaxTotalSupply(uint256 _maxTotalSupply) external;
/**
* @notice Set the Origami performance fee collector address
*/
function setFeeCollector(address _feeCollector) external;
/**
* @notice Set the helper to calculate current off-chain/subgraph integration
*/
function setTokenPrices(address _tokenPrices) external;
/**
* @notice Collect the performance fees to the Origami Treasury
*/
function collectPerformanceFees() external returns (uint256 amount);
/**
* @notice How many reserve tokens would one get given a number of lovToken shares
* @dev Implementations must use the Oracle 'SPOT_PRICE' to value any debt in terms of the reserve token
*/
function sharesToReserves(uint256 shares) external view returns (uint256);
/**
* @notice How many lovToken shares would one get given a number of reserve tokens
* @dev Implementations must use the Oracle 'SPOT_PRICE' to value any debt in terms of the reserve token
*/
function reservesToShares(uint256 reserves) external view returns (uint256);
/**
* @notice How many reserve tokens would one get given a single share, as of now
* @dev Implementations must use the Oracle 'HISTORIC_PRICE' to value any debt in terms of the reserve token
*/
function reservesPerShare() external view returns (uint256);
/**
* @notice The current amount of available reserves for redemptions
* @dev Implementations must use the Oracle 'SPOT_PRICE' to value any debt in terms of the reserve token
*/
function totalReserves() external view returns (uint256);
/**
* @notice The maximum allowed supply of this token for user investments
* @dev The actual totalSupply() may be greater than `maxTotalSupply`
* in order to start organically shrinking supply or from performance fees
*/
function maxTotalSupply() external view returns (uint256);
/**
* @notice Retrieve the current assets, liabilities and calculate the ratio
* @dev Implementations must use the Oracle 'SPOT_PRICE' to value any debt in terms of the reserve token
*/
function assetsAndLiabilities() external view returns (
uint256 assets,
uint256 liabilities,
uint256 ratio
);
/**
* @notice The current effective exposure (EE) of this lovToken
* to `PRECISION` precision
* @dev = reserves / (reserves - liabilities)
* Implementations must use the Oracle 'SPOT_PRICE' to value any debt in terms of the reserve token
*/
function effectiveExposure() external view returns (uint128);
/**
* @notice The valid lower and upper bounds of A/L allowed when users deposit/exit into lovToken
* @dev Transactions will revert if the resulting A/L is outside of this range
*/
function userALRange() external view returns (uint128 floor, uint128 ceiling);
/**
* @notice The current deposit and exit fee based on market conditions.
* Fees are the equivalent of burning lovToken shares - benefit remaining vault users
* @dev represented in basis points
*/
function getDynamicFeesBps() external view returns (uint256 depositFeeBps, uint256 exitFeeBps);
/**
* @notice The address used to collect the Origami performance fees.
*/
function feeCollector() external view returns (address);
/**
* @notice The annual performance fee to Origami treasury
* Represented in basis points
*/
function annualPerformanceFeeBps() external view returns (uint48);
/**
* @notice The last time the performance fee was collected
*/
function lastPerformanceFeeTime() external view returns (uint48);
/**
* @notice The performance fee amount which would be collected as of now,
* based on the total supply
*/
function accruedPerformanceFee() external view returns (uint256);
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (interfaces/investments/lovToken/managers/IOrigamiLovTokenFlashAndBorrowManager.sol)
import { IOrigamiOracle } from "contracts/interfaces/common/oracle/IOrigamiOracle.sol";
import { IOrigamiSwapper } from "contracts/interfaces/common/swappers/IOrigamiSwapper.sol";
import { IOrigamiLovTokenManager } from "contracts/interfaces/investments/lovToken/managers/IOrigamiLovTokenManager.sol";
import { IOrigamiFlashLoanReceiver } from "contracts/interfaces/common/flashLoan/IOrigamiFlashLoanReceiver.sol";
import { IOrigamiFlashLoanProvider } from "contracts/interfaces/common/flashLoan/IOrigamiFlashLoanProvider.sol";
/**
* @title Origami lovToken Manager
* @notice The delegated logic to handle deposits/exits, and borrow/repay (rebalances) into the underlying reserve token
*/
interface IOrigamiLovTokenFlashAndBorrowManager is IOrigamiLovTokenManager, IOrigamiFlashLoanReceiver {
event SwapperSet(address indexed swapper);
event FlashLoanProviderSet(address indexed provider);
event OraclesSet(address indexed debtTokenToReserveTokenOracle, address indexed dynamicFeePriceOracle);
event BorrowLendSet(address indexed addr);
/**
* @notice Set the swapper responsible for `reserveToken` <--> `debtToken` swaps
*/
function setSwapper(address _swapper) external;
/**
* @notice Set the `reserveToken` <--> `debtToken` oracle configuration
*/
function setOracles(address _debtTokenToReserveTokenOracle, address _dynamicFeePriceOracle) external;
/**
* @notice Set the flash loan provider
*/
function setFlashLoanProvider(address _provider) external;
/**
* @notice Set the Origami Borrow/Lend position holder
*/
function setBorrowLend(address _address) external;
struct RebalanceUpParams {
// The amount of `debtToken` to flashloan, used to repay Aave/Spark debt
uint256 flashLoanAmount;
// The amount of `reserveToken` collateral to withdraw after debt is repaid
uint256 collateralToWithdraw;
// The swap quote data to swap from `reserveToken` -> `debtToken`
bytes swapData;
// The min balance threshold for when surplus balance of `debtToken` is repaid to the Spark/Aave position
uint256 repaySurplusThreshold;
// The minimum acceptable A/L, will revert if below this
uint128 minNewAL;
// The maximum acceptable A/L, will revert if above this
uint128 maxNewAL;
}
/**
* @notice Increase the A/L by reducing liabilities. Flash loan and repay debt, and withdraw collateral to repay the flash loan
*/
function rebalanceUp(RebalanceUpParams calldata params) external;
/**
* @notice Force a rebalanceUp ignoring A/L ceiling/floor
* @dev Separate function to above to have stricter control on who can force
*/
function forceRebalanceUp(RebalanceUpParams calldata params) external;
struct RebalanceDownParams {
// The amount of new `debtToken` to flashloan
uint256 flashLoanAmount;
// The minimum amount of `reserveToken` expected when swapping from the flashloaned amount
uint256 minExpectedReserveToken;
// The swap quote data to swap from `debtToken` -> `reserveToken`
bytes swapData;
// The minimum acceptable A/L, will revert if below this
uint128 minNewAL;
// The maximum acceptable A/L, will revert if above this
uint128 maxNewAL;
}
/**
* @notice Decrease the A/L by increasing liabilities. Flash loan `debtToken` swap to `reserveToken`
* and add as collateral into Aave/Spark. Then borrow `debtToken` to repay the flash loan.
*/
function rebalanceDown(RebalanceDownParams calldata params) external;
/**
* @notice Force a rebalanceDown ignoring A/L ceiling/floor
* @dev Separate function to above to have stricter control on who can force
*/
function forceRebalanceDown(RebalanceDownParams calldata params) external;
/**
* @notice The flashLoan provider contract, which may be through Aave/Spark/Balancer/etc
*/
function flashLoanProvider() external view returns (IOrigamiFlashLoanProvider);
/**
* @notice The swapper for `debtToken` <--> `reserveToken`
*/
function swapper() external view returns (IOrigamiSwapper);
/**
* @notice The oracle to convert `debtToken` <--> `reserveToken`
*/
function debtTokenToReserveTokenOracle() external view returns (IOrigamiOracle);
/**
* @notice The base asset used when retrieving the prices for dynamic fee calculations.
*/
function dynamicFeeOracleBaseToken() external view returns (address);
/**
* @notice The oracle to use when observing prices which are used for the dynamic fee calculations
*/
function dynamicFeePriceOracle() external view returns (IOrigamiOracle);
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (interfaces/investments/lovToken/managers/IOrigamiLovTokenManager.sol)
import { IOrigamiOTokenManager } from "contracts/interfaces/investments/IOrigamiOTokenManager.sol";
import { IWhitelisted } from "contracts/interfaces/common/access/IWhitelisted.sol";
import { IOrigamiOracle } from "contracts/interfaces/common/oracle/IOrigamiOracle.sol";
import { IOrigamiLovToken } from "contracts/interfaces/investments/lovToken/IOrigamiLovToken.sol";
/**
* @title Origami lovToken Manager
* @notice The delegated logic to handle deposits/exits, and borrow/repay (rebalances) into the underlying reserve token
*/
interface IOrigamiLovTokenManager is IOrigamiOTokenManager, IWhitelisted {
event FeeConfigSet(uint16 maxExitFeeBps, uint16 minExitFeeBps, uint24 feeLeverageFactor);
event UserALRangeSet(uint128 floor, uint128 ceiling);
event RebalanceALRangeSet(uint128 floor, uint128 ceiling);
event Rebalance(
/// @dev positive when Origami supplies the `reserveToken` as new collateral, negative when Origami withdraws collateral
/// Represented in the units of the `reserveToken` of this lovToken
int256 collateralChange,
/// @dev positive when Origami borrows new debt, negative when Origami repays debt
/// Represented in the units of the `debtToken` of this lovToken
int256 debtChange,
/// @dev The Assets/Liabilities ratio before the rebalance
uint256 alRatioBefore,
/// @dev The Assets/Liabilities ratio after the rebalance
uint256 alRatioAfter
);
error ALTooLow(uint128 ratioBefore, uint128 ratioAfter, uint128 minRatio);
error ALTooHigh(uint128 ratioBefore, uint128 ratioAfter, uint128 maxRatio);
error NoAvailableReserves();
/**
* @notice Set the minimum fee (in basis points) of lovToken's for deposit and exit,
* and also the nominal leverage factor applied within the fee calculations
* @dev feeLeverageFactor has 4dp precision
*/
function setFeeConfig(uint16 _minDepositFeeBps, uint16 _minExitFeeBps, uint24 _feeLeverageFactor) external;
/**
* @notice Set the valid lower and upper bounds of A/L when users deposit/exit into lovToken
*/
function setUserALRange(uint128 floor, uint128 ceiling) external;
/**
* @notice Set the valid range for when a rebalance is not required.
*/
function setRebalanceALRange(uint128 floor, uint128 ceiling) external;
/**
* @notice lovToken contract - eg lovDSR
*/
function lovToken() external view returns (IOrigamiLovToken);
/**
* @notice The min deposit/exit fee and feeLeverageFactor configuration
* @dev feeLeverageFactor has 4dp precision
*/
function getFeeConfig() external view returns (uint64 minDepositFeeBps, uint64 minExitFeeBps, uint64 feeLeverageFactor);
/**
* @notice The current deposit and exit fee based on market conditions.
* Fees are the equivalent of burning lovToken shares - benefit remaining vault users
* @dev represented in basis points
*/
function getDynamicFeesBps() external view returns (uint256 depositFeeBps, uint256 exitFeeBps);
/**
* @notice The valid lower and upper bounds of A/L allowed when users deposit/exit into lovToken
* @dev Transactions will revert if the resulting A/L is outside of this range
*/
function userALRange() external view returns (uint128 floor, uint128 ceiling);
/**
* @notice The valid range for when a rebalance is not required.
* When a rebalance occurs, the transaction will revert if the resulting A/L is outside of this range.
*/
function rebalanceALRange() external view returns (uint128 floor, uint128 ceiling);
/**
* @notice The common precision used
*/
function PRECISION() external view returns (uint256);
/**
* @notice The reserveToken that the lovToken levers up on
*/
function reserveToken() external view returns (address);
/**
* @notice The token which lovToken borrows to increase the A/L ratio
*/
function debtToken() external view returns (address);
/**
* @notice The total balance of reserve tokens this lovToken holds, and also if deployed as collateral
* in other platforms
*/
function reservesBalance() external view returns (uint256);
/**
* @notice The debt of the lovToken from the borrower, converted into the reserveToken
* @dev Use the Oracle `debtPriceType` to value any debt in terms of the reserve token
*/
function liabilities(IOrigamiOracle.PriceType debtPriceType) external view returns (uint256);
/**
* @notice The current asset/liability (A/L) of this lovToken
* to `PRECISION` precision
* @dev = reserves / liabilities
*/
function assetToLiabilityRatio() external view returns (uint128);
/**
* @notice Retrieve the current assets, liabilities and calculate the ratio
* @dev Use the Oracle `debtPriceType` to value any debt in terms of the reserve token
*/
function assetsAndLiabilities(IOrigamiOracle.PriceType debtPriceType) external view returns (
uint256 assets,
uint256 liabilities,
uint256 ratio
);
/**
* @notice The current effective exposure (EE) of this lovToken
* to `PRECISION` precision
* @dev = reserves / (reserves - liabilities)
* Use the Oracle `debtPriceType` to value any debt in terms of the reserve token
*/
function effectiveExposure(IOrigamiOracle.PriceType debtPriceType) external view returns (uint128);
/**
* @notice The amount of reserves that users may redeem their lovTokens as of this block
* @dev = reserves - liabilities
* Use the Oracle `debtPriceType` to value any debt in terms of the reserve token
*/
function userRedeemableReserves(IOrigamiOracle.PriceType debtPriceType) external view returns (uint256);
/**
* @notice How many reserve tokens would one get given a number of lovToken shares
* @dev Use the Oracle `debtPriceType` to value any debt in terms of the reserve token
*/
function sharesToReserves(uint256 shares, IOrigamiOracle.PriceType debtPriceType) external view returns (uint256);
/**
* @notice How many lovToken shares would one get given a number of reserve tokens
* @dev Use the Oracle `debtPriceType` to value any debt in terms of the reserve token
*/
function reservesToShares(uint256 reserves, IOrigamiOracle.PriceType debtPriceType) external view returns (uint256);
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (interfaces/investments/util/IOrigamiManagerPausable.sol)
/**
* @title A mixin to add pause/unpause for Origami manager contracts
*/
interface IOrigamiManagerPausable {
struct Paused {
bool investmentsPaused;
bool exitsPaused;
}
event PauserSet(address indexed account, bool canPause);
event PausedSet(Paused paused);
/// @notice A set of accounts which are allowed to pause deposits/withdrawals immediately
/// under emergency
function pausers(address) external view returns (bool);
/// @notice Pause/unpause deposits or withdrawals
/// @dev Can only be called by allowed pausers or governance.
function setPaused(Paused memory updatedPaused) external;
/// @notice Allow/Deny an account to pause/unpause deposits or withdrawals
function setPauser(address account, bool canPause) external;
/// @notice Check if given account can pause investments/exits
function isPauser(address account) external view returns (bool canPause);
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (interfaces/investments/IOrigamiOTokenManager.sol)
import { IOrigamiInvestment } from "contracts/interfaces/investments/IOrigamiInvestment.sol";
import { IOrigamiManagerPausable } from "contracts/interfaces/investments/util/IOrigamiManagerPausable.sol";
/**
* @title Origami oToken Manager (no native ETH/AVAX/etc)
* @notice The delegated logic to handle deposits/exits into an oToken, and allocating the deposit tokens
* into the underlying protocol
*/
interface IOrigamiOTokenManager is IOrigamiManagerPausable {
/**
* @notice The underlying token this investment wraps.
* @dev For informational purposes only, eg integrations/FE
*/
function baseToken() external view returns (address);
/**
* @notice The set of accepted tokens which can be used to invest.
*/
function acceptedInvestTokens() external view returns (address[] memory);
/**
* @notice The set of accepted tokens which can be used to exit into.
*/
function acceptedExitTokens() external view returns (address[] memory);
/**
* @notice Whether new investments are paused.
*/
function areInvestmentsPaused() external view returns (bool);
/**
* @notice Whether exits are temporarily paused.
*/
function areExitsPaused() external view returns (bool);
/**
* @notice Get a quote to buy this oToken using one of the accepted tokens.
* @param fromTokenAmount How much of `fromToken` to invest with
* @param fromToken What ERC20 token to purchase with. This must be one of `acceptedInvestTokens`
* @param maxSlippageBps The maximum acceptable slippage of the received investment amount
* @param deadline The maximum deadline to execute the exit.
* @return quoteData The quote data, including any params required for the underlying investment type.
* @return investFeeBps Any fees expected when investing with the given token, either from Origami or from the underlying investment.
*/
function investQuote(
uint256 fromTokenAmount,
address fromToken,
uint256 maxSlippageBps,
uint256 deadline
) external view returns (
IOrigamiInvestment.InvestQuoteData memory quoteData,
uint256[] memory investFeeBps
);
/**
* @notice User buys this Origami investment with an amount of one of the approved ERC20 tokens.
* @param account The account to deposit on behalf of
* @param quoteData The quote data received from investQuote()
* @return investmentAmount The actual number of this Origami investment tokens received.
*/
function investWithToken(
address account,
IOrigamiInvestment.InvestQuoteData calldata quoteData
) external returns (
uint256 investmentAmount
);
/**
* @notice Get a quote to sell this oToken to receive one of the accepted tokens.
* @param investmentAmount The number of oTokens to sell
* @param toToken The token to receive when selling. This must be one of `acceptedExitTokens`
* @param maxSlippageBps The maximum acceptable slippage of the received `toToken`
* @param deadline The maximum deadline to execute the exit.
* @return quoteData The quote data, including any params required for the underlying investment type.
* @return exitFeeBps Any fees expected when exiting the investment to the nominated token, either from Origami or from the underlying protocol.
*/
function exitQuote(
uint256 investmentAmount,
address toToken,
uint256 maxSlippageBps,
uint256 deadline
) external view returns (
IOrigamiInvestment.ExitQuoteData memory quoteData,
uint256[] memory exitFeeBps
);
/**
* @notice Sell this oToken to receive one of the accepted tokens.
* @param account The account to exit on behalf of
* @param quoteData The quote data received from exitQuote()
* @param recipient The receiving address of the `toToken`
* @return toTokenAmount The number of `toToken` tokens received upon selling the oToken
* @return toBurnAmount The number of oToken to be burnt after exiting this position
*/
function exitToToken(
address account,
IOrigamiInvestment.ExitQuoteData calldata quoteData,
address recipient
) external returns (uint256 toTokenAmount, uint256 toBurnAmount);
/**
* @notice The maximum amount of fromToken's that can be deposited
* taking any other underlying protocol constraints into consideration
*/
function maxInvest(address fromToken) external view returns (uint256 amount);
/**
* @notice The maximum amount of tokens that can be exited into the toToken
* taking any other underlying protocol constraints into consideration
*/
function maxExit(address toToken) external view returns (uint256 amount);
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (interfaces/common/oracle/IOrigamiOracle.sol)
import { OrigamiMath } from "contracts/libraries/OrigamiMath.sol";
/**
* @notice An oracle which returns prices for pairs of assets, where an asset
* could refer to a token (eg DAI) or a currency (eg USD)
* Convention is the same as the FX market. Given the DAI/USD pair:
* - DAI = Base Asset (LHS of pair)
* - USD = Quote Asset (RHS of pair)
* This price defines how many USD you get if selling 1 DAI
*
* Further, an oracle can define two PriceType's:
* - SPOT_PRICE: The latest spot price, for example from a chainlink oracle
* - HISTORIC_PRICE: An expected (eg 1:1 peg) or calculated historic price (eg TWAP)
*
* For assets which do are not tokens (eg USD), an internal address reference will be used
* since this is for internal purposes only
*/
interface IOrigamiOracle {
error InvalidPrice(address oracle, int256 price);
error InvalidOracleData(address oracle);
error StalePrice(address oracle, uint256 lastUpdatedAt, int256 price);
error UnknownPriceType(uint8 priceType);
error BelowMinValidRange(address oracle, uint256 price, uint128 floor);
error AboveMaxValidRange(address oracle, uint256 price, uint128 ceiling);
event ValidPriceRangeSet(uint128 validFloor, uint128 validCeiling);
enum PriceType {
/// @notice The current spot price of this Oracle
SPOT_PRICE,
/// @notice The historic price of this Oracle.
/// It may be a fixed expectation (eg DAI/USD would be fixed to 1)
/// or use a TWAP or some other moving average, etc.
HISTORIC_PRICE
}
/**
* @dev Wrapped in a struct to remove stack-too-deep constraints
*/
struct BaseOracleParams {
string description;
address baseAssetAddress;
uint8 baseAssetDecimals;
address quoteAssetAddress;
uint8 quoteAssetDecimals;
}
/**
* @notice The address used to reference the baseAsset for amount conversions
*/
function baseAsset() external view returns (address);
/**
* @notice The address used to reference the quoteAsset for amount conversions
*/
function quoteAsset() external view returns (address);
/**
* @notice The number of decimals of precision the price is returned as
*/
function decimals() external view returns (uint8);
/**
* @notice The precision that the cross rate oracle price is returned as: `10^decimals`
*/
function precision() external view returns (uint256);
/**
* @notice A human readable description for this oracle
*/
function description() external view returns (string memory);
/**
* @notice Return the latest oracle price, to `decimals` precision
* @dev This may still revert - eg if deemed stale, div by 0, negative price
* @param priceType What kind of price - Spot or Historic
* @param roundingMode Round the price at each intermediate step such that the final price rounds in the specified direction.
*/
function latestPrice(
PriceType priceType,
OrigamiMath.Rounding roundingMode
) external view returns (uint256 price);
/**
* @notice Same as `latestPrice()` but for two separate prices from this oracle
*/
function latestPrices(
PriceType priceType1,
OrigamiMath.Rounding roundingMode1,
PriceType priceType2,
OrigamiMath.Rounding roundingMode2
) external view returns (
uint256 price1,
uint256 price2,
address oracleBaseAsset,
address oracleQuoteAsset
);
/**
* @notice Convert either the baseAsset->quoteAsset or quoteAsset->baseAsset
* @dev The `fromAssetAmount` needs to be in it's natural fixed point precision (eg USDC=6dp)
* The `toAssetAmount` will also be returned in it's natural fixed point precision
*/
function convertAmount(
address fromAsset,
uint256 fromAssetAmount,
PriceType priceType,
OrigamiMath.Rounding roundingMode
) external view returns (uint256 toAssetAmount);
/**
* @notice Match whether a pair of assets match the base and quote asset on this oracle, in either order
*/
function matchAssets(address asset1, address asset2) external view returns (bool);
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (interfaces/common/swappers/IOrigamiSwapper.sol)
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @notice An on chain swapper contract to integrate with the 1Inch router | 0x proxy,
* possibly others which obtain quote calldata offchain and then execute via a low level call
* to perform the swap onchain
*/
interface IOrigamiSwapper {
error UnknownSwapError(bytes result);
error InvalidSwap();
event Swap(address indexed sellToken, uint256 sellTokenAmount, address indexed buyToken, uint256 buyTokenAmount);
/**
* @notice Pull tokens from sender then execute the swap
*/
function execute(
IERC20 sellToken,
uint256 sellTokenAmount,
IERC20 buyToken,
bytes memory swapData
) external returns (uint256 buyTokenAmount);
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (interfaces/common/access/Whitelisted.sol)
/**
* @title Whitelisted abstract contract
* @notice Functionality to deny non-EOA addresses unless whitelisted
*/
interface IWhitelisted {
event AllowAllSet(bool value);
event AllowAccountSet(address indexed account, bool value);
/**
* @notice Allow all (both EOAs and contracts) without whitelisting
*/
function allowAll() external view returns (bool);
/**
* @notice A mapping of whitelisted accounts (not required for EOAs)
*/
function allowedAccounts(address account) external view returns (bool allowed);
/**
* @notice Allow all callers without whitelisting
*/
function setAllowAll(bool value) external;
/**
* @notice Set whether a given account is allowed or not
*/
function setAllowAccount(address account, bool value) external;
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (investments/lovToken/managers/OrigamiAbstractLovTokenManager.sol)
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { IOrigamiInvestment } from "contracts/interfaces/investments/IOrigamiInvestment.sol";
import { IOrigamiLovTokenManager } from "contracts/interfaces/investments/lovToken/managers/IOrigamiLovTokenManager.sol";
import { IOrigamiOracle } from "contracts/interfaces/common/oracle/IOrigamiOracle.sol";
import { IOrigamiLovToken } from "contracts/interfaces/investments/lovToken/IOrigamiLovToken.sol";
import { OrigamiElevatedAccess } from "contracts/common/access/OrigamiElevatedAccess.sol";
import { CommonEventsAndErrors } from "contracts/libraries/CommonEventsAndErrors.sol";
import { OrigamiManagerPausable } from "contracts/investments/util/OrigamiManagerPausable.sol";
import { Range } from "contracts/libraries/Range.sol";
import { Whitelisted } from "contracts/common/access/Whitelisted.sol";
import { OrigamiMath } from "contracts/libraries/OrigamiMath.sol";
/**
* @title Abstract Origami lovToken Manager
* @notice The delegated logic to handle deposits/exits, and borrow/repay (rebalances) into the underlying reserve token
* @dev The `reserveToken` must have <= 18 decimal places.
*/
abstract contract OrigamiAbstractLovTokenManager is IOrigamiLovTokenManager, OrigamiElevatedAccess, OrigamiManagerPausable, Whitelisted {
using Range for Range.Data;
using OrigamiMath for uint256;
/**
* @notice lovToken contract - eg lovDSR
*/
IOrigamiLovToken public immutable override lovToken;
/**
* @notice The minimum fee (in basis points) when users deposit into from the lovToken.
* The fee is applied on the lovToken shares -- which are not minted, benefiting remaining holders.
*/
uint64 internal _minDepositFeeBps;
/**
* @notice The minimum fee (in basis points) when users exit out from the lovToken.
* The fee is applied on the lovToken shares which are being exited
* These lovToken shares are burned, benefiting remaining holders.
*/
uint64 internal _minExitFeeBps;
/**
* @notice The nominal leverage factor applied to the difference between the
* oracle SPOT_PRICE vs the HISTORIC_PRICE. Used within the fee calculation.
* eg: depositFee = 15 * (HISTORIC_PRICE - SPOT_PRICE) [when spot < historic]
* @dev feeLeverageFactor has 4dp precision
*/
uint64 internal _feeLeverageFactor;
/**
* @notice The valid lower and upper bounds of A/L allowed when users deposit/exit into lovToken
* @dev Transactions will revert if the resulting A/L is outside of this range
*/
Range.Data public override userALRange;
/**
* @notice The valid range for when a rebalance is not required.
* When a rebalance occurs, the transaction will revert if the resulting A/L is outside of this range.
*/
Range.Data public override rebalanceALRange;
/**
* @notice The common precision used
*/
uint256 public constant override PRECISION = 1e18;
/**
* @notice The maximum A/L ratio possible (eg if debt=0)
*/
uint128 internal constant MAX_AL_RATIO = type(uint128).max;
/**
* @notice The maxmimum EE ratio possible (eg if liabilities >= reserves)
*/
uint128 internal constant MAX_EFECTIVE_EXPOSURE = type(uint128).max;
/**
* @dev Max ERC20 token amount for supply/allowances/etc
*/
uint256 internal constant MAX_TOKEN_AMOUNT = type(uint256).max;
enum AlValidationMode {
LOWER_THAN_BEFORE,
HIGHER_THAN_BEFORE
}
constructor(
address _initialOwner,
address _lovToken
) OrigamiElevatedAccess(_initialOwner) {
lovToken = IOrigamiLovToken(_lovToken);
}
/**
* @notice Set the minimum fee (in basis points) of lovToken's for deposit and exit,
* and also the nominal leverage factor applied within the fee calculations
* @dev feeLeverageFactor has 4dp precision
*/
function setFeeConfig(
uint16 minDepositFeeBps,
uint16 minExitFeeBps,
uint24 feeLeverageFactor
) external override onlyElevatedAccess {
if (minDepositFeeBps > OrigamiMath.BASIS_POINTS_DIVISOR) revert CommonEventsAndErrors.InvalidParam();
if (minExitFeeBps > OrigamiMath.BASIS_POINTS_DIVISOR) revert CommonEventsAndErrors.InvalidParam();
emit FeeConfigSet(minDepositFeeBps, minExitFeeBps, feeLeverageFactor);
_minDepositFeeBps = minDepositFeeBps;
_minExitFeeBps = minExitFeeBps;
_feeLeverageFactor = feeLeverageFactor;
}
/**
* @notice The min deposit/exit fee and feeLeverageFactor configuration
* @dev feeLeverageFactor has 4dp precision
*/
function getFeeConfig() external override view returns (uint64, uint64, uint64) {
return (_minDepositFeeBps, _minExitFeeBps, _feeLeverageFactor);
}
/**
* @notice Set the valid lower and upper bounds of A/L when users deposit/exit into lovToken
*/
function setUserALRange(uint128 floor, uint128 ceiling) external override onlyElevatedAccess {
if (floor <= PRECISION) revert Range.InvalidRange(floor, ceiling);
emit UserALRangeSet(floor, ceiling);
userALRange.set(floor, ceiling);
// Any extra validation on AL depending on the strategy
_validateAlRange(userALRange);
}
/**
* @notice Set the valid range for when a rebalance is not required.
*/
function setRebalanceALRange(uint128 floor, uint128 ceiling) external override onlyElevatedAccess {
if (floor <= PRECISION) revert Range.InvalidRange(floor, ceiling);
emit RebalanceALRangeSet(floor, ceiling);
rebalanceALRange.set(floor, ceiling);
// Any extra validation on AL depending on the strategy
_validateAlRange(rebalanceALRange);
}
/**
* @notice Recover any token - should not be able to recover tokens which are normally
* held in this contract
* @param token Token to recover
* @param to Recipient address
* @param amount Amount to recover
*/
function recoverToken(address token, address to, uint256 amount) external virtual;
/**
* @notice Deposit into the reserve token on behalf of a user
* @param account The user account which is investing.
* @param quoteData The quote data to deposit into the reserve token
* @return investmentAmount The actual number of receipt tokens received, inclusive of any fees.
*/
function investWithToken(
address account,
IOrigamiInvestment.InvestQuoteData calldata quoteData
) external virtual override onlyLovToken returns (
uint256 investmentAmount
) {
if (_paused.investmentsPaused) revert CommonEventsAndErrors.IsPaused();
if (!_isAllowed(account)) revert CommonEventsAndErrors.InvalidAccess();
Cache memory cache = populateCache(IOrigamiOracle.PriceType.SPOT_PRICE);
// Note this also checks that the debtToken/reserveToken oracle prices are valid.
uint128 oldAL = _assetToLiabilityRatio(cache);
uint256 newReservesAmount = _depositIntoReserves(quoteData.fromToken, quoteData.fromTokenAmount);
// The number of shares is calculated based off this `newReservesAmount`
// However not all of these shares are minted and given to the user -- the deposit fee is removed
investmentAmount = _reservesToShares(cache, newReservesAmount);
investmentAmount = investmentAmount.subtractBps(_dynamicDepositFeeBps(), OrigamiMath.Rounding.ROUND_DOWN);
// Verify the amount
if (investmentAmount == 0) revert CommonEventsAndErrors.ExpectedNonZero();
if (investmentAmount < quoteData.minInvestmentAmount) {
revert CommonEventsAndErrors.Slippage(quoteData.minInvestmentAmount, investmentAmount);
}
// A user deposit will raise the A/L (more reserves, but the same debt)
// This needs to be validated so it doesn't go above the ceiling
// Not required if there are not yet any liabilities (where A/L would be uint128.max)
if (cache.liabilities != 0) {
uint128 newAL = refreshCacheAL(cache, IOrigamiOracle.PriceType.SPOT_PRICE);
_validateALRatio(userALRange, oldAL, newAL, AlValidationMode.HIGHER_THAN_BEFORE);
}
}
/**
* @notice Exit from the reserve token on behalf of a user.
* param account The account to exit on behalf of
* @param quoteData The quote data received from exitQuote()
* @param recipient The receiving address of the exit token
* @return toTokenAmount The number of tokens received upon selling the lovToken
* @return toBurnAmount The number of lovTokens to be burnt after exiting this position
*/
function exitToToken(
address /*account*/,
IOrigamiInvestment.ExitQuoteData calldata quoteData,
address recipient
) external virtual override onlyLovToken returns (
uint256 toTokenAmount,
uint256 toBurnAmount
) {
if (_paused.exitsPaused) revert CommonEventsAndErrors.IsPaused();
Cache memory cache = populateCache(IOrigamiOracle.PriceType.SPOT_PRICE);
// Note this also checks that the debtToken/reserveToken oracle prices are valid.
uint128 oldAL = _assetToLiabilityRatio(cache);
// The entire amount of lovTokens will be burned
// But only the non-fee portion is redeemed to reserves and sent to the user
toBurnAmount = quoteData.investmentTokenAmount;
uint256 reservesAmount = toBurnAmount.subtractBps(_dynamicExitFeeBps(), OrigamiMath.Rounding.ROUND_DOWN);
// Given the number of redeemable lovToken's calculate how many reserves this equates to
// at the current share price and the reserve supply prior to exiting
reservesAmount = _sharesToReserves(cache, reservesAmount);
// Now exit from the reserves and check slippage
toTokenAmount = _redeemFromReserves(reservesAmount, quoteData.toToken, recipient);
if (toTokenAmount < quoteData.minToTokenAmount) {
revert CommonEventsAndErrors.Slippage(quoteData.minToTokenAmount, toTokenAmount);
}
// A user exit will lower the A/L (less reserves, but the same debt)
// This needs to be validated so it doesn't go below the floor
// Not required if there are not yet any liabilities (where A/L would be uint128.max)
if (cache.liabilities != 0) {
uint128 newAL = refreshCacheAL(cache, IOrigamiOracle.PriceType.SPOT_PRICE);
_validateALRatio(userALRange, oldAL, newAL, AlValidationMode.LOWER_THAN_BEFORE);
}
}
/**
* @notice Get a quote to buy this Origami investment using one of the accepted tokens.
* @param fromTokenAmount How much of `fromToken` to invest with
* @param fromToken What ERC20 token to purchase with. This must be one of `acceptedInvestTokens`
* @param maxSlippageBps The maximum acceptable slippage of the received investment amount
* @param deadline The maximum deadline to execute the exit.
* @return quoteData The quote data, including any params required for the underlying investment type.
* @return investFeeBps Any fees expected when investing with the given token, either from Origami or from the underlying investment.
*/
function investQuote(
uint256 fromTokenAmount,
address fromToken,
uint256 maxSlippageBps,
uint256 deadline
) external virtual override view returns (
IOrigamiInvestment.InvestQuoteData memory quoteData,
uint256[] memory investFeeBps
) {
if (fromTokenAmount == 0) revert CommonEventsAndErrors.ExpectedNonZero();
Cache memory cache = populateCache(IOrigamiOracle.PriceType.SPOT_PRICE);
uint256 _newReservesAmount = _previewDepositIntoReserves(fromToken, fromTokenAmount);
// The number of shares is calculated based off this `_newReservesAmount`
// However not all of these shares are minted and given to the user -- the deposit fee is removed
uint256 _investmentAmount = _reservesToShares(cache, _newReservesAmount);
uint256 _depositFeeRate = _dynamicDepositFeeBps();
_investmentAmount = _investmentAmount.subtractBps(_depositFeeRate, OrigamiMath.Rounding.ROUND_DOWN);
quoteData.fromToken = fromToken;
quoteData.fromTokenAmount = fromTokenAmount;
quoteData.maxSlippageBps = maxSlippageBps;
quoteData.deadline = deadline;
quoteData.expectedInvestmentAmount = _investmentAmount;
quoteData.minInvestmentAmount = _investmentAmount.subtractBps(maxSlippageBps, OrigamiMath.Rounding.ROUND_UP);
// quoteData.underlyingInvestmentQuoteData remains as bytes(0)
investFeeBps = new uint256[](1);
investFeeBps[0] = _depositFeeRate;
}
/**
* @notice The maximum amount of fromToken's that can be deposited into the lovToken
* taking into consideration:
* 1/ The max reserves in possible until the A/L ceiling would be hit
* 2/ Any other constraints of the underlying implementation
*/
function maxInvest(address fromToken) external override view returns (uint256 fromTokenAmount) {
Cache memory cache = populateCache(IOrigamiOracle.PriceType.SPOT_PRICE);
// First get the underlying implementation's max allowed
fromTokenAmount = _maxDepositIntoReserves(fromToken);
// Use the minimum number of reserves from both the lovToken.maxTotalSupply and userAL.ceiling restrictions
uint256 _minRemainingCapacity = _reservesCapacityFromTotalSupply(cache);
uint256 _remainingCapacityForAlCeiling = _reservesCapacityFromAlCeiling(cache);
if (_remainingCapacityForAlCeiling < _minRemainingCapacity) {
_minRemainingCapacity = _remainingCapacityForAlCeiling;
}
// Convert to the fromToken. Use previewMint as this amount of fromToken's
// should return the exact shares when invested
if (_minRemainingCapacity < type(uint256).max) {
_minRemainingCapacity = _previewMintReserves(fromToken, _minRemainingCapacity);
}
// Finally, use this remaining capcity if it's less than the underlying implementation's max allowed of fromToken
if (_minRemainingCapacity < fromTokenAmount) {
fromTokenAmount = _minRemainingCapacity;
}
}
/**
* @notice Get a quote to sell this Origami investment to receive one of the accepted tokens.
* @param investmentAmount The number of Origami investment tokens to sell
* @param toToken The token to receive when selling. This must be one of `acceptedExitTokens`
* @param maxSlippageBps The maximum acceptable slippage of the received `toToken`
* @param deadline The maximum deadline to execute the exit.
* @return quoteData The quote data, including any params required for the underlying investment type.
* @return exitFeeBps Any fees expected when exiting the investment to the nominated token, either from Origami or from the underlying investment.
*/
function exitQuote(
uint256 investmentAmount,
address toToken,
uint256 maxSlippageBps,
uint256 deadline
) external virtual override view returns (
IOrigamiInvestment.ExitQuoteData memory quoteData,
uint256[] memory exitFeeBps
) {
if (investmentAmount == 0) revert CommonEventsAndErrors.ExpectedNonZero();
// Exit fees are taken from the lovToken amount, so get the non-fee amount to actually exit
uint256 _exitFeeRate = _dynamicExitFeeBps();
uint256 toExitAmount = investmentAmount.subtractBps(_exitFeeRate, OrigamiMath.Rounding.ROUND_DOWN);
Cache memory cache = populateCache(IOrigamiOracle.PriceType.SPOT_PRICE);
// Convert to the underlying toToken
toExitAmount = _previewRedeemFromReserves(
// Convert the non-fee lovToken amount to ERC-4626 reserves
_sharesToReserves(cache, toExitAmount),
toToken
);
quoteData.investmentTokenAmount = investmentAmount;
quoteData.toToken = toToken;
quoteData.maxSlippageBps = maxSlippageBps;
quoteData.deadline = deadline;
quoteData.expectedToTokenAmount = toExitAmount;
quoteData.minToTokenAmount = toExitAmount.subtractBps(maxSlippageBps, OrigamiMath.Rounding.ROUND_UP);
// quoteData.underlyingInvestmentQuoteData remains as bytes(0)
exitFeeBps = new uint256[](1);
exitFeeBps[0] = _exitFeeRate;
}
/**
* @notice The maximum amount of lovToken shares that can be exited into the `toToken`
* taking into consideration:
* 1/ The max reserves out possible until the A/L floor would be hit
* 2/ Any other constraints from the underyling implementation
*/
function maxExit(address toToken) external override view returns (uint256 sharesAmount) {
// Calculate the max reserves which can be removed before the A/L floor is hit
// Round up for the minimum reserves
Cache memory cache = populateCache(IOrigamiOracle.PriceType.SPOT_PRICE);
uint256 _minReserves = cache.liabilities.mulDiv(
userALRange.floor,
PRECISION,
OrigamiMath.Rounding.ROUND_UP
);
// Only check the underlying implementation if there's capacity to remove reserves
if (cache.assets > _minReserves) {
// Calculate the max number of lovToken shares which can be exited given the A/L
// floor on reserves
uint256 _amountFromAvailableCapacity;
unchecked {
_amountFromAvailableCapacity = cache.assets - _minReserves;
}
// Check the underlying implementation's max reserves that can be redeemed
uint256 _underlyingAmount = _maxRedeemFromReserves(toToken);
// Use the minimum of both the underlying implementation max and
// the capacity based on the A/L floor
if (_underlyingAmount < _amountFromAvailableCapacity) {
_amountFromAvailableCapacity = _underlyingAmount;
}
// Convert reserves to lovToken shares
sharesAmount = _reservesToShares(cache, _amountFromAvailableCapacity);
// Since exit fees are taken when exiting (so these reserves aren't actually redeemed),
// reverse out the fees
// Round down to be the inverse of when they're applied (and rounded up) when exiting
sharesAmount = sharesAmount.inverseSubtractBps(_dynamicExitFeeBps(), OrigamiMath.Rounding.ROUND_DOWN);
// Finally use the min of the derived amount and the lovToken total supply
if (sharesAmount > cache.totalSupply) {
sharesAmount = cache.totalSupply;
}
}
}
/**
* @notice The current deposit and exit fee based on market conditions.
* Fees are the equivalent of burning lovToken shares - benefit remaining vault users
* @dev represented in basis points
*/
function getDynamicFeesBps() external view returns (uint256 depositFeeBps, uint256 exitFeeBps) {
depositFeeBps = _dynamicDepositFeeBps();
exitFeeBps = _dynamicExitFeeBps();
}
/**
* @notice Whether new investments are paused.
*/
function areInvestmentsPaused() external override view returns (bool) {
return _paused.investmentsPaused;
}
/**
* @notice Whether exits are temporarily paused.
*/
function areExitsPaused() external override view returns (bool) {
return _paused.exitsPaused;
}
/**
* @notice The reserveToken that the lovToken levers up on
*/
function reserveToken() public virtual override view returns (address);
/**
* @notice The total balance of reserve tokens this lovToken holds, and also if deployed as collateral
* in other platforms
* @dev Explicitly tracked rather than via reserveToken.balanceOf() to avoid donation/inflation vectors.
*/
function reservesBalance() public virtual override view returns (uint256);
/**
* @notice The debt of the lovToken from the borrower, converted into the reserveToken
* @dev Use the Oracle `debtPriceType` to value any debt in terms of the reserve token
*/
function liabilities(IOrigamiOracle.PriceType debtPriceType) public virtual override view returns (uint256);
/**
* @notice The current asset/liability (A/L) of this lovToken
* to `PRECISION` precision
* @dev = reserves / liabilities
*/
function assetToLiabilityRatio() external override view returns (uint128) {
return _assetToLiabilityRatio(populateCache(IOrigamiOracle.PriceType.SPOT_PRICE));
}
/**
* @notice Retrieve the current assets, liabilities and calculate the ratio
* @dev Use the Oracle `debtPriceType` to value any debt in terms of the reserve token
*/
function assetsAndLiabilities(IOrigamiOracle.PriceType debtPriceType) external override view returns (
uint256 /*assets*/,
uint256 /*liabilities*/,
uint256 /*ratio*/
) {
Cache memory cache = populateCache(debtPriceType);
return (
cache.assets,
cache.liabilities,
_assetToLiabilityRatio(cache)
);
}
/**
* @notice The current effective exposure (EE) of this lovToken
* to `PRECISION` precision
* @dev = reserves / (reserves - liabilities)
* Use the Oracle `debtPriceType` to value any debt in terms of the reserve token
*/
function effectiveExposure(IOrigamiOracle.PriceType debtPriceType) external override view returns (uint128) {
Cache memory cache = populateCache(debtPriceType);
if (cache.assets > cache.liabilities) {
uint256 redeemableReserves;
unchecked {
redeemableReserves = cache.assets - cache.liabilities;
}
// Round up for EE calc
uint256 ee = cache.assets.mulDiv(PRECISION, redeemableReserves, OrigamiMath.Rounding.ROUND_UP);
if (ee < MAX_EFECTIVE_EXPOSURE) {
return uint128(ee);
}
}
return MAX_EFECTIVE_EXPOSURE;
}
/**
* @notice The amount of reserves that users may redeem their lovTokens as of this block
* @dev = reserves - liabilities
* Use the Oracle `debtPriceType` to value any debt in terms of the reserve token
*/
function userRedeemableReserves(IOrigamiOracle.PriceType debtPriceType) external override view returns (uint256) {
return _userRedeemableReserves(populateCache(debtPriceType));
}
/**
* @notice How many reserve tokens would one get given a number of lovToken shares
* and the current lovToken totalSupply
* @dev Use the Oracle `debtPriceType` to value any debt in terms of the reserve token
*/
function sharesToReserves(uint256 shares, IOrigamiOracle.PriceType debtPriceType) external override view returns (uint256) {
return _sharesToReserves(populateCache(debtPriceType), shares);
}
/**
* @notice How many lovToken shares would one get given a number of reserve tokens
* and the current lovToken totalSupply
* @dev Use the Oracle `debtPriceType` to value any debt in terms of the reserve token
*/
function reservesToShares(uint256 reserves, IOrigamiOracle.PriceType debtPriceType) external override view returns (uint256) {
return _reservesToShares(populateCache(debtPriceType), reserves);
}
// An internal cache to save having to recalculate
struct Cache {
uint256 assets;
uint256 liabilities;
uint256 totalSupply;
}
function populateCache(IOrigamiOracle.PriceType debtPriceType) internal view returns (Cache memory cache) {
cache.assets = reservesBalance();
cache.liabilities = liabilities(debtPriceType);
cache.totalSupply = lovToken.totalSupply();
}
function refreshCacheAL(Cache memory cache, IOrigamiOracle.PriceType debtPriceType) internal view returns (uint128) {
cache.assets = reservesBalance();
cache.liabilities = liabilities(debtPriceType);
return _assetToLiabilityRatio(cache);
}
/**
* @notice The current deposit fee based on market conditions.
* Deposit fees are applied to the portion of lovToken shares the depositor
* would have received. Instead that fee portion isn't minted (benefiting remaining users)
* @dev represented in basis points
*/
function _dynamicDepositFeeBps() internal virtual view returns (uint256);
/**
* @notice The current exit fee based on market conditions.
* Exit fees are applied to the lovToken shares the user is exiting.
* That portion is burned prior to being redeemed (benefiting remaining users)
* @dev represented in basis points
*/
function _dynamicExitFeeBps() internal virtual view returns (uint256);
/**
* @dev Perform any extra validation on the A/L range
* By default, nothing extra validation is required, however a manager implementation
* may decide to perform extra. For example if borrowing from Aave/Spark,
* this can check that the A/L floor is within a tolerable range which won't get liquidated
* Since those parameters could be updated at a later date by Aave/Spark
*/
function _validateAlRange(Range.Data storage range) internal virtual view {}
function _userRedeemableReserves(Cache memory cache) internal pure returns (uint256) {
unchecked {
return cache.assets > cache.liabilities
? cache.assets - cache.liabilities
: 0;
}
}
function _assetToLiabilityRatio(Cache memory cache) internal pure returns (uint128) {
if (cache.liabilities != 0) {
// Round down for A/L calc
uint256 alr = cache.assets.mulDiv(PRECISION, cache.liabilities, OrigamiMath.Rounding.ROUND_DOWN);
if (alr < MAX_AL_RATIO) {
return uint128(alr);
}
}
return MAX_AL_RATIO;
}
function _sharesToReserves(Cache memory cache, uint256 shares) internal view returns (uint256) {
// If totalSupply is zero, then just return shares 1:1 scaled down to the reserveToken decimals
// If > 0 then the decimal conversion is handled already (numerator cancels out denominator)
// Round down for calculating reserves from shares
return cache.totalSupply == 0
? shares.scaleDown(_reservesToSharesScalar(), OrigamiMath.Rounding.ROUND_DOWN)
: shares.mulDiv(_userRedeemableReserves(cache), cache.totalSupply, OrigamiMath.Rounding.ROUND_DOWN);
}
function _reservesToShares(Cache memory cache, uint256 reserves) private view returns (uint256) {
// If totalSupply is zero, then just return reserves 1:1 scaled up to the shares decimals
// If > 0 then the decimal conversion is handled already (numerator cancels out denominator)
if (cache.totalSupply == 0) {
return reserves.scaleUp(_reservesToSharesScalar());
}
// In the unlikely case that no available reserves for user withdrawals (100% of reserves are held back to repay debt),
// then revert
uint256 _redeemableReserves = _userRedeemableReserves(cache);
if (_redeemableReserves == 0) {
revert NoAvailableReserves();
}
// Round down for calculating shares from reserves
return reserves.mulDiv(cache.totalSupply, _redeemableReserves, OrigamiMath.Rounding.ROUND_DOWN);
}
/**
* @dev Calculate the asset scalar to convert from reserveToken --> 18 decimal places (`PRECISION`)
* The reserveToken cannot have more than the lovToken decimals (18dp)
*/
function _reservesToSharesScalar() internal view returns (uint256) {
uint8 _reservesDecimals = IERC20Metadata(reserveToken()).decimals();
uint8 _sharesDecimals = IERC20Metadata(address(lovToken)).decimals();
if (_reservesDecimals > _sharesDecimals) revert CommonEventsAndErrors.InvalidToken(reserveToken());
return 10 ** (_sharesDecimals - _reservesDecimals);
}
/**
* @notice Deposit a number of `fromToken` into the `reserveToken`
*/
function _depositIntoReserves(address fromToken, uint256 fromTokenAmount) internal virtual returns (uint256 newReservesAmount);
/**
* @notice Calculate the amount of `reserveToken` will be deposited given an amount of `fromToken`
*/
function _previewDepositIntoReserves(address fromToken, uint256 fromTokenAmount) internal virtual view returns (uint256 newReservesAmount);
/**
* @notice Maximum amount of `fromToken` that can be deposited into the `reserveToken`
*/
function _maxDepositIntoReserves(address fromToken) internal virtual view returns (uint256 fromTokenAmount);
/**
* @notice Calculate the number of `toToken` required in order to mint a given number of `reserveTokens`
*/
function _previewMintReserves(address toToken, uint256 reservesAmount) internal virtual view returns (uint256 toTokenAmount);
/**
* @notice Redeem a number of `reserveToken` into `toToken`
*/
function _redeemFromReserves(uint256 reservesAmount, address toToken, address recipient) internal virtual returns (uint256 toTokenAmount);
/**
* @notice Calculate the number of `toToken` recevied if redeeming a number of `reserveToken`
*/
function _previewRedeemFromReserves(uint256 reservesAmount, address toToken) internal virtual view returns (uint256 toTokenAmount);
/**
* @notice Maximum amount of `reserveToken` that can be redeemed to `toToken`
*/
function _maxRedeemFromReserves(address toToken) internal virtual view returns (uint256 reservesAmount);
/**
* @notice Validate that the A/L ratio hasn't moved beyond the given A/L range.
*/
function _validateALRatio(Range.Data storage validRange, uint128 ratioBefore, uint128 ratioAfter, AlValidationMode alMode) internal virtual {
if (alMode == AlValidationMode.LOWER_THAN_BEFORE) {
// A/L needs to be decreasing (may be equal if a very small amount is deposited/exited)
if (ratioAfter > ratioBefore) revert ALTooHigh(ratioBefore, ratioAfter, ratioBefore);
// Check that the new A/L is not below the floor
// In this mode, the A/L may be above the ceiling still, but should be decreasing
// Note: The A/L may not be strictly decreasing in this mode since the liabilities (in reserve terms) is also
// fluctuating
if (ratioAfter < validRange.floor) revert ALTooLow(ratioBefore, ratioAfter, validRange.floor);
} else {
// A/L needs to be increasing (may be equal if a very small amount is deposited/exited)
if (ratioAfter < ratioBefore) revert ALTooLow(ratioBefore, ratioAfter, ratioBefore);
// Check that the new A/L is not above the ceiling
// In this mode, the A/L may be below the floor still, but should be increasing
// Note: The A/L may not be strictly increasing in this mode since the liabilities (in reserve terms) is also
// fluctuating
if (ratioAfter > validRange.ceiling) revert ALTooHigh(ratioBefore, ratioAfter, validRange.ceiling);
}
}
/**
* @dev Recalculate the A/L and validate that it is still within the `rebalanceALRange`
*/
function _validateAfterRebalance(
Cache memory cache,
uint128 alRatioBefore,
uint128 minNewAL,
uint128 maxNewAL,
AlValidationMode alValidationMode,
bool force
) internal returns (uint128 alRatioAfter) {
// Need to recalculate both the assets and liabilities in the cache
alRatioAfter = refreshCacheAL(cache, IOrigamiOracle.PriceType.SPOT_PRICE);
// Ensure the A/L is within the expected slippage range
{
if (alRatioAfter < minNewAL) revert ALTooLow(alRatioBefore, alRatioAfter, minNewAL);
if (alRatioAfter > maxNewAL) revert ALTooHigh(alRatioBefore, alRatioAfter, maxNewAL);
}
if (!force)
_validateALRatio(rebalanceALRange, alRatioBefore, alRatioAfter, alValidationMode);
}
/**
* @dev Calculate the free capacity for new reserves, given the lovToken maxTotalSupply restriction
*/
function _reservesCapacityFromTotalSupply(Cache memory cache) internal view returns (uint256) {
uint256 _maxTotalSupply = lovToken.maxTotalSupply();
if (_maxTotalSupply == type(uint256).max) {
return type(uint256).max;
}
// Number of lovToken shares available
uint256 _availableShares;
unchecked {
_availableShares = _maxTotalSupply > cache.totalSupply
? _maxTotalSupply - cache.totalSupply
: 0;
}
// Take deposit fees into account
// Round down to be the inverse of when they're applied when depositing
_availableShares = _availableShares.inverseSubtractBps(_dynamicDepositFeeBps(), OrigamiMath.Rounding.ROUND_DOWN);
// Convert to reserve tokens
return _sharesToReserves(cache, _availableShares);
}
/**
* @dev Calculate the free capacity for new reserves, given the A/L ceiling restriction
*/
function _reservesCapacityFromAlCeiling(Cache memory cache) internal view returns (uint256) {
if (cache.liabilities == 0) {
return type(uint256).max;
}
// This is ever so slightly conservative, as it calculates maxReserves which would result in
// an A/L strictly less than (<) the `userALRange.ceiling`, rather than exacly less-than-or-equal (<=)
// This is intentional to provide a slightly more conservative max amount which can be deposited.
// To get it exact, the userALRange.ceiling would need to be incremented by 1 (if not already type(uint128).max)
uint256 _maxReservesForAlCeiling = cache.liabilities.mulDiv(
userALRange.ceiling,
PRECISION,
OrigamiMath.Rounding.ROUND_DOWN
);
if (_maxReservesForAlCeiling > cache.assets) {
unchecked {
return _maxReservesForAlCeiling - cache.assets;
}
}
return 0;
}
modifier onlyLovToken() {
if (msg.sender != address(lovToken)) revert CommonEventsAndErrors.InvalidAccess();
_;
}
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (common/access/OrigamiElevatedAccessBase.sol)
import { OrigamiElevatedAccessBase } from "contracts/common/access/OrigamiElevatedAccessBase.sol";
/**
* @notice Inherit to add Owner roles for DAO elevated access.
*/
abstract contract OrigamiElevatedAccess is OrigamiElevatedAccessBase {
constructor(address initialOwner) {
_init(initialOwner);
}
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (common/access/OrigamiElevatedAccessBase.sol)
import { IOrigamiElevatedAccess } from "contracts/interfaces/common/access/IOrigamiElevatedAccess.sol";
import { CommonEventsAndErrors } from "contracts/libraries/CommonEventsAndErrors.sol";
/**
* @notice Inherit to add Owner roles for DAO elevated access.
*/
abstract contract OrigamiElevatedAccessBase is IOrigamiElevatedAccess {
/**
* @notice The address of the current owner.
*/
address public override owner;
/**
* @notice Explicit approval for an address to execute a function.
* allowedCaller => function selector => true/false
*/
mapping(address => mapping(bytes4 => bool)) public override explicitFunctionAccess;
/// @dev Track proposed owner
address private _proposedNewOwner;
function _init(address initialOwner) internal {
if (owner != address(0)) revert CommonEventsAndErrors.InvalidAccess();
if (initialOwner == address(0)) revert CommonEventsAndErrors.InvalidAddress(address(0));
owner = initialOwner;
}
/**
* @notice Proposes a new Owner.
* Can only be called by the current owner
*/
function proposeNewOwner(address account) external override onlyElevatedAccess {
if (account == address(0)) revert CommonEventsAndErrors.InvalidAddress(account);
emit NewOwnerProposed(owner, _proposedNewOwner, account);
_proposedNewOwner = account;
}
/**
* @notice Caller accepts the role as new Owner.
* Can only be called by the proposed owner
*/
function acceptOwner() external override {
if (msg.sender != _proposedNewOwner) revert CommonEventsAndErrors.InvalidAccess();
emit NewOwnerAccepted(owner, msg.sender);
owner = msg.sender;
delete _proposedNewOwner;
}
/**
* @notice Grant `allowedCaller` the rights to call the function selectors in the access list.
* @dev fnSelector == bytes4(keccak256("fn(argType1,argType2,...)"))
*/
function setExplicitAccess(address allowedCaller, ExplicitAccess[] calldata access) external override onlyElevatedAccess {
if (allowedCaller == address(0)) revert CommonEventsAndErrors.InvalidAddress(allowedCaller);
ExplicitAccess memory _access;
for (uint256 i; i < access.length; ++i) {
_access = access[i];
emit ExplicitAccessSet(allowedCaller, _access.fnSelector, _access.allowed);
explicitFunctionAccess[allowedCaller][_access.fnSelector] = _access.allowed;
}
}
function isElevatedAccess(address caller, bytes4 fnSelector) internal view returns (bool) {
return (
caller == owner ||
explicitFunctionAccess[caller][fnSelector]
);
}
/**
* @notice The owner is allowed to call, or if explicit access has been given to the caller.
* @dev Important: Only for use when called from an *external* contract.
* If a function with this modifier is called internally then the `msg.sig`
* will still refer to the top level externally called function.
*/
modifier onlyElevatedAccess() {
if (!isElevatedAccess(msg.sender, msg.sig)) revert CommonEventsAndErrors.InvalidAccess();
_;
}
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (investments/lovToken/managers/OrigamiLovTokenFlashAndBorrowManager.sol)
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { IOrigamiLovTokenFlashAndBorrowManager } from "contracts/interfaces/investments/lovToken/managers/IOrigamiLovTokenFlashAndBorrowManager.sol";
import { IOrigamiOracle } from "contracts/interfaces/common/oracle/IOrigamiOracle.sol";
import { IOrigamiSwapper } from "contracts/interfaces/common/swappers/IOrigamiSwapper.sol";
import { IOrigamiLovTokenManager } from "contracts/interfaces/investments/lovToken/managers/IOrigamiLovTokenManager.sol";
import { IOrigamiFlashLoanProvider } from "contracts/interfaces/common/flashLoan/IOrigamiFlashLoanProvider.sol";
import { CommonEventsAndErrors } from "contracts/libraries/CommonEventsAndErrors.sol";
import { OrigamiAbstractLovTokenManager } from "contracts/investments/lovToken/managers/OrigamiAbstractLovTokenManager.sol";
import { OrigamiMath } from "contracts/libraries/OrigamiMath.sol";
import { Range } from "contracts/libraries/Range.sol";
import { DynamicFees } from "contracts/libraries/DynamicFees.sol";
import { IOrigamiBorrowAndLend } from "contracts/interfaces/common/borrowAndLend/IOrigamiBorrowAndLend.sol";
/**
* @title Origami LovToken Flash And Borrow Manager
* @notice The `reserveToken` is deposited by users and supplied into a money market as collateral
* Upon a rebalanceDown (to decrease the A/L), `debtToken` is borrowed (via a flashloan), swapped into `reserveToken` and added
* back in as more collateral.
* @dev `reserveToken`, `debtToken` must be 18 decimals. If other precision is needed later
* then this contract can be extended
*/
contract OrigamiLovTokenFlashAndBorrowManager is IOrigamiLovTokenFlashAndBorrowManager, OrigamiAbstractLovTokenManager {
using SafeERC20 for IERC20;
using OrigamiMath for uint256;
/**
* @notice reserveToken that this lovToken levers up on
* This is also the asset which users deposit/exit with in this lovToken manager
*/
IERC20 private immutable _reserveToken;
/**
* @notice The asset which lovToken borrows from the money market to increase the A/L ratio
*/
IERC20 private immutable _debtToken;
/**
* @notice The base asset used when retrieving the prices for dynamic fee calculations.
*/
address public immutable override dynamicFeeOracleBaseToken;
/**
* @notice The contract responsible for borrow/lend via external markets
*/
IOrigamiBorrowAndLend public borrowLend;
/**
* @notice The Origami flashLoan provider contract, which may be via Aave/Spark/Balancer/etc
*/
IOrigamiFlashLoanProvider public override flashLoanProvider;
/**
* @notice The swapper for `debtToken` <--> `reserveToken`
*/
IOrigamiSwapper public override swapper;
/**
* @notice The oracle to convert `debtToken` <--> `reserveToken`
*/
IOrigamiOracle public override debtTokenToReserveTokenOracle;
/**
* @notice The oracle to use when observing prices which are used for the dynamic fee calculations
*/
IOrigamiOracle public override dynamicFeePriceOracle;
/**
* @dev Internal struct used to abi.encode params through a flashloan request
*/
enum RebalanceCallbackType {
REBALANCE_DOWN,
REBALANCE_UP
}
constructor(
address _initialOwner,
address _reserveToken_,
address _debtToken_,
address _dynamicFeeOracleBaseToken,
address _lovToken,
address _flashLoanProvider,
address _borrowLend
) OrigamiAbstractLovTokenManager(_initialOwner, _lovToken) {
_reserveToken = IERC20(_reserveToken_);
_debtToken = IERC20(_debtToken_);
dynamicFeeOracleBaseToken = _dynamicFeeOracleBaseToken;
flashLoanProvider = IOrigamiFlashLoanProvider(_flashLoanProvider);
borrowLend = IOrigamiBorrowAndLend(_borrowLend);
// Validate the decimals of the tokens
{
uint256 _decimals = IERC20Metadata(_lovToken).decimals();
if (IERC20Metadata(_reserveToken_).decimals() != _decimals) revert CommonEventsAndErrors.InvalidToken(_reserveToken_);
if (IERC20Metadata(_debtToken_).decimals() != _decimals) revert CommonEventsAndErrors.InvalidToken(_debtToken_);
}
}
/**
* @notice Set the swapper responsible for `debtToken` <--> `reserveToken` swaps
*/
function setSwapper(address _swapper) external override onlyElevatedAccess {
if (_swapper == address(0)) revert CommonEventsAndErrors.InvalidAddress(_swapper);
// Update the approval's for both `reserveToken` and `debtToken`
address _oldSwapper = address(swapper);
if (_oldSwapper != address(0)) {
_reserveToken.forceApprove(_oldSwapper, 0);
_debtToken.forceApprove(_oldSwapper, 0);
}
_reserveToken.forceApprove(_swapper, type(uint256).max);
_debtToken.forceApprove(_swapper, type(uint256).max);
emit SwapperSet(_swapper);
swapper = IOrigamiSwapper(_swapper);
}
/**
* @notice Set the `debtToken` <--> `reserveToken` oracle configuration
*/
function setOracles(address _debtTokenToReserveTokenOracle, address _dynamicFeePriceOracle) external override onlyElevatedAccess {
debtTokenToReserveTokenOracle = _validatedOracle(_debtTokenToReserveTokenOracle, address(_reserveToken), address(_debtToken));
dynamicFeePriceOracle = _validatedOracle(_dynamicFeePriceOracle, dynamicFeeOracleBaseToken, address(_debtToken));
emit OraclesSet(_debtTokenToReserveTokenOracle, _dynamicFeePriceOracle);
}
/**
* @notice Set the Origami flash loan provider
*/
function setFlashLoanProvider(address provider) external override onlyElevatedAccess {
if (provider == address(0)) revert CommonEventsAndErrors.InvalidAddress(address(0));
flashLoanProvider = IOrigamiFlashLoanProvider(provider);
emit FlashLoanProviderSet(provider);
}
/**
* @notice Set the Origami Borrow/Lend position holder
*/
function setBorrowLend(address _address) external override onlyElevatedAccess {
if (_address == address(0)) revert CommonEventsAndErrors.InvalidAddress(address(0));
borrowLend = IOrigamiBorrowAndLend(_address);
emit BorrowLendSet(_address);
}
/**
* @notice Increase the A/L by reducing liabilities. Flash loan and repay debt, and withdraw collateral to repay the flash loan
*/
function rebalanceUp(RebalanceUpParams calldata params) external override onlyElevatedAccess {
flashLoanProvider.flashLoan(
_debtToken,
params.flashLoanAmount,
abi.encode(
RebalanceCallbackType.REBALANCE_UP,
false,
abi.encode(params)
)
);
}
/**
* @notice Force a rebalanceUp ignoring A/L ceiling/floor
* @dev Separate function to above to have stricter control on who can force
*/
function forceRebalanceUp(RebalanceUpParams calldata params) external override onlyElevatedAccess {
flashLoanProvider.flashLoan(
_debtToken,
params.flashLoanAmount,
abi.encode(
RebalanceCallbackType.REBALANCE_UP,
true,
abi.encode(params)
)
);
}
/**
* @notice Decrease the A/L by increasing liabilities. Flash loan `debtToken` swap to `reserveToken`
* and add as collateral into a money market. Then borrow `debtToken` to repay the flash loan.
*/
function rebalanceDown(RebalanceDownParams calldata params) external override onlyElevatedAccess {
flashLoanProvider.flashLoan(
_debtToken,
params.flashLoanAmount,
abi.encode(
RebalanceCallbackType.REBALANCE_DOWN,
false,
abi.encode(params)
)
);
}
/**
* @notice Force a rebalanceDown ignoring A/L ceiling/floor
* @dev Separate function to above to have stricter control on who can force
*/
function forceRebalanceDown(RebalanceDownParams calldata params) external override onlyElevatedAccess {
flashLoanProvider.flashLoan(
_debtToken,
params.flashLoanAmount,
abi.encode(
RebalanceCallbackType.REBALANCE_DOWN,
true,
abi.encode(params)
)
);
}
/**
* @notice Recover accidental donations. `collateralSupplyToken` can only be recovered for amounts greater than the
* internally tracked balance.
* @param token Token to recover
* @param to Recipient address
* @param amount Amount to recover
*/
function recoverToken(address token, address to, uint256 amount) external override onlyElevatedAccess {
emit CommonEventsAndErrors.TokenRecovered(to, token, amount);
IERC20(token).safeTransfer(to, amount);
}
/**
* @notice The total balance of reserve tokens this lovToken holds.
*/
function reservesBalance() public override(OrigamiAbstractLovTokenManager,IOrigamiLovTokenManager) view returns (uint256) {
return borrowLend.suppliedBalance();
}
/**
* @notice The underlying token this investment wraps. In this case, it's the `reserveToken`
*/
function baseToken() external override view returns (address) {
return address(_reserveToken);
}
/**
* @notice The set of accepted tokens which can be used to invest.
* Only the `reserveToken` in this instance
*/
function acceptedInvestTokens() external override view returns (address[] memory tokens) {
tokens = new address[](1);
tokens[0] = address(_reserveToken);
}
/**
* @notice The set of accepted tokens which can be used to exit into.
* Only the `reserveToken` in this instance
*/
function acceptedExitTokens() external override view returns (address[] memory tokens) {
tokens = new address[](1);
tokens[0] = address(_reserveToken);
}
/**
* @notice The reserveToken that the lovToken levers up on
*/
function reserveToken() public override(OrigamiAbstractLovTokenManager,IOrigamiLovTokenManager) view returns (address) {
return address(_reserveToken);
}
/**
* @notice The asset which lovToken borrows to increase the A/L ratio
*/
function debtToken() external override view returns (address) {
return address(_debtToken);
}
/**
* @notice The debt of the lovToken to the money market, converted into the `reserveToken`
* @dev Use the Oracle `debtPriceType` to value any debt in terms of the reserve token
*/
function liabilities(IOrigamiOracle.PriceType debtPriceType) public override(OrigamiAbstractLovTokenManager,IOrigamiLovTokenManager) view returns (uint256) {
// In [debtToken] terms.
uint256 debt = borrowLend.debtBalance();
if (debt == 0) return 0;
// Convert the [debtToken] into the [reserveToken] terms
return debtTokenToReserveTokenOracle.convertAmount(
address(_debtToken),
debt,
debtPriceType,
OrigamiMath.Rounding.ROUND_UP
);
}
/**
* @notice Invoked from IOrigamiFlashLoanProvider once a flash loan is successfully
* received, to the msg.sender of `flashLoan()`
* @param token The ERC20 token which has been borrowed
* @param amount The amount which has been borrowed
* @param fee The flashloan fee amount (in the same token)
* @param params Client specific abi encoded params which are passed through from the original `flashLoan()` call
*/
function flashLoanCallback(
IERC20 token,
uint256 amount,
uint256 fee,
bytes calldata params
) external override returns (bool) {
if (msg.sender != address(flashLoanProvider)) revert CommonEventsAndErrors.InvalidAccess();
if (address(token) != address(_debtToken)) revert CommonEventsAndErrors.InvalidToken(address(token));
// Decode the type & params and call the relevant callback function.
// Each function must result in the `amount + fee` sitting in this contract such that it can be
// transferred back to the flash loan provider.
(RebalanceCallbackType _rebalanceType, bool force, bytes memory _rebalanceParams) = abi.decode(
params,
(RebalanceCallbackType, bool, bytes)
);
if (_rebalanceType == RebalanceCallbackType.REBALANCE_DOWN) {
(RebalanceDownParams memory _rdParams) = abi.decode(_rebalanceParams, (RebalanceDownParams));
_rebalanceDownFlashLoanCallback(
amount,
fee,
_rdParams,
force
);
} else {
(RebalanceUpParams memory _ruParams) = abi.decode(_rebalanceParams, (RebalanceUpParams));
_rebalanceUpFlashLoanCallback(
amount,
fee,
_ruParams,
force
);
}
// Transfer the total flashloan amount + fee back to the `flashLoanProvider` for repayment
_debtToken.safeTransfer(msg.sender, amount+fee);
return true;
}
/**
* @dev Handle the rebalanceUp once the flash loan amount has been received
*/
function _rebalanceUpFlashLoanCallback(
uint256 flashLoanAmount,
uint256 fee,
RebalanceUpParams memory params,
bool force
) internal {
if (flashLoanAmount != params.flashLoanAmount) revert CommonEventsAndErrors.InvalidParam();
// Get the current A/L to check for oracle prices, and so we can compare that the new A/L is higher after the rebalance
Cache memory cache = populateCache(IOrigamiOracle.PriceType.SPOT_PRICE);
uint128 alRatioBefore = _assetToLiabilityRatio(cache);
uint256 totalDebtRepaid = flashLoanAmount;
uint256 flashRepayAmount = flashLoanAmount + fee;
IOrigamiBorrowAndLend _borrowLend = borrowLend;
// Repay the [debtToken]
{
_debtToken.safeTransfer(address(_borrowLend), flashLoanAmount);
// No need to check the withdrawnAmount returned, the amount passed in can never be type(uint256).max, so this will
// be the exact `amount`
(uint256 amountRepaid, uint256 withdrawnAmount) = _borrowLend.repayAndWithdraw(flashLoanAmount, params.collateralToWithdraw, address(this));
if (withdrawnAmount != params.collateralToWithdraw) {
revert CommonEventsAndErrors.InvalidAmount(address(_reserveToken), params.collateralToWithdraw);
}
// Repaying less than what was asked is only allowed in force mode.
// This will only happen when there is no more debt in the money market, ie we are fully delevered
if (amountRepaid != flashLoanAmount) {
if (!force) revert CommonEventsAndErrors.InvalidAmount(address(_debtToken), flashLoanAmount);
totalDebtRepaid = amountRepaid;
}
}
// Swap from [reserveToken] to [debtToken]
// The expected amount of [debtToken] received after swapping from [reserveToken]
// needs to at least cover the total flash loan amount + fee
{
uint256 debtTokenReceived = swapper.execute(_reserveToken, params.collateralToWithdraw, _debtToken, params.swapData);
if (debtTokenReceived < flashRepayAmount) {
revert CommonEventsAndErrors.Slippage(flashRepayAmount, debtTokenReceived);
}
}
// If over the threshold, return any surplus [debtToken] from the swap to the borrowLend
// And pay down residual debt
{
uint256 surplusAfterSwap = _debtToken.balanceOf(address(this)) - flashRepayAmount;
uint256 borrowLendSurplus = _debtToken.balanceOf(address(_borrowLend));
uint256 totalSurplus = borrowLendSurplus + surplusAfterSwap;
if (totalSurplus > params.repaySurplusThreshold) {
if (surplusAfterSwap != 0) {
_debtToken.safeTransfer(address(_borrowLend), surplusAfterSwap);
}
totalDebtRepaid = totalDebtRepaid + _borrowLend.repay(totalSurplus);
}
}
// Validate that the new A/L is still within the `rebalanceALRange` and expected slippage range
uint128 alRatioAfter = _validateAfterRebalance(
cache,
alRatioBefore,
params.minNewAL,
params.maxNewAL,
AlValidationMode.HIGHER_THAN_BEFORE,
force
);
emit Rebalance(
-int256(params.collateralToWithdraw),
-int256(totalDebtRepaid),
alRatioBefore,
alRatioAfter
);
}
/**
* @dev Handle the rebalanceDown once the flash loan amount has been received
*/
function _rebalanceDownFlashLoanCallback(
uint256 flashLoanAmount,
uint256 fee,
RebalanceDownParams memory params,
bool force
) internal {
if (flashLoanAmount != params.flashLoanAmount) revert CommonEventsAndErrors.InvalidParam();
// Get the current A/L to check for oracle prices, and so we can compare that the new A/L is lower after the rebalance
Cache memory cache = populateCache(IOrigamiOracle.PriceType.SPOT_PRICE);
uint128 alRatioBefore = _assetToLiabilityRatio(cache);
// Swap from the `debtToken` to the `reserveToken`,
// based on the quotes obtained off chain
uint256 collateralSupplied = swapper.execute(_debtToken, flashLoanAmount, _reserveToken, params.swapData);
if (collateralSupplied < params.minExpectedReserveToken) {
revert CommonEventsAndErrors.Slippage(params.minExpectedReserveToken, collateralSupplied);
}
// Supply `reserveToken` into the money market, and borrow `debtToken`
uint256 borrowAmount = flashLoanAmount + fee;
IOrigamiBorrowAndLend _borrowLend = borrowLend;
_reserveToken.safeTransfer(address(_borrowLend), collateralSupplied);
_borrowLend.supplyAndBorrow(collateralSupplied, borrowAmount, address(this));
// Validate that the new A/L is still within the `rebalanceALRange` and expected slippage range
uint128 alRatioAfter = _validateAfterRebalance(
cache,
alRatioBefore,
params.minNewAL,
params.maxNewAL,
AlValidationMode.LOWER_THAN_BEFORE,
force
);
emit Rebalance(
int256(collateralSupplied),
int256(borrowAmount),
alRatioBefore,
alRatioAfter
);
}
/**
* @notice The current deposit fee based on market conditions.
* Deposit fees are applied to the portion of lovToken shares the depositor
* would have received. Instead that fee portion isn't minted (benefiting remaining users)
* @dev represented in basis points
*/
function _dynamicDepositFeeBps() internal override view returns (uint256) {
return DynamicFees.dynamicFeeBps(
DynamicFees.FeeType.DEPOSIT_FEE,
dynamicFeePriceOracle,
dynamicFeeOracleBaseToken,
_minDepositFeeBps,
_feeLeverageFactor
);
}
/**
* @notice The current exit fee based on market conditions.
* Exit fees are applied to the lovToken shares the user is exiting.
* That portion is burned prior to being redeemed (benefiting remaining users)
* @dev represented in basis points
*/
function _dynamicExitFeeBps() internal override view returns (uint256) {
return DynamicFees.dynamicFeeBps(
DynamicFees.FeeType.EXIT_FEE,
dynamicFeePriceOracle,
dynamicFeeOracleBaseToken,
_minExitFeeBps,
_feeLeverageFactor
);
}
/**
* @notice Deposit a number of `fromToken` into the `reserveToken`
* This vault only accepts where `fromToken` == `reserveToken`
*/
function _depositIntoReserves(address fromToken, uint256 fromTokenAmount) internal override returns (uint256 newReservesAmount) {
if (fromToken == address(_reserveToken)) {
newReservesAmount = fromTokenAmount;
// Supply into the money market
IOrigamiBorrowAndLend _borrowLend = borrowLend;
_reserveToken.safeTransfer(address(_borrowLend), fromTokenAmount);
_borrowLend.supply(fromTokenAmount);
} else {
revert CommonEventsAndErrors.InvalidToken(fromToken);
}
}
/**
* @notice Calculate the amount of `reserveToken` will be deposited given an amount of `fromToken`
* This vault only accepts where `fromToken` == `reserveToken`
*/
function _previewDepositIntoReserves(address fromToken, uint256 fromTokenAmount) internal override view returns (uint256 newReservesAmount) {
return fromToken == address(_reserveToken) ? fromTokenAmount : 0;
}
/**
* @notice Maximum amount of `fromToken` that can be deposited into the `reserveToken`
* This vault only accepts where `fromToken` == `reserveToken`
*/
function _maxDepositIntoReserves(address fromToken) internal override view returns (uint256 fromTokenAmount) {
if (fromToken == address(_reserveToken)) {
(uint256 _supplyCap, uint256 _available) = borrowLend.availableToSupply();
return _supplyCap == 0 ? MAX_TOKEN_AMOUNT : _available;
}
// Anything else returns 0
}
/**
* @notice Calculate the number of `toToken` required in order to mint a given number of `reserveToken`
* This vault only accepts where `fromToken` == `reserveToken`
*/
function _previewMintReserves(address toToken, uint256 reservesAmount) internal override view returns (uint256 newReservesAmount) {
return toToken == address(_reserveToken) ? reservesAmount : 0;
}
/**
* @notice Redeem a number of `reserveToken` into `toToken`
* This vault only accepts where `fromToken` == `reserveToken`
*/
function _redeemFromReserves(uint256 reservesAmount, address toToken, address recipient) internal override returns (uint256 toTokenAmount) {
if (toToken == address(_reserveToken)) {
toTokenAmount = reservesAmount;
uint256 _amountWithdrawn = borrowLend.withdraw(reservesAmount, recipient);
if (_amountWithdrawn != reservesAmount) revert CommonEventsAndErrors.InvalidAmount(toToken, reservesAmount);
} else {
revert CommonEventsAndErrors.InvalidToken(toToken);
}
}
/**
* @notice Calculate the number of `toToken` recevied if redeeming a number of `reserveToken`
* This vault only accepts where `fromToken` == `reserveToken`
*/
function _previewRedeemFromReserves(uint256 reservesAmount, address toToken) internal override view returns (uint256 toTokenAmount) {
return toToken == address(_reserveToken) ? reservesAmount : 0;
}
/**
* @notice Maximum amount of `reserveToken` that can be redeemed to `toToken`
* This vault only accepts where `fromToken` == `reserveToken`
* @dev If the A/L is now unsafe (eg if the money market Liquidation LTV is now lower than the floor)
* Then this will return zero
*/
function _maxRedeemFromReserves(address toToken) internal override view returns (uint256 reservesAmount) {
// If the A/L range is invalid, then return 0
IOrigamiBorrowAndLend _borrowLend = borrowLend;
if (!_borrowLend.isSafeAlRatio(userALRange.floor)) return 0;
if (toToken == address(_reserveToken)) {
// The max number of reserveToken available for redemption is the minimum
// of our position (the reserves balance) and what's available to withdraw from the money market (the balance
// of the reserve token within the collateralSupplyToken)
uint256 _reservesBalance = _borrowLend.suppliedBalance();
uint256 _availableInAave = _borrowLend.availableToWithdraw();
reservesAmount = _reservesBalance < _availableInAave ? _reservesBalance : _availableInAave;
}
// Anything else returns 0
}
/**
* @dev Revert if the range is invalid comparing to upstrea Aave/Spark
*/
function _validateAlRange(Range.Data storage range) internal override view {
if (!borrowLend.isSafeAlRatio(range.floor)) revert Range.InvalidRange(range.floor, range.ceiling);
}
function _validatedOracle(
address oracleAddress,
address baseAsset,
address quoteAsset
) private view returns (IOrigamiOracle oracle) {
if (oracleAddress == address(0)) revert CommonEventsAndErrors.InvalidAddress(address(0));
oracle = IOrigamiOracle(oracleAddress);
// Validate the assets on the oracle match what this lovToken needs
if (!oracle.matchAssets(baseAsset, quoteAsset)) {
revert CommonEventsAndErrors.InvalidParam();
}
}
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (investments/util/OrigamiManagerPausable.sol)
import { CommonEventsAndErrors } from "contracts/libraries/CommonEventsAndErrors.sol";
import { OrigamiElevatedAccess } from "contracts/common/access/OrigamiElevatedAccess.sol";
import { IOrigamiManagerPausable } from "contracts/interfaces/investments/util/IOrigamiManagerPausable.sol";
/**
* @title A mixin to add pause/unpause for Origami manager contracts
*/
abstract contract OrigamiManagerPausable is IOrigamiManagerPausable, OrigamiElevatedAccess {
/**
* @notice A set of accounts which are allowed to pause deposits/withdrawals immediately
* under emergency
*/
mapping(address account => bool canPause) public pausers;
/**
* @notice The current paused/unpaused state of deposits/exits.
*/
Paused internal _paused;
/**
* @notice Pause/unpause deposits or exits
* @dev Can only be called by allowed pausers.
*/
function setPaused(Paused calldata updatedPaused) external {
if (!pausers[msg.sender]) revert CommonEventsAndErrors.InvalidAccess();
emit PausedSet(updatedPaused);
_paused = updatedPaused;
}
/**
* @notice Allow/Deny an account to pause/unpause deposits or exits
*/
function setPauser(address account, bool canPause) external onlyElevatedAccess {
pausers[account] = canPause;
emit PauserSet(account, canPause);
}
/**
* @notice Check if given account can pause deposits/exits
*/
function isPauser(address account) external view override returns (bool canPause) {
canPause = pausers[account];
}
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (libraries/OrigamiMath.sol)
import { mulDiv as prbMulDiv, PRBMath_MulDiv_Overflow } from "@prb/math/src/Common.sol";
import { CommonEventsAndErrors } from "contracts/libraries/CommonEventsAndErrors.sol";
/**
* @notice Utilities to operate on fixed point math multipliation and division
* taking rounding into consideration
*/
library OrigamiMath {
enum Rounding {
ROUND_DOWN,
ROUND_UP
}
uint256 public constant BASIS_POINTS_DIVISOR = 10_000;
function scaleUp(uint256 amount, uint256 scalar) internal pure returns (uint256) {
// Special case for scalar == 1, as it's common for token amounts to not need
// scaling if decimal places are the same
return scalar == 1 ? amount : amount * scalar;
}
function scaleDown(
uint256 amount,
uint256 scalar,
Rounding roundingMode
) internal pure returns (uint256 result) {
// Special case for scalar == 1, as it's common for token amounts to not need
// scaling if decimal places are the same
unchecked {
if (scalar == 1) {
result = amount;
} else if (roundingMode == Rounding.ROUND_DOWN) {
result = amount / scalar;
} else {
// ROUND_UP uses the same logic as OZ Math.ceilDiv()
result = amount == 0 ? 0 : (amount - 1) / scalar + 1;
}
}
}
/**
* @notice Calculates x * y / denominator with full precision,
* rounding up
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator,
Rounding roundingMode
) internal pure returns (uint256 result) {
result = prbMulDiv(x, y, denominator);
if (roundingMode == Rounding.ROUND_UP) {
if (mulmod(x, y, denominator) != 0) {
if (result < type(uint256).max) {
unchecked {
result = result + 1;
}
} else {
revert PRBMath_MulDiv_Overflow(x, y, denominator);
}
}
}
}
function subtractBps(
uint256 inputAmount,
uint256 basisPoints,
Rounding roundingMode
) internal pure returns (uint256 result) {
uint256 numeratorBps;
unchecked {
numeratorBps = BASIS_POINTS_DIVISOR - basisPoints;
}
result = basisPoints < BASIS_POINTS_DIVISOR
? mulDiv(
inputAmount,
numeratorBps,
BASIS_POINTS_DIVISOR,
roundingMode
) : 0;
}
function addBps(
uint256 inputAmount,
uint256 basisPoints,
Rounding roundingMode
) internal pure returns (uint256 result) {
uint256 numeratorBps;
unchecked {
numeratorBps = BASIS_POINTS_DIVISOR + basisPoints;
}
// Round up for max amounts out expected
result = mulDiv(
inputAmount,
numeratorBps,
BASIS_POINTS_DIVISOR,
roundingMode
);
}
/**
* @notice Split the `inputAmount` into two parts based on the `basisPoints` fraction.
* eg: 3333 BPS (33.3%) can be used to split an input amount of 600 into: (result=400, removed=200).
* @dev The rounding mode is applied to the `result`
*/
function splitSubtractBps(
uint256 inputAmount,
uint256 basisPoints,
Rounding roundingMode
) internal pure returns (uint256 result, uint256 removed) {
result = subtractBps(inputAmount, basisPoints, roundingMode);
unchecked {
removed = inputAmount - result;
}
}
/**
* @notice Reverse the fractional amount of an input.
* eg: For 3333 BPS (33.3%) and the remainder=400, the result is 600
*/
function inverseSubtractBps(
uint256 remainderAmount,
uint256 basisPoints,
Rounding roundingMode
) internal pure returns (uint256 result) {
if (basisPoints == 0) return remainderAmount; // gas shortcut for 0
if (basisPoints >= BASIS_POINTS_DIVISOR) revert CommonEventsAndErrors.InvalidParam();
uint256 denominatorBps;
unchecked {
denominatorBps = BASIS_POINTS_DIVISOR - basisPoints;
}
result = mulDiv(
remainderAmount,
BASIS_POINTS_DIVISOR,
denominatorBps,
roundingMode
);
}
/**
* @notice Calculate the relative difference of a value to a reference
* @dev `value` and `referenceValue` must have the same precision
* The denominator is always the referenceValue
*/
function relativeDifferenceBps(
uint256 value,
uint256 referenceValue,
Rounding roundingMode
) internal pure returns (uint256) {
if (referenceValue == 0) revert CommonEventsAndErrors.InvalidParam();
uint256 absDelta;
unchecked {
absDelta = value < referenceValue
? referenceValue - value
: value - referenceValue;
}
return mulDiv(
absDelta,
BASIS_POINTS_DIVISOR,
referenceValue,
roundingMode
);
}
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (libraries/Range.sol)
/**
* @notice A helper library to track a valid range from floor <= x <= ceiling
*/
library Range {
error InvalidRange(uint128 floor, uint128 ceiling);
struct Data {
uint128 floor;
uint128 ceiling;
}
function set(Data storage range, uint128 floor, uint128 ceiling) internal {
if (floor > ceiling) {
revert InvalidRange(floor, ceiling);
}
range.floor = floor;
range.ceiling = ceiling;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
* Revert on invalid signature.
*/
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return
success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
}
}
pragma solidity 0.8.19;
// SPDX-License-Identifier: AGPL-3.0-or-later
// Origami (common/access/Whitelisted.sol)
import { IWhitelisted } from "contracts/interfaces/common/access/IWhitelisted.sol";
import { OrigamiElevatedAccess } from "contracts/common/access/OrigamiElevatedAccess.sol";
import { CommonEventsAndErrors } from "contracts/libraries/CommonEventsAndErrors.sol";
/**
* @title Whitelisted abstract contract
* @notice Functionality to deny non-EOA addresses unless whitelisted
*/
abstract contract Whitelisted is IWhitelisted, OrigamiElevatedAccess {
/**
* @notice Allow all (both EOAs and contracts) without whitelisting
*/
bool public override allowAll;
/**
* @notice A mapping of whitelisted accounts (not required for EOAs)
*/
mapping(address account => bool allowed) public override allowedAccounts;
/**
* @notice Allow all callers without whitelisting
*/
function setAllowAll(bool value) external override onlyElevatedAccess {
allowAll = value;
emit AllowAllSet(value);
}
/**
* @notice Set whether a given account is allowed or not
*/
function setAllowAccount(address account, bool value) external override onlyElevatedAccess {
if (account == address(0)) revert CommonEventsAndErrors.InvalidAddress(account);
if (account.code.length == 0) revert CommonEventsAndErrors.InvalidAddress(account);
allowedAccounts[account] = value;
emit AllowAccountSet(account, value);
}
/**
* @notice Returns false for contracts unless whitelisted, or until allowAll is set to true.
* @dev This cannot block contracts which deposit within their constructor, but the goal is to minimise 3rd
* party integrations. This will also deny contract based wallets (eg Gnosis Safe)
*/
function _isAllowed(address account) internal view returns (bool) {
if (allowAll) return true;
// Note: If the account is a contract and access is checked within it's constructor
// then this will still return true (unavoidable). This is just a deterrant for non-approved integrations,
// not intended as full protection.
if (account.code.length == 0) return true;
// Contracts need to be explicitly allowed
return allowedAccounts[account];
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/draft-IERC20Permit.sol)
pragma solidity ^0.8.0;
// EIP-2612 is Final as of 2022-11-01. This file is deprecated.
import "./IERC20Permit.sol";
{
"compilationTarget": {
"contracts/investments/lovToken/managers/OrigamiLovTokenFlashAndBorrowManager.sol": "OrigamiLovTokenFlashAndBorrowManager"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 10000
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_initialOwner","type":"address"},{"internalType":"address","name":"_reserveToken_","type":"address"},{"internalType":"address","name":"_debtToken_","type":"address"},{"internalType":"address","name":"_dynamicFeeOracleBaseToken","type":"address"},{"internalType":"address","name":"_lovToken","type":"address"},{"internalType":"address","name":"_flashLoanProvider","type":"address"},{"internalType":"address","name":"_borrowLend","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint128","name":"ratioBefore","type":"uint128"},{"internalType":"uint128","name":"ratioAfter","type":"uint128"},{"internalType":"uint128","name":"maxRatio","type":"uint128"}],"name":"ALTooHigh","type":"error"},{"inputs":[{"internalType":"uint128","name":"ratioBefore","type":"uint128"},{"internalType":"uint128","name":"ratioAfter","type":"uint128"},{"internalType":"uint128","name":"minRatio","type":"uint128"}],"name":"ALTooLow","type":"error"},{"inputs":[],"name":"ExpectedNonZero","type":"error"},{"inputs":[],"name":"InvalidAccess","type":"error"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"InvalidAddress","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"InvalidAmount","type":"error"},{"inputs":[],"name":"InvalidParam","type":"error"},{"inputs":[{"internalType":"uint128","name":"floor","type":"uint128"},{"internalType":"uint128","name":"ceiling","type":"uint128"}],"name":"InvalidRange","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"InvalidToken","type":"error"},{"inputs":[],"name":"IsPaused","type":"error"},{"inputs":[],"name":"NoAvailableReserves","type":"error"},{"inputs":[{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"},{"internalType":"uint256","name":"denominator","type":"uint256"}],"name":"PRBMath_MulDiv_Overflow","type":"error"},{"inputs":[{"internalType":"uint256","name":"minAmountExpected","type":"uint256"},{"internalType":"uint256","name":"actualAmount","type":"uint256"}],"name":"Slippage","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"bool","name":"value","type":"bool"}],"name":"AllowAccountSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"value","type":"bool"}],"name":"AllowAllSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addr","type":"address"}],"name":"BorrowLendSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"bytes4","name":"fnSelector","type":"bytes4"},{"indexed":true,"internalType":"bool","name":"value","type":"bool"}],"name":"ExplicitAccessSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"maxExitFeeBps","type":"uint16"},{"indexed":false,"internalType":"uint16","name":"minExitFeeBps","type":"uint16"},{"indexed":false,"internalType":"uint24","name":"feeLeverageFactor","type":"uint24"}],"name":"FeeConfigSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"provider","type":"address"}],"name":"FlashLoanProviderSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"NewOwnerAccepted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":true,"internalType":"address","name":"oldProposedOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newProposedOwner","type":"address"}],"name":"NewOwnerProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"debtTokenToReserveTokenOracle","type":"address"},{"indexed":true,"internalType":"address","name":"dynamicFeePriceOracle","type":"address"}],"name":"OraclesSet","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"bool","name":"investmentsPaused","type":"bool"},{"internalType":"bool","name":"exitsPaused","type":"bool"}],"indexed":false,"internalType":"struct IOrigamiManagerPausable.Paused","name":"paused","type":"tuple"}],"name":"PausedSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"bool","name":"canPause","type":"bool"}],"name":"PauserSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"int256","name":"collateralChange","type":"int256"},{"indexed":false,"internalType":"int256","name":"debtChange","type":"int256"},{"indexed":false,"internalType":"uint256","name":"alRatioBefore","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"alRatioAfter","type":"uint256"}],"name":"Rebalance","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint128","name":"floor","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"ceiling","type":"uint128"}],"name":"RebalanceALRangeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"swapper","type":"address"}],"name":"SwapperSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint128","name":"floor","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"ceiling","type":"uint128"}],"name":"UserALRangeSet","type":"event"},{"inputs":[],"name":"PRECISION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"acceptedExitTokens","outputs":[{"internalType":"address[]","name":"tokens","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptedInvestTokens","outputs":[{"internalType":"address[]","name":"tokens","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"allowAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"allowedAccounts","outputs":[{"internalType":"bool","name":"allowed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"areExitsPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"areInvestmentsPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"assetToLiabilityRatio","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"enum IOrigamiOracle.PriceType","name":"debtPriceType","type":"uint8"}],"name":"assetsAndLiabilities","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"borrowLend","outputs":[{"internalType":"contract IOrigamiBorrowAndLend","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"debtToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"debtTokenToReserveTokenOracle","outputs":[{"internalType":"contract IOrigamiOracle","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dynamicFeeOracleBaseToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dynamicFeePriceOracle","outputs":[{"internalType":"contract IOrigamiOracle","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"enum IOrigamiOracle.PriceType","name":"debtPriceType","type":"uint8"}],"name":"effectiveExposure","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"investmentAmount","type":"uint256"},{"internalType":"address","name":"toToken","type":"address"},{"internalType":"uint256","name":"maxSlippageBps","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"exitQuote","outputs":[{"components":[{"internalType":"uint256","name":"investmentTokenAmount","type":"uint256"},{"internalType":"address","name":"toToken","type":"address"},{"internalType":"uint256","name":"maxSlippageBps","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint256","name":"expectedToTokenAmount","type":"uint256"},{"internalType":"uint256","name":"minToTokenAmount","type":"uint256"},{"internalType":"bytes","name":"underlyingInvestmentQuoteData","type":"bytes"}],"internalType":"struct IOrigamiInvestment.ExitQuoteData","name":"quoteData","type":"tuple"},{"internalType":"uint256[]","name":"exitFeeBps","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"components":[{"internalType":"uint256","name":"investmentTokenAmount","type":"uint256"},{"internalType":"address","name":"toToken","type":"address"},{"internalType":"uint256","name":"maxSlippageBps","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint256","name":"expectedToTokenAmount","type":"uint256"},{"internalType":"uint256","name":"minToTokenAmount","type":"uint256"},{"internalType":"bytes","name":"underlyingInvestmentQuoteData","type":"bytes"}],"internalType":"struct IOrigamiInvestment.ExitQuoteData","name":"quoteData","type":"tuple"},{"internalType":"address","name":"recipient","type":"address"}],"name":"exitToToken","outputs":[{"internalType":"uint256","name":"toTokenAmount","type":"uint256"},{"internalType":"uint256","name":"toBurnAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bytes4","name":"","type":"bytes4"}],"name":"explicitFunctionAccess","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"fee","type":"uint256"},{"internalType":"bytes","name":"params","type":"bytes"}],"name":"flashLoanCallback","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"flashLoanProvider","outputs":[{"internalType":"contract IOrigamiFlashLoanProvider","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"flashLoanAmount","type":"uint256"},{"internalType":"uint256","name":"minExpectedReserveToken","type":"uint256"},{"internalType":"bytes","name":"swapData","type":"bytes"},{"internalType":"uint128","name":"minNewAL","type":"uint128"},{"internalType":"uint128","name":"maxNewAL","type":"uint128"}],"internalType":"struct IOrigamiLovTokenFlashAndBorrowManager.RebalanceDownParams","name":"params","type":"tuple"}],"name":"forceRebalanceDown","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"flashLoanAmount","type":"uint256"},{"internalType":"uint256","name":"collateralToWithdraw","type":"uint256"},{"internalType":"bytes","name":"swapData","type":"bytes"},{"internalType":"uint256","name":"repaySurplusThreshold","type":"uint256"},{"internalType":"uint128","name":"minNewAL","type":"uint128"},{"internalType":"uint128","name":"maxNewAL","type":"uint128"}],"internalType":"struct IOrigamiLovTokenFlashAndBorrowManager.RebalanceUpParams","name":"params","type":"tuple"}],"name":"forceRebalanceUp","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getDynamicFeesBps","outputs":[{"internalType":"uint256","name":"depositFeeBps","type":"uint256"},{"internalType":"uint256","name":"exitFeeBps","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getFeeConfig","outputs":[{"internalType":"uint64","name":"","type":"uint64"},{"internalType":"uint64","name":"","type":"uint64"},{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"fromTokenAmount","type":"uint256"},{"internalType":"address","name":"fromToken","type":"address"},{"internalType":"uint256","name":"maxSlippageBps","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"investQuote","outputs":[{"components":[{"internalType":"address","name":"fromToken","type":"address"},{"internalType":"uint256","name":"fromTokenAmount","type":"uint256"},{"internalType":"uint256","name":"maxSlippageBps","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint256","name":"expectedInvestmentAmount","type":"uint256"},{"internalType":"uint256","name":"minInvestmentAmount","type":"uint256"},{"internalType":"bytes","name":"underlyingInvestmentQuoteData","type":"bytes"}],"internalType":"struct IOrigamiInvestment.InvestQuoteData","name":"quoteData","type":"tuple"},{"internalType":"uint256[]","name":"investFeeBps","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"components":[{"internalType":"address","name":"fromToken","type":"address"},{"internalType":"uint256","name":"fromTokenAmount","type":"uint256"},{"internalType":"uint256","name":"maxSlippageBps","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint256","name":"expectedInvestmentAmount","type":"uint256"},{"internalType":"uint256","name":"minInvestmentAmount","type":"uint256"},{"internalType":"bytes","name":"underlyingInvestmentQuoteData","type":"bytes"}],"internalType":"struct IOrigamiInvestment.InvestQuoteData","name":"quoteData","type":"tuple"}],"name":"investWithToken","outputs":[{"internalType":"uint256","name":"investmentAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"isPauser","outputs":[{"internalType":"bool","name":"canPause","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"enum IOrigamiOracle.PriceType","name":"debtPriceType","type":"uint8"}],"name":"liabilities","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lovToken","outputs":[{"internalType":"contract IOrigamiLovToken","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"toToken","type":"address"}],"name":"maxExit","outputs":[{"internalType":"uint256","name":"sharesAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"fromToken","type":"address"}],"name":"maxInvest","outputs":[{"internalType":"uint256","name":"fromTokenAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"pausers","outputs":[{"internalType":"bool","name":"canPause","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"proposeNewOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rebalanceALRange","outputs":[{"internalType":"uint128","name":"floor","type":"uint128"},{"internalType":"uint128","name":"ceiling","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"flashLoanAmount","type":"uint256"},{"internalType":"uint256","name":"minExpectedReserveToken","type":"uint256"},{"internalType":"bytes","name":"swapData","type":"bytes"},{"internalType":"uint128","name":"minNewAL","type":"uint128"},{"internalType":"uint128","name":"maxNewAL","type":"uint128"}],"internalType":"struct IOrigamiLovTokenFlashAndBorrowManager.RebalanceDownParams","name":"params","type":"tuple"}],"name":"rebalanceDown","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"flashLoanAmount","type":"uint256"},{"internalType":"uint256","name":"collateralToWithdraw","type":"uint256"},{"internalType":"bytes","name":"swapData","type":"bytes"},{"internalType":"uint256","name":"repaySurplusThreshold","type":"uint256"},{"internalType":"uint128","name":"minNewAL","type":"uint128"},{"internalType":"uint128","name":"maxNewAL","type":"uint128"}],"internalType":"struct IOrigamiLovTokenFlashAndBorrowManager.RebalanceUpParams","name":"params","type":"tuple"}],"name":"rebalanceUp","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"recoverToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reserveToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"reservesBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"reserves","type":"uint256"},{"internalType":"enum IOrigamiOracle.PriceType","name":"debtPriceType","type":"uint8"}],"name":"reservesToShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bool","name":"value","type":"bool"}],"name":"setAllowAccount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"value","type":"bool"}],"name":"setAllowAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"setBorrowLend","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"allowedCaller","type":"address"},{"components":[{"internalType":"bytes4","name":"fnSelector","type":"bytes4"},{"internalType":"bool","name":"allowed","type":"bool"}],"internalType":"struct IOrigamiElevatedAccess.ExplicitAccess[]","name":"access","type":"tuple[]"}],"name":"setExplicitAccess","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"minDepositFeeBps","type":"uint16"},{"internalType":"uint16","name":"minExitFeeBps","type":"uint16"},{"internalType":"uint24","name":"feeLeverageFactor","type":"uint24"}],"name":"setFeeConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"provider","type":"address"}],"name":"setFlashLoanProvider","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_debtTokenToReserveTokenOracle","type":"address"},{"internalType":"address","name":"_dynamicFeePriceOracle","type":"address"}],"name":"setOracles","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bool","name":"investmentsPaused","type":"bool"},{"internalType":"bool","name":"exitsPaused","type":"bool"}],"internalType":"struct IOrigamiManagerPausable.Paused","name":"updatedPaused","type":"tuple"}],"name":"setPaused","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bool","name":"canPause","type":"bool"}],"name":"setPauser","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"floor","type":"uint128"},{"internalType":"uint128","name":"ceiling","type":"uint128"}],"name":"setRebalanceALRange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_swapper","type":"address"}],"name":"setSwapper","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"floor","type":"uint128"},{"internalType":"uint128","name":"ceiling","type":"uint128"}],"name":"setUserALRange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"enum IOrigamiOracle.PriceType","name":"debtPriceType","type":"uint8"}],"name":"sharesToReserves","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"swapper","outputs":[{"internalType":"contract IOrigamiSwapper","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"userALRange","outputs":[{"internalType":"uint128","name":"floor","type":"uint128"},{"internalType":"uint128","name":"ceiling","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"enum IOrigamiOracle.PriceType","name":"debtPriceType","type":"uint8"}],"name":"userRedeemableReserves","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]