// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (utils/Address.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @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.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
(bool success, bytes memory returndata) = recipient.call{value: amount}("");
if (!success) {
_revert(returndata);
}
}
/**
* @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 or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {Errors.FailedCall} error.
*
* 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.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @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`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case
* of an unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {Errors.FailedCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}.
*/
function _revert(bytes memory returndata) 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
assembly ("memory-safe") {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert Errors.FailedCall();
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
// Common.sol
//
// Common mathematical functions used in 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 maximum value a uint64 number can have.
uint64 constant MAX_UINT64 = type(uint64).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;
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (metatx/ERC2771Context.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Context variant with ERC-2771 support.
*
* WARNING: Avoid using this pattern in contracts that rely in a specific calldata length as they'll
* be affected by any forwarder whose `msg.data` is suffixed with the `from` address according to the ERC-2771
* specification adding the address size in bytes (20) to the calldata size. An example of an unexpected
* behavior could be an unintended fallback (or another function) invocation while trying to invoke the `receive`
* function only accessible if `msg.data.length == 0`.
*
* WARNING: The usage of `delegatecall` in this contract is dangerous and may result in context corruption.
* Any forwarded request to this contract triggering a `delegatecall` to itself will result in an invalid {_msgSender}
* recovery.
*/
abstract contract ERC2771Context is Context {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address private immutable _trustedForwarder;
/**
* @dev Initializes the contract with a trusted forwarder, which will be able to
* invoke functions on this contract on behalf of other accounts.
*
* NOTE: The trusted forwarder can be replaced by overriding {trustedForwarder}.
*/
/// @custom:oz-upgrades-unsafe-allow constructor
constructor(address trustedForwarder_) {
_trustedForwarder = trustedForwarder_;
}
/**
* @dev Returns the address of the trusted forwarder.
*/
function trustedForwarder() public view virtual returns (address) {
return _trustedForwarder;
}
/**
* @dev Indicates whether any particular address is the trusted forwarder.
*/
function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
return forwarder == trustedForwarder();
}
/**
* @dev Override for `msg.sender`. Defaults to the original `msg.sender` whenever
* a call is not performed by the trusted forwarder or the calldata length is less than
* 20 bytes (an address length).
*/
function _msgSender() internal view virtual override returns (address) {
uint256 calldataLength = msg.data.length;
uint256 contextSuffixLength = _contextSuffixLength();
if (isTrustedForwarder(msg.sender) && calldataLength >= contextSuffixLength) {
return address(bytes20(msg.data[calldataLength - contextSuffixLength:]));
} else {
return super._msgSender();
}
}
/**
* @dev Override for `msg.data`. Defaults to the original `msg.data` whenever
* a call is not performed by the trusted forwarder or the calldata length is less than
* 20 bytes (an address length).
*/
function _msgData() internal view virtual override returns (bytes calldata) {
uint256 calldataLength = msg.data.length;
uint256 contextSuffixLength = _contextSuffixLength();
if (isTrustedForwarder(msg.sender) && calldataLength >= contextSuffixLength) {
return msg.data[:calldataLength - contextSuffixLength];
} else {
return super._msgData();
}
}
/**
* @dev ERC-2771 specifies the context as being a single address (20 bytes).
*/
function _contextSuffixLength() internal view virtual override returns (uint256) {
return 20;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of common custom errors used in multiple contracts
*
* IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
* It is recommended to avoid relying on the error API for critical functionality.
*
* _Available since v5.1._
*/
library Errors {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error InsufficientBalance(uint256 balance, uint256 needed);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedCall();
/**
* @dev The deployment failed.
*/
error FailedDeployment();
/**
* @dev A necessary precompile is missing.
*/
error MissingPrecompile(address);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IEIP712} from "./IEIP712.sol";
/// @title AllowanceTransfer
/// @notice Handles ERC20 token permissions through signature based allowance setting and ERC20 token transfers by checking allowed amounts
/// @dev Requires user's token approval on the Permit2 contract
interface IAllowanceTransfer is IEIP712 {
/// @notice Thrown when an allowance on a token has expired.
/// @param deadline The timestamp at which the allowed amount is no longer valid
error AllowanceExpired(uint256 deadline);
/// @notice Thrown when an allowance on a token has been depleted.
/// @param amount The maximum amount allowed
error InsufficientAllowance(uint256 amount);
/// @notice Thrown when too many nonces are invalidated.
error ExcessiveInvalidation();
/// @notice Emits an event when the owner successfully invalidates an ordered nonce.
event NonceInvalidation(
address indexed owner, address indexed token, address indexed spender, uint48 newNonce, uint48 oldNonce
);
/// @notice Emits an event when the owner successfully sets permissions on a token for the spender.
event Approval(
address indexed owner, address indexed token, address indexed spender, uint160 amount, uint48 expiration
);
/// @notice Emits an event when the owner successfully sets permissions using a permit signature on a token for the spender.
event Permit(
address indexed owner,
address indexed token,
address indexed spender,
uint160 amount,
uint48 expiration,
uint48 nonce
);
/// @notice Emits an event when the owner sets the allowance back to 0 with the lockdown function.
event Lockdown(address indexed owner, address token, address spender);
/// @notice The permit data for a token
struct PermitDetails {
// ERC20 token address
address token;
// the maximum amount allowed to spend
uint160 amount;
// timestamp at which a spender's token allowances become invalid
uint48 expiration;
// an incrementing value indexed per owner,token,and spender for each signature
uint48 nonce;
}
/// @notice The permit message signed for a single token allowance
struct PermitSingle {
// the permit data for a single token alownce
PermitDetails details;
// address permissioned on the allowed tokens
address spender;
// deadline on the permit signature
uint256 sigDeadline;
}
/// @notice The permit message signed for multiple token allowances
struct PermitBatch {
// the permit data for multiple token allowances
PermitDetails[] details;
// address permissioned on the allowed tokens
address spender;
// deadline on the permit signature
uint256 sigDeadline;
}
/// @notice The saved permissions
/// @dev This info is saved per owner, per token, per spender and all signed over in the permit message
/// @dev Setting amount to type(uint160).max sets an unlimited approval
struct PackedAllowance {
// amount allowed
uint160 amount;
// permission expiry
uint48 expiration;
// an incrementing value indexed per owner,token,and spender for each signature
uint48 nonce;
}
/// @notice A token spender pair.
struct TokenSpenderPair {
// the token the spender is approved
address token;
// the spender address
address spender;
}
/// @notice Details for a token transfer.
struct AllowanceTransferDetails {
// the owner of the token
address from;
// the recipient of the token
address to;
// the amount of the token
uint160 amount;
// the token to be transferred
address token;
}
/// @notice A mapping from owner address to token address to spender address to PackedAllowance struct, which contains details and conditions of the approval.
/// @notice The mapping is indexed in the above order see: allowance[ownerAddress][tokenAddress][spenderAddress]
/// @dev The packed slot holds the allowed amount, expiration at which the allowed amount is no longer valid, and current nonce thats updated on any signature based approvals.
function allowance(address user, address token, address spender)
external
view
returns (uint160 amount, uint48 expiration, uint48 nonce);
/// @notice Approves the spender to use up to amount of the specified token up until the expiration
/// @param token The token to approve
/// @param spender The spender address to approve
/// @param amount The approved amount of the token
/// @param expiration The timestamp at which the approval is no longer valid
/// @dev The packed allowance also holds a nonce, which will stay unchanged in approve
/// @dev Setting amount to type(uint160).max sets an unlimited approval
function approve(address token, address spender, uint160 amount, uint48 expiration) external;
/// @notice Permit a spender to a given amount of the owners token via the owner's EIP-712 signature
/// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce
/// @param owner The owner of the tokens being approved
/// @param permitSingle Data signed over by the owner specifying the terms of approval
/// @param signature The owner's signature over the permit data
function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external;
/// @notice Permit a spender to the signed amounts of the owners tokens via the owner's EIP-712 signature
/// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce
/// @param owner The owner of the tokens being approved
/// @param permitBatch Data signed over by the owner specifying the terms of approval
/// @param signature The owner's signature over the permit data
function permit(address owner, PermitBatch memory permitBatch, bytes calldata signature) external;
/// @notice Transfer approved tokens from one address to another
/// @param from The address to transfer from
/// @param to The address of the recipient
/// @param amount The amount of the token to transfer
/// @param token The token address to transfer
/// @dev Requires the from address to have approved at least the desired amount
/// of tokens to msg.sender.
function transferFrom(address from, address to, uint160 amount, address token) external;
/// @notice Transfer approved tokens in a batch
/// @param transferDetails Array of owners, recipients, amounts, and tokens for the transfers
/// @dev Requires the from addresses to have approved at least the desired amount
/// of tokens to msg.sender.
function transferFrom(AllowanceTransferDetails[] calldata transferDetails) external;
/// @notice Enables performing a "lockdown" of the sender's Permit2 identity
/// by batch revoking approvals
/// @param approvals Array of approvals to revoke.
function lockdown(TokenSpenderPair[] calldata approvals) external;
/// @notice Invalidate nonces for a given (token, spender) pair
/// @param token The token to invalidate nonces for
/// @param spender The spender to invalidate nonces for
/// @param newNonce The new nonce to set. Invalidates all nonces less than it.
/// @dev Can't invalidate more than 2**16 nonces per transaction.
function invalidateNonces(address token, address spender, uint48 newNonce) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IEIP712 {
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";
/**
* @title IERC1363
* @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
*
* Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
* after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
*/
interface IERC1363 is IERC20, IERC165 {
/*
* Note: the ERC-165 identifier for this interface is 0xb0202a11.
* 0xb0202a11 ===
* bytes4(keccak256('transferAndCall(address,uint256)')) ^
* bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
* bytes4(keccak256('approveAndCall(address,uint256)')) ^
* bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
*/
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @param data Additional data with no specified format, sent in call to `spender`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../token/ERC20/IERC20.sol";
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC-20 standard.
*/
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 v5.1.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC-721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC-721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
* {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the address zero.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {JBAfterCashOutRecordedContext} from "./../structs/JBAfterCashOutRecordedContext.sol";
/// @notice Hook called after a terminal's `cashOutTokensOf(...)` logic completes (if passed by the ruleset's data
/// hook).
interface IJBCashOutHook is IERC165 {
/// @notice This function is called by the terminal's `cashOutTokensOf(...)` function after the cash out has been
/// recorded in the terminal store.
/// @dev Critical business logic should be protected by appropriate access control.
/// @param context The context passed in by the terminal, as a `JBAfterCashOutRecordedContext` struct.
function afterCashOutRecordedWith(JBAfterCashOutRecordedContext calldata context) external payable;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IJBCashOutHook} from "./IJBCashOutHook.sol";
import {IJBTerminal} from "./IJBTerminal.sol";
import {JBAfterCashOutRecordedContext} from "../structs/JBAfterCashOutRecordedContext.sol";
/// @notice A terminal that can be cashed out from.
interface IJBCashOutTerminal is IJBTerminal {
event HookAfterRecordCashOut(
IJBCashOutHook indexed hook,
JBAfterCashOutRecordedContext context,
uint256 specificationAmount,
uint256 fee,
address caller
);
event CashOutTokens(
uint256 indexed rulesetId,
uint256 indexed rulesetCycleNumber,
uint256 indexed projectId,
address holder,
address beneficiary,
uint256 cashOutCount,
uint256 cashOutTaxRate,
uint256 reclaimAmount,
bytes metadata,
address caller
);
function cashOutTokensOf(
address holder,
uint256 projectId,
uint256 cashOutCount,
address tokenToReclaim,
uint256 minTokensReclaimed,
address payable beneficiary,
bytes calldata metadata
)
external
returns (uint256 reclaimAmount);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IJBDirectory} from "./IJBDirectory.sol";
import {IJBDirectoryAccessControl} from "./IJBDirectoryAccessControl.sol";
import {IJBFundAccessLimits} from "./IJBFundAccessLimits.sol";
import {IJBPriceFeed} from "./IJBPriceFeed.sol";
import {IJBPrices} from "./IJBPrices.sol";
import {IJBProjects} from "./IJBProjects.sol";
import {IJBProjectUriRegistry} from "./IJBProjectUriRegistry.sol";
import {IJBRulesets} from "./IJBRulesets.sol";
import {IJBSplits} from "./IJBSplits.sol";
import {IJBTerminal} from "./IJBTerminal.sol";
import {IJBToken} from "./IJBToken.sol";
import {IJBTokens} from "./IJBTokens.sol";
import {JBApprovalStatus} from "./../enums/JBApprovalStatus.sol";
import {JBRuleset} from "./../structs/JBRuleset.sol";
import {JBRulesetConfig} from "./../structs/JBRulesetConfig.sol";
import {JBRulesetMetadata} from "./../structs/JBRulesetMetadata.sol";
import {JBRulesetWithMetadata} from "./../structs/JBRulesetWithMetadata.sol";
import {JBSplit} from "./../structs/JBSplit.sol";
import {JBSplitGroup} from "./../structs/JBSplitGroup.sol";
import {JBTerminalConfig} from "./../structs/JBTerminalConfig.sol";
interface IJBController is IERC165, IJBProjectUriRegistry, IJBDirectoryAccessControl {
event BurnTokens(
address indexed holder, uint256 indexed projectId, uint256 tokenCount, string memo, address caller
);
event LaunchProject(uint256 rulesetId, uint256 projectId, string projectUri, string memo, address caller);
event LaunchRulesets(uint256 rulesetId, uint256 projectId, string memo, address caller);
event MintTokens(
address indexed beneficiary,
uint256 indexed projectId,
uint256 tokenCount,
uint256 beneficiaryTokenCount,
string memo,
uint256 reservedPercent,
address caller
);
event PrepMigration(uint256 indexed projectId, address from, address caller);
event QueueRulesets(uint256 rulesetId, uint256 projectId, string memo, address caller);
event ReservedDistributionReverted(
uint256 indexed projectId, JBSplit split, uint256 tokenCount, bytes reason, address caller
);
event SendReservedTokensToSplit(
uint256 indexed projectId,
uint256 indexed rulesetId,
uint256 indexed groupId,
JBSplit split,
uint256 tokenCount,
address caller
);
event SendReservedTokensToSplits(
uint256 indexed rulesetId,
uint256 indexed rulesetCycleNumber,
uint256 indexed projectId,
address owner,
uint256 tokenCount,
uint256 leftoverAmount,
address caller
);
event SetUri(uint256 indexed projectId, string uri, address caller);
function DIRECTORY() external view returns (IJBDirectory);
function FUND_ACCESS_LIMITS() external view returns (IJBFundAccessLimits);
function PRICES() external view returns (IJBPrices);
function PROJECTS() external view returns (IJBProjects);
function RULESETS() external view returns (IJBRulesets);
function SPLITS() external view returns (IJBSplits);
function TOKENS() external view returns (IJBTokens);
function allRulesetsOf(
uint256 projectId,
uint256 startingId,
uint256 size
)
external
view
returns (JBRulesetWithMetadata[] memory rulesets);
function currentRulesetOf(uint256 projectId)
external
view
returns (JBRuleset memory ruleset, JBRulesetMetadata memory metadata);
function getRulesetOf(
uint256 projectId,
uint256 rulesetId
)
external
view
returns (JBRuleset memory ruleset, JBRulesetMetadata memory metadata);
function latestQueuedRulesetOf(uint256 projectId)
external
view
returns (JBRuleset memory, JBRulesetMetadata memory metadata, JBApprovalStatus);
function pendingReservedTokenBalanceOf(uint256 projectId) external view returns (uint256);
function totalTokenSupplyWithReservedTokensOf(uint256 projectId) external view returns (uint256);
function upcomingRulesetOf(uint256 projectId)
external
view
returns (JBRuleset memory ruleset, JBRulesetMetadata memory metadata);
function addPriceFeed(
uint256 projectId,
uint256 pricingCurrency,
uint256 unitCurrency,
IJBPriceFeed feed
)
external;
function burnTokensOf(address holder, uint256 projectId, uint256 tokenCount, string calldata memo) external;
function claimTokensFor(address holder, uint256 projectId, uint256 tokenCount, address beneficiary) external;
function deployERC20For(
uint256 projectId,
string calldata name,
string calldata symbol,
bytes32 salt
)
external
returns (IJBToken token);
function launchProjectFor(
address owner,
string calldata projectUri,
JBRulesetConfig[] calldata rulesetConfigurations,
JBTerminalConfig[] memory terminalConfigurations,
string calldata memo
)
external
returns (uint256 projectId);
function launchRulesetsFor(
uint256 projectId,
JBRulesetConfig[] calldata rulesetConfigurations,
JBTerminalConfig[] memory terminalConfigurations,
string calldata memo
)
external
returns (uint256 rulesetId);
function mintTokensOf(
uint256 projectId,
uint256 tokenCount,
address beneficiary,
string calldata memo,
bool useReservedPercent
)
external
returns (uint256 beneficiaryTokenCount);
function queueRulesetsOf(
uint256 projectId,
JBRulesetConfig[] calldata rulesetConfigurations,
string calldata memo
)
external
returns (uint256 rulesetId);
function sendReservedTokensToSplitsOf(uint256 projectId) external returns (uint256);
function setSplitGroupsOf(uint256 projectId, uint256 rulesetId, JBSplitGroup[] calldata splitGroups) external;
function setTokenFor(uint256 projectId, IJBToken token) external;
function transferCreditsFrom(address holder, uint256 projectId, address recipient, uint256 creditCount) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IJBProjects} from "./IJBProjects.sol";
import {IJBTerminal} from "./IJBTerminal.sol";
interface IJBDirectory {
event AddTerminal(uint256 indexed projectId, IJBTerminal indexed terminal, address caller);
event SetController(uint256 indexed projectId, IERC165 indexed controller, address caller);
event SetIsAllowedToSetFirstController(address indexed addr, bool indexed isAllowed, address caller);
event SetPrimaryTerminal(
uint256 indexed projectId, address indexed token, IJBTerminal indexed terminal, address caller
);
event SetTerminals(uint256 indexed projectId, IJBTerminal[] terminals, address caller);
function PROJECTS() external view returns (IJBProjects);
function controllerOf(uint256 projectId) external view returns (IERC165);
function isAllowedToSetFirstController(address account) external view returns (bool);
function isTerminalOf(uint256 projectId, IJBTerminal terminal) external view returns (bool);
function primaryTerminalOf(uint256 projectId, address token) external view returns (IJBTerminal);
function terminalsOf(uint256 projectId) external view returns (IJBTerminal[] memory);
function setControllerOf(uint256 projectId, IERC165 controller) external;
function setIsAllowedToSetFirstController(address account, bool flag) external;
function setPrimaryTerminalOf(uint256 projectId, address token, IJBTerminal terminal) external;
function setTerminalsOf(uint256 projectId, IJBTerminal[] calldata terminals) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IJBDirectoryAccessControl {
function setControllerAllowed(uint256 projectId) external view returns (bool);
function setTerminalsAllowed(uint256 projectId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IJBFeelessAddresses} from "./IJBFeelessAddresses.sol";
import {IJBTerminal} from "./IJBTerminal.sol";
import {JBFee} from "../structs/JBFee.sol";
/// @notice A terminal that can process and hold fees.
interface IJBFeeTerminal is IJBTerminal {
event FeeReverted(
uint256 indexed projectId,
address indexed token,
uint256 indexed feeProjectId,
uint256 amount,
bytes reason,
address caller
);
event HoldFee(
uint256 indexed projectId,
address indexed token,
uint256 indexed amount,
uint256 fee,
address beneficiary,
address caller
);
event ProcessFee(
uint256 indexed projectId,
address indexed token,
uint256 indexed amount,
bool wasHeld,
address beneficiary,
address caller
);
event ReturnHeldFees(
uint256 indexed projectId,
address indexed token,
uint256 indexed amount,
uint256 returnedFees,
uint256 leftoverAmount,
address caller
);
function FEE() external view returns (uint256);
function FEELESS_ADDRESSES() external view returns (IJBFeelessAddresses);
function heldFeesOf(uint256 projectId, address token, uint256 count) external view returns (JBFee[] memory);
function processHeldFeesOf(uint256 projectId, address token, uint256 count) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IJBFeelessAddresses {
event SetFeelessAddress(address indexed addr, bool indexed isFeeless, address caller);
function isFeeless(address account) external view returns (bool);
function setFeelessAddress(address account, bool flag) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {JBCurrencyAmount} from "./../structs/JBCurrencyAmount.sol";
import {JBFundAccessLimitGroup} from "./../structs/JBFundAccessLimitGroup.sol";
interface IJBFundAccessLimits {
event SetFundAccessLimits(
uint256 indexed rulesetId,
uint256 indexed projectId,
JBFundAccessLimitGroup fundAccessLimitGroup,
address caller
);
function payoutLimitOf(
uint256 projectId,
uint256 rulesetId,
address terminal,
address token,
uint256 currency
)
external
view
returns (uint256 payoutLimit);
function payoutLimitsOf(
uint256 projectId,
uint256 rulesetId,
address terminal,
address token
)
external
view
returns (JBCurrencyAmount[] memory payoutLimits);
function surplusAllowanceOf(
uint256 projectId,
uint256 rulesetId,
address terminal,
address token,
uint256 currency
)
external
view
returns (uint256 surplusAllowance);
function surplusAllowancesOf(
uint256 projectId,
uint256 rulesetId,
address terminal,
address token
)
external
view
returns (JBCurrencyAmount[] memory surplusAllowances);
function setFundAccessLimitsFor(
uint256 projectId,
uint256 rulesetId,
JBFundAccessLimitGroup[] memory fundAccessLimitGroups
)
external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IJBCashOutTerminal} from "./IJBCashOutTerminal.sol";
import {IJBDirectory} from "./IJBDirectory.sol";
import {IJBFeeTerminal} from "./IJBFeeTerminal.sol";
import {IJBPayoutTerminal} from "./IJBPayoutTerminal.sol";
import {IJBPermitTerminal} from "./IJBPermitTerminal.sol";
import {IJBProjects} from "./IJBProjects.sol";
import {IJBRulesets} from "./IJBRulesets.sol";
import {IJBSplits} from "./IJBSplits.sol";
import {IJBTerminal} from "./IJBTerminal.sol";
import {IJBTerminalStore} from "./IJBTerminalStore.sol";
import {IJBTokens} from "./IJBTokens.sol";
interface IJBMultiTerminal is IJBTerminal, IJBFeeTerminal, IJBCashOutTerminal, IJBPayoutTerminal, IJBPermitTerminal {
function DIRECTORY() external view returns (IJBDirectory);
function PROJECTS() external view returns (IJBProjects);
function RULESETS() external view returns (IJBRulesets);
function SPLITS() external view returns (IJBSplits);
function STORE() external view returns (IJBTerminalStore);
function TOKENS() external view returns (IJBTokens);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {JBAfterPayRecordedContext} from "./../structs/JBAfterPayRecordedContext.sol";
/// @notice Hook called after a terminal's `pay(...)` logic completes (if passed by the ruleset's data hook).
interface IJBPayHook is IERC165 {
/// @notice This function is called by the terminal's `pay(...)` function after the payment has been recorded in the
/// terminal store.
/// @dev Critical business logic should be protected by appropriate access control.
/// @param context The context passed in by the terminal, as a `JBAfterPayRecordedContext` struct.
function afterPayRecordedWith(JBAfterPayRecordedContext calldata context) external payable;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IJBSplits} from "./IJBSplits.sol";
import {IJBTerminal} from "./IJBTerminal.sol";
import {JBSplit} from "../structs/JBSplit.sol";
/// @notice A terminal that can send payouts.
interface IJBPayoutTerminal is IJBTerminal {
event PayoutReverted(uint256 indexed projectId, JBSplit split, uint256 amount, bytes reason, address caller);
event PayoutTransferReverted(
uint256 indexed projectId,
address addr,
address token,
uint256 amount,
uint256 fee,
bytes reason,
address caller
);
event SendPayouts(
uint256 indexed rulesetId,
uint256 indexed rulesetCycleNumber,
uint256 indexed projectId,
address projectOwner,
uint256 amount,
uint256 amountPaidOut,
uint256 fee,
uint256 netLeftoverPayoutAmount,
address caller
);
event SendPayoutToSplit(
uint256 indexed projectId,
uint256 indexed rulesetId,
uint256 indexed group,
JBSplit split,
uint256 amount,
uint256 netAmount,
address caller
);
event UseAllowance(
uint256 indexed rulesetId,
uint256 indexed rulesetCycleNumber,
uint256 indexed projectId,
address beneficiary,
address feeBeneficiary,
uint256 amount,
uint256 amountPaidOut,
uint256 netAmountPaidOut,
string memo,
address caller
);
function sendPayoutsOf(
uint256 projectId,
address token,
uint256 amount,
uint256 currency,
uint256 minTokensPaidOut
)
external
returns (uint256 netLeftoverPayoutAmount);
function useAllowanceOf(
uint256 projectId,
address token,
uint256 amount,
uint256 currency,
uint256 minTokensPaidOut,
address payable beneficiary,
address payable feeBeneficiary,
string calldata memo
)
external
returns (uint256 netAmountPaidOut);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IJBPermissions} from "./IJBPermissions.sol";
interface IJBPermissioned {
function PERMISSIONS() external view returns (IJBPermissions);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {JBPermissionsData} from "./../structs/JBPermissionsData.sol";
interface IJBPermissions {
event OperatorPermissionsSet(
address indexed operator,
address indexed account,
uint256 indexed projectId,
uint8[] permissionIds,
uint256 packed,
address caller
);
function WILDCARD_PROJECT_ID() external view returns (uint256);
function permissionsOf(address operator, address account, uint256 projectId) external view returns (uint256);
function hasPermission(
address operator,
address account,
uint256 projectId,
uint256 permissionId,
bool includeRoot,
bool includeWildcardProjectId
)
external
view
returns (bool);
function hasPermissions(
address operator,
address account,
uint256 projectId,
uint256[] calldata permissionIds,
bool includeRoot,
bool includeWildcardProjectId
)
external
view
returns (bool);
function setPermissionsFor(address account, JBPermissionsData calldata permissionsData) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IPermit2} from "@uniswap/permit2/src/interfaces/IPermit2.sol";
import {IJBTerminal} from "./IJBTerminal.sol";
interface IJBPermitTerminal is IJBTerminal {
function PERMIT2() external returns (IPermit2);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IJBPriceFeed {
function currentUnitPrice(uint256 targetDecimals) external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IJBPriceFeed} from "./IJBPriceFeed.sol";
import {IJBProjects} from "./IJBProjects.sol";
interface IJBPrices {
event AddPriceFeed(
uint256 indexed projectId,
uint256 indexed pricingCurrency,
uint256 indexed unitCurrency,
IJBPriceFeed feed,
address caller
);
function DEFAULT_PROJECT_ID() external view returns (uint256);
function PROJECTS() external view returns (IJBProjects);
function priceFeedFor(
uint256 projectId,
uint256 pricingCurrency,
uint256 unitCurrency
)
external
view
returns (IJBPriceFeed);
function pricePerUnitOf(
uint256 projectId,
uint256 pricingCurrency,
uint256 unitCurrency,
uint256 decimals
)
external
view
returns (uint256);
function addPriceFeedFor(
uint256 projectId,
uint256 pricingCurrency,
uint256 unitCurrency,
IJBPriceFeed feed
)
external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IJBProjectUriRegistry {
function uriOf(uint256 projectId) external view returns (string memory);
function setUriOf(uint256 projectId, string calldata uri) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IJBTokenUriResolver} from "./IJBTokenUriResolver.sol";
interface IJBProjects is IERC721 {
event Create(uint256 indexed projectId, address indexed owner, address caller);
event SetTokenUriResolver(IJBTokenUriResolver indexed resolver, address caller);
function count() external view returns (uint256);
function tokenUriResolver() external view returns (IJBTokenUriResolver);
function createFor(address owner) external returns (uint256 projectId);
function setTokenUriResolver(IJBTokenUriResolver resolver) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {JBApprovalStatus} from "./../enums/JBApprovalStatus.sol";
/// @notice `IJBRulesetApprovalHook`s are used to determine whether the next ruleset in the ruleset queue is approved or
/// rejected.
/// @dev Project rulesets are stored in a queue. Rulesets take effect after the previous ruleset in the queue ends, and
/// only if they are approved by the previous ruleset's approval hook.
interface IJBRulesetApprovalHook is IERC165 {
function DURATION() external view returns (uint256);
function approvalStatusOf(
uint256 projectId,
uint256 rulesetId,
uint256 start
)
external
view
returns (JBApprovalStatus);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {JBApprovalStatus} from "./../enums/JBApprovalStatus.sol";
import {JBRuleset} from "./../structs/JBRuleset.sol";
import {IJBRulesetApprovalHook} from "./IJBRulesetApprovalHook.sol";
interface IJBRulesets {
event RulesetInitialized(
uint256 indexed rulesetId, uint256 indexed projectId, uint256 indexed basedOnId, address caller
);
event RulesetQueued(
uint256 indexed rulesetId,
uint256 indexed projectId,
uint256 duration,
uint256 weight,
uint256 weightCutPercent,
IJBRulesetApprovalHook approvalHook,
uint256 metadata,
uint256 mustStartAtOrAfter,
address caller
);
event WeightCacheUpdated(uint256 projectId, uint112 weight, uint256 weightCutMultiple, address caller);
function latestRulesetIdOf(uint256 projectId) external view returns (uint256);
function currentApprovalStatusForLatestRulesetOf(uint256 projectId) external view returns (JBApprovalStatus);
function currentOf(uint256 projectId) external view returns (JBRuleset memory ruleset);
function deriveCycleNumberFrom(
uint256 baseRulesetCycleNumber,
uint256 baseRulesetStart,
uint256 baseRulesetDuration,
uint256 start
)
external
returns (uint256);
function deriveStartFrom(
uint256 baseRulesetStart,
uint256 baseRulesetDuration,
uint256 mustStartAtOrAfter
)
external
view
returns (uint256 start);
function deriveWeightFrom(
uint256 projectId,
uint256 baseRulesetStart,
uint256 baseRulesetDuration,
uint256 baseRulesetWeight,
uint256 baseRulesetWeightCutPercent,
uint256 baseRulesetCacheId,
uint256 start
)
external
view
returns (uint256 weight);
function getRulesetOf(uint256 projectId, uint256 rulesetId) external view returns (JBRuleset memory);
function latestQueuedOf(uint256 projectId)
external
view
returns (JBRuleset memory ruleset, JBApprovalStatus approvalStatus);
function allOf(
uint256 projectId,
uint256 startingId,
uint256 size
)
external
view
returns (JBRuleset[] memory rulesets);
function upcomingOf(uint256 projectId) external view returns (JBRuleset memory ruleset);
function queueFor(
uint256 projectId,
uint256 duration,
uint256 weight,
uint256 weightCutPercent,
IJBRulesetApprovalHook approvalHook,
uint256 metadata,
uint256 mustStartAtOrAfter
)
external
returns (JBRuleset memory ruleset);
function updateRulesetWeightCache(uint256 projectId) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {JBSplitHookContext} from "../structs/JBSplitHookContext.sol";
/// @title Split hook
/// @notice Allows processing a single split with custom logic.
/// @dev The split hook's address should be set as the `hook` in the relevant split.
interface IJBSplitHook is IERC165 {
/// @notice If a split has a split hook, payment terminals and controllers call this function while processing the
/// split.
/// @dev Critical business logic should be protected by appropriate access control. The tokens and/or native tokens
/// are optimistically transferred to the split hook when this function is called.
/// @param context The context passed by the terminal/controller to the split hook as a `JBSplitHookContext` struct:
function processSplitWith(JBSplitHookContext calldata context) external payable;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {JBSplit} from "./../structs/JBSplit.sol";
import {JBSplitGroup} from "./../structs/JBSplitGroup.sol";
interface IJBSplits {
event SetSplit(
uint256 indexed projectId, uint256 indexed rulesetId, uint256 indexed groupId, JBSplit split, address caller
);
function FALLBACK_RULESET_ID() external view returns (uint256);
function splitsOf(uint256 projectId, uint256 rulesetId, uint256 groupId) external view returns (JBSplit[] memory);
function setSplitGroupsOf(uint256 projectId, uint256 rulesetId, JBSplitGroup[] memory splitGroups) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IJBPayHook} from "./IJBPayHook.sol";
import {JBAccountingContext} from "../structs/JBAccountingContext.sol";
import {JBAfterPayRecordedContext} from "../structs/JBAfterPayRecordedContext.sol";
/// @notice A terminal that accepts payments and can be migrated.
interface IJBTerminal is IERC165 {
event AddToBalance(
uint256 indexed projectId, uint256 amount, uint256 returnedFees, string memo, bytes metadata, address caller
);
event HookAfterRecordPay(
IJBPayHook indexed hook, JBAfterPayRecordedContext context, uint256 specificationAmount, address caller
);
event MigrateTerminal(
uint256 indexed projectId, address indexed token, IJBTerminal indexed to, uint256 amount, address caller
);
event Pay(
uint256 indexed rulesetId,
uint256 indexed rulesetCycleNumber,
uint256 indexed projectId,
address payer,
address beneficiary,
uint256 amount,
uint256 newlyIssuedTokenCount,
string memo,
bytes metadata,
address caller
);
event SetAccountingContext(uint256 indexed projectId, JBAccountingContext context, address caller);
function accountingContextForTokenOf(
uint256 projectId,
address token
)
external
view
returns (JBAccountingContext memory);
function accountingContextsOf(uint256 projectId) external view returns (JBAccountingContext[] memory);
function currentSurplusOf(
uint256 projectId,
JBAccountingContext[] memory accountingContexts,
uint256 decimals,
uint256 currency
)
external
view
returns (uint256);
function addAccountingContextsFor(uint256 projectId, JBAccountingContext[] calldata accountingContexts) external;
function addToBalanceOf(
uint256 projectId,
address token,
uint256 amount,
bool shouldReturnHeldFees,
string calldata memo,
bytes calldata metadata
)
external
payable;
function migrateBalanceOf(uint256 projectId, address token, IJBTerminal to) external returns (uint256 balance);
function pay(
uint256 projectId,
address token,
uint256 amount,
address beneficiary,
uint256 minReturnedTokens,
string calldata memo,
bytes calldata metadata
)
external
payable
returns (uint256 beneficiaryTokenCount);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IJBDirectory} from "./IJBDirectory.sol";
import {IJBPrices} from "./IJBPrices.sol";
import {IJBRulesets} from "./IJBRulesets.sol";
import {IJBTerminal} from "./IJBTerminal.sol";
import {JBAccountingContext} from "./../structs/JBAccountingContext.sol";
import {JBCashOutHookSpecification} from "./../structs/JBCashOutHookSpecification.sol";
import {JBPayHookSpecification} from "./../structs/JBPayHookSpecification.sol";
import {JBRuleset} from "./../structs/JBRuleset.sol";
import {JBTokenAmount} from "./../structs/JBTokenAmount.sol";
interface IJBTerminalStore {
function DIRECTORY() external view returns (IJBDirectory);
function PRICES() external view returns (IJBPrices);
function RULESETS() external view returns (IJBRulesets);
function balanceOf(address terminal, uint256 projectId, address token) external view returns (uint256);
function usedPayoutLimitOf(
address terminal,
uint256 projectId,
address token,
uint256 rulesetCycleNumber,
uint256 currency
)
external
view
returns (uint256);
function usedSurplusAllowanceOf(
address terminal,
uint256 projectId,
address token,
uint256 rulesetId,
uint256 currency
)
external
view
returns (uint256);
function currentReclaimableSurplusOf(
uint256 projectId,
uint256 tokenCount,
uint256 totalSupply,
uint256 surplus
)
external
view
returns (uint256);
function currentReclaimableSurplusOf(
uint256 projectId,
uint256 cashOutCount,
IJBTerminal[] calldata terminals,
JBAccountingContext[] calldata accountingContexts,
uint256 decimals,
uint256 currency
)
external
view
returns (uint256);
function currentSurplusOf(
address terminal,
uint256 projectId,
JBAccountingContext[] calldata accountingContexts,
uint256 decimals,
uint256 currency
)
external
view
returns (uint256);
function currentTotalSurplusOf(
uint256 projectId,
uint256 decimals,
uint256 currency
)
external
view
returns (uint256);
function recordAddedBalanceFor(uint256 projectId, address token, uint256 amount) external;
function recordPaymentFrom(
address payer,
JBTokenAmount memory amount,
uint256 projectId,
address beneficiary,
bytes calldata metadata
)
external
returns (JBRuleset memory ruleset, uint256 tokenCount, JBPayHookSpecification[] memory hookSpecifications);
function recordPayoutFor(
uint256 projectId,
JBAccountingContext calldata accountingContext,
uint256 amount,
uint256 currency
)
external
returns (JBRuleset memory ruleset, uint256 amountPaidOut);
function recordCashOutFor(
address holder,
uint256 projectId,
uint256 cashOutCount,
JBAccountingContext calldata accountingContext,
JBAccountingContext[] calldata balanceAccountingContexts,
bytes calldata metadata
)
external
returns (
JBRuleset memory ruleset,
uint256 reclaimAmount,
uint256 cashOutTaxRate,
JBCashOutHookSpecification[] memory hookSpecifications
);
function recordTerminalMigration(uint256 projectId, address token) external returns (uint256 balance);
function recordUsedAllowanceOf(
uint256 projectId,
JBAccountingContext calldata accountingContext,
uint256 amount,
uint256 currency
)
external
returns (JBRuleset memory ruleset, uint256 usedAmount);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IJBToken {
function balanceOf(address account) external view returns (uint256);
function canBeAddedTo(uint256 projectId) external view returns (bool);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint256);
function initialize(string memory name, string memory symbol, address owner) external;
function burn(address account, uint256 amount) external;
function mint(address account, uint256 amount) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IJBTokenUriResolver {
function getUri(uint256 projectId) external view returns (string memory tokenUri);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IJBToken} from "./IJBToken.sol";
interface IJBTokens {
event DeployERC20(
uint256 indexed projectId, IJBToken indexed token, string name, string symbol, bytes32 salt, address caller
);
event Burn(
address indexed holder,
uint256 indexed projectId,
uint256 count,
uint256 creditBalance,
uint256 tokenBalance,
address caller
);
event ClaimTokens(
address indexed holder,
uint256 indexed projectId,
uint256 creditBalance,
uint256 count,
address beneficiary,
address caller
);
event Mint(
address indexed holder, uint256 indexed projectId, uint256 count, bool tokensWereClaimed, address caller
);
event SetToken(uint256 indexed projectId, IJBToken indexed token, address caller);
event TransferCredits(
address indexed holder, uint256 indexed projectId, address indexed recipient, uint256 count, address caller
);
function creditBalanceOf(address holder, uint256 projectId) external view returns (uint256);
function projectIdOf(IJBToken token) external view returns (uint256);
function tokenOf(uint256 projectId) external view returns (IJBToken);
function totalCreditSupplyOf(uint256 projectId) external view returns (uint256);
function totalBalanceOf(address holder, uint256 projectId) external view returns (uint256 result);
function totalSupplyOf(uint256 projectId) external view returns (uint256);
function burnFrom(address holder, uint256 projectId, uint256 count) external;
function claimTokensFor(address holder, uint256 projectId, uint256 count, address beneficiary) external;
function deployERC20For(
uint256 projectId,
string calldata name,
string calldata symbol,
bytes32 salt
)
external
returns (IJBToken token);
function mintFor(address holder, uint256 projectId, uint256 count) external returns (IJBToken token);
function setTokenFor(uint256 projectId, IJBToken token) external;
function transferCreditsFrom(address holder, uint256 projectId, address recipient, uint256 count) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {ISignatureTransfer} from "./ISignatureTransfer.sol";
import {IAllowanceTransfer} from "./IAllowanceTransfer.sol";
/// @notice Permit2 handles signature-based transfers in SignatureTransfer and allowance-based transfers in AllowanceTransfer.
/// @dev Users must approve Permit2 before calling any of the transfer functions.
interface IPermit2 is ISignatureTransfer, IAllowanceTransfer {
// IPermit2 unifies the two interfaces so users have maximal flexibility with their approval.
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IEIP712} from "./IEIP712.sol";
/// @title SignatureTransfer
/// @notice Handles ERC20 token transfers through signature based actions
/// @dev Requires user's token approval on the Permit2 contract
interface ISignatureTransfer is IEIP712 {
/// @notice Thrown when the requested amount for a transfer is larger than the permissioned amount
/// @param maxAmount The maximum amount a spender can request to transfer
error InvalidAmount(uint256 maxAmount);
/// @notice Thrown when the number of tokens permissioned to a spender does not match the number of tokens being transferred
/// @dev If the spender does not need to transfer the number of tokens permitted, the spender can request amount 0 to be transferred
error LengthMismatch();
/// @notice Emits an event when the owner successfully invalidates an unordered nonce.
event UnorderedNonceInvalidation(address indexed owner, uint256 word, uint256 mask);
/// @notice The token and amount details for a transfer signed in the permit transfer signature
struct TokenPermissions {
// ERC20 token address
address token;
// the maximum amount that can be spent
uint256 amount;
}
/// @notice The signed permit message for a single token transfer
struct PermitTransferFrom {
TokenPermissions permitted;
// a unique value for every token owner's signature to prevent signature replays
uint256 nonce;
// deadline on the permit signature
uint256 deadline;
}
/// @notice Specifies the recipient address and amount for batched transfers.
/// @dev Recipients and amounts correspond to the index of the signed token permissions array.
/// @dev Reverts if the requested amount is greater than the permitted signed amount.
struct SignatureTransferDetails {
// recipient address
address to;
// spender requested amount
uint256 requestedAmount;
}
/// @notice Used to reconstruct the signed permit message for multiple token transfers
/// @dev Do not need to pass in spender address as it is required that it is msg.sender
/// @dev Note that a user still signs over a spender address
struct PermitBatchTransferFrom {
// the tokens and corresponding amounts permitted for a transfer
TokenPermissions[] permitted;
// a unique value for every token owner's signature to prevent signature replays
uint256 nonce;
// deadline on the permit signature
uint256 deadline;
}
/// @notice A map from token owner address and a caller specified word index to a bitmap. Used to set bits in the bitmap to prevent against signature replay protection
/// @dev Uses unordered nonces so that permit messages do not need to be spent in a certain order
/// @dev The mapping is indexed first by the token owner, then by an index specified in the nonce
/// @dev It returns a uint256 bitmap
/// @dev The index, or wordPosition is capped at type(uint248).max
function nonceBitmap(address, uint256) external view returns (uint256);
/// @notice Transfers a token using a signed permit message
/// @dev Reverts if the requested amount is greater than the permitted signed amount
/// @param permit The permit data signed over by the owner
/// @param owner The owner of the tokens to transfer
/// @param transferDetails The spender's requested transfer details for the permitted token
/// @param signature The signature to verify
function permitTransferFrom(
PermitTransferFrom memory permit,
SignatureTransferDetails calldata transferDetails,
address owner,
bytes calldata signature
) external;
/// @notice Transfers a token using a signed permit message
/// @notice Includes extra data provided by the caller to verify signature over
/// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition
/// @dev Reverts if the requested amount is greater than the permitted signed amount
/// @param permit The permit data signed over by the owner
/// @param owner The owner of the tokens to transfer
/// @param transferDetails The spender's requested transfer details for the permitted token
/// @param witness Extra data to include when checking the user signature
/// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash
/// @param signature The signature to verify
function permitWitnessTransferFrom(
PermitTransferFrom memory permit,
SignatureTransferDetails calldata transferDetails,
address owner,
bytes32 witness,
string calldata witnessTypeString,
bytes calldata signature
) external;
/// @notice Transfers multiple tokens using a signed permit message
/// @param permit The permit data signed over by the owner
/// @param owner The owner of the tokens to transfer
/// @param transferDetails Specifies the recipient and requested amount for the token transfer
/// @param signature The signature to verify
function permitTransferFrom(
PermitBatchTransferFrom memory permit,
SignatureTransferDetails[] calldata transferDetails,
address owner,
bytes calldata signature
) external;
/// @notice Transfers multiple tokens using a signed permit message
/// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition
/// @notice Includes extra data provided by the caller to verify signature over
/// @param permit The permit data signed over by the owner
/// @param owner The owner of the tokens to transfer
/// @param transferDetails Specifies the recipient and requested amount for the token transfer
/// @param witness Extra data to include when checking the user signature
/// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash
/// @param signature The signature to verify
function permitWitnessTransferFrom(
PermitBatchTransferFrom memory permit,
SignatureTransferDetails[] calldata transferDetails,
address owner,
bytes32 witness,
string calldata witnessTypeString,
bytes calldata signature
) external;
/// @notice Invalidates the bits specified in mask for the bitmap at the word position
/// @dev The wordPos is maxed at type(uint248).max
/// @param wordPos A number to index the nonceBitmap at
/// @param mask A bitmap masked against msg.sender's current bitmap at the word position
function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @custom:member token The address of the token that accounting is being done with.
/// @custom:member decimals The number of decimals expected in that token's fixed point accounting.
/// @custom:member currency The currency that the token is priced in terms of. By convention, this is
/// `uint32(uint160(tokenAddress))` for tokens, or a constant ID from e.g. `JBCurrencyIds` for other currencies.
struct JBAccountingContext {
address token;
uint8 decimals;
uint32 currency;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {JBTokenAmount} from "./JBTokenAmount.sol";
/// @custom:member holder The holder of the tokens being cashed out.
/// @custom:member projectId The ID of the project being cashed out from.
/// @custom:member rulesetId The ID of the ruleset the cash out is being made during.
/// @custom:member cashOutCount The number of project tokens being cashed out.
/// @custom:member cashOutTaxRate The current ruleset's cash out tax rate.
/// @custom:member reclaimedAmount The token amount being reclaimed from the project's terminal balance. Includes the
/// token being
/// reclaimed, the value, the number of decimals included, and the currency of the amount.
/// @custom:member forwardedAmount The token amount being forwarded to the cash out hook. Includes the token
/// being forwarded, the value, the number of decimals included, and the currency of the amount.
/// @custom:member beneficiary The address the reclaimed amount will be sent to.
/// @custom:member hookMetadata Extra data specified by the data hook, which is sent to the cash out hook.
/// @custom:member cashOutMetadata Extra data specified by the account cashing out, which is sent to the cash out hook.
struct JBAfterCashOutRecordedContext {
address holder;
uint256 projectId;
uint256 rulesetId;
uint256 cashOutCount;
JBTokenAmount reclaimedAmount;
JBTokenAmount forwardedAmount;
uint256 cashOutTaxRate;
address payable beneficiary;
bytes hookMetadata;
bytes cashOutMetadata;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {JBTokenAmount} from "./JBTokenAmount.sol";
/// @custom:member payer The address the payment originated from.
/// @custom:member projectId The ID of the project being paid.
/// @custom:member rulesetId The ID of the ruleset the payment is being made during.
/// @custom:member amount The payment's token amount. Includes the token being paid, the value, the number of decimals
/// included, and the currency of the amount.
/// @custom:member forwardedAmount The token amount being forwarded to the pay hook. Includes the token
/// being paid, the value, the number of decimals included, and the currency of the amount.
/// @custom:member weight The current ruleset's weight (used to determine how many tokens should be minted).
/// @custom:member newlyIssuedTokenCount The number of project tokens minted for the beneficiary.
/// @custom:member beneficiary The address which receives any tokens this payment yields.
/// @custom:member hookMetadata Extra data specified by the data hook, which is sent to the pay hook.
/// @custom:member payerMetadata Extra data specified by the payer, which is sent to the pay hook.
struct JBAfterPayRecordedContext {
address payer;
uint256 projectId;
uint256 rulesetId;
JBTokenAmount amount;
JBTokenAmount forwardedAmount;
uint256 weight;
uint256 newlyIssuedTokenCount;
address beneficiary;
bytes hookMetadata;
bytes payerMetadata;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @notice A ruleset's approval status in a ruleset approval hook.
enum JBApprovalStatus {
Empty,
Upcoming,
Active,
ApprovalExpected,
Approved,
Failed
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IJBCashOutHook} from "../interfaces/IJBCashOutHook.sol";
/// @notice A cash out hook specification sent from the ruleset's data hook back to the terminal. This specification is
/// fulfilled by the terminal.
/// @custom:member hook The cash out hook to use when fulfilling this specification.
/// @custom:member amount The amount to send to the hook.
/// @custom:member metadata Metadata to pass to the hook.
struct JBCashOutHookSpecification {
IJBCashOutHook hook;
uint256 amount;
bytes metadata;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @notice Global constants used across Juicebox contracts.
library JBConstants {
/// @notice Each chain's native token address in Juicebox is represented by
/// 0x000000000000000000000000000000000000EEEe.
address public constant NATIVE_TOKEN = address(0x000000000000000000000000000000000000EEEe);
uint16 public constant MAX_RESERVED_PERCENT = 10_000;
uint16 public constant MAX_CASH_OUT_TAX_RATE = 10_000;
uint32 public constant MAX_WEIGHT_CUT_PERCENT = 1_000_000_000;
uint32 public constant SPLITS_TOTAL_PERCENT = 1_000_000_000;
uint16 public constant MAX_FEE = 1000;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @custom:member amount The amount of the currency.
/// @custom:member currency The currency. By convention, this is `uint32(uint160(tokenAddress))` for tokens, or a
/// constant ID from e.g. `JBCurrencyIds` for other currencies.
struct JBCurrencyAmount {
uint224 amount;
uint32 currency;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @custom:member amount The total amount the fee was taken from, as a fixed point number with the same number of
/// decimals as the terminal in which this struct was created.
/// @custom:member beneficiary The address that will receive the tokens that are minted as a result of the fee payment.
/// @custom:member unlockTimestamp The timestamp at which the fee is unlocked and can be processed.
struct JBFee {
uint256 amount;
address beneficiary;
uint48 unlockTimestamp;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import {mulDiv} from "@prb/math/src/Common.sol";
import {JBConstants} from "./../libraries/JBConstants.sol";
/// @notice Fee calculations.
library JBFees {
/// @notice Returns the amount of tokens to pay as a fee relative to the specified `amount`.
/// @param amountAfterFee The amount that the fee is based on, as a fixed point number.
/// @param feePercent The fee percent, out of `JBConstants.MAX_FEE`.
/// @return The amount of tokens to pay as a fee, as a fixed point number with the same number of decimals as the
/// provided `amount`.
function feeAmountResultingIn(uint256 amountAfterFee, uint256 feePercent) internal pure returns (uint256) {
// The amount of tokens from the `amount` to pay as a fee. If reverse, the fee taken from a payout of
// `amount`.
return mulDiv(amountAfterFee, JBConstants.MAX_FEE, JBConstants.MAX_FEE - feePercent) - amountAfterFee;
}
/// @notice Returns the fee that would have been paid based on an `amount` which has already had the fee subtracted
/// from it.
/// @param amountBeforeFee The amount that the fee is based on, as a fixed point number with the same amount of
/// decimals as
/// this terminal.
/// @param feePercent The fee percent, out of `JBConstants.MAX_FEE`.
/// @return The amount of the fee, as a fixed point number with the same amount of decimals as this terminal.
function feeAmountFrom(uint256 amountBeforeFee, uint256 feePercent) internal pure returns (uint256) {
// The amount of tokens from the `amount` to pay as a fee. If reverse, the fee taken from a payout of
// `amount`.
return mulDiv(amountBeforeFee, feePercent, JBConstants.MAX_FEE);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {JBCurrencyAmount} from "./JBCurrencyAmount.sol";
/// @dev Payout limit example: if the `amount` is 5, the `currency` is 1 (USD), and the terminal's token is ETH, then
/// the project can pay out 5 USD worth of ETH during a ruleset.
/// @dev Surplus allowance example: if the `amount` is 5, the `currency` is 1 (USD), and the terminal's token is ETH,
/// then the project can pay out 5 USD worth of ETH from its surplus during a ruleset. A project's surplus is its
/// balance minus its current combined payout limit.
/// @dev If a project has multiple payout limits or surplus allowances, they are all available. They can all be used
/// during a single ruleset.
/// @dev The payout limits' and surplus allowances' fixed point amounts have the same number of decimals as the
/// terminal.
/// @custom:member terminal The terminal that the payout limits and surplus allowances apply to.
/// @custom:member token The token that the payout limits and surplus allowances apply to within the `terminal`.
/// @custom:member payoutLimits An array of payout limits. The payout limits cumulatively dictate the maximum value of
/// `token`s a project can pay out from its balance in a terminal during a ruleset. Each payout limit can have a unique
/// currency and amount.
/// @custom:member surplusAllowances An array of surplus allowances. The surplus allowances cumulatively dictates the
/// maximum value of `token`s a project can pay out from its surplus (balance less payouts) in a terminal during a
/// ruleset. Each surplus allowance can have a unique currency and amount.
struct JBFundAccessLimitGroup {
address terminal;
address token;
JBCurrencyAmount[] payoutLimits;
JBCurrencyAmount[] surplusAllowances;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
/**
* @notice Library to parse and create metadata to store {id: data} entries.
*
* @dev Metadata are built as:
* - 32B of reserved space for the protocol
* - a lookup table `Id: offset`, defining the offset of the data for a given 4 bytes id.
* The offset fits 1 bytes, the ID 4 bytes. This table is padded to 32B.
* - the data for each id, padded to 32B each
*
* +-----------------------+ offset: 0
* | 32B reserved |
* +-----------------------+ offset: 1 = end of first 32B
* | (ID1,offset1) |
* | (ID2,offset2) |
* | 0's padding |
* +-----------------------+ offset: offset1 = 1 + number of words taken by the padded table
* | id1 data1 |
* | 0's padding |
* +-----------------------+ offset: offset2 = offset1 + number of words taken by the data1
* | id2 data2 |
* | 0's padding |
* +-----------------------+
*/
library JBMetadataResolver {
error JBMetadataResolver_DataNotPadded();
error JBMetadataResolver_LengthMismatch();
error JBMetadataResolver_MetadataTooLong();
error JBMetadataResolver_MetadataTooShort();
// The various sizes used in bytes.
uint256 constant ID_SIZE = 4;
uint256 constant ID_OFFSET_SIZE = 1;
uint256 constant WORD_SIZE = 32;
// The size that an ID takes in the lookup table (Identifier + Offset).
uint256 constant TOTAL_ID_SIZE = 5; // ID_SIZE + ID_OFFSET_SIZE;
// The amount of bytes to go forward to get to the offset of the next ID (aka. the end of the offset of the current
// ID).
uint256 constant NEXT_ID_OFFSET = 9; // TOTAL_ID_SIZE + ID_SIZE;
// 1 word (32B) is reserved for the protocol .
uint256 constant RESERVED_SIZE = 32; // 1 * WORD_SIZE;
uint256 constant MIN_METADATA_LENGTH = 37; // RESERVED_SIZE + ID_SIZE + ID_OFFSET_SIZE;
/// @notice Add an {id: data} entry to an existing metadata. This is an append-only mechanism.
/// @param originalMetadata The original metadata
/// @param idToAdd The id to add
/// @param dataToAdd The data to add
/// @return newMetadata The new metadata with the entry added
function addToMetadata(
bytes memory originalMetadata,
bytes4 idToAdd,
bytes memory dataToAdd
)
internal
pure
returns (bytes memory newMetadata)
{
// Empty original metadata and maybe something in the first 32 bytes: create new metadata
if (originalMetadata.length <= RESERVED_SIZE) {
return abi.encodePacked(bytes32(originalMetadata), bytes32(abi.encodePacked(idToAdd, uint8(2))), dataToAdd);
}
// There is something in the table offset, but not a valid entry - avoid overwriting
if (originalMetadata.length < RESERVED_SIZE + ID_SIZE + 1) revert JBMetadataResolver_MetadataTooShort();
// Make sure the data is padded to 32 bytes.
if (dataToAdd.length < 32) revert JBMetadataResolver_DataNotPadded();
// Get the first data offset - upcast to avoid overflow (same for other offset)...
uint256 firstOffset = uint8(originalMetadata[RESERVED_SIZE + ID_SIZE]);
// ...go back to the beginning of the previous word (ie the last word of the table, as it can be padded)
uint256 lastWordOfTable = firstOffset - 1;
// The last offset stored in the table and its index
uint256 lastOffset;
// The number of words taken by the last data stored
uint256 numberOfWordslastData;
// Iterate to find the last entry of the table, lastOffset - we start from the end as the first value
// encountered
// will be the last offset
for (uint256 i = firstOffset * WORD_SIZE - 1; i > lastWordOfTable * WORD_SIZE - 1; i--) {
// If the byte is not 0, this is the last offset we're looking for
if (originalMetadata[i] != 0) {
lastOffset = uint8(originalMetadata[i]);
uint256 lastOffsetIndex = i;
// No rounding as this should be padded to 32B
numberOfWordslastData = (originalMetadata.length - lastOffset * WORD_SIZE) / WORD_SIZE;
// Copy the reserved word and the table and remove the previous padding
newMetadata = _sliceBytes(originalMetadata, 0, lastOffsetIndex + 1);
// Check if the new entry is still fitting in this word
if (i + TOTAL_ID_SIZE >= firstOffset * WORD_SIZE) {
// Increment every offset by 1 (as the table now takes one more word)
for (uint256 j = RESERVED_SIZE + ID_SIZE; j < lastOffsetIndex + 1; j += TOTAL_ID_SIZE) {
newMetadata[j] = bytes1(uint8(originalMetadata[j]) + 1);
}
// Increment the last offset so the new offset will be properly set too
lastOffset++;
}
break;
}
}
// Add the new entry after the last entry of the table, the new offset is the last offset + the number of words
// taken by the last data
newMetadata = abi.encodePacked(newMetadata, idToAdd, bytes1(uint8(lastOffset + numberOfWordslastData)));
// Pad as needed - inlined for gas saving
uint256 paddedLength =
newMetadata.length % WORD_SIZE == 0 ? newMetadata.length : (newMetadata.length / WORD_SIZE + 1) * WORD_SIZE;
assembly {
mstore(newMetadata, paddedLength)
}
// Add existing data at the end
newMetadata = abi.encodePacked(
newMetadata, _sliceBytes(originalMetadata, firstOffset * WORD_SIZE, originalMetadata.length)
);
// Pad as needed
paddedLength =
newMetadata.length % WORD_SIZE == 0 ? newMetadata.length : (newMetadata.length / WORD_SIZE + 1) * WORD_SIZE;
assembly {
mstore(newMetadata, paddedLength)
}
// Append new data at the end
newMetadata = abi.encodePacked(newMetadata, dataToAdd);
// Pad again again as needed
paddedLength =
newMetadata.length % WORD_SIZE == 0 ? newMetadata.length : (newMetadata.length / WORD_SIZE + 1) * WORD_SIZE;
assembly {
mstore(newMetadata, paddedLength)
}
}
/// @notice Create the metadata for a list of {id:data}
/// @dev Intended for offchain use (gas heavy)
/// @param ids The list of ids
/// @param datas The list of corresponding datas
/// @return metadata The resulting metadata
function createMetadata(bytes4[] memory ids, bytes[] memory datas) internal pure returns (bytes memory metadata) {
if (ids.length != datas.length) revert JBMetadataResolver_LengthMismatch();
// Add a first empty 32B for the protocol reserved word
metadata = abi.encodePacked(bytes32(0));
// First offset for the data is after the first reserved word...
uint256 offset = 1;
// ... and after the id/offset lookup table, rounding up to 32 bytes words if not a multiple
offset += ((ids.length * JBMetadataResolver.TOTAL_ID_SIZE) - 1) / JBMetadataResolver.WORD_SIZE + 1;
// Keep a reference to the number of ids.
uint256 numberOfIds = ids.length;
// For each id, add it to the lookup table with the next free offset, then increment the offset by the data
// length (rounded up)
for (uint256 i; i < numberOfIds; ++i) {
// Set the data being iterated on.
bytes memory data = datas[i];
if (data.length < 32 || data.length % JBMetadataResolver.WORD_SIZE != 0) {
revert JBMetadataResolver_DataNotPadded();
}
metadata = abi.encodePacked(metadata, ids[i], bytes1(uint8(offset)));
offset += data.length / JBMetadataResolver.WORD_SIZE;
// Overflowing a bytes1?
if (offset > 255) revert JBMetadataResolver_MetadataTooLong();
}
// Pad the table to a multiple of 32B
uint256 paddedLength = metadata.length % JBMetadataResolver.WORD_SIZE == 0
? metadata.length
: (metadata.length / JBMetadataResolver.WORD_SIZE + 1) * JBMetadataResolver.WORD_SIZE;
assembly {
mstore(metadata, paddedLength)
}
// Keep a reference to the number of datas.
uint256 numberOfDatas = datas.length;
// Add each metadata to the array, each padded to 32 bytes
for (uint256 i; i < numberOfDatas; i++) {
metadata = abi.encodePacked(metadata, datas[i]);
paddedLength = metadata.length % JBMetadataResolver.WORD_SIZE == 0
? metadata.length
: (metadata.length / JBMetadataResolver.WORD_SIZE + 1) * JBMetadataResolver.WORD_SIZE;
assembly {
mstore(metadata, paddedLength)
}
}
}
/// @notice Parse the metadata to find the data for a specific ID
/// @dev Returns false and an empty bytes if no data is found
/// @param id The ID to find.
/// @param metadata The metadata to parse.
/// @return found Whether the {id:data} was found
/// @return targetData The data for the ID (can be empty)
function getDataFor(bytes4 id, bytes memory metadata) internal pure returns (bool found, bytes memory targetData) {
// Either no data or empty one with only one selector (32+4+1)
if (metadata.length <= MIN_METADATA_LENGTH) return (false, "");
// Get the first data offset - upcast to avoid overflow (same for other offset)
uint256 firstOffset = uint8(metadata[RESERVED_SIZE + ID_SIZE]);
// Parse the id's to find id, stop when next offset == 0 or current = first offset
for (uint256 i = RESERVED_SIZE; metadata[i + ID_SIZE] != bytes1(0) && i < firstOffset * WORD_SIZE;) {
// Set the current offset.
uint256 currentOffset = uint256(uint8(metadata[i + ID_SIZE]));
bytes4 parsedId;
assembly {
parsedId := mload(add(add(metadata, 0x20), i))
}
// _id found?
if (parsedId == id) {
// Are we at the end of the lookup table (either at the start of data's or next offset is 0/in the
// padding)
// If not, only return until from this offset to the begining of the next offset
uint256 end = (i + NEXT_ID_OFFSET >= firstOffset * WORD_SIZE || metadata[i + NEXT_ID_OFFSET] == 0)
? metadata.length
: uint256(uint8(metadata[i + NEXT_ID_OFFSET])) * WORD_SIZE;
return (true, _sliceBytes(metadata, currentOffset * WORD_SIZE, end));
}
unchecked {
i += TOTAL_ID_SIZE;
}
}
}
/// @notice Returns an unique id following a suggested format (`xor(address(this), purpose name)` where purpose name
/// is a string giving context to the id (Permit2, quoteForSwap, etc)
/// @param purpose A string describing the purpose associated with the id
/// @return id The resulting ID.
function getId(string memory purpose) internal view returns (bytes4) {
return getId(purpose, address(this));
}
/// @notice Returns an unique id following a suggested format (`xor(address(this), purpose name)` where purpose name
/// is a string giving context to the id (Permit2, quoteForSwap, etc)
/// @param purpose A string describing the purpose associated with the id
/// @param target The target which will use the metadata
/// @return id The resulting ID.
function getId(string memory purpose, address target) internal pure returns (bytes4) {
return bytes4(bytes20(target) ^ bytes20(keccak256(bytes(purpose))));
}
/// @notice Slice bytes from a start index to an end index.
/// @param data The bytes array to slice
/// @param start The start index to slice at.
/// @param end The end index to slice at.
/// @param slicedBytes The sliced array.
function _sliceBytes(
bytes memory data,
uint256 start,
uint256 end
)
private
pure
returns (bytes memory slicedBytes)
{
assembly {
let length := sub(end, start)
// Allocate memory at the freemem(add 0x20 to include the length)
slicedBytes := mload(0x40)
mstore(0x40, add(add(slicedBytes, length), 0x20))
// Store the length (first element)
mstore(slicedBytes, length)
// compute the actual data first offset only once
let startBytes := add(add(data, 0x20), start)
// same for the out array
let sliceBytesStartOfData := add(slicedBytes, 0x20)
// store dem data
for { let i := 0 } lt(i, end) { i := add(i, 0x20) } {
mstore(add(sliceBytesStartOfData, i), mload(add(startBytes, i)))
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import {JBPermissionIds} from "@bananapus/permission-ids/src/JBPermissionIds.sol";
import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {Context} from "@openzeppelin/contracts/utils/Context.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {mulDiv} from "@prb/math/src/Common.sol";
import {IAllowanceTransfer} from "@uniswap/permit2/src/interfaces/IAllowanceTransfer.sol";
import {IPermit2} from "@uniswap/permit2/src/interfaces/IPermit2.sol";
import {JBPermissioned} from "./abstract/JBPermissioned.sol";
import {IJBCashOutTerminal} from "./interfaces/IJBCashOutTerminal.sol";
import {IJBController} from "./interfaces/IJBController.sol";
import {IJBDirectory} from "./interfaces/IJBDirectory.sol";
import {IJBFeelessAddresses} from "./interfaces/IJBFeelessAddresses.sol";
import {IJBFeeTerminal} from "./interfaces/IJBFeeTerminal.sol";
import {IJBMultiTerminal} from "./interfaces/IJBMultiTerminal.sol";
import {IJBPayoutTerminal} from "./interfaces/IJBPayoutTerminal.sol";
import {IJBPermissioned} from "./interfaces/IJBPermissioned.sol";
import {IJBPermissions} from "./interfaces/IJBPermissions.sol";
import {IJBPermitTerminal} from "./interfaces/IJBPermitTerminal.sol";
import {IJBProjects} from "./interfaces/IJBProjects.sol";
import {IJBRulesets} from "./interfaces/IJBRulesets.sol";
import {IJBSplitHook} from "./interfaces/IJBSplitHook.sol";
import {IJBSplits} from "./interfaces/IJBSplits.sol";
import {IJBTerminal} from "./interfaces/IJBTerminal.sol";
import {IJBTerminalStore} from "./interfaces/IJBTerminalStore.sol";
import {IJBTokens} from "./interfaces/IJBTokens.sol";
import {JBConstants} from "./libraries/JBConstants.sol";
import {JBFees} from "./libraries/JBFees.sol";
import {JBMetadataResolver} from "./libraries/JBMetadataResolver.sol";
import {JBRulesetMetadataResolver} from "./libraries/JBRulesetMetadataResolver.sol";
import {JBAccountingContext} from "./structs/JBAccountingContext.sol";
import {JBAfterPayRecordedContext} from "./structs/JBAfterPayRecordedContext.sol";
import {JBAfterCashOutRecordedContext} from "./structs/JBAfterCashOutRecordedContext.sol";
import {JBCashOutHookSpecification} from "./structs/JBCashOutHookSpecification.sol";
import {JBFee} from "./structs/JBFee.sol";
import {JBPayHookSpecification} from "./structs/JBPayHookSpecification.sol";
import {JBRuleset} from "./structs/JBRuleset.sol";
import {JBSingleAllowance} from "./structs/JBSingleAllowance.sol";
import {JBSplit} from "./structs/JBSplit.sol";
import {JBSplitHookContext} from "./structs/JBSplitHookContext.sol";
import {JBTokenAmount} from "./structs/JBTokenAmount.sol";
/// @notice `JBMultiTerminal` manages native/ERC-20 payments, cash outs, and surplus allowance usage for any number of
/// projects. Terminals are the entry point for operations involving inflows and outflows of funds.
contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
// A library that parses the packed ruleset metadata into a friendlier format.
using JBRulesetMetadataResolver for JBRuleset;
// A library that adds default safety checks to ERC20 functionality.
using SafeERC20 for IERC20;
//*********************************************************************//
// --------------------------- custom errors ------------------------- //
//*********************************************************************//
error JBMultiTerminal_AccountingContextAlreadySet(address token);
error JBMultiTerminal_AddingAccountingContextNotAllowed();
error JBMultiTerminal_FeeTerminalNotFound();
error JBMultiTerminal_NoMsgValueAllowed(uint256 value);
error JBMultiTerminal_OverflowAlert(uint256 value, uint256 limit);
error JBMultiTerminal_PermitAllowanceNotEnough(uint256 amount, uint256 allowance);
error JBMultiTerminal_RecipientProjectTerminalNotFound(uint256 projectId, address token);
error JBMultiTerminal_SplitHookInvalid(IJBSplitHook hook);
error JBMultiTerminal_TerminalTokensIncompatible();
error JBMultiTerminal_TokenNotAccepted(address token);
error JBMultiTerminal_UnderMinReturnedTokens(uint256 count, uint256 min);
error JBMultiTerminal_UnderMinTokensPaidOut(uint256 amount, uint256 min);
error JBMultiTerminal_UnderMinTokensReclaimed(uint256 amount, uint256 min);
error JBMultiTerminal_ZeroAccountingContextDecimals();
error JBMultiTerminal_ZeroAccountingContextCurrency();
//*********************************************************************//
// ------------------------- public constants ------------------------ //
//*********************************************************************//
/// @notice This terminal's fee (as a fraction out of `JBConstants.MAX_FEE`).
/// @dev Fees are charged on payouts to addresses and surplus allowance usage, as well as cash outs while the
/// cash out tax rate is less than 100%.
uint256 public constant override FEE = 25; // 2.5%
//*********************************************************************//
// ------------------------ internal constants ----------------------- //
//*********************************************************************//
/// @notice Project ID #1 receives fees. It should be the first project launched during the deployment process.
uint256 internal constant _FEE_BENEFICIARY_PROJECT_ID = 1;
/// @notice The number of seconds fees can be held for.
uint256 internal constant _FEE_HOLDING_SECONDS = 2_419_200; // 28 days
//*********************************************************************//
// ---------------- public immutable stored properties --------------- //
//*********************************************************************//
/// @notice The directory of terminals and controllers for PROJECTS.
IJBDirectory public immutable override DIRECTORY;
/// @notice The contract that stores addresses that shouldn't incur fees when being paid towards or from.
IJBFeelessAddresses public immutable override FEELESS_ADDRESSES;
/// @notice The permit2 utility.
IPermit2 public immutable override PERMIT2;
/// @notice Mints ERC-721s that represent project ownership and transfers.
IJBProjects public immutable override PROJECTS;
/// @notice The contract storing and managing project rulesets.
IJBRulesets public immutable override RULESETS;
/// @notice The contract that stores splits for each project.
IJBSplits public immutable override SPLITS;
/// @notice The contract that stores and manages the terminal's data.
IJBTerminalStore public immutable override STORE;
/// @notice The contract storing and managing project rulesets.
IJBTokens public immutable override TOKENS;
//*********************************************************************//
// --------------------- internal stored properties ------------------ //
//*********************************************************************//
/// @notice Context describing how a token is accounted for by a project.
/// @custom:param projectId The ID of the project that the token accounting context applies to.
/// @custom:param token The address of the token being accounted for.
mapping(uint256 projectId => mapping(address token => JBAccountingContext)) internal _accountingContextForTokenOf;
/// @notice A list of tokens accepted by each project.
/// @custom:param projectId The ID of the project to get a list of accepted tokens for.
mapping(uint256 projectId => JBAccountingContext[]) internal _accountingContextsOf;
/// @notice Fees that are being held for each project.
/// @dev Projects can temporarily hold fees and unlock them later by adding funds to the project's balance.
/// @dev Held fees can be processed at any time by this terminal's owner.
/// @custom:param projectId The ID of the project that is holding fees.
/// @custom:param token The token that the fees are held in.
mapping(uint256 projectId => mapping(address token => JBFee[])) internal _heldFeesOf;
/// @notice The next index to use when processing a next held fee.
/// @custom:param projectId The ID of the project that is holding fees.
/// @custom:param token The token that the fees are held in.
mapping(uint256 projectId => mapping(address token => uint256)) internal _nextHeldFeeIndexOf;
//*********************************************************************//
// -------------------------- constructor ---------------------------- //
//*********************************************************************//
/// @param feelessAddresses A contract that stores addresses that shouldn't incur fees when being paid towards or
/// from.
/// @param permissions A contract storing permissions.
/// @param projects A contract which mints ERC-721s that represent project ownership and transfers.
/// @param splits A contract that stores splits for each project.
/// @param store A contract that stores the terminal's data.
/// @param permit2 A permit2 utility.
/// @param trustedForwarder A trusted forwarder of transactions to this contract.
constructor(
IJBFeelessAddresses feelessAddresses,
IJBPermissions permissions,
IJBProjects projects,
IJBSplits splits,
IJBTerminalStore store,
IJBTokens tokens,
IPermit2 permit2,
address trustedForwarder
)
JBPermissioned(permissions)
ERC2771Context(trustedForwarder)
{
DIRECTORY = store.DIRECTORY();
FEELESS_ADDRESSES = feelessAddresses;
PROJECTS = projects;
RULESETS = store.RULESETS();
SPLITS = splits;
STORE = store;
TOKENS = tokens;
PERMIT2 = permit2;
}
//*********************************************************************//
// ------------------------- external views -------------------------- //
//*********************************************************************//
/// @notice A project's accounting context for a token.
/// @dev See the `JBAccountingContext` struct for more information.
/// @param projectId The ID of the project to get token accounting context of.
/// @param token The token to check the accounting context of.
/// @return The token's accounting context for the token.
function accountingContextForTokenOf(
uint256 projectId,
address token
)
external
view
override
returns (JBAccountingContext memory)
{
return _accountingContextForTokenOf[projectId][token];
}
/// @notice The tokens accepted by a project.
/// @param projectId The ID of the project to get the accepted tokens of.
/// @return tokenContexts The accounting contexts of the accepted tokens.
function accountingContextsOf(uint256 projectId) external view override returns (JBAccountingContext[] memory) {
return _accountingContextsOf[projectId];
}
/// @notice Gets the total current surplus amount in this terminal for a project, in terms of a given currency.
/// @dev This total surplus only includes tokens that the project accepts (as returned by
/// `accountingContextsOf(...)`).
/// @param projectId The ID of the project to get the current total surplus of.
/// @param accountingContexts The accounting contexts to use to calculate the surplus. Pass an empty array to use
/// all of the project's accounting contexts.
/// @param decimals The number of decimals to include in the fixed point returned value.
/// @param currency The currency to express the returned value in terms of.
/// @return The current surplus amount the project has in this terminal, in terms of `currency` and with the
/// specified number of decimals.
function currentSurplusOf(
uint256 projectId,
JBAccountingContext[] memory accountingContexts,
uint256 decimals,
uint256 currency
)
external
view
override
returns (uint256)
{
return STORE.currentSurplusOf({
terminal: address(this),
projectId: projectId,
accountingContexts: accountingContexts.length != 0 ? accountingContexts : _accountingContextsOf[projectId],
decimals: decimals,
currency: currency
});
}
/// @notice Fees that are being held for a project.
/// @dev Projects can temporarily hold fees and unlock them later by adding funds to the project's balance.
/// @dev Held fees can be processed at any time by this terminal's owner.
/// @param projectId The ID of the project that is holding fees.
/// @param token The token that the fees are held in.
function heldFeesOf(
uint256 projectId,
address token,
uint256 count
)
external
view
override
returns (JBFee[] memory heldFees)
{
// Keep a reference to the start index.
uint256 startIndex = _nextHeldFeeIndexOf[projectId][token];
// Get a reference to the number of held fees.
uint256 numberOfHeldFees = _heldFeesOf[projectId][token].length;
// If the start index is greater than or equal to the number of held fees, return 0.
if (startIndex >= numberOfHeldFees) return new JBFee[](0);
// If the start index plus the count is greater than the number of fees, set the count to the number of fees
if (startIndex + count > numberOfHeldFees) count = numberOfHeldFees - startIndex;
// Create a new array to hold the fees.
heldFees = new JBFee[](count);
// Copy the fees into the array.
for (uint256 i; i < count; i++) {
heldFees[i] = _heldFeesOf[projectId][token][startIndex + i];
}
}
//*********************************************************************//
// -------------------------- public views --------------------------- //
//*********************************************************************//
/// @notice Indicates whether this contract adheres to the specified interface.
/// @dev See {IERC165-supportsInterface}.
/// @param interfaceId The ID of the interface to check for adherence to.
/// @return A flag indicating if the provided interface ID is supported.
function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
return interfaceId == type(IJBMultiTerminal).interfaceId || interfaceId == type(IJBPermissioned).interfaceId
|| interfaceId == type(IJBTerminal).interfaceId || interfaceId == type(IJBCashOutTerminal).interfaceId
|| interfaceId == type(IJBPayoutTerminal).interfaceId || interfaceId == type(IJBPermitTerminal).interfaceId
|| interfaceId == type(IJBMultiTerminal).interfaceId || interfaceId == type(IJBFeeTerminal).interfaceId
|| interfaceId == type(IERC165).interfaceId;
}
//*********************************************************************//
// -------------------------- internal views ------------------------- //
//*********************************************************************//
/// @notice Checks this terminal's balance of a specific token.
/// @param token The address of the token to get this terminal's balance of.
/// @return This terminal's balance.
function _balanceOf(address token) internal view returns (uint256) {
// If the `token` is native, get the native token balance.
return token == JBConstants.NATIVE_TOKEN ? address(this).balance : IERC20(token).balanceOf(address(this));
}
/// @dev `ERC-2771` specifies the context as being a single address (20 bytes).
function _contextSuffixLength() internal view override(ERC2771Context, Context) returns (uint256) {
return super._contextSuffixLength();
}
/// @notice Returns the current controller of a project.
/// @param projectId The ID of the project to get the controller of.
/// @return controller The project's controller.
function _controllerOf(uint256 projectId) internal view returns (IJBController) {
return IJBController(address(DIRECTORY.controllerOf(projectId)));
}
/// @notice Returns a flag indicating if interacting with an address should not incur fees.
/// @param addr The address to check.
/// @return A flag indicating if the address should not incur fees.
function _isFeeless(address addr) internal view returns (bool) {
return FEELESS_ADDRESSES.isFeeless(addr);
}
/// @notice The calldata. Preferred to use over `msg.data`.
/// @return calldata The `msg.data` of this call.
function _msgData() internal view override(ERC2771Context, Context) returns (bytes calldata) {
return ERC2771Context._msgData();
}
/// @notice The message's sender. Preferred to use over `msg.sender`.
/// @return sender The address which sent this call.
function _msgSender() internal view override(ERC2771Context, Context) returns (address sender) {
return ERC2771Context._msgSender();
}
/// @notice The owner of a project.
/// @param projectId The ID of the project to get the owner of.
/// @return The owner of the project.
function _ownerOf(uint256 projectId) internal view returns (address) {
return PROJECTS.ownerOf(projectId);
}
/// @notice The primary terminal of a project for a token.
/// @param projectId The ID of the project to get the primary terminal of.
/// @param token The token to get the primary terminal of.
/// @return The primary terminal of the project for the token.
function _primaryTerminalOf(uint256 projectId, address token) internal view returns (IJBTerminal) {
return DIRECTORY.primaryTerminalOf({projectId: projectId, token: token});
}
//*********************************************************************//
// ---------------------- external transactions ---------------------- //
//*********************************************************************//
/// @notice Adds accounting contexts for a project to this terminal so the project can begin accepting the tokens in
/// those contexts.
/// @dev Only a project's owner, an operator with the `ADD_ACCOUNTING_CONTEXTS` permission from that owner, or a
/// project's controller can add accounting contexts for the project.
/// @param projectId The ID of the project having to add accounting contexts for.
/// @param accountingContexts The accounting contexts to add.
function addAccountingContextsFor(
uint256 projectId,
JBAccountingContext[] calldata accountingContexts
)
external
override
{
// Enforce permissions.
_requirePermissionAllowingOverrideFrom({
account: _ownerOf(projectId),
projectId: projectId,
permissionId: JBPermissionIds.ADD_ACCOUNTING_CONTEXTS,
alsoGrantAccessIf: _msgSender() == address(_controllerOf(projectId))
});
// Get a reference to the project's current ruleset.
JBRuleset memory ruleset = RULESETS.currentOf(projectId);
// Make sure that if there's a ruleset, it allows adding accounting contexts.
if (ruleset.id != 0 && !ruleset.allowAddAccountingContext()) {
revert JBMultiTerminal_AddingAccountingContextNotAllowed();
}
// Start accepting each token.
for (uint256 i; i < accountingContexts.length; i++) {
// Set the accounting context being iterated on.
JBAccountingContext memory accountingContext = accountingContexts[i];
// Get a storage reference to the currency accounting context for the token.
JBAccountingContext storage storedAccountingContext =
_accountingContextForTokenOf[projectId][accountingContext.token];
// Make sure the token accounting context isn't already set.
if (storedAccountingContext.token != address(0)) {
revert JBMultiTerminal_AccountingContextAlreadySet(storedAccountingContext.token);
}
// Keep track of a flag indiciating if we know the provided decimals are incorrect.
bool knownInvalidDecimals;
// Check if the token is the native token and has the correct decimals
if (accountingContext.token == JBConstants.NATIVE_TOKEN && accountingContext.decimals != 18) {
knownInvalidDecimals = true;
} else if (accountingContext.token != JBConstants.NATIVE_TOKEN) {
// slither-disable-next-line calls-loop
try IERC20Metadata(accountingContext.token).decimals() returns (uint8 decimals) {
// slither-disable-next-line calls-loop
if (accountingContext.decimals != decimals) {
knownInvalidDecimals = true;
}
} catch {
// The token didn't support `decimals`.
knownInvalidDecimals = false;
}
}
// Make sure the decimals are correct.
if (knownInvalidDecimals) {
revert JBMultiTerminal_ZeroAccountingContextDecimals();
}
// Make sure the currency is non-zero.
if (accountingContext.currency == 0) revert JBMultiTerminal_ZeroAccountingContextCurrency();
// Define the context from the config.
storedAccountingContext.token = accountingContext.token;
storedAccountingContext.decimals = accountingContext.decimals;
storedAccountingContext.currency = accountingContext.currency;
// Add the token to the list of accepted tokens of the project.
_accountingContextsOf[projectId].push(storedAccountingContext);
emit SetAccountingContext({projectId: projectId, context: storedAccountingContext, caller: _msgSender()});
}
}
/// @notice Adds funds to a project's balance without minting tokens.
/// @dev Adding to balance can unlock held fees if `shouldUnlockHeldFees` is true.
/// @param projectId The ID of the project to add funds to the balance of.
/// @param amount The amount of tokens to add to the balance, as a fixed point number with the same number of
/// decimals as this terminal. If this is a native token terminal, this is ignored and `msg.value` is used instead.
/// @param token The token being added to the balance.
/// @param shouldReturnHeldFees A flag indicating if held fees should be returned based on the amount being added.
/// @param memo A memo to pass along to the emitted event.
/// @param metadata Extra data to pass along to the emitted event.
function addToBalanceOf(
uint256 projectId,
address token,
uint256 amount,
bool shouldReturnHeldFees,
string calldata memo,
bytes calldata metadata
)
external
payable
override
{
// Add to balance.
_addToBalanceOf({
projectId: projectId,
token: token,
amount: _acceptFundsFor(projectId, token, amount, metadata),
shouldReturnHeldFees: shouldReturnHeldFees,
memo: memo,
metadata: metadata
});
}
/// @notice Holders can cash out a project's tokens to reclaim some of that project's surplus tokens, or to trigger
/// rules determined by the current ruleset's data hook and cash out hook.
/// @dev Only a token's holder or an operator with the `CASH_OUT_TOKENS` permission from that holder can cash out
/// those tokens.
/// @param holder The account whose tokens are being cashed out.
/// @param projectId The ID of the project the project tokens belong to.
/// @param cashOutCount The number of project tokens to cash out, as a fixed point number with 18 decimals.
/// @param tokenToReclaim The token being reclaimed.
/// @param minTokensReclaimed The minimum number of terminal tokens expected in return, as a fixed point number with
/// the same number of decimals as this terminal. If the amount of tokens minted for the beneficiary would be less
/// than this amount, the cash out is reverted.
/// @param beneficiary The address to send the cashed out terminal tokens to, and to pass along to the ruleset's
/// data hook and cash out hook if applicable.
/// @param metadata Bytes to send along to the emitted event, as well as the data hook and cash out hook if
/// applicable.
/// @return reclaimAmount The amount of terminal tokens that the project tokens were cashed out for, as a fixed
/// point
/// number with 18 decimals.
function cashOutTokensOf(
address holder,
uint256 projectId,
uint256 cashOutCount,
address tokenToReclaim,
uint256 minTokensReclaimed,
address payable beneficiary,
bytes calldata metadata
)
external
override
returns (uint256 reclaimAmount)
{
// Enforce permissions.
_requirePermissionFrom({account: holder, projectId: projectId, permissionId: JBPermissionIds.CASH_OUT_TOKENS});
reclaimAmount = _cashOutTokensOf({
holder: holder,
projectId: projectId,
cashOutCount: cashOutCount,
tokenToReclaim: tokenToReclaim,
beneficiary: beneficiary,
metadata: metadata
});
// The amount being reclaimed must be at least as much as was expected.
if (reclaimAmount < minTokensReclaimed) {
revert JBMultiTerminal_UnderMinTokensReclaimed(reclaimAmount, minTokensReclaimed);
}
}
/// @notice Executes a payout to a split.
/// @dev Only accepts calls from this terminal itself.
/// @param split The split to pay.
/// @param projectId The ID of the project the split belongs to.
/// @param token The address of the token being paid to the split.
/// @param amount The total amount being paid to the split, as a fixed point number with the same number of
/// decimals as this terminal.
/// @return netPayoutAmount The amount sent to the split after subtracting fees.
function executePayout(
JBSplit calldata split,
uint256 projectId,
address token,
uint256 amount,
address originalMessageSender
)
external
returns (uint256 netPayoutAmount)
{
// NOTICE: May only be called by this terminal itself.
require(msg.sender == address(this));
// By default, the net payout amount is the full amount. This will be adjusted if fees are taken.
netPayoutAmount = amount;
// If there's a split hook set, transfer to its `process` function.
if (split.hook != IJBSplitHook(address(0))) {
// Make sure that the address supports the split hook interface.
if (!split.hook.supportsInterface(type(IJBSplitHook).interfaceId)) {
revert JBMultiTerminal_SplitHookInvalid(split.hook);
}
// This payout is eligible for a fee since the funds are leaving this contract and the split hook isn't a
// feeless address.
if (!_isFeeless(address(split.hook))) {
netPayoutAmount -= JBFees.feeAmountFrom({amountBeforeFee: amount, feePercent: FEE});
}
// Create the context to send to the split hook.
JBSplitHookContext memory context = JBSplitHookContext({
token: token,
amount: netPayoutAmount,
decimals: _accountingContextForTokenOf[projectId][token].decimals,
projectId: projectId,
groupId: uint256(uint160(token)),
split: split
});
// Trigger any inherited pre-transfer logic.
// Get a reference to the amount being paid in `msg.value`.
uint256 payValue = _beforeTransferTo({to: address(split.hook), token: token, amount: netPayoutAmount});
// If this terminal's token is the native token, send it in `msg.value`.
split.hook.processSplitWith{value: payValue}(context);
// Otherwise, if a project is specified, make a payment to it.
} else if (split.projectId != 0) {
// Get a reference to the terminal being used.
IJBTerminal terminal = _primaryTerminalOf({projectId: split.projectId, token: token});
// The project must have a terminal to send funds to.
if (terminal == IJBTerminal(address(0))) {
revert JBMultiTerminal_RecipientProjectTerminalNotFound(split.projectId, token);
}
// This payout is eligible for a fee if the funds are leaving this contract and the receiving terminal isn't
// a feelss address.
if (terminal != this && !_isFeeless(address(terminal))) {
netPayoutAmount -= JBFees.feeAmountFrom({amountBeforeFee: amount, feePercent: FEE});
}
// Send the `projectId` in the metadata as a referral.
bytes memory metadata = bytes(abi.encodePacked(projectId));
// Add to balance if preferred.
if (split.preferAddToBalance) {
_efficientAddToBalance({
terminal: terminal,
projectId: split.projectId,
token: token,
amount: netPayoutAmount,
metadata: metadata
});
} else {
// Keep a reference to the beneficiary of the payment.
address beneficiary = split.beneficiary != address(0) ? split.beneficiary : originalMessageSender;
_efficientPay({
terminal: terminal,
projectId: split.projectId,
token: token,
amount: netPayoutAmount,
beneficiary: beneficiary,
metadata: metadata
});
}
} else {
// If there's a beneficiary, send the funds directly to the beneficiary.
// If there isn't a beneficiary, send the funds to the `_msgSender()`.
address payable recipient =
split.beneficiary != address(0) ? split.beneficiary : payable(originalMessageSender);
// This payout is eligible for a fee since the funds are leaving this contract and the recipient isn't a
// feeless address.
if (!_isFeeless(recipient)) {
netPayoutAmount -= JBFees.feeAmountFrom({amountBeforeFee: amount, feePercent: FEE});
}
// If there's a beneficiary, send the funds directly to the beneficiary. Otherwise send to the
// `_msgSender()`.
_transferFrom({from: address(this), to: recipient, token: token, amount: netPayoutAmount});
}
}
/// @notice Process a specified amount of fees for a project.
/// @dev Only accepts calls from this terminal itself.
/// @param projectId The ID of the project paying the fee.
/// @param token The token the fee is being paid in.
/// @param amount The fee amount, as a fixed point number with 18 decimals.
/// @param beneficiary The address to mint tokens to (from the project which receives fees), and pass along to the
/// ruleset's data hook and pay hook if applicable.
/// @param feeTerminal The terminal that'll receive the fees.
function executeProcessFee(
uint256 projectId,
address token,
uint256 amount,
address beneficiary,
IJBTerminal feeTerminal
)
external
{
// NOTICE: May only be called by this terminal itself.
require(msg.sender == address(this));
if (address(feeTerminal) == address(0)) {
revert JBMultiTerminal_FeeTerminalNotFound();
}
// Send the projectId in the metadata.
bytes memory metadata = bytes(abi.encodePacked(projectId));
_efficientPay({
terminal: feeTerminal,
projectId: _FEE_BENEFICIARY_PROJECT_ID,
token: token,
amount: amount,
beneficiary: beneficiary,
metadata: metadata
});
}
/// @notice Transfer funds to an address.
/// @dev Only accepts calls from this terminal itself.
/// @param addr The address to transfer funds to.
/// @param token The token to transfer.
/// @param amount The amount of tokens to transfer.
function executeTransferTo(address payable addr, address token, uint256 amount) external {
// NOTICE: May only be called by this terminal itself.
require(msg.sender == address(this));
_transferFrom({from: address(this), to: addr, token: token, amount: amount});
}
/// @notice Migrate a project's funds and operations to a new terminal that accepts the same token type.
/// @dev Only a project's owner or an operator with the `MIGRATE_TERMINAL` permission from that owner can migrate
/// the project's terminal.
/// @param projectId The ID of the project being migrated.
/// @param token The address of the token being migrated.
/// @param to The terminal contract being migrated to, which will receive the project's funds and operations.
/// @return balance The amount of funds that were migrated, as a fixed point number with the same amount of decimals
/// as this terminal.
function migrateBalanceOf(
uint256 projectId,
address token,
IJBTerminal to
)
external
override
returns (uint256 balance)
{
// Enforce permissions.
_requirePermissionFrom({
account: _ownerOf(projectId),
projectId: projectId,
permissionId: JBPermissionIds.MIGRATE_TERMINAL
});
// The terminal being migrated to must accept the same token as this terminal.
if (to.accountingContextForTokenOf({projectId: projectId, token: token}).currency == 0) {
revert JBMultiTerminal_TerminalTokensIncompatible();
}
// Record the migration in the store.
// slither-disable-next-line reentrancy-events
balance = STORE.recordTerminalMigration({projectId: projectId, token: token});
emit MigrateTerminal({projectId: projectId, token: token, to: to, amount: balance, caller: _msgSender()});
// Transfer the balance if needed.
if (balance != 0) {
// Trigger any inherited pre-transfer logic.
// If this terminal's token is the native token, send it in `msg.value`.
// slither-disable-next-line reentrancy-events
uint256 payValue = _beforeTransferTo({to: address(to), token: token, amount: balance});
// Withdraw the balance to transfer to the new terminal;
// slither-disable-next-line reentrancy-events
to.addToBalanceOf{value: payValue}({
projectId: projectId,
token: token,
amount: balance,
shouldReturnHeldFees: false,
memo: "",
metadata: bytes("")
});
}
}
/// @notice Pay a project with tokens.
/// @param projectId The ID of the project being paid.
/// @param amount The amount of terminal tokens being received, as a fixed point number with the same number of
/// decimals as this terminal. If this terminal's token is native, this is ignored and `msg.value` is used in its
/// place.
/// @param token The token being paid.
/// @param beneficiary The address to mint tokens to, and pass along to the ruleset's data hook and pay hook if
/// applicable.
/// @param minReturnedTokens The minimum number of project tokens expected in return for this payment, as a fixed
/// point number with the same number of decimals as this terminal. If the amount of tokens minted for the
/// beneficiary would be less than this amount, the payment is reverted.
/// @param memo A memo to pass along to the emitted event.
/// @param metadata Bytes to pass along to the emitted event, as well as the data hook and pay hook if applicable.
/// @return beneficiaryTokenCount The number of tokens minted to the beneficiary, as a fixed point number with 18
/// decimals.
function pay(
uint256 projectId,
address token,
uint256 amount,
address beneficiary,
uint256 minReturnedTokens,
string calldata memo,
bytes calldata metadata
)
external
payable
override
returns (uint256 beneficiaryTokenCount)
{
// Get a reference to the beneficiary's balance before the payment.
uint256 beneficiaryBalanceBefore = TOKENS.totalBalanceOf({holder: beneficiary, projectId: projectId});
// Pay the project.
_pay({
projectId: projectId,
token: token,
amount: _acceptFundsFor(projectId, token, amount, metadata),
payer: _msgSender(),
beneficiary: beneficiary,
memo: memo,
metadata: metadata
});
// Get a reference to the beneficiary's balance after the payment.
uint256 beneficiaryBalanceAfter = TOKENS.totalBalanceOf({holder: beneficiary, projectId: projectId});
// Set the beneficiary token count.
if (beneficiaryBalanceAfter > beneficiaryBalanceBefore) {
beneficiaryTokenCount = beneficiaryBalanceAfter - beneficiaryBalanceBefore;
}
// The token count for the beneficiary must be greater than or equal to the specified minimum.
if (beneficiaryTokenCount < minReturnedTokens) {
revert JBMultiTerminal_UnderMinReturnedTokens(beneficiaryTokenCount, minReturnedTokens);
}
}
/// @notice Process any fees that are being held for the project.
/// @param projectId The ID of the project to process held fees for.
/// @param token The token to process held fees for.
/// @param count The number of fees to process.
function processHeldFeesOf(uint256 projectId, address token, uint256 count) external override {
// Keep a reference to the start index.
uint256 startIndex = _nextHeldFeeIndexOf[projectId][token];
// Get a reference to the project's held fees.
uint256 numberOfHeldFees = _heldFeesOf[projectId][token].length;
// If the start index is greater than or equal to the number of held fees, return.
if (startIndex >= numberOfHeldFees) return;
// Keep a reference to the terminal that'll receive the fees.
IJBTerminal feeTerminal = _primaryTerminalOf({projectId: _FEE_BENEFICIARY_PROJECT_ID, token: token});
// Calculate the number of iterations to perform.
if (startIndex + count > numberOfHeldFees) count = numberOfHeldFees - startIndex;
// Process each fee.
for (uint256 i; i < count; i++) {
// Keep a reference to the held fee being iterated on.
JBFee memory heldFee = _heldFeesOf[projectId][token][startIndex + i];
// Can't process fees that aren't yet unlocked. Fees unlock sequentially in the array, so nothing left to do
// if the current fee isn't yet unlocked.
if (heldFee.unlockTimestamp > block.timestamp) {
// Restart at this index next time.
if (i > 0) _nextHeldFeeIndexOf[projectId][token] = startIndex + i;
return;
}
// Process the fee.
// slither-disable-next-line reentrancy-no-eth
_processFee({
projectId: projectId,
token: token,
amount: JBFees.feeAmountFrom({amountBeforeFee: heldFee.amount, feePercent: FEE}),
beneficiary: heldFee.beneficiary,
feeTerminal: feeTerminal,
wasHeld: true
});
}
// Restart at the next fee next time.
_nextHeldFeeIndexOf[projectId][token] = startIndex + count;
}
/// @notice Sends payouts to a project's current payout split group, according to its ruleset, up to its current
/// payout limit.
/// @dev If the percentages of the splits in the project's payout split group do not add up to 100%, the remainder
/// is sent to the project's owner.
/// @dev Anyone can send payouts on a project's behalf. Projects can include a wildcard split (a split with no
/// `hook`, `projectId`, or `beneficiary`) to send funds to the `_msgSender()` which calls this function. This can
/// be used to incentivize calling this function.
/// @dev payouts sent to addresses which aren't feeless incur the protocol fee.
/// @dev Payouts a projects don't incur fees if its terminal is feeless.
/// @param projectId The ID of the project having its payouts sent.
/// @param token The token being sent.
/// @param amount The total number of terminal tokens to send, as a fixed point number with same number of decimals
/// as this terminal.
/// @param currency The expected currency of the payouts being sent. Must match the currency of one of the
/// project's current ruleset's payout limits.
/// @param minTokensPaidOut The minimum number of terminal tokens that the `amount` should be worth (if expressed
/// in terms of this terminal's currency), as a fixed point number with the same number of decimals as this
/// terminal. If the amount of tokens paid out would be less than this amount, the send is reverted.
/// @return amountPaidOut The total amount paid out.
function sendPayoutsOf(
uint256 projectId,
address token,
uint256 amount,
uint256 currency,
uint256 minTokensPaidOut
)
external
override
returns (uint256 amountPaidOut)
{
amountPaidOut = _sendPayoutsOf({projectId: projectId, token: token, amount: amount, currency: currency});
// The amount being paid out must be at least as much as was expected.
if (amountPaidOut < minTokensPaidOut) {
revert JBMultiTerminal_UnderMinTokensPaidOut(amountPaidOut, minTokensPaidOut);
}
}
/// @notice Allows a project to pay out funds from its surplus up to the current surplus allowance.
/// @dev Only a project's owner or an operator with the `USE_ALLOWANCE` permission from that owner can use the
/// surplus allowance.
/// @dev Incurs the protocol fee unless the caller is a feeless address.
/// @param projectId The ID of the project to use the surplus allowance of.
/// @param token The token being paid out from the surplus.
/// @param amount The amount of terminal tokens to use from the project's current surplus allowance, as a fixed
/// point number with the same amount of decimals as this terminal.
/// @param currency The expected currency of the amount being paid out. Must match the currency of one of the
/// project's current ruleset's surplus allowances.
/// @param minTokensPaidOut The minimum number of terminal tokens that should be returned from the surplus allowance
/// (excluding fees), as a fixed point number with 18 decimals. If the amount of surplus used would be less than
/// this amount, the transaction is reverted.
/// @param beneficiary The address to send the surplus funds to.
/// @param feeBeneficiary The address to send the tokens resulting from paying the fee.
/// @param memo A memo to pass along to the emitted event.
/// @return netAmountPaidOut The number of tokens that were sent to the beneficiary, as a fixed point number with
/// the same amount of decimals as the terminal.
function useAllowanceOf(
uint256 projectId,
address token,
uint256 amount,
uint256 currency,
uint256 minTokensPaidOut,
address payable beneficiary,
address payable feeBeneficiary,
string calldata memo
)
external
override
returns (uint256 netAmountPaidOut)
{
// Keep a reference to the project's owner.
address owner = _ownerOf(projectId);
// Enforce permissions.
_requirePermissionFrom({account: owner, projectId: projectId, permissionId: JBPermissionIds.USE_ALLOWANCE});
netAmountPaidOut = _useAllowanceOf({
projectId: projectId,
owner: owner,
token: token,
amount: amount,
currency: currency,
beneficiary: beneficiary,
feeBeneficiary: feeBeneficiary,
memo: memo
});
// The amount being withdrawn must be at least as much as was expected.
if (netAmountPaidOut < minTokensPaidOut) {
revert JBMultiTerminal_UnderMinTokensPaidOut(netAmountPaidOut, minTokensPaidOut);
}
}
//*********************************************************************//
// ------------------------ internal functions ----------------------- //
//*********************************************************************//
/// @notice Accepts an incoming token.
/// @param projectId The ID of the project that the transfer is being accepted for.
/// @param token The token being accepted.
/// @param amount The number of tokens being accepted.
/// @param metadata The metadata in which permit2 context is provided.
/// @return amount The number of tokens which have been accepted.
function _acceptFundsFor(
uint256 projectId,
address token,
uint256 amount,
bytes calldata metadata
)
internal
returns (uint256)
{
// Make sure the project has an accounting context for the token being paid.
if (_accountingContextForTokenOf[projectId][token].token == address(0)) {
revert JBMultiTerminal_TokenNotAccepted(token);
}
// If the terminal's token is the native token, override `amount` with `msg.value`.
if (token == JBConstants.NATIVE_TOKEN) return msg.value;
// If the terminal's token is not native, revert if there is a non-zero `msg.value`.
if (msg.value != 0) revert JBMultiTerminal_NoMsgValueAllowed(msg.value);
// Unpack the allowance to use, if any, given by the frontend.
(bool exists, bytes memory parsedMetadata) =
JBMetadataResolver.getDataFor({id: JBMetadataResolver.getId("permit2"), metadata: metadata});
// Check if the metadata contains permit data.
if (exists) {
// Keep a reference to the allowance context parsed from the metadata.
(JBSingleAllowance memory allowance) = abi.decode(parsedMetadata, (JBSingleAllowance));
// Make sure the permit allowance is enough for this payment. If not we revert early.
if (amount > allowance.amount) {
revert JBMultiTerminal_PermitAllowanceNotEnough(amount, allowance.amount);
}
// Keep a reference to the permit rules.
IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer.PermitSingle({
details: IAllowanceTransfer.PermitDetails({
token: token,
amount: allowance.amount,
expiration: allowance.expiration,
nonce: allowance.nonce
}),
spender: address(this),
sigDeadline: allowance.sigDeadline
});
// Set the allowance to `spend` tokens for the user.
try PERMIT2.permit({owner: _msgSender(), permitSingle: permitSingle, signature: allowance.signature}) {}
catch (bytes memory) {}
}
// Get a reference to the balance before receiving tokens.
uint256 balanceBefore = _balanceOf(token);
// Transfer tokens to this terminal from the msg sender.
_transferFrom({from: _msgSender(), to: payable(address(this)), token: token, amount: amount});
// The amount should reflect the change in balance.
return _balanceOf(token) - balanceBefore;
}
/// @notice Adds funds to a project's balance without minting tokens.
/// @param projectId The ID of the project to add funds to the balance of.
/// @param token The address of the token being added to the project's balance.
/// @param amount The amount of tokens to add as a fixed point number with the same number of decimals as this
/// terminal. If this is a native token terminal, this is ignored and `msg.value` is used instead.
/// @param shouldReturnHeldFees A flag indicating if held fees should be returned based on the amount being added.
/// @param memo A memo to pass along to the emitted event.
/// @param metadata Extra data to pass along to the emitted event.
function _addToBalanceOf(
uint256 projectId,
address token,
uint256 amount,
bool shouldReturnHeldFees,
string memory memo,
bytes memory metadata
)
internal
{
// Return held fees if desired. This mechanism means projects don't pay fees multiple times when funds go out of
// and back into the protocol.
uint256 returnedFees =
shouldReturnHeldFees ? _returnHeldFees({projectId: projectId, token: token, amount: amount}) : 0;
emit AddToBalance({
projectId: projectId,
amount: amount,
returnedFees: returnedFees,
memo: memo,
metadata: metadata,
caller: _msgSender()
});
// Record the added funds with any returned fees.
_recordAddedBalanceFor({projectId: projectId, token: token, amount: amount + returnedFees});
}
/// @notice Logic to be triggered before transferring tokens from this terminal.
/// @param to The address the transfer is going to.
/// @param token The token being transferred.
/// @param amount The number of tokens being transferred, as a fixed point number with the same number of decimals
/// as this terminal.
/// @return payValue The value to attach to the transaction being sent.
function _beforeTransferTo(address to, address token, uint256 amount) internal returns (uint256) {
// If the token is the native token, no allowance needed, and the full amount should be used as the payValue.
if (token == JBConstants.NATIVE_TOKEN) return amount;
// Otherwise, set the allowance, and the payValue should be 0.
IERC20(token).safeIncreaseAllowance({spender: to, value: amount});
return 0;
}
/// @notice Holders can cash out their tokens to reclaim some of a project's surplus, or to trigger rules determined
/// by
/// the project's current ruleset's data hook.
/// @dev Only a token holder or an operator with the `CASH_OUT_TOKENS` permission from that holder can cash out
/// those
/// tokens.
/// @param holder The account cashing out tokens.
/// @param projectId The ID of the project whose tokens are being cashed out.
/// @param cashOutCount The number of project tokens to cash out, as a fixed point number with 18 decimals.
/// @param tokenToReclaim The address of the token which is being cashed out.
/// @param beneficiary The address to send the reclaimed terminal tokens to.
/// @param metadata Bytes to send along to the emitted event, as well as the data hook and cash out hook if
/// applicable.
/// @return reclaimAmount The number of terminal tokens reclaimed for the `beneficiary`, as a fixed point number
/// with 18 decimals.
function _cashOutTokensOf(
address holder,
uint256 projectId,
uint256 cashOutCount,
address tokenToReclaim,
address payable beneficiary,
bytes memory metadata
)
internal
returns (uint256 reclaimAmount)
{
// Keep a reference to the ruleset the cash out is being made during.
JBRuleset memory ruleset;
// Keep a reference to the cash out hook specifications.
JBCashOutHookSpecification[] memory hookSpecifications;
// Keep a reference to the cash out tax rate being used.
uint256 cashOutTaxRate;
// Keep a reference to the accounting context of the token being reclaimed.
JBAccountingContext memory accountingContext = _accountingContextForTokenOf[projectId][tokenToReclaim];
// Scoped section prevents stack too deep.
{
JBAccountingContext[] memory balanceAccountingContexts = _accountingContextsOf[projectId];
// Record the cash out.
(ruleset, reclaimAmount, cashOutTaxRate, hookSpecifications) = STORE.recordCashOutFor({
holder: holder,
projectId: projectId,
accountingContext: accountingContext,
balanceAccountingContexts: balanceAccountingContexts,
cashOutCount: cashOutCount,
metadata: metadata
});
}
// Burn the project tokens.
if (cashOutCount != 0) {
_controllerOf(projectId).burnTokensOf({
holder: holder,
projectId: projectId,
tokenCount: cashOutCount,
memo: ""
});
}
// Keep a reference to the amount being reclaimed that is subject to fees.
uint256 amountEligibleForFees;
// Send the reclaimed funds to the beneficiary.
if (reclaimAmount != 0) {
// Determine if a fee should be taken. Fees are not taked if the cash out tax rate is zero,
// if the beneficiary is feeless, or if the fee beneficiary doesn't accept the given token.
if (!_isFeeless(beneficiary) && cashOutTaxRate != 0) {
amountEligibleForFees += reclaimAmount;
// Subtract the fee for the reclaimed amount.
reclaimAmount -= JBFees.feeAmountFrom({amountBeforeFee: reclaimAmount, feePercent: FEE});
}
// Subtract the fee from the reclaim amount.
if (reclaimAmount != 0) {
_transferFrom({from: address(this), to: beneficiary, token: tokenToReclaim, amount: reclaimAmount});
}
}
// If the data hook returned cash out hook specifications, fulfill them.
if (hookSpecifications.length != 0) {
// Fulfill the cash out hook specifications.
amountEligibleForFees += _fulfillCashOutHookSpecificationsFor({
projectId: projectId,
holder: holder,
cashOutCount: cashOutCount,
ruleset: ruleset,
cashOutTaxRate: cashOutTaxRate,
beneficiary: beneficiary,
beneficiaryReclaimAmount: JBTokenAmount({
token: tokenToReclaim,
decimals: accountingContext.decimals,
currency: accountingContext.currency,
value: reclaimAmount
}),
specifications: hookSpecifications,
metadata: metadata
});
}
// Take the fee from all outbound reclaimings.
amountEligibleForFees != 0
? _takeFeeFrom({
projectId: projectId,
token: tokenToReclaim,
amount: amountEligibleForFees,
beneficiary: beneficiary,
shouldHoldFees: false
})
: 0;
emit CashOutTokens({
rulesetId: ruleset.id,
rulesetCycleNumber: ruleset.cycleNumber,
projectId: projectId,
holder: holder,
beneficiary: beneficiary,
cashOutCount: cashOutCount,
cashOutTaxRate: cashOutTaxRate,
reclaimAmount: reclaimAmount,
metadata: metadata,
caller: _msgSender()
});
}
/// @notice Fund a project either by calling this terminal's internal `addToBalance` function or by calling the
/// recipient
/// terminal's `addToBalance` function.
/// @param terminal The terminal on which the project is expecting to receive funds.
/// @param projectId The ID of the project being funded.
/// @param token The token being used.
/// @param amount The amount being funded, as a fixed point number with the amount of decimals that the terminal's
/// accounting context specifies.
/// @param metadata Additional metadata to include with the payment.
function _efficientAddToBalance(
IJBTerminal terminal,
uint256 projectId,
address token,
uint256 amount,
bytes memory metadata
)
internal
{
// Call the internal method if this terminal is being used.
if (terminal == IJBTerminal(address(this))) {
_addToBalanceOf({
projectId: projectId,
token: token,
amount: amount,
shouldReturnHeldFees: false,
memo: "",
metadata: metadata
});
} else {
// Trigger any inherited pre-transfer logic.
// Keep a reference to the amount that'll be paid as a `msg.value`.
// slither-disable-next-line reentrancy-events
uint256 payValue = _beforeTransferTo({to: address(terminal), token: token, amount: amount});
// Add to balance.
// If this terminal's token is the native token, send it in `msg.value`.
terminal.addToBalanceOf{value: payValue}({
projectId: projectId,
token: token,
amount: amount,
shouldReturnHeldFees: false,
memo: "",
metadata: metadata
});
}
}
/// @notice Pay a project either by calling this terminal's internal `pay` function or by calling the recipient
/// terminal's `pay` function.
/// @param terminal The terminal on which the project is expecting to receive payments.
/// @param projectId The ID of the project being paid.
/// @param token The token being paid in.
/// @param amount The amount being paid, as a fixed point number with the amount of decimals that the terminal's
/// accounting context specifies.
/// @param beneficiary The address to receive any platform tokens minted.
/// @param metadata Additional metadata to include with the payment.
function _efficientPay(
IJBTerminal terminal,
uint256 projectId,
address token,
uint256 amount,
address beneficiary,
bytes memory metadata
)
internal
{
if (terminal == IJBTerminal(address(this))) {
_pay({
projectId: projectId,
token: token,
amount: amount,
payer: address(this),
beneficiary: beneficiary,
memo: "",
metadata: metadata
});
} else {
// Trigger any inherited pre-transfer logic.
// Keep a reference to the amount that'll be paid as a `msg.value`.
// slither-disable-next-line reentrancy-events
uint256 payValue = _beforeTransferTo({to: address(terminal), token: token, amount: amount});
// Send the fee.
// If this terminal's token is ETH, send it in msg.value.
// slither-disable-next-line unused-return
terminal.pay{value: payValue}({
projectId: projectId,
token: token,
amount: amount,
beneficiary: beneficiary,
minReturnedTokens: 0,
memo: "",
metadata: metadata
});
}
}
/// @notice Fulfills a list of pay hook specifications.
/// @param projectId The ID of the project being paid.
/// @param specifications The pay hook specifications to be fulfilled.
/// @param tokenAmount The amount of tokens that the project was paid.
/// @param payer The address that sent the payment.
/// @param ruleset The ruleset the payment is being accepted during.
/// @param beneficiary The address which will receive any tokens that the payment yields.
/// @param newlyIssuedTokenCount The amount of tokens that are being issued and sent to the beneificary.
/// @param metadata Bytes to send along to the emitted event and pay hooks as applicable.
function _fulfillPayHookSpecificationsFor(
uint256 projectId,
JBPayHookSpecification[] memory specifications,
JBTokenAmount memory tokenAmount,
address payer,
JBRuleset memory ruleset,
address beneficiary,
uint256 newlyIssuedTokenCount,
bytes memory metadata
)
internal
{
// Keep a reference to payment context for the pay hooks.
JBAfterPayRecordedContext memory context = JBAfterPayRecordedContext({
payer: payer,
projectId: projectId,
rulesetId: ruleset.id,
amount: tokenAmount,
forwardedAmount: tokenAmount,
weight: ruleset.weight,
newlyIssuedTokenCount: newlyIssuedTokenCount,
beneficiary: beneficiary,
hookMetadata: bytes(""),
payerMetadata: metadata
});
// Fulfill each specification through their pay hooks.
for (uint256 i; i < specifications.length; i++) {
// Set the specification being iterated on.
JBPayHookSpecification memory specification = specifications[i];
// Pass the correct token `forwardedAmount` to the hook.
context.forwardedAmount = JBTokenAmount({
value: specification.amount,
token: tokenAmount.token,
decimals: tokenAmount.decimals,
currency: tokenAmount.currency
});
// Pass the correct metadata from the data hook's specification.
context.hookMetadata = specification.metadata;
// Trigger any inherited pre-transfer logic.
// Keep a reference to the amount that'll be paid as a `msg.value`.
// slither-disable-next-line reentrancy-events
uint256 payValue = _beforeTransferTo({
to: address(specification.hook),
token: tokenAmount.token,
amount: specification.amount
});
// Fulfill the specification.
// slither-disable-next-line reentrancy-events
specification.hook.afterPayRecordedWith{value: payValue}(context);
emit HookAfterRecordPay({
hook: specification.hook,
context: context,
specificationAmount: specification.amount,
caller: _msgSender()
});
}
}
/// @notice Fulfills a list of cash out hook specifications.
/// @param projectId The ID of the project being cashed out from.
/// @param beneficiaryReclaimAmount The number of tokens that are being cashed out from the project.
/// @param holder The address that holds the tokens being cashed out.
/// @param cashOutCount The number of tokens being cashed out.
/// @param metadata Bytes to send along to the emitted event and cash out hooks as applicable.
/// @param ruleset The ruleset the cash out is being made during as a `JBRuleset` struct.
/// @param cashOutTaxRate The cash out tax rate influencing the reclaim amount.
/// @param beneficiary The address which will receive any terminal tokens that are cashed out.
/// @param specifications The hook specifications being fulfilled.
/// @return amountEligibleForFees The amount of funds which were allocated to cash out hooks and are eligible for
/// fees.
function _fulfillCashOutHookSpecificationsFor(
uint256 projectId,
JBTokenAmount memory beneficiaryReclaimAmount,
address holder,
uint256 cashOutCount,
bytes memory metadata,
JBRuleset memory ruleset,
uint256 cashOutTaxRate,
address payable beneficiary,
JBCashOutHookSpecification[] memory specifications
)
internal
returns (uint256 amountEligibleForFees)
{
// Keep a reference to cash out context for the cash out hooks.
JBAfterCashOutRecordedContext memory context = JBAfterCashOutRecordedContext({
holder: holder,
projectId: projectId,
rulesetId: ruleset.id,
cashOutCount: cashOutCount,
reclaimedAmount: beneficiaryReclaimAmount,
forwardedAmount: beneficiaryReclaimAmount,
cashOutTaxRate: cashOutTaxRate,
beneficiary: beneficiary,
hookMetadata: "",
cashOutMetadata: metadata
});
for (uint256 i; i < specifications.length; i++) {
// Set the specification being iterated on.
JBCashOutHookSpecification memory specification = specifications[i];
// Get the fee for the specified amount.
uint256 specificationAmountFee = _isFeeless(address(specification.hook))
? 0
: JBFees.feeAmountFrom({amountBeforeFee: specification.amount, feePercent: FEE});
// Add the specification's amount to the amount eligible for fees.
if (specificationAmountFee != 0) {
amountEligibleForFees += specification.amount;
specification.amount -= specificationAmountFee;
}
// Pass the correct token `forwardedAmount` to the hook.
context.forwardedAmount = JBTokenAmount({
value: specification.amount,
token: beneficiaryReclaimAmount.token,
decimals: beneficiaryReclaimAmount.decimals,
currency: beneficiaryReclaimAmount.currency
});
// Pass the correct metadata from the data hook's specification.
context.hookMetadata = specification.metadata;
// Trigger any inherited pre-transfer logic.
// Keep a reference to the amount that'll be paid as a `msg.value`.
// slither-disable-next-line reentrancy-events
uint256 payValue = _beforeTransferTo({
to: address(specification.hook),
token: beneficiaryReclaimAmount.token,
amount: specification.amount
});
// Fulfill the specification.
// slither-disable-next-line reentrancy-events
specification.hook.afterCashOutRecordedWith{value: payValue}(context);
emit HookAfterRecordCashOut({
hook: specification.hook,
context: context,
specificationAmount: specification.amount,
fee: specificationAmountFee,
caller: _msgSender()
});
}
}
/// @notice Pay a project with tokens.
/// @param projectId The ID of the project being paid.
/// @param token The address of the token which the project is being paid with.
/// @param amount The amount of terminal tokens being received, as a fixed point number with the same number of
/// decimals as this terminal. If this terminal's token is the native token, `amount` is ignored and `msg.value` is
/// used in its place.
/// @param payer The address making the payment.
/// @param beneficiary The address to mint tokens to, and pass along to the ruleset's data hook and pay hook if
/// applicable.
/// @param memo A memo to pass along to the emitted event.
/// @param metadata Bytes to send along to the emitted event, as well as the data hook and pay hook if applicable.
function _pay(
uint256 projectId,
address token,
uint256 amount,
address payer,
address beneficiary,
string memory memo,
bytes memory metadata
)
internal
{
// Keep a reference to the token amount to forward to the store.
JBTokenAmount memory tokenAmount;
// Scoped section prevents stack too deep. `context` only used within scope.
{
// Get a reference to the token's accounting context.
JBAccountingContext memory context = _accountingContextForTokenOf[projectId][token];
// Bundle the amount info into a `JBTokenAmount` struct.
tokenAmount =
JBTokenAmount({token: token, decimals: context.decimals, currency: context.currency, value: amount});
}
// Record the payment.
// Keep a reference to the ruleset the payment is being made during.
// Keep a reference to the pay hook specifications.
// Keep a reference to the token count that'll be minted as a result of the payment.
// slither-disable-next-line reentrancy-events
(JBRuleset memory ruleset, uint256 tokenCount, JBPayHookSpecification[] memory hookSpecifications) = STORE
.recordPaymentFrom({
payer: payer,
amount: tokenAmount,
projectId: projectId,
beneficiary: beneficiary,
metadata: metadata
});
// Keep a reference to the number of tokens issued for the beneficiary.
uint256 newlyIssuedTokenCount;
// Mint tokens if needed.
if (tokenCount != 0) {
// Set the token count to be the number of tokens minted for the beneficiary instead of the total
// amount.
// slither-disable-next-line reentrancy-events
newlyIssuedTokenCount = _controllerOf(projectId).mintTokensOf({
projectId: projectId,
tokenCount: tokenCount,
beneficiary: beneficiary,
memo: "",
useReservedPercent: true
});
}
emit Pay({
rulesetId: ruleset.id,
rulesetCycleNumber: ruleset.cycleNumber,
projectId: projectId,
payer: payer,
beneficiary: beneficiary,
amount: amount,
newlyIssuedTokenCount: newlyIssuedTokenCount,
memo: memo,
metadata: metadata,
caller: _msgSender()
});
// If the data hook returned pay hook specifications, fulfill them.
if (hookSpecifications.length != 0) {
_fulfillPayHookSpecificationsFor({
projectId: projectId,
specifications: hookSpecifications,
tokenAmount: tokenAmount,
payer: payer,
ruleset: ruleset,
beneficiary: beneficiary,
newlyIssuedTokenCount: newlyIssuedTokenCount,
metadata: metadata
});
}
}
/// @notice Process a fee of the specified amount from a project.
/// @param projectId The ID of the project paying the fee.
/// @param token The token the fee is being paid in.
/// @param amount The fee amount, as a fixed point number with 18 decimals.
/// @param beneficiary The address which will receive any platform tokens minted.
/// @param feeTerminal The terminal that'll receive the fee.
/// @param wasHeld A flag indicating if the fee being processed was being held by this terminal.
function _processFee(
uint256 projectId,
address token,
uint256 amount,
address beneficiary,
IJBTerminal feeTerminal,
bool wasHeld
)
internal
{
// slither-disable-next-line reentrancy-events,calls-loop
try this.executeProcessFee({
projectId: projectId,
token: token,
amount: amount,
beneficiary: beneficiary,
feeTerminal: feeTerminal
}) {
emit ProcessFee({
projectId: projectId,
token: token,
amount: amount,
wasHeld: wasHeld,
beneficiary: beneficiary,
caller: _msgSender()
});
} catch (bytes memory reason) {
emit FeeReverted({
projectId: projectId,
token: token,
feeProjectId: _FEE_BENEFICIARY_PROJECT_ID,
amount: amount,
reason: reason,
caller: _msgSender()
});
_recordAddedBalanceFor({projectId: projectId, token: token, amount: amount});
}
}
/// @notice Records an added balance for a project.
/// @param projectId The ID of the project to record the added balance for.
/// @param token The token to record the added balance for.
/// @param amount The amount of the token to record, as a fixed point number with the same number of decimals as
/// this
/// terminal.
function _recordAddedBalanceFor(uint256 projectId, address token, uint256 amount) internal {
// slither-disable-next-line calls-loop
STORE.recordAddedBalanceFor({projectId: projectId, token: token, amount: amount});
}
/// @notice Returns held fees to the project who paid them based on the specified amount.
/// @param projectId The project held fees are being returned to.
/// @param token The token that the held fees are in.
/// @param amount The amount to base the calculation on, as a fixed point number with the same number of decimals
/// as this terminal.
/// @return returnedFees The amount of held fees that were returned, as a fixed point number with the same number of
/// decimals as this terminal
function _returnHeldFees(
uint256 projectId,
address token,
uint256 amount
)
internal
returns (uint256 returnedFees)
{
// Keep a reference to the start index.
uint256 startIndex = _nextHeldFeeIndexOf[projectId][token];
// Get a reference to the project's held fees.
uint256 numberOfHeldFees = _heldFeesOf[projectId][token].length;
// If the start index is greater than or equal to the number of held fees, return 0.
if (startIndex >= numberOfHeldFees) return 0;
// Get a reference to the leftover amount once all fees have been settled.
uint256 leftoverAmount = amount;
// Keep a reference to the number of iterations to perform.
uint256 count = numberOfHeldFees - startIndex;
// Keep a reference to the new start index.
uint256 newStartIndex = startIndex;
// Process each fee.
for (uint256 i; i < count; i++) {
// Save the fee being iterated on.
JBFee memory heldFee = _heldFeesOf[projectId][token][startIndex + i];
// slither-disable-next-line incorrect-equality
if (leftoverAmount == 0) {
break;
} else {
// Notice here we take `feeAmountFrom` on the stored `.amount`.
uint256 feeAmount = JBFees.feeAmountFrom({amountBeforeFee: heldFee.amount, feePercent: FEE});
// Keep a reference to the amount from which the fee was taken.
uint256 amountPaidOut = heldFee.amount - feeAmount;
if (leftoverAmount >= amountPaidOut) {
unchecked {
leftoverAmount -= amountPaidOut;
returnedFees += feeAmount;
}
// Move the start index forward to the held fee after the current one.
newStartIndex = startIndex + i + 1;
} else {
// And here we overwrite with `feeAmountResultingIn` the `leftoverAmount`
feeAmount = JBFees.feeAmountResultingIn({amountAfterFee: leftoverAmount, feePercent: FEE});
// Get fee from `leftoverAmount`.
unchecked {
_heldFeesOf[projectId][token][startIndex + i].amount -= (leftoverAmount + feeAmount);
returnedFees += feeAmount;
}
leftoverAmount = 0;
}
}
}
// Update the next held fee index.
if (startIndex != newStartIndex) _nextHeldFeeIndexOf[projectId][token] = newStartIndex;
emit ReturnHeldFees({
projectId: projectId,
token: token,
amount: amount,
returnedFees: returnedFees,
leftoverAmount: leftoverAmount,
caller: _msgSender()
});
}
/// @notice Sends payouts to a project's current payout split group, according to its ruleset, up to its current
/// payout limit.
/// @dev If the percentages of the splits in the project's payout split group do not add up to 100%, the remainder
/// is sent to the project's owner.
/// @dev Anyone can send payouts on a project's behalf. Projects can include a wildcard split (a split with no
/// `hook`, `projectId`, or `beneficiary`) to send funds to the `_msgSender()` which calls this function. This can
/// be used to incentivize calling this function.
/// @dev Payouts sent to addresses which aren't feeless incur the protocol fee.
/// @param projectId The ID of the project to send the payouts of.
/// @param token The token being paid out.
/// @param amount The number of terminal tokens to pay out, as a fixed point number with same number of decimals as
/// this terminal.
/// @param currency The expected currency of the amount being paid out. Must match the currency of one of the
/// project's current ruleset's payout limits.
/// @return amountPaidOut The total amount that was paid out.
function _sendPayoutsOf(
uint256 projectId,
address token,
uint256 amount,
uint256 currency
)
internal
returns (uint256 amountPaidOut)
{
// Keep a reference to the ruleset.
JBRuleset memory ruleset;
// Record the payout.
(ruleset, amountPaidOut) = STORE.recordPayoutFor({
projectId: projectId,
accountingContext: _accountingContextForTokenOf[projectId][token],
amount: amount,
currency: currency
});
// Get a reference to the project's owner.
// The owner will receive tokens minted by paying the platform fee and receive any leftover funds not sent to
// payout splits.
address payable projectOwner = payable(_ownerOf(projectId));
// If the ruleset requires privileged payout distribution, ensure the caller has the permission.
if (ruleset.ownerMustSendPayouts()) {
// Enforce permissions.
_requirePermissionFrom({
account: projectOwner,
projectId: projectId,
permissionId: JBPermissionIds.SEND_PAYOUTS
});
}
// Send payouts to the splits and get a reference to the amount left over after the splits have been paid.
// Also get a reference to the amount which was paid out to splits that is eligible for fees.
(uint256 leftoverPayoutAmount, uint256 amountEligibleForFees) = _sendPayoutsToSplitGroupOf({
projectId: projectId,
token: token,
rulesetId: ruleset.id,
amount: amountPaidOut
});
// Send any leftover funds to the project owner and update the fee tracking accordingly.
if (leftoverPayoutAmount != 0) {
// Keep a reference to the fee for the leftover payout amount.
uint256 fee = _isFeeless(projectOwner)
? 0
: JBFees.feeAmountFrom({amountBeforeFee: leftoverPayoutAmount, feePercent: FEE});
// Transfer the amount to the project owner.
try this.executeTransferTo({addr: projectOwner, token: token, amount: leftoverPayoutAmount - fee}) {
if (fee > 0) {
amountEligibleForFees += leftoverPayoutAmount;
leftoverPayoutAmount -= fee;
}
} catch (bytes memory reason) {
emit PayoutTransferReverted({
projectId: projectId,
addr: projectOwner,
token: token,
amount: leftoverPayoutAmount - fee,
fee: fee,
reason: reason,
caller: _msgSender()
});
// Add balance back to the project.
_recordAddedBalanceFor({projectId: projectId, token: token, amount: leftoverPayoutAmount});
}
}
// Take the fee.
uint256 feeTaken = _takeFeeFrom({
projectId: projectId,
token: token,
amount: amountEligibleForFees,
beneficiary: projectOwner,
shouldHoldFees: ruleset.holdFees()
});
emit SendPayouts({
rulesetId: ruleset.id,
rulesetCycleNumber: ruleset.cycleNumber,
projectId: projectId,
projectOwner: projectOwner,
amount: amount,
amountPaidOut: amountPaidOut,
fee: feeTaken,
netLeftoverPayoutAmount: leftoverPayoutAmount,
caller: _msgSender()
});
}
/// @notice Sends a payout to a split.
/// @param split The split to pay.
/// @param projectId The ID of the project the split was specified by.
/// @param token The address of the token being paid out.
/// @param amount The total amount that the split is being paid, as a fixed point number with the same number of
/// decimals as this terminal.
/// @return netPayoutAmount The amount sent to the split after subtracting fees.
function _sendPayoutToSplit(
JBSplit memory split,
uint256 projectId,
address token,
uint256 amount
)
internal
returns (uint256)
{
// Attempt to distribute this split.
// slither-disable-next-line reentrancy-events
try this.executePayout({
split: split,
projectId: projectId,
token: token,
amount: amount,
originalMessageSender: _msgSender()
}) returns (uint256 netPayoutAmount) {
return netPayoutAmount;
} catch (bytes memory failureReason) {
emit PayoutReverted({
projectId: projectId,
split: split,
amount: amount,
reason: failureReason,
caller: _msgSender()
});
// Add balance back to the project.
_recordAddedBalanceFor({projectId: projectId, token: token, amount: amount});
// Since the payout failed the netPayoutAmount is zero.
return 0;
}
}
/// @notice Sends payouts to the payout splits group specified in a project's ruleset.
/// @param projectId The ID of the project to send the payouts of.
/// @param token The address of the token being paid out.
/// @param rulesetId The ID of the ruleset of the split group being paid.
/// @param amount The total amount being paid out, as a fixed point number with the same number of decimals as this
/// terminal.
/// @return amount The leftover amount (zero if the splits add up to 100%).
/// @return amountEligibleForFees The total amount of funds which were paid out and are eligible for fees.
function _sendPayoutsToSplitGroupOf(
uint256 projectId,
address token,
uint256 rulesetId,
uint256 amount
)
internal
returns (uint256, uint256 amountEligibleForFees)
{
// The total percentage available to split
uint256 leftoverPercentage = JBConstants.SPLITS_TOTAL_PERCENT;
// Get a reference to the project's payout splits.
JBSplit[] memory splits =
SPLITS.splitsOf({projectId: projectId, rulesetId: rulesetId, groupId: uint256(uint160(token))});
// Transfer between all splits.
for (uint256 i; i < splits.length; i++) {
// Get a reference to the split being iterated on.
JBSplit memory split = splits[i];
// The amount to send to the split.
uint256 payoutAmount = mulDiv(amount, split.percent, leftoverPercentage);
// The final payout amount after taking out any fees.
uint256 netPayoutAmount =
_sendPayoutToSplit({split: split, projectId: projectId, token: token, amount: payoutAmount});
// If the split hook is a feeless address, this payout doesn't incur a fee.
if (netPayoutAmount != 0 && netPayoutAmount != payoutAmount) {
amountEligibleForFees += payoutAmount;
}
if (payoutAmount != 0) {
// Subtract from the amount to be sent to the beneficiary.
unchecked {
amount -= payoutAmount;
}
}
unchecked {
// Decrement the leftover percentage.
leftoverPercentage -= split.percent;
}
emit SendPayoutToSplit({
projectId: projectId,
rulesetId: rulesetId,
group: uint256(uint160(token)),
split: split,
amount: payoutAmount,
netAmount: netPayoutAmount,
caller: _msgSender()
});
}
return (amount, amountEligibleForFees);
}
/// @notice Takes a fee into the platform's project (with the `_FEE_BENEFICIARY_PROJECT_ID`).
/// @param projectId The ID of the project paying the fee.
/// @param token The address of the token that the fee is being paid in.
/// @param amount The fee's token amount, as a fixed point number with 18 decimals.
/// @param beneficiary The address to mint the platform's project's tokens for.
/// @param shouldHoldFees If fees should be tracked and held instead of being exercised immediately.
/// @return feeAmount The amount of the fee taken.
function _takeFeeFrom(
uint256 projectId,
address token,
uint256 amount,
address beneficiary,
bool shouldHoldFees
)
internal
returns (uint256 feeAmount)
{
// Get a reference to the fee amount.
feeAmount = JBFees.feeAmountFrom({amountBeforeFee: amount, feePercent: FEE});
if (shouldHoldFees) {
// Store the held fee.
_heldFeesOf[projectId][token].push(
JBFee({
amount: amount,
beneficiary: beneficiary,
unlockTimestamp: uint48(block.timestamp + _FEE_HOLDING_SECONDS)
})
);
emit HoldFee({
projectId: projectId,
token: token,
amount: amount,
fee: FEE,
beneficiary: beneficiary,
caller: _msgSender()
});
} else {
// Get the terminal that'll receive the fee if one wasn't provided.
IJBTerminal feeTerminal = _primaryTerminalOf({projectId: _FEE_BENEFICIARY_PROJECT_ID, token: token});
// Process the fee.
_processFee({
projectId: projectId,
token: token,
amount: feeAmount,
beneficiary: beneficiary,
feeTerminal: feeTerminal,
wasHeld: false
});
}
}
/// @notice Transfers tokens.
/// @param from The address the transfer should originate from.
/// @param to The address the transfer should go to.
/// @param token The token being transfered.
/// @param amount The number of tokens being transferred, as a fixed point number with the same number of decimals
/// as this terminal.
function _transferFrom(address from, address payable to, address token, uint256 amount) internal {
if (from == address(this)) {
// If the token is the native token, transfer natively.
if (token == JBConstants.NATIVE_TOKEN) return Address.sendValue({recipient: to, amount: amount});
return IERC20(token).safeTransfer({to: to, value: amount});
}
// If there's sufficient approval, transfer normally.
if (IERC20(token).allowance(address(from), address(this)) >= amount) {
return IERC20(token).safeTransferFrom({from: from, to: to, value: amount});
}
// Make sure the amount being paid is less than the maximum permit2 allowance.
if (amount > type(uint160).max) revert JBMultiTerminal_OverflowAlert(amount, type(uint160).max);
// Otherwise we attempt to use the PERMIT2 method.
PERMIT2.transferFrom({from: from, to: to, amount: uint160(amount), token: token});
}
/// @notice Allows a project to send out funds from its surplus up to the current surplus allowance.
/// @dev Only a project's owner or an operator with the `USE_ALLOWANCE` permission from that owner can use the
/// surplus allowance.
/// @dev Incurs the protocol fee unless the caller is a feeless address.
/// @param projectId The ID of the project to use the surplus allowance of.
/// @param owner The project's owner.
/// @param token The token being paid out from the surplus.
/// @param amount The amount of terminal tokens to use from the project's current surplus allowance, as a fixed
/// point number with the same amount of decimals as this terminal.
/// @param currency The expected currency of the amount being paid out. Must match the currency of one of the
/// project's current ruleset's surplus allowances.
/// @param beneficiary The address to send the funds to.
/// @param feeBeneficiary The address to send the tokens resulting from paying the fee.
/// @param memo A memo to pass along to the emitted event.
/// @return netAmountPaidOut The amount of tokens paid out.
function _useAllowanceOf(
uint256 projectId,
address owner,
address token,
uint256 amount,
uint256 currency,
address payable beneficiary,
address payable feeBeneficiary,
string memory memo
)
internal
returns (uint256 netAmountPaidOut)
{
// Keep a reference to the ruleset.
JBRuleset memory ruleset;
// Keep a reference to the amount paid out before fees.
uint256 amountPaidOut;
// Record the use of the allowance.
(ruleset, amountPaidOut) = STORE.recordUsedAllowanceOf({
projectId: projectId,
accountingContext: _accountingContextForTokenOf[projectId][token],
amount: amount,
currency: currency
});
// Take a fee from the `amountPaidOut`, if needed.
// The net amount is the final amount withdrawn after the fee has been taken.
// slither-disable-next-line reentrancy-events
netAmountPaidOut = amountPaidOut
- (
_isFeeless(owner) || _isFeeless(beneficiary)
? 0
: _takeFeeFrom({
projectId: projectId,
token: token,
amount: amountPaidOut,
// The project owner will receive tokens minted by paying the platform fee.
beneficiary: feeBeneficiary,
shouldHoldFees: ruleset.holdFees()
})
);
emit UseAllowance({
rulesetId: ruleset.id,
rulesetCycleNumber: ruleset.cycleNumber,
projectId: projectId,
beneficiary: beneficiary,
feeBeneficiary: feeBeneficiary,
amount: amount,
amountPaidOut: amountPaidOut,
netAmountPaidOut: netAmountPaidOut,
memo: memo,
caller: _msgSender()
});
// Transfer any remaining balance to the beneficiary.
if (netAmountPaidOut != 0) {
_transferFrom({from: address(this), to: beneficiary, token: token, amount: netAmountPaidOut});
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IJBPayHook} from "../interfaces/IJBPayHook.sol";
/// @notice A pay hook specification sent from the ruleset's data hook back to the terminal. This specification is
/// fulfilled by the terminal.
/// @custom:member hook The pay hook to use when fulfilling this specification.
/// @custom:member amount The amount to send to the hook.
/// @custom:member metadata Metadata to pass the hook.
struct JBPayHookSpecification {
IJBPayHook hook;
uint256 amount;
bytes metadata;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @notice Permission IDs for `JBPermissions`, used throughout the Bananapus ecosystem. See
/// [`JBPermissions`](https://github.com/Bananapus/nana-core/blob/main/src/JBPermissions.sol)
/// @dev `JBPermissions` allows one address to grant another address permission to call functions in Juicebox contracts
/// on their behalf. Each ID in `JBPermissionIds` grants access to a specific set of these functions.
library JBPermissionIds {
uint8 internal constant ROOT = 1; // All permissions across every contract. Very dangerous. BE CAREFUL!
/* Used by `nana-core`: https://github.com/Bananapus/nana-core */
uint8 internal constant QUEUE_RULESETS = 2; // Permission to call `JBController.queueRulesetsOf` and
// `JBController.launchRulesetsFor`.
uint8 internal constant CASH_OUT_TOKENS = 3; // Permission to call `JBMultiTerminal.cashOutTokensOf`.
uint8 internal constant SEND_PAYOUTS = 4; // Permission to call `JBMultiTerminal.sendPayoutsOf`.
uint8 internal constant MIGRATE_TERMINAL = 5; // Permission to call `JBMultiTerminal.migrateBalanceOf`.
uint8 internal constant SET_PROJECT_URI = 6; // Permission to call `JBController.setUriOf`.
uint8 internal constant DEPLOY_ERC20 = 7; // Permission to call `JBController.deployERC20For`.
uint8 internal constant SET_TOKEN = 8; // Permission to call `JBController.setTokenFor`.
uint8 internal constant MINT_TOKENS = 9; // Permission to call `JBController.mintTokensOf`.
uint8 internal constant BURN_TOKENS = 10; // Permission to call `JBController.burnTokensOf`.
uint8 internal constant CLAIM_TOKENS = 11; // Permission to call `JBController.claimTokensFor`.
uint8 internal constant TRANSFER_CREDITS = 12; // Permission to call `JBController.transferCreditsFrom`.
uint8 internal constant SET_CONTROLLER = 13; // Permission to call `JBDirectory.setControllerOf`.
uint8 internal constant SET_TERMINALS = 14; // Permission to call `JBDirectory.setTerminalsOf`.
// Be careful - `SET_TERMINALS` can be used to remove the primary terminal.
uint8 internal constant SET_PRIMARY_TERMINAL = 15; // Permission to call `JBDirectory.setPrimaryTerminalOf`.
uint8 internal constant USE_ALLOWANCE = 16; // Permission to call `JBMultiTerminal.useAllowanceOf`.
uint8 internal constant SET_SPLIT_GROUPS = 17; // Permission to call `JBController.setSplitGroupsOf`.
uint8 internal constant ADD_PRICE_FEED = 18; // Permission to call `JBPrices.addPriceFeedFor`.
uint8 internal constant ADD_ACCOUNTING_CONTEXTS = 19; // Permission to call
// `JBMultiTerminal.addAccountingContextsFor`.
/* Used by `nana-721-hook`: https://github.com/Bananapus/nana-721-hook */
uint8 internal constant ADJUST_721_TIERS = 20; // Permission to call `JB721TiersHook.adjustTiers`.
uint8 internal constant SET_721_METADATA = 21; // Permission to call `JB721TiersHook.setMetadata`.
uint8 internal constant MINT_721 = 22; // Permission to call `JB721TiersHook.mintFor`.
uint8 internal constant SET_721_DISCOUNT_PERCENT = 23; // Permission to call `JB721TiersHook.setDiscountPercentOf`.
/* Used by `nana-buyback-hook`: https://github.com/Bananapus/nana-buyback-hook */
uint8 internal constant SET_BUYBACK_TWAP = 24; // Permission to call `JBBuybackHook.setTwapWindowOf` and
// `JBBuybackHook.setTwapSlippageToleranceOf`.
uint8 internal constant SET_BUYBACK_POOL = 25; // Permission to call `JBBuybackHook.setPoolFor`.
/* Used by `nana-swap-terminal`: https://github.com/Bananapus/nana-swap-terminal */
uint8 internal constant ADD_SWAP_TERMINAL_POOL = 26; // Permission to call `JBSwapTerminal.addDefaultPool`.
uint8 internal constant ADD_SWAP_TERMINAL_TWAP_PARAMS = 27; // Permission to call
// `JBSwapTerminal.addTwapParamsFor`.
/* Used by `nana-suckers`: https://github.com/Bananapus/nana-suckers */
uint8 internal constant MAP_SUCKER_TOKEN = 28; // Permission to call `BPSucker.mapToken`.
uint8 internal constant DEPLOY_SUCKERS = 29; // Permission to call `BPSuckerRegistry.deploySuckersFor`.
uint8 internal constant SUCKER_SAFETY = 30; // Permission to call `BPSucker.enableEmergencyHatchFor` and
// `BPSucker.setDeprecation`.
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Context} from "@openzeppelin/contracts/utils/Context.sol";
import {IJBPermissioned} from "./../interfaces/IJBPermissioned.sol";
import {IJBPermissions} from "./../interfaces/IJBPermissions.sol";
/// @notice Modifiers to allow access to transactions based on which permissions the message's sender has.
abstract contract JBPermissioned is Context, IJBPermissioned {
//*********************************************************************//
// --------------------------- custom errors -------------------------- //
//*********************************************************************//
error JBPermissioned_Unauthorized(address account, address sender, uint256 projectId, uint256 permissionId);
//*********************************************************************//
// ---------------- public immutable stored properties --------------- //
//*********************************************************************//
/// @notice A contract storing permissions.
IJBPermissions public immutable override PERMISSIONS;
//*********************************************************************//
// -------------------------- constructor ---------------------------- //
//*********************************************************************//
/// @param permissions A contract storing permissions.
constructor(IJBPermissions permissions) {
PERMISSIONS = permissions;
}
//*********************************************************************//
// -------------------------- internal views ------------------------- //
//*********************************************************************//
/// @notice Require the message sender to be the account or have the relevant permission.
/// @param account The account to allow.
/// @param projectId The project ID to check the permission under.
/// @param permissionId The required permission ID. The operator must have this permission within the specified
/// project ID.
function _requirePermissionFrom(address account, uint256 projectId, uint256 permissionId) internal view {
address sender = _msgSender();
if (
sender != account
&& !PERMISSIONS.hasPermission({
operator: sender,
account: account,
projectId: projectId,
permissionId: permissionId,
includeRoot: true,
includeWildcardProjectId: true
})
) revert JBPermissioned_Unauthorized(account, sender, projectId, permissionId);
}
/// @notice If the 'alsoGrantAccessIf' condition is truthy, proceed. Otherwise, require the message sender to be the
/// account or
/// have the relevant permission.
/// @param account The account to allow.
/// @param projectId The project ID to check the permission under.
/// @param permissionId The required permission ID. The operator must have this permission within the specified
/// project ID.
/// @param alsoGrantAccessIf An override condition which will allow access regardless of permissions.
function _requirePermissionAllowingOverrideFrom(
address account,
uint256 projectId,
uint256 permissionId,
bool alsoGrantAccessIf
)
internal
view
{
if (alsoGrantAccessIf) return;
_requirePermissionFrom(account, projectId, permissionId);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @custom:member operator The address that permissions are being given to.
/// @custom:member projectId The ID of the project the operator is being given permissions for. Operators only have
/// permissions under this project's scope. An ID of 0 is a wildcard, which gives an operator permissions across all
/// projects.
/// @custom:member permissionIds The IDs of the permissions being given. See the `JBPermissionIds` library.
struct JBPermissionsData {
address operator;
uint64 projectId;
uint8[] permissionIds;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IJBRulesetApprovalHook} from "./../interfaces/IJBRulesetApprovalHook.sol";
/// @dev `JBRuleset` timestamps are unix timestamps (seconds since 00:00 January 1st, 1970 UTC).
/// @custom:member cycleNumber The ruleset's cycle number. Each ruleset's `cycleNumber` is the previous ruleset's
/// `cycleNumber` plus one. Each project's first ruleset has a `cycleNumber` of 1.
/// @custom:member id The ruleset's ID, which is a timestamp of when this ruleset's rules were initialized. The
/// `rulesetId` stays the same for rulesets that automatically cycle over from a manually queued ruleset.
/// @custom:member basedOnId The `rulesetId` of the ruleset which was active when this ruleset was created.
/// @custom:member start The timestamp from which this ruleset is considered active.
/// @custom:member duration The number of seconds the ruleset lasts for. After this duration, a new ruleset will start.
/// The project owner can queue new rulesets at any time, which will take effect once the current ruleset's duration is
/// over. If the `duration` is 0, newly queued rulesets will take effect immediately. If a ruleset ends and there are no
/// new rulesets queued, the current ruleset cycles over to another one with the same properties but a new `start`
/// timestamp and a `weight` reduced by the ruleset's `weightCutPercent`.
/// @custom:member weight A fixed point number with 18 decimals which is typically used by payment terminals to
/// determine how many tokens should be minted when a payment is received. This can be used by other contracts for
/// arbitrary calculations.
/// @custom:member weightCutPercent The percentage by which to reduce the `weight` each time a new ruleset starts.
/// `weight`
/// is
/// a percentage out of `JBConstants.MAX_WEIGHT_CUT_PERCENT`. If it's 0, the next ruleset will have the same `weight` by
/// default. If it's 90%, the next ruleset's `weight` will be 10% smaller. If a ruleset explicitly sets a new `weight`,
/// the `weightCutPercent` doesn't apply.
/// @custom:member approvalHook An address of a contract that says whether a queued ruleset should be approved or
/// rejected. If a
/// ruleset is rejected, it won't go into effect. An approval hook can be used to create rules which dictate how a
/// project owner can change their ruleset over time.
/// @custom:member metadata Extra data associated with a ruleset which can be used by other contracts.
struct JBRuleset {
uint48 cycleNumber;
uint48 id;
uint48 basedOnId;
uint48 start;
uint32 duration;
uint112 weight;
uint32 weightCutPercent;
IJBRulesetApprovalHook approvalHook;
uint256 metadata;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IJBRulesetApprovalHook} from "../interfaces/IJBRulesetApprovalHook.sol";
import {JBFundAccessLimitGroup} from "./JBFundAccessLimitGroup.sol";
import {JBRulesetMetadata} from "./JBRulesetMetadata.sol";
import {JBSplitGroup} from "./JBSplitGroup.sol";
/// @custom:member mustStartAtOrAfter The earliest time the ruleset can start.
/// @custom:member duration The number of seconds the ruleset lasts for, after which a new ruleset will start. A
/// duration of 0 means that the ruleset will stay active until the project owner explicitly issues a reconfiguration,
/// at which point a new ruleset will immediately start with the updated properties. If the duration is greater than 0,
/// a project owner cannot make changes to a ruleset's parameters while it is active – any proposed changes will apply
/// to the subsequent ruleset. If no changes are proposed, a ruleset rolls over to another one with the same properties
/// but new `start` timestamp and a cut `weight`.
/// @custom:member weight A fixed point number with 18 decimals that contracts can use to base arbitrary calculations
/// on. For example, payment terminals can use this to determine how many tokens should be minted when a payment is
/// received.
/// @custom:member weightCutPercent A percent by how much the `weight` of the subsequent ruleset should be reduced, if
/// the
/// project owner hasn't queued the subsequent ruleset with an explicit `weight`. If it's 0, each ruleset will have
/// equal weight. If the number is 90%, the next ruleset will have a 10% smaller weight. This weight is out of
/// `JBConstants.MAX_WEIGHT_CUT_PERCENT`.
/// @custom:member approvalHook An address of a contract that says whether a proposed ruleset should be accepted or
/// rejected. It
/// can be used to create rules around how a project owner can change ruleset parameters over time.
/// @custom:member metadata Metadata specifying the controller-specific parameters that a ruleset can have. These
/// properties cannot change until the next ruleset starts.
/// @custom:member splitGroups An array of splits to use for any number of groups while the ruleset is active.
/// @custom:member fundAccessLimitGroups An array of structs which dictate the amount of funds a project can access from
/// its balance in each payment terminal while the ruleset is active. Amounts are fixed point numbers using the same
/// number of decimals as the corresponding terminal. The `_payoutLimit` and `_surplusAllowance` parameters must fit in
/// a `uint232`.
struct JBRulesetConfig {
uint48 mustStartAtOrAfter;
uint32 duration;
uint112 weight;
uint32 weightCutPercent;
IJBRulesetApprovalHook approvalHook;
JBRulesetMetadata metadata;
JBSplitGroup[] splitGroups;
JBFundAccessLimitGroup[] fundAccessLimitGroups;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @custom:member reservedPercent The reserved percent of the ruleset. This number is a percentage calculated out of
/// `JBConstants.MAX_RESERVED_PERCENT`.
/// @custom:member cashOutTaxRate The cash out tax rate of the ruleset. This number is a percentage calculated out of
/// `JBConstants.MAX_CASH_OUT_TAX_RATE`.
/// @custom:member baseCurrency The currency on which to base the ruleset's weight. By convention, this is
/// `uint32(uint160(tokenAddress))` for tokens, or a constant ID from e.g. `JBCurrencyIds` for other currencies.
/// @custom:member pausePay A flag indicating if the pay functionality should be paused during the ruleset.
/// @custom:member pauseCreditTransfers A flag indicating if the project token transfer functionality should be paused
/// during the funding cycle.
/// @custom:member allowOwnerMinting A flag indicating if the project owner or an operator with the `MINT_TOKENS`
/// permission from the owner should be allowed to mint project tokens on demand during this ruleset.
/// @custom:member allowTerminalMigration A flag indicating if migrating terminals should be allowed during this
/// ruleset.
/// @custom:member allowSetTerminals A flag indicating if a project's terminals can be added or removed.
/// @custom:member allowSetController A flag indicating if a project's controller can be changed.
/// @custom:member allowAddAccountingContext A flag indicating if a project can add new accounting contexts for its
/// terminals to use.
/// @custom:member allowAddPriceFeed A flag indicating if a project can add new price feeds to calculate exchange rates
/// between its tokens.
/// @custom:member ownerMustSendPayouts A flag indicating if privileged payout distribution should be
/// enforced, otherwise payouts can be distributed by anyone.
/// @custom:member holdFees A flag indicating if fees should be held during this ruleset.
/// @custom:member useTotalSurplusForCashOuts A flag indicating if cash outs should use the project's balance held
/// in all terminals instead of the project's local terminal balance from which the cash out is being fulfilled.
/// @custom:member useDataHookForPay A flag indicating if the data hook should be used for pay transactions during this
/// ruleset.
/// @custom:member useDataHookForCashOut A flag indicating if the data hook should be used for cash out transactions
/// during
/// this ruleset.
/// @custom:member dataHook The data hook to use during this ruleset.
/// @custom:member metadata Metadata of the metadata, only the 14 least significant bits can be used, the 2 most
/// significant bits are disregarded.
struct JBRulesetMetadata {
uint16 reservedPercent;
uint16 cashOutTaxRate;
uint32 baseCurrency;
bool pausePay;
bool pauseCreditTransfers;
bool allowOwnerMinting;
bool allowSetCustomToken;
bool allowTerminalMigration;
bool allowSetTerminals;
bool allowSetController;
bool allowAddAccountingContext;
bool allowAddPriceFeed;
bool ownerMustSendPayouts;
bool holdFees;
bool useTotalSurplusForCashOuts;
bool useDataHookForPay;
bool useDataHookForCashOut;
address dataHook;
uint16 metadata;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import {JBRuleset} from "./../structs/JBRuleset.sol";
import {JBRulesetMetadata} from "./../structs/JBRulesetMetadata.sol";
library JBRulesetMetadataResolver {
function reservedPercent(JBRuleset memory ruleset) internal pure returns (uint16) {
return uint16(ruleset.metadata >> 4);
}
function cashOutTaxRate(JBRuleset memory ruleset) internal pure returns (uint16) {
// Cash out tax rate is a number 0-10000.
return uint16(ruleset.metadata >> 20);
}
function baseCurrency(JBRuleset memory ruleset) internal pure returns (uint32) {
// Currency is a number 0-4294967296.
return uint32(ruleset.metadata >> 36);
}
function pausePay(JBRuleset memory ruleset) internal pure returns (bool) {
return ((ruleset.metadata >> 68) & 1) == 1;
}
function pauseCreditTransfers(JBRuleset memory ruleset) internal pure returns (bool) {
return ((ruleset.metadata >> 69) & 1) == 1;
}
function allowOwnerMinting(JBRuleset memory ruleset) internal pure returns (bool) {
return ((ruleset.metadata >> 70) & 1) == 1;
}
function allowSetCustomToken(JBRuleset memory ruleset) internal pure returns (bool) {
return ((ruleset.metadata >> 71) & 1) == 1;
}
function allowTerminalMigration(JBRuleset memory ruleset) internal pure returns (bool) {
return ((ruleset.metadata >> 72) & 1) == 1;
}
function allowSetTerminals(JBRuleset memory ruleset) internal pure returns (bool) {
return ((ruleset.metadata >> 73) & 1) == 1;
}
function allowSetController(JBRuleset memory ruleset) internal pure returns (bool) {
return ((ruleset.metadata >> 74) & 1) == 1;
}
function allowAddAccountingContext(JBRuleset memory ruleset) internal pure returns (bool) {
return ((ruleset.metadata >> 75) & 1) == 1;
}
function allowAddPriceFeed(JBRuleset memory ruleset) internal pure returns (bool) {
return ((ruleset.metadata >> 76) & 1) == 1;
}
function ownerMustSendPayouts(JBRuleset memory ruleset) internal pure returns (bool) {
return ((ruleset.metadata >> 77) & 1) == 1;
}
function holdFees(JBRuleset memory ruleset) internal pure returns (bool) {
return ((ruleset.metadata >> 78) & 1) == 1;
}
function useTotalSurplusForCashOuts(JBRuleset memory ruleset) internal pure returns (bool) {
return ((ruleset.metadata >> 79) & 1) == 1;
}
function useDataHookForPay(JBRuleset memory ruleset) internal pure returns (bool) {
return (ruleset.metadata >> 80) & 1 == 1;
}
function useDataHookForCashOut(JBRuleset memory ruleset) internal pure returns (bool) {
return (ruleset.metadata >> 81) & 1 == 1;
}
function dataHook(JBRuleset memory ruleset) internal pure returns (address) {
return address(uint160(ruleset.metadata >> 82));
}
function metadata(JBRuleset memory ruleset) internal pure returns (uint16) {
return uint16(ruleset.metadata >> 242);
}
/// @notice Pack the funding cycle metadata.
/// @param rulesetMetadata The ruleset metadata to validate and pack.
/// @return packed The packed uint256 of all metadata params. The first 8 bits specify the version.
function packRulesetMetadata(JBRulesetMetadata memory rulesetMetadata) internal pure returns (uint256 packed) {
// version 1 in the bits 0-3 (4 bits).
packed = 1;
// reserved percent in bits 4-19 (16 bits).
packed |= uint256(rulesetMetadata.reservedPercent) << 4;
// cash out tax rate in bits 20-35 (16 bits).
// cash out tax rate is a number 0-10000.
packed |= uint256(rulesetMetadata.cashOutTaxRate) << 20;
// base currency in bits 36-67 (32 bits).
// base currency is a number 0-16777215.
packed |= uint256(rulesetMetadata.baseCurrency) << 36;
// pause pay in bit 68.
if (rulesetMetadata.pausePay) packed |= 1 << 68;
// pause credit transfers in bit 69.
if (rulesetMetadata.pauseCreditTransfers) packed |= 1 << 69;
// allow discretionary minting in bit 70.
if (rulesetMetadata.allowOwnerMinting) packed |= 1 << 70;
// allow a custom token to be set in bit 71.
if (rulesetMetadata.allowSetCustomToken) packed |= 1 << 71;
// allow terminal migration in bit 72.
if (rulesetMetadata.allowTerminalMigration) packed |= 1 << 72;
// allow set terminals in bit 73.
if (rulesetMetadata.allowSetTerminals) packed |= 1 << 73;
// allow set controller in bit 74.
if (rulesetMetadata.allowSetController) packed |= 1 << 74;
// allow add accounting context in bit 75.
if (rulesetMetadata.allowAddAccountingContext) packed |= 1 << 75;
// allow add price feed in bit 76.
if (rulesetMetadata.allowAddPriceFeed) packed |= 1 << 76;
// allow controller migration in bit 77.
if (rulesetMetadata.ownerMustSendPayouts) packed |= 1 << 77;
// hold fees in bit 78.
if (rulesetMetadata.holdFees) packed |= 1 << 78;
// useTotalSurplusForCashOuts in bit 79.
if (rulesetMetadata.useTotalSurplusForCashOuts) packed |= 1 << 79;
// use pay data source in bit 80.
if (rulesetMetadata.useDataHookForPay) packed |= 1 << 80;
// use cash out data source in bit 81.
if (rulesetMetadata.useDataHookForCashOut) packed |= 1 << 81;
// data source address in bits 82-241.
packed |= uint256(uint160(address(rulesetMetadata.dataHook))) << 82;
// metadata in bits 242-255 (14 bits).
packed |= (uint256(rulesetMetadata.metadata) & 0x3FFF) << 242;
}
/// @notice Expand the funding cycle metadata.
/// @param ruleset The funding cycle having its metadata expanded.
/// @return rulesetMetadata The ruleset's metadata object.
function expandMetadata(JBRuleset memory ruleset) internal pure returns (JBRulesetMetadata memory) {
return JBRulesetMetadata(
reservedPercent(ruleset),
cashOutTaxRate(ruleset),
baseCurrency(ruleset),
pausePay(ruleset),
pauseCreditTransfers(ruleset),
allowOwnerMinting(ruleset),
allowSetCustomToken(ruleset),
allowTerminalMigration(ruleset),
allowSetTerminals(ruleset),
allowSetController(ruleset),
allowAddAccountingContext(ruleset),
allowAddPriceFeed(ruleset),
ownerMustSendPayouts(ruleset),
holdFees(ruleset),
useTotalSurplusForCashOuts(ruleset),
useDataHookForPay(ruleset),
useDataHookForCashOut(ruleset),
dataHook(ruleset),
metadata(ruleset)
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {JBRuleset} from "./JBRuleset.sol";
import {JBRulesetMetadata} from "./JBRulesetMetadata.sol";
/// @custom:member ruleset The ruleset.
/// @custom:member metadata The ruleset's metadata.
struct JBRulesetWithMetadata {
JBRuleset ruleset;
JBRulesetMetadata metadata;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @custom:member sigDeadline Deadline on the permit signature.
/// @custom:member amount The maximum amount allowed to spend.
/// @custom:member expiration Timestamp at which a spender's token allowances become invalid.
/// @custom:member nonce An incrementing value indexed per owner,token,and spender for each signature.
/// @custom:member signature The signature over the permit data. Supports EOA signatures, compact signatures defined by
/// EIP-2098, and contract signatures defined by EIP-1271.
struct JBSingleAllowance {
uint256 sigDeadline;
uint160 amount;
uint48 expiration;
uint48 nonce;
bytes signature;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IJBSplitHook} from "./../interfaces/IJBSplitHook.sol";
/// @notice Splits are used to send a percentage of a total token amount to a specific contract, project, or address.
/// Splits are used to send payouts and reserved tokens.
/// @dev 1. If a non-zero split hook contract is specified, this split's tokens are sent there along with this split's
/// properties.
/// @dev 2. Otherwise, if a non-zero project ID is specified, this split's tokens are used to `pay` it through its
/// terminal if possible, or sent to the project's owner if not. If this payment yields tokens, those go to the split's
/// `beneficiary`.
/// @dev 3. Otherwise, this split's tokens are sent directly to the `beneficiary`.
/// @dev To summarize, this split's tokens are sent according to the following priority: `split hook` > `projectId` >
/// `beneficiary`.
/// @custom:member percent The percent of the total token amount that this split sends. This number is out of
/// `JBConstants.SPLITS_TOTAL_PERCENT`.
/// @custom:member projectId The ID of a project to `pay`, if applicable. Resulting tokens will be routed to the
/// `beneficiary`.
/// @custom:member beneficiary Receives this split's tokens if the `hook` and `projectId` are zero. If the `projectId`
/// is specified, the `beneficiary` receives any project tokens minted by this split.
/// @custom:member preferAddToBalance If this split were to `pay` a project through its terminal, this flag indicates
/// whether it should prefer using the terminal's `addToBalance` function instead.
/// @custom:member lockedUntil The split cannot be changed until this timestamp. The `lockedUntil` timestamp can be
/// increased while a split is locked. If `lockedUntil` is zero, this split can be changed at any time.
/// @custom:member hook A contract which will receive this split's tokens and properties, and can define custom
/// behavior.
struct JBSplit {
uint32 percent;
uint64 projectId;
address payable beneficiary;
bool preferAddToBalance;
uint48 lockedUntil;
IJBSplitHook hook;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {JBSplit} from "./JBSplit.sol";
/// @custom:member groupId An identifier for the group. By convention, this ID is `uint256(uint160(tokenAddress))` for
/// payouts and `1` for reserved tokens.
/// @custom:member splits The splits in the group.
struct JBSplitGroup {
uint256 groupId;
JBSplit[] splits;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {JBSplit} from "./JBSplit.sol";
/// @custom:member token The token being sent to the split hook.
/// @custom:member amount The amount being sent to the split hook, as a fixed point number.
/// @custom:member decimals The number of decimals in the amount.
/// @custom:member projectId The project the split belongs to.
/// @custom:member groupId The group the split belongs to. By convention, this ID is `uint256(uint160(tokenAddress))`
/// for payouts and `1` for reserved tokens.
/// @custom:member split The split which specified the hook.
struct JBSplitHookContext {
address token;
uint256 amount;
uint256 decimals;
uint256 projectId;
uint256 groupId;
JBSplit split;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {JBAccountingContext} from "./JBAccountingContext.sol";
import {IJBTerminal} from "./../interfaces/IJBTerminal.sol";
/// @custom:member terminal The terminal to configure.
/// @custom:member accountingContextsToAccept The accounting contexts to accept from the terminal.
struct JBTerminalConfig {
IJBTerminal terminal;
JBAccountingContext[] accountingContextsToAccept;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @custom:member token The token the payment was made in.
/// @custom:member decimals The number of decimals included in the value fixed point number.
/// @custom:member currency The currency. By convention, this is `uint32(uint160(tokenAddress))` for tokens, or a
/// constant ID from e.g. `JBCurrencyIds` for other currencies.
/// @custom:member value The amount of tokens that was paid, as a fixed point number.
struct JBTokenAmount {
address token;
uint8 decimals;
uint32 currency;
uint256 value;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 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 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @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.encodeCall(token.transfer, (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.encodeCall(token.transferFrom, (from, to, 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.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @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.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @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 {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @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 silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}
{
"compilationTarget": {
"src/JBMultiTerminal.sol": "JBMultiTerminal"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":@arbitrum/=node_modules/@arbitrum/",
":@bananapus/=node_modules/@bananapus/",
":@chainlink/=node_modules/@chainlink/",
":@eth-optimism/=node_modules/@eth-optimism/",
":@offchainlabs/=node_modules/@offchainlabs/",
":@openzeppelin/=node_modules/@openzeppelin/",
":@prb/=node_modules/@prb/",
":@scroll-tech/=node_modules/@scroll-tech/",
":@sphinx-labs/contracts/=lib/sphinx/packages/contracts/contracts/foundry/",
":@uniswap/=node_modules/@uniswap/",
":@zksync/=node_modules/@zksync/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":forge-std/=lib/forge-std/src/",
":hardhat/=node_modules/hardhat/",
":solmate/=node_modules/solmate/",
":sphinx/=lib/sphinx/packages/contracts/contracts/forge-std/src/"
]
}
[{"inputs":[{"internalType":"contract IJBFeelessAddresses","name":"feelessAddresses","type":"address"},{"internalType":"contract IJBPermissions","name":"permissions","type":"address"},{"internalType":"contract IJBProjects","name":"projects","type":"address"},{"internalType":"contract IJBSplits","name":"splits","type":"address"},{"internalType":"contract IJBTerminalStore","name":"store","type":"address"},{"internalType":"contract IJBTokens","name":"tokens","type":"address"},{"internalType":"contract IPermit2","name":"permit2","type":"address"},{"internalType":"address","name":"trustedForwarder","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"FailedCall","type":"error"},{"inputs":[{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"InsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"JBMultiTerminal_AccountingContextAlreadySet","type":"error"},{"inputs":[],"name":"JBMultiTerminal_AddingAccountingContextNotAllowed","type":"error"},{"inputs":[],"name":"JBMultiTerminal_FeeTerminalNotFound","type":"error"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"JBMultiTerminal_NoMsgValueAllowed","type":"error"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"limit","type":"uint256"}],"name":"JBMultiTerminal_OverflowAlert","type":"error"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"allowance","type":"uint256"}],"name":"JBMultiTerminal_PermitAllowanceNotEnough","type":"error"},{"inputs":[{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"address","name":"token","type":"address"}],"name":"JBMultiTerminal_RecipientProjectTerminalNotFound","type":"error"},{"inputs":[{"internalType":"contract IJBSplitHook","name":"hook","type":"address"}],"name":"JBMultiTerminal_SplitHookInvalid","type":"error"},{"inputs":[],"name":"JBMultiTerminal_TerminalTokensIncompatible","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"JBMultiTerminal_TokenNotAccepted","type":"error"},{"inputs":[{"internalType":"uint256","name":"count","type":"uint256"},{"internalType":"uint256","name":"min","type":"uint256"}],"name":"JBMultiTerminal_UnderMinReturnedTokens","type":"error"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"min","type":"uint256"}],"name":"JBMultiTerminal_UnderMinTokensPaidOut","type":"error"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"min","type":"uint256"}],"name":"JBMultiTerminal_UnderMinTokensReclaimed","type":"error"},{"inputs":[],"name":"JBMultiTerminal_ZeroAccountingContextCurrency","type":"error"},{"inputs":[],"name":"JBMultiTerminal_ZeroAccountingContextDecimals","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"uint256","name":"permissionId","type":"uint256"}],"name":"JBPermissioned_Unauthorized","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":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"projectId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"returnedFees","type":"uint256"},{"indexed":false,"internalType":"string","name":"memo","type":"string"},{"indexed":false,"internalType":"bytes","name":"metadata","type":"bytes"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"AddToBalance","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"rulesetId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"rulesetCycleNumber","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"projectId","type":"uint256"},{"indexed":false,"internalType":"address","name":"holder","type":"address"},{"indexed":false,"internalType":"address","name":"beneficiary","type":"address"},{"indexed":false,"internalType":"uint256","name":"cashOutCount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"cashOutTaxRate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"reclaimAmount","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"metadata","type":"bytes"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"CashOutTokens","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"projectId","type":"uint256"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"uint256","name":"feeProjectId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"reason","type":"bytes"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"FeeReverted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"projectId","type":"uint256"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"},{"indexed":false,"internalType":"address","name":"beneficiary","type":"address"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"HoldFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IJBCashOutHook","name":"hook","type":"address"},{"components":[{"internalType":"address","name":"holder","type":"address"},{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"uint256","name":"rulesetId","type":"uint256"},{"internalType":"uint256","name":"cashOutCount","type":"uint256"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint32","name":"currency","type":"uint32"},{"internalType":"uint256","name":"value","type":"uint256"}],"internalType":"struct JBTokenAmount","name":"reclaimedAmount","type":"tuple"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint32","name":"currency","type":"uint32"},{"internalType":"uint256","name":"value","type":"uint256"}],"internalType":"struct JBTokenAmount","name":"forwardedAmount","type":"tuple"},{"internalType":"uint256","name":"cashOutTaxRate","type":"uint256"},{"internalType":"address payable","name":"beneficiary","type":"address"},{"internalType":"bytes","name":"hookMetadata","type":"bytes"},{"internalType":"bytes","name":"cashOutMetadata","type":"bytes"}],"indexed":false,"internalType":"struct JBAfterCashOutRecordedContext","name":"context","type":"tuple"},{"indexed":false,"internalType":"uint256","name":"specificationAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"HookAfterRecordCashOut","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IJBPayHook","name":"hook","type":"address"},{"components":[{"internalType":"address","name":"payer","type":"address"},{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"uint256","name":"rulesetId","type":"uint256"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint32","name":"currency","type":"uint32"},{"internalType":"uint256","name":"value","type":"uint256"}],"internalType":"struct JBTokenAmount","name":"amount","type":"tuple"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint32","name":"currency","type":"uint32"},{"internalType":"uint256","name":"value","type":"uint256"}],"internalType":"struct JBTokenAmount","name":"forwardedAmount","type":"tuple"},{"internalType":"uint256","name":"weight","type":"uint256"},{"internalType":"uint256","name":"newlyIssuedTokenCount","type":"uint256"},{"internalType":"address","name":"beneficiary","type":"address"},{"internalType":"bytes","name":"hookMetadata","type":"bytes"},{"internalType":"bytes","name":"payerMetadata","type":"bytes"}],"indexed":false,"internalType":"struct JBAfterPayRecordedContext","name":"context","type":"tuple"},{"indexed":false,"internalType":"uint256","name":"specificationAmount","type":"uint256"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"HookAfterRecordPay","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"projectId","type":"uint256"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"contract IJBTerminal","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"MigrateTerminal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"rulesetId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"rulesetCycleNumber","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"projectId","type":"uint256"},{"indexed":false,"internalType":"address","name":"payer","type":"address"},{"indexed":false,"internalType":"address","name":"beneficiary","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newlyIssuedTokenCount","type":"uint256"},{"indexed":false,"internalType":"string","name":"memo","type":"string"},{"indexed":false,"internalType":"bytes","name":"metadata","type":"bytes"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"Pay","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"projectId","type":"uint256"},{"components":[{"internalType":"uint32","name":"percent","type":"uint32"},{"internalType":"uint64","name":"projectId","type":"uint64"},{"internalType":"address payable","name":"beneficiary","type":"address"},{"internalType":"bool","name":"preferAddToBalance","type":"bool"},{"internalType":"uint48","name":"lockedUntil","type":"uint48"},{"internalType":"contract IJBSplitHook","name":"hook","type":"address"}],"indexed":false,"internalType":"struct JBSplit","name":"split","type":"tuple"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"reason","type":"bytes"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"PayoutReverted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"projectId","type":"uint256"},{"indexed":false,"internalType":"address","name":"addr","type":"address"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"reason","type":"bytes"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"PayoutTransferReverted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"projectId","type":"uint256"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"bool","name":"wasHeld","type":"bool"},{"indexed":false,"internalType":"address","name":"beneficiary","type":"address"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"ProcessFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"projectId","type":"uint256"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"returnedFees","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"leftoverAmount","type":"uint256"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"ReturnHeldFees","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"projectId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"rulesetId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"group","type":"uint256"},{"components":[{"internalType":"uint32","name":"percent","type":"uint32"},{"internalType":"uint64","name":"projectId","type":"uint64"},{"internalType":"address payable","name":"beneficiary","type":"address"},{"internalType":"bool","name":"preferAddToBalance","type":"bool"},{"internalType":"uint48","name":"lockedUntil","type":"uint48"},{"internalType":"contract IJBSplitHook","name":"hook","type":"address"}],"indexed":false,"internalType":"struct JBSplit","name":"split","type":"tuple"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"netAmount","type":"uint256"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"SendPayoutToSplit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"rulesetId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"rulesetCycleNumber","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"projectId","type":"uint256"},{"indexed":false,"internalType":"address","name":"projectOwner","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountPaidOut","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"netLeftoverPayoutAmount","type":"uint256"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"SendPayouts","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"projectId","type":"uint256"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint32","name":"currency","type":"uint32"}],"indexed":false,"internalType":"struct JBAccountingContext","name":"context","type":"tuple"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"SetAccountingContext","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"rulesetId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"rulesetCycleNumber","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"projectId","type":"uint256"},{"indexed":false,"internalType":"address","name":"beneficiary","type":"address"},{"indexed":false,"internalType":"address","name":"feeBeneficiary","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountPaidOut","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"netAmountPaidOut","type":"uint256"},{"indexed":false,"internalType":"string","name":"memo","type":"string"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"UseAllowance","type":"event"},{"inputs":[],"name":"DIRECTORY","outputs":[{"internalType":"contract IJBDirectory","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEELESS_ADDRESSES","outputs":[{"internalType":"contract IJBFeelessAddresses","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PERMISSIONS","outputs":[{"internalType":"contract IJBPermissions","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PERMIT2","outputs":[{"internalType":"contract IPermit2","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PROJECTS","outputs":[{"internalType":"contract IJBProjects","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RULESETS","outputs":[{"internalType":"contract IJBRulesets","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SPLITS","outputs":[{"internalType":"contract IJBSplits","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STORE","outputs":[{"internalType":"contract IJBTerminalStore","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKENS","outputs":[{"internalType":"contract IJBTokens","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"address","name":"token","type":"address"}],"name":"accountingContextForTokenOf","outputs":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint32","name":"currency","type":"uint32"}],"internalType":"struct JBAccountingContext","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"projectId","type":"uint256"}],"name":"accountingContextsOf","outputs":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint32","name":"currency","type":"uint32"}],"internalType":"struct JBAccountingContext[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"projectId","type":"uint256"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint32","name":"currency","type":"uint32"}],"internalType":"struct JBAccountingContext[]","name":"accountingContexts","type":"tuple[]"}],"name":"addAccountingContextsFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bool","name":"shouldReturnHeldFees","type":"bool"},{"internalType":"string","name":"memo","type":"string"},{"internalType":"bytes","name":"metadata","type":"bytes"}],"name":"addToBalanceOf","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"holder","type":"address"},{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"uint256","name":"cashOutCount","type":"uint256"},{"internalType":"address","name":"tokenToReclaim","type":"address"},{"internalType":"uint256","name":"minTokensReclaimed","type":"uint256"},{"internalType":"address payable","name":"beneficiary","type":"address"},{"internalType":"bytes","name":"metadata","type":"bytes"}],"name":"cashOutTokensOf","outputs":[{"internalType":"uint256","name":"reclaimAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"projectId","type":"uint256"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint32","name":"currency","type":"uint32"}],"internalType":"struct JBAccountingContext[]","name":"accountingContexts","type":"tuple[]"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"uint256","name":"currency","type":"uint256"}],"name":"currentSurplusOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint32","name":"percent","type":"uint32"},{"internalType":"uint64","name":"projectId","type":"uint64"},{"internalType":"address payable","name":"beneficiary","type":"address"},{"internalType":"bool","name":"preferAddToBalance","type":"bool"},{"internalType":"uint48","name":"lockedUntil","type":"uint48"},{"internalType":"contract IJBSplitHook","name":"hook","type":"address"}],"internalType":"struct JBSplit","name":"split","type":"tuple"},{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"originalMessageSender","type":"address"}],"name":"executePayout","outputs":[{"internalType":"uint256","name":"netPayoutAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"beneficiary","type":"address"},{"internalType":"contract IJBTerminal","name":"feeTerminal","type":"address"}],"name":"executeProcessFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"addr","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"executeTransferTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"count","type":"uint256"}],"name":"heldFeesOf","outputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"beneficiary","type":"address"},{"internalType":"uint48","name":"unlockTimestamp","type":"uint48"}],"internalType":"struct JBFee[]","name":"heldFees","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"forwarder","type":"address"}],"name":"isTrustedForwarder","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"contract IJBTerminal","name":"to","type":"address"}],"name":"migrateBalanceOf","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"beneficiary","type":"address"},{"internalType":"uint256","name":"minReturnedTokens","type":"uint256"},{"internalType":"string","name":"memo","type":"string"},{"internalType":"bytes","name":"metadata","type":"bytes"}],"name":"pay","outputs":[{"internalType":"uint256","name":"beneficiaryTokenCount","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"count","type":"uint256"}],"name":"processHeldFeesOf","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"currency","type":"uint256"},{"internalType":"uint256","name":"minTokensPaidOut","type":"uint256"}],"name":"sendPayoutsOf","outputs":[{"internalType":"uint256","name":"amountPaidOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"trustedForwarder","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"currency","type":"uint256"},{"internalType":"uint256","name":"minTokensPaidOut","type":"uint256"},{"internalType":"address payable","name":"beneficiary","type":"address"},{"internalType":"address payable","name":"feeBeneficiary","type":"address"},{"internalType":"string","name":"memo","type":"string"}],"name":"useAllowanceOf","outputs":[{"internalType":"uint256","name":"netAmountPaidOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]