// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
library FixedPointMathLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The operation failed, as the output exceeds the maximum value of uint256.
error ExpOverflow();
/// @dev The operation failed, as the output exceeds the maximum value of uint256.
error FactorialOverflow();
/// @dev The operation failed, due to an multiplication overflow.
error MulWadFailed();
/// @dev The operation failed, either due to a
/// multiplication overflow, or a division by a zero.
error DivWadFailed();
/// @dev The multiply-divide operation failed, either due to a
/// multiplication overflow, or a division by a zero.
error MulDivFailed();
/// @dev The division failed, as the denominator is zero.
error DivFailed();
/// @dev The full precision multiply-divide operation failed, either due
/// to the result being larger than 256 bits, or a division by a zero.
error FullMulDivFailed();
/// @dev The output is undefined, as the input is less-than-or-equal to zero.
error LnWadUndefined();
/// @dev The output is undefined, as the input is zero.
error Log2Undefined();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The scalar of ETH and most ERC20s.
uint256 internal constant WAD = 1e18;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* SIMPLIFIED FIXED POINT OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Equivalent to `(x * y) / WAD` rounded down.
function mulWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
if mul(y, gt(x, div(not(0), y))) {
// Store the function selector of `MulWadFailed()`.
mstore(0x00, 0xbac65e5b)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
z := div(mul(x, y), WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded up.
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
if mul(y, gt(x, div(not(0), y))) {
// Store the function selector of `MulWadFailed()`.
mstore(0x00, 0xbac65e5b)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(mul(x, y), WAD))), div(mul(x, y), WAD))
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down.
function divWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y != 0 && (WAD == 0 || x <= type(uint256).max / WAD))`.
if iszero(mul(y, iszero(mul(WAD, gt(x, div(not(0), WAD)))))) {
// Store the function selector of `DivWadFailed()`.
mstore(0x00, 0x7c5f487d)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
z := div(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded up.
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y != 0 && (WAD == 0 || x <= type(uint256).max / WAD))`.
if iszero(mul(y, iszero(mul(WAD, gt(x, div(not(0), WAD)))))) {
// Store the function selector of `DivWadFailed()`.
mstore(0x00, 0x7c5f487d)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y))
}
}
/// @dev Equivalent to `x` to the power of `y`.
/// because `x ** y = (e ** ln(x)) ** y = e ** (ln(x) * y)`.
function powWad(int256 x, int256 y) internal pure returns (int256) {
// Using `ln(x)` means `x` must be greater than 0.
return expWad((lnWad(x) * y) / int256(WAD));
}
/// @dev Returns `exp(x)`, denominated in `WAD`.
function expWad(int256 x) internal pure returns (int256 r) {
unchecked {
// When the result is < 0.5 we return zero. This happens when
// x <= floor(log(0.5e18) * 1e18) ~ -42e18
if (x <= -42139678854452767551) return r;
/// @solidity memory-safe-assembly
assembly {
// When the result is > (2**255 - 1) / 1e18 we can not represent it as an
// int. This happens when x >= floor(log((2**255 - 1) / 1e18) * 1e18) ~ 135.
if iszero(slt(x, 135305999368893231589)) {
// Store the function selector of `ExpOverflow()`.
mstore(0x00, 0xa37bfec9)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
}
// x is now in the range (-42, 136) * 1e18. Convert to (-42, 136) * 2**96
// for more intermediate precision and a binary basis. This base conversion
// is a multiplication by 1e18 / 2**96 = 5**18 / 2**78.
x = (x << 78) / 5 ** 18;
// Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers
// of two such that exp(x) = exp(x') * 2**k, where k is an integer.
// Solving this gives k = round(x / log(2)) and x' = x - k * log(2).
int256 k = ((x << 96) / 54916777467707473351141471128 + 2 ** 95) >> 96;
x = x - k * 54916777467707473351141471128;
// k is in the range [-61, 195].
// Evaluate using a (6, 7)-term rational approximation.
// p is made monic, we'll multiply by a scale factor later.
int256 y = x + 1346386616545796478920950773328;
y = ((y * x) >> 96) + 57155421227552351082224309758442;
int256 p = y + x - 94201549194550492254356042504812;
p = ((p * y) >> 96) + 28719021644029726153956944680412240;
p = p * x + (4385272521454847904659076985693276 << 96);
// We leave p in 2**192 basis so we don't need to scale it back up for the division.
int256 q = x - 2855989394907223263936484059900;
q = ((q * x) >> 96) + 50020603652535783019961831881945;
q = ((q * x) >> 96) - 533845033583426703283633433725380;
q = ((q * x) >> 96) + 3604857256930695427073651918091429;
q = ((q * x) >> 96) - 14423608567350463180887372962807573;
q = ((q * x) >> 96) + 26449188498355588339934803723976023;
/// @solidity memory-safe-assembly
assembly {
// Div in assembly because solidity adds a zero check despite the unchecked.
// The q polynomial won't have zeros in the domain as all its roots are complex.
// No scaling is necessary because p is already 2**96 too large.
r := sdiv(p, q)
}
// r should be in the range (0.09, 0.25) * 2**96.
// We now need to multiply r by:
// * the scale factor s = ~6.031367120.
// * the 2**k factor from the range reduction.
// * the 1e18 / 2**96 factor for base conversion.
// We do this all at once, with an intermediate result in 2**213
// basis, so the final right shift is always by a positive amount.
r = int256(
(uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k)
);
}
}
/// @dev Returns `ln(x)`, denominated in `WAD`.
function lnWad(int256 x) internal pure returns (int256 r) {
unchecked {
/// @solidity memory-safe-assembly
assembly {
if iszero(sgt(x, 0)) {
// Store the function selector of `LnWadUndefined()`.
mstore(0x00, 0x1615e638)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
}
// We want to convert x from 10**18 fixed point to 2**96 fixed point.
// We do this by multiplying by 2**96 / 10**18. But since
// ln(x * C) = ln(x) + ln(C), we can simply do nothing here
// and add ln(2**96 / 10**18) at the end.
// Compute k = log2(x) - 96.
int256 k;
/// @solidity memory-safe-assembly
assembly {
let v := x
k := shl(7, lt(0xffffffffffffffffffffffffffffffff, v))
k := or(k, shl(6, lt(0xffffffffffffffff, shr(k, v))))
k := or(k, shl(5, lt(0xffffffff, shr(k, v))))
// For the remaining 32 bits, use a De Bruijn lookup.
// See: https://graphics.stanford.edu/~seander/bithacks.html
v := shr(k, v)
v := or(v, shr(1, v))
v := or(v, shr(2, v))
v := or(v, shr(4, v))
v := or(v, shr(8, v))
v := or(v, shr(16, v))
// forgefmt: disable-next-item
k := sub(or(k, byte(shr(251, mul(v, shl(224, 0x07c4acdd))),
0x0009010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f)), 96)
}
// Reduce range of x to (1, 2) * 2**96
// ln(2^k * x) = k * ln(2) + ln(x)
x <<= uint256(159 - k);
x = int256(uint256(x) >> 159);
// Evaluate using a (8, 8)-term rational approximation.
// p is made monic, we will multiply by a scale factor later.
int256 p = x + 3273285459638523848632254066296;
p = ((p * x) >> 96) + 24828157081833163892658089445524;
p = ((p * x) >> 96) + 43456485725739037958740375743393;
p = ((p * x) >> 96) - 11111509109440967052023855526967;
p = ((p * x) >> 96) - 45023709667254063763336534515857;
p = ((p * x) >> 96) - 14706773417378608786704636184526;
p = p * x - (795164235651350426258249787498 << 96);
// We leave p in 2**192 basis so we don't need to scale it back up for the division.
// q is monic by convention.
int256 q = x + 5573035233440673466300451813936;
q = ((q * x) >> 96) + 71694874799317883764090561454958;
q = ((q * x) >> 96) + 283447036172924575727196451306956;
q = ((q * x) >> 96) + 401686690394027663651624208769553;
q = ((q * x) >> 96) + 204048457590392012362485061816622;
q = ((q * x) >> 96) + 31853899698501571402653359427138;
q = ((q * x) >> 96) + 909429971244387300277376558375;
/// @solidity memory-safe-assembly
assembly {
// Div in assembly because solidity adds a zero check despite the unchecked.
// The q polynomial is known not to have zeros in the domain.
// No scaling required because p is already 2**96 too large.
r := sdiv(p, q)
}
// r is in the range (0, 0.125) * 2**96
// Finalization, we need to:
// * multiply by the scale factor s = 5.549…
// * add ln(2**96 / 10**18)
// * add k * ln(2)
// * multiply by 10**18 / 2**96 = 5**18 >> 78
// mul s * 5e18 * 2**96, base is now 5**18 * 2**192
r *= 1677202110996718588342820967067443963516166;
// add ln(2) * k * 5e18 * 2**192
r += 16597577552685614221487285958193947469193820559219878177908093499208371 * k;
// add ln(2**96 / 10**18) * 5e18 * 2**192
r += 600920179829731861736702779321621459595472258049074101567377883020018308;
// base conversion: mul 2**18 / 2**192
r >>= 174;
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* GENERAL NUMBER UTILITIES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Calculates `floor(a * b / d)` with full precision.
/// Throws if result overflows a uint256 or when `d` is zero.
/// Credit to Remco Bloemen under MIT license: https://2π.com/21/muldiv
function fullMulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
// forgefmt: disable-next-item
for {} 1 {} {
// 512-bit multiply `[prod1 prod0] = x * y`.
// Compute the product mod `2**256` and mod `2**256 - 1`
// then 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`.
// Least significant 256 bits of the product.
let prod0 := mul(x, y)
let mm := mulmod(x, y, not(0))
// Most significant 256 bits of the product.
let prod1 := sub(mm, add(prod0, lt(mm, prod0)))
// Handle non-overflow cases, 256 by 256 division.
if iszero(prod1) {
if iszero(d) {
// Store the function selector of `FullMulDivFailed()`.
mstore(0x00, 0xae47f702)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
result := div(prod0, d)
break
}
// Make sure the result is less than `2**256`.
// Also prevents `d == 0`.
if iszero(gt(d, prod1)) {
// Store the function selector of `FullMulDivFailed()`.
mstore(0x00, 0xae47f702)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from `[prod1 prod0]`.
// Compute remainder using mulmod.
let remainder := mulmod(x, y, d)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
// Factor powers of two out of `d`.
// Compute largest power of two divisor of `d`.
// Always greater or equal to 1.
let twos := and(d, sub(0, d))
// Divide d by power of two.
d := div(d, twos)
// Divide [prod1 prod0] by the factors of two.
prod0 := div(prod0, twos)
// Shift in bits from `prod1` into `prod0`. For this we need
// to flip `twos` such that it is `2**256 / twos`.
// If `twos` is zero, then it becomes one.
prod0 := or(prod0, mul(prod1, add(div(sub(0, twos), twos), 1)))
// Invert `d mod 2**256`
// Now that `d` is an odd number, it has an inverse
// modulo `2**256` such that `d * inv = 1 mod 2**256`.
// Compute the inverse by starting with a seed that is correct
// correct for four bits. That is, `d * inv = 1 mod 2**4`.
let inv := xor(mul(3, d), 2)
// Now use 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.
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**8
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**16
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**32
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**64
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**128
result := mul(prod0, mul(inv, sub(2, mul(d, inv)))) // inverse mod 2**256
break
}
}
}
/// @dev Calculates `floor(x * y / d)` with full precision, rounded up.
/// Throws if result overflows a uint256 or when `d` is zero.
/// Credit to Uniswap-v3-core under MIT license:
/// https://github.com/Uniswap/v3-core/blob/contracts/libraries/FullMath.sol
function fullMulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 result) {
result = fullMulDiv(x, y, d);
/// @solidity memory-safe-assembly
assembly {
if mulmod(x, y, d) {
if iszero(add(result, 1)) {
// Store the function selector of `FullMulDivFailed()`.
mstore(0x00, 0xae47f702)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
result := add(result, 1)
}
}
}
/// @dev Returns `floor(x * y / d)`.
/// Reverts if `x * y` overflows, or `d` is zero.
function mulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(d != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(d, iszero(mul(y, gt(x, div(not(0), y)))))) {
// Store the function selector of `MulDivFailed()`.
mstore(0x00, 0xad251c27)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
z := div(mul(x, y), d)
}
}
/// @dev Returns `ceil(x * y / d)`.
/// Reverts if `x * y` overflows, or `d` is zero.
function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(d != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(d, iszero(mul(y, gt(x, div(not(0), y)))))) {
// Store the function selector of `MulDivFailed()`.
mstore(0x00, 0xad251c27)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(mul(x, y), d))), div(mul(x, y), d))
}
}
/// @dev Returns `ceil(x / d)`.
/// Reverts if `d` is zero.
function divUp(uint256 x, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
if iszero(d) {
// Store the function selector of `DivFailed()`.
mstore(0x00, 0x65244e4e)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(x, d))), div(x, d))
}
}
/// @dev Returns `max(0, x - y)`.
function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(gt(x, y), sub(x, y))
}
}
/// @dev Returns the square root of `x`.
function sqrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// `floor(sqrt(2**15)) = 181`. `sqrt(2**15) - 181 = 2.84`.
z := 181 // The "correct" value is 1, but this saves a multiplication later.
// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
// Let `y = x / 2**r`.
// We check `y >= 2**(k + 8)` but shift right by `k` bits
// each branch to ensure that if `x >= 256`, then `y >= 256`.
let r := shl(7, lt(0xffffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffffff, shr(r, x))))
z := shl(shr(1, r), z)
// Goal was to get `z*z*y` within a small factor of `x`. More iterations could
// get y in a tighter range. Currently, we will have y in `[256, 256*(2**16))`.
// We ensured `y >= 256` so that the relative difference between `y` and `y+1` is small.
// That's not possible if `x < 256` but we can just verify those cases exhaustively.
// Now, `z*z*y <= x < z*z*(y+1)`, and `y <= 2**(16+8)`, and either `y >= 256`, or `x < 256`.
// Correctness can be checked exhaustively for `x < 256`, so we assume `y >= 256`.
// Then `z*sqrt(y)` is within `sqrt(257)/sqrt(256)` of `sqrt(x)`, or about 20bps.
// For `s` in the range `[1/256, 256]`, the estimate `f(s) = (181/1024) * (s+1)`
// is in the range `(1/2.84 * sqrt(s), 2.84 * sqrt(s))`,
// with largest error when `s = 1` and when `s = 256` or `1/256`.
// Since `y` is in `[256, 256*(2**16))`, let `a = y/65536`, so that `a` is in `[1/256, 256)`.
// Then we can estimate `sqrt(y)` using
// `sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2**18`.
// There is no overflow risk here since `y < 2**136` after the first branch above.
z := shr(18, mul(z, add(shr(r, x), 65536))) // A `mul()` is saved from starting `z` at 181.
// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
// If `x+1` is a perfect square, the Babylonian method cycles between
// `floor(sqrt(x))` and `ceil(sqrt(x))`. This statement ensures we return floor.
// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
// Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
// If you don't care whether the floor or ceil square root is returned, you can remove this statement.
z := sub(z, lt(div(x, z), z))
}
}
/// @dev Returns the factorial of `x`.
function factorial(uint256 x) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
for {} 1 {} {
if iszero(lt(10, x)) {
// forgefmt: disable-next-item
result := and(
shr(mul(22, x), 0x375f0016260009d80004ec0002d00001e0000180000180000200000400001),
0x3fffff
)
break
}
if iszero(lt(57, x)) {
let end := 31
result := 8222838654177922817725562880000000
if iszero(lt(end, x)) {
end := 10
result := 3628800
}
for { let w := not(0) } 1 {} {
result := mul(result, x)
x := add(x, w)
if eq(x, end) { break }
}
break
}
// Store the function selector of `FactorialOverflow()`.
mstore(0x00, 0xaba0f2a2)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
}
}
/// @dev Returns the log2 of `x`.
/// Equivalent to computing the index of the most significant bit (MSB) of `x`.
function log2(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
if iszero(x) {
// Store the function selector of `Log2Undefined()`.
mstore(0x00, 0x5be3aa5c)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
// For the remaining 32 bits, use a De Bruijn lookup.
// See: https://graphics.stanford.edu/~seander/bithacks.html
x := shr(r, x)
x := or(x, shr(1, x))
x := or(x, shr(2, x))
x := or(x, shr(4, x))
x := or(x, shr(8, x))
x := or(x, shr(16, x))
// forgefmt: disable-next-item
r := or(r, byte(shr(251, mul(x, shl(224, 0x07c4acdd))),
0x0009010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f))
}
}
/// @dev Returns the log2 of `x`, rounded up.
function log2Up(uint256 x) internal pure returns (uint256 r) {
unchecked {
uint256 isNotPo2;
assembly {
isNotPo2 := iszero(iszero(and(x, sub(x, 1))))
}
return log2(x) + isNotPo2;
}
}
/// @dev Returns the average of `x` and `y`.
function avg(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = (x & y) + ((x ^ y) >> 1);
}
}
/// @dev Returns the average of `x` and `y`.
function avg(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = (x >> 1) + (y >> 1) + (((x & 1) + (y & 1)) >> 1);
}
}
/// @dev Returns the absolute value of `x`.
function abs(int256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
let mask := sub(0, shr(255, x))
z := xor(mask, add(mask, x))
}
}
/// @dev Returns the absolute distance between `x` and `y`.
function dist(int256 x, int256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
let a := sub(y, x)
z := xor(a, mul(xor(a, sub(x, y)), sgt(x, y)))
}
}
/// @dev Returns the minimum of `x` and `y`.
function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), lt(y, x)))
}
}
/// @dev Returns the minimum of `x` and `y`.
function min(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), slt(y, x)))
}
}
/// @dev Returns the maximum of `x` and `y`.
function max(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), gt(y, x)))
}
}
/// @dev Returns the maximum of `x` and `y`.
function max(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), sgt(y, x)))
}
}
/// @dev Returns `x`, bounded to `minValue` and `maxValue`.
function clamp(uint256 x, uint256 minValue, uint256 maxValue)
internal
pure
returns (uint256 z)
{
z = min(max(x, minValue), maxValue);
}
/// @dev Returns `x`, bounded to `minValue` and `maxValue`.
function clamp(int256 x, int256 minValue, int256 maxValue) internal pure returns (int256 z) {
z = min(max(x, minValue), maxValue);
}
/// @dev Returns greatest common divisor of `x` and `y`.
function gcd(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// forgefmt: disable-next-item
for { z := x } y {} {
let t := y
y := mod(z, y)
z := t
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* RAW NUMBER OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns `x + y`, without checking for overflow.
function rawAdd(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = x + y;
}
}
/// @dev Returns `x + y`, without checking for overflow.
function rawAdd(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = x + y;
}
}
/// @dev Returns `x - y`, without checking for underflow.
function rawSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = x - y;
}
}
/// @dev Returns `x - y`, without checking for underflow.
function rawSub(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = x - y;
}
}
/// @dev Returns `x * y`, without checking for overflow.
function rawMul(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = x * y;
}
}
/// @dev Returns `x * y`, without checking for overflow.
function rawMul(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = x * y;
}
}
/// @dev Returns `x / y`, returning 0 if `y` is zero.
function rawDiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := div(x, y)
}
}
/// @dev Returns `x / y`, returning 0 if `y` is zero.
function rawSDiv(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := sdiv(x, y)
}
}
/// @dev Returns `x % y`, returning 0 if `y` is zero.
function rawMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mod(x, y)
}
}
/// @dev Returns `x % y`, returning 0 if `y` is zero.
function rawSMod(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := smod(x, y)
}
}
/// @dev Returns `(x + y) % d`, return 0 if `d` if zero.
function rawAddMod(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := addmod(x, y, d)
}
}
/// @dev Returns `(x * y) % d`, return 0 if `d` if zero.
function rawMulMod(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mulmod(x, y, d)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
interface GaugeController {
struct VotedSlope {
uint256 slope;
uint256 power;
uint256 end;
}
struct Point {
uint256 bias;
uint256 slope;
}
function vote_user_slopes(address, address) external view returns (VotedSlope memory);
function add_gauge(address, int128) external;
function WEIGHT_VOTE_DELAY() external view returns (uint256);
function last_user_vote(address, address) external view returns (uint256);
function points_weight(address, uint256) external view returns (Point memory);
function checkpoint_gauge(address) external;
//solhint-disable-next-line
function gauge_types(address addr) external view returns (int128);
//solhint-disable-next-line
function gauge_relative_weight_write(address addr, uint256 timestamp) external returns (uint256);
//solhint-disable-next-line
function gauge_relative_weight(address addr) external view returns (uint256);
//solhint-disable-next-line
function gauge_relative_weight(address addr, uint256 timestamp) external view returns (uint256);
//solhint-disable-next-line
function get_total_weight() external view returns (uint256);
//solhint-disable-next-line
function get_gauge_weight(address addr) external view returns (uint256);
function vote_for_gauge_weights(address, uint256) external;
function add_type(string memory, uint256) external;
function admin() external view returns (address);
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Simple single owner authorization mixin.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol)
abstract contract Owned {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event OwnershipTransferred(address indexed user, address indexed newOwner);
/*//////////////////////////////////////////////////////////////
OWNERSHIP STORAGE
//////////////////////////////////////////////////////////////*/
address public owner;
modifier onlyOwner() virtual {
require(msg.sender == owner, "UNAUTHORIZED");
_;
}
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(address _owner) {
owner = _owner;
emit OwnershipTransferred(address(0), _owner);
}
/*//////////////////////////////////////////////////////////////
OWNERSHIP LOGIC
//////////////////////////////////////////////////////////////*/
function transferOwnership(address newOwner) public virtual onlyOwner {
owner = newOwner;
emit OwnershipTransferred(msg.sender, newOwner);
}
}
// SPDX-License-Identifier: Unlicense
pragma solidity 0.8.17;
/*
▄▄▄█████▓ ██░ ██ ▓█████ ██░ ██ ▓█████ ██▀███ ▓█████▄
▓ ██▒ ▓▒▓██░ ██▒▓█ ▀ ▓██░ ██▒▓█ ▀ ▓██ ▒ ██▒▒██▀ ██▌
▒ ▓██░ ▒░▒██▀▀██░▒███ ▒██▀▀██░▒███ ▓██ ░▄█ ▒░██ █▌
░ ▓██▓ ░ ░▓█ ░██ ▒▓█ ▄ ░▓█ ░██ ▒▓█ ▄ ▒██▀▀█▄ ░▓█▄ ▌
▒██▒ ░ ░▓█▒░██▓░▒████▒ ░▓█▒░██▓░▒████▒░██▓ ▒██▒░▒████▓
▒ ░░ ▒ ░░▒░▒░░ ▒░ ░ ▒ ░░▒░▒░░ ▒░ ░░ ▒▓ ░▒▓░ ▒▒▓ ▒
░ ▒ ░▒░ ░ ░ ░ ░ ▒ ░▒░ ░ ░ ░ ░ ░▒ ░ ▒░ ░ ▒ ▒
░ ░ ░░ ░ ░ ░ ░░ ░ ░ ░░ ░ ░ ░ ░
░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
░
.,;>>%%%%%>>;,.
.>%%%%%%%%%%%%%%%%%%%%>,.
.>%%%%%%%%%%%%%%%%%%>>,%%%%%%;,.
.>>>>%%%%%%%%%%%%%>>,%%%%%%%%%%%%,>>%%,.
.>>%>>>>%%%%%%%%%>>,%%%%%%%%%%%%%%%%%,>>%%%%%,.
.>>%%%%%>>%%%%>>,%%>>%%%%%%%%%%%%%%%%%%%%,>>%%%%%%%,
.>>%%%%%%%%%%>>,%%%%%%>>%%%%%%%%%%%%%%%%%%,>>%%%%%%%%%%.
.>>%%%%%%%%%%>>,>>>>%%%%%%%%%%'..`%%%%%%%%,;>>%%%%%%%%%>%%.
.>>%%%>>>%%%%%>,%%%%%%%%%%%%%%.%%%,`%%%%%%,;>>%%%%%%%%>>>%%%%.
>>%%>%>>>%>%%%>,%%%%%>>%%%%%%%%%%%%%`%%%%%%,>%%%%%%%>>>>%%%%%%%.
>>%>>>%%>>>%%%%>,%>>>%%%%%%%%%%%%%%%%`%%%%%%%%%%%%%%%%%%%%%%%%%%.
>>%%%%%%%%%%%%%%,>%%%%%%%%%%%%%%%%%%%'%%%,>>%%%%%%%%%%%%%%%%%%%%%.
>>%%%%%%%%%%%%%%%,>%%%>>>%%%%%%%%%%%%%%%,>>%%%%%%%%>>>>%%%%%%%%%%%.
>>%%%%%%%%;%;%;%%;,%>>>>%%%%%%%%%%%%%%%,>>>%%%%%%>>;";>>%%%%%%%%%%%%.
`>%%%%%%%%%;%;;;%;%,>%%%%%%%%%>>%%%%%%%%,>>>%%%%%%%%%%%%%%%%%%%%%%%%%%.
>>%%%%%%%%%,;;;;;%%>,%%%%%%%%>>>>%%%%%%%%,>>%%%%%%%%%%%%%%%%%%%%%%%%%%%.
`>>%%%%%%%%%,%;;;;%%%>,%%%%%%%%>>>>%%%%%%%%,>%%%%%%'%%%%%%%%%%%%%%%%%%%>>.
`>>%%%%%%%%%%>,;;%%%%%>>,%%%%%%%%>>%%%%%%';;;>%%%%%,`%%%%%%%%%%%%%%%>>%%>.
>>>%%%%%%%%%%>> %%%%%%%%>>,%%%%>>>%%%%%';;;;;;>>,%%%,`% `;>%%%%%%>>%%
`>>%%%%%%%%%%>> %%%%%%%%%>>>>>>>>;;;;'.;;;;;>>%%' `%%' ;>%%%%%>
>>%%%%%%%%%>>; %%%%%%%%>>;;;;;;'' ;;;;;>>%%% ;>%%%%
`>>%%%%%%%>>>, %%%%%%%%%>>;;' ;;;;>>%%%' ;>%%%
>>%%%%%%>>>':.%%%%%%%%%%>>; .;;;>>%%%% ;>%%%'
`>>%%%%%>>> ::`%%%%%%%%%%>>;. ;;;>>%%%%' ;>%%%'
`>>%%%%>>> `:::`%%%%%%%%%%>;. ;;>>%%%%% ;>%%'
`>>%%%%>>, `::::`%%%%%%%%%%>, .;>>%%%%%' ;>%'
`>>%%%%>>, `:::::`%%%%%%%%%>>. ;;>%%%%%% ;>%,
`>>%%%%>>, :::::::`>>>%%%%>>> ;;>%%%%%' ;>%,
`>>%%%%>>,::::::,>>>>>>>>>>' ;;>%%%%% ;%%,
>>%%%%>>,:::,%%>>>>>>>>' ;>%%%%%. ;%%
>>%%%%>>``%%%%%>>>>>' `>%%%%%%.
>>%%%%>> `@@a%%%%%%' .%%%%%%%%%.
`a@@a%@' `%a@@' `a@@a%a@@a
*/
import {Owned} from "solmate/auth/Owned.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";
import {GaugeController} from "src/interfaces/GaugeController.sol";
import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol";
/// @title Platform
/// @author Stake DAO
contract Platform is Owned, ReentrancyGuard {
using SafeTransferLib for ERC20;
using FixedPointMathLib for uint256;
////////////////////////////////////////////////////////////////
/// --- EMERGENCY SHUTDOWN
///////////////////////////////////////////////////////////////
/// @notice Emergency shutdown flag
bool public isKilled;
////////////////////////////////////////////////////////////////
/// --- STRUCTS
///////////////////////////////////////////////////////////////
/// @notice Bounty struct requirements.
struct Bounty {
// Address of the target gauge.
address gauge;
// Manager.
address manager;
// Address of the ERC20 used for rewards.
address rewardToken;
// Number of periods.
uint8 numberOfPeriods;
// Timestamp where the bounty become unclaimable.
uint256 endTimestamp;
// Max Price per vote.
uint256 maxRewardPerVote;
// Total Reward Added.
uint256 totalRewardAmount;
// Blacklisted addresses.
address[] blacklist;
}
struct Upgrade {
// Number of periods after increase.
uint8 numberOfPeriods;
// Total reward amount after increase.
uint256 totalRewardAmount;
// New max reward per vote after increase.
uint256 maxRewardPerVote;
// New end timestamp after increase.
uint256 endTimestamp;
}
/// @notice Period struct.
struct Period {
// Period id.
// Eg: 0 is the first period, 1 is the second period, etc.
uint8 id;
// Timestamp of the period start.
uint256 timestamp;
// Reward amount distributed during the period.
uint256 rewardPerPeriod;
}
////////////////////////////////////////////////////////////////
/// --- CONSTANTS & IMMUTABLES
///////////////////////////////////////////////////////////////
/// @notice Minimum duration a Bounty.
uint8 public constant MINIMUM_PERIOD = 2;
/// @notice Week in seconds.
uint256 private constant _WEEK = 1 weeks;
/// @notice Base unit for fixed point compute.
uint256 private constant _BASE_UNIT = 1e18;
/// @notice Default fee.
uint256 internal constant _DEFAULT_FEE = 2e16; // 2%
/// @notice Gauge Controller.
GaugeController public immutable gaugeController;
////////////////////////////////////////////////////////////////
/// --- STORAGE VARS
///////////////////////////////////////////////////////////////
/// @notice Fee.
uint256 public fee;
/// @notice Bounty ID Counter.
uint256 public nextID;
/// @notice Fee collector.
address public feeCollector;
/// @notice ID => Bounty.
mapping(uint256 => Bounty) public bounties;
/// @notice Recipient per address.
mapping(address => address) public recipient;
/// @notice Fee accrued per rewardToken.
mapping(address => uint256) public feeAccrued;
/// @notice BountyId => isUpgradeable. If true, the bounty can be upgraded.
mapping(uint256 => bool) public isUpgradeable;
/// @notice ID => Period running.
mapping(uint256 => Period) public activePeriod;
/// @notice ID => Amount Claimed per Bounty.
mapping(uint256 => uint256) public amountClaimed;
/// @notice ID => Amount of reward per vote distributed.
mapping(uint256 => uint256) public rewardPerVote;
/// @notice ID => Bounty In Queue to be upgraded.
mapping(uint256 => Upgrade) public upgradeBountyQueue;
/// @notice Blacklisted addresses per bounty that aren't counted for rewards arithmetics.
mapping(uint256 => mapping(address => bool)) public isBlacklisted;
/// @notice Last time a user claimed
mapping(address => mapping(uint256 => uint256)) public lastUserClaim;
////////////////////////////////////////////////////////////////
/// --- MODIFIERS
///////////////////////////////////////////////////////////////
modifier notKilled() {
if (isKilled) revert KILLED();
_;
}
modifier onlyManager(uint256 _id) {
if (msg.sender != bounties[_id].manager) revert AUTH_MANAGER_ONLY();
_;
}
////////////////////////////////////////////////////////////////
/// --- EVENTS
///////////////////////////////////////////////////////////////
/// @notice Emitted when a new bounty is created.
/// @param id Bounty ID.
/// @param gauge Gauge address.
/// @param manager Manager address.
/// @param rewardToken Reward token address.
/// @param numberOfPeriods Number of periods.
/// @param maxRewardPerVote Max reward per vote.
/// @param rewardPerPeriod Reward per period.
/// @param totalRewardAmount Total reward amount.
/// @param isUpgradeable If true, the bounty can be upgraded.
event BountyCreated(
uint256 indexed id,
address indexed gauge,
address manager,
address rewardToken,
uint8 numberOfPeriods,
uint256 maxRewardPerVote,
uint256 rewardPerPeriod,
uint256 totalRewardAmount,
bool isUpgradeable
);
/// @notice Emitted when a bounty is closed.
/// @param id Bounty ID.
/// @param remainingReward Remaining reward.
event BountyClosed(uint256 id, uint256 remainingReward);
/// @notice Emitted when a bounty period is rolled over.
/// @param id Bounty ID.
/// @param periodId Period ID.
/// @param timestamp Period timestamp.
/// @param rewardPerPeriod Reward per period.
event PeriodRolledOver(uint256 id, uint256 periodId, uint256 timestamp, uint256 rewardPerPeriod);
/// @notice Emitted on claim.
/// @param user User address.
/// @param rewardToken Reward token address.
/// @param bountyId Bounty ID.
/// @param amount Amount claimed.
/// @param protocolFees Protocol fees.
/// @param period Period timestamp.
event Claimed(
address indexed user,
address rewardToken,
uint256 indexed bountyId,
uint256 amount,
uint256 protocolFees,
uint256 period
);
/// @notice Emitted when a bounty is queued to upgrade.
/// @param id Bounty ID.
/// @param numberOfPeriods Number of periods.
/// @param totalRewardAmount Total reward amount.
/// @param maxRewardPerVote Max reward per vote.
event BountyDurationIncreaseQueued(
uint256 id, uint8 numberOfPeriods, uint256 totalRewardAmount, uint256 maxRewardPerVote
);
/// @notice Emitted when a bounty is upgraded.
/// @param id Bounty ID.
/// @param numberOfPeriods Number of periods.
/// @param totalRewardAmount Total reward amount.
/// @param maxRewardPerVote Max reward per vote.
event BountyDurationIncrease(
uint256 id, uint8 numberOfPeriods, uint256 totalRewardAmount, uint256 maxRewardPerVote
);
/// @notice Emitted when a bounty manager is updated.
/// @param id Bounty ID.
/// @param manager Manager address.
event ManagerUpdated(uint256 id, address indexed manager);
/// @notice Emitted when a recipient is set for an address.
/// @param sender Sender address.
/// @param recipient Recipient address.
event RecipientSet(address indexed sender, address indexed recipient);
/// @notice Emitted when fee is updated.
/// @param fee Fee.
event FeeUpdated(uint256 fee);
/// @notice Emitted when fee collector is updated.
/// @param feeCollector Fee collector.
event FeeCollectorUpdated(address feeCollector);
/// @notice Emitted when fees are collected.
/// @param rewardToken Reward token address.
/// @param amount Amount collected.
event FeesCollected(address indexed rewardToken, uint256 amount);
////////////////////////////////////////////////////////////////
/// --- ERRORS
///////////////////////////////////////////////////////////////
error KILLED();
error WRONG_INPUT();
error ZERO_ADDRESS();
error ALREADY_CLOSED();
error NO_PERIODS_LEFT();
error NOT_UPGRADEABLE();
error AUTH_MANAGER_ONLY();
error INVALID_NUMBER_OF_PERIODS();
////////////////////////////////////////////////////////////////
/// --- CONSTRUCTOR
///////////////////////////////////////////////////////////////
/// @notice Create Bounty platform.
/// @param _gaugeController Address of the gauge controller.
constructor(address _gaugeController, address _feeCollector, address _owner) Owned(_owner) {
fee = _DEFAULT_FEE;
feeCollector = _feeCollector;
gaugeController = GaugeController(_gaugeController);
}
////////////////////////////////////////////////////////////////
/// --- BOUNTY CREATION LOGIC
///////////////////////////////////////////////////////////////
/// @notice Create a new bounty.
/// @param gauge Address of the target gauge.
/// @param rewardToken Address of the ERC20 used or rewards.
/// @param numberOfPeriods Number of periods.
/// @param maxRewardPerVote Target Bias for the Gauge.
/// @param totalRewardAmount Total Reward Added.
/// @param blacklist Array of addresses to blacklist.
/// @return newBountyId of the bounty created.
function createBounty(
address gauge,
address manager,
address rewardToken,
uint8 numberOfPeriods,
uint256 maxRewardPerVote,
uint256 totalRewardAmount,
address[] calldata blacklist,
bool upgradeable
) external nonReentrant notKilled returns (uint256 newBountyId) {
if (gaugeController.gauge_types(gauge) < 0) return newBountyId;
if (numberOfPeriods < MINIMUM_PERIOD) revert INVALID_NUMBER_OF_PERIODS();
if (totalRewardAmount == 0 || maxRewardPerVote == 0) revert WRONG_INPUT();
if (rewardToken == address(0) || manager == address(0)) revert ZERO_ADDRESS();
// Transfer the rewards to the contracts.
SafeTransferLib.safeTransferFrom(rewardToken, msg.sender, address(this), totalRewardAmount);
unchecked {
// Get the ID for that new Bounty and increment the nextID counter.
newBountyId = nextID;
++nextID;
}
uint256 rewardPerPeriod = totalRewardAmount.mulDiv(1, numberOfPeriods);
uint256 currentPeriod = getCurrentPeriod();
bounties[newBountyId] = Bounty({
gauge: gauge,
manager: manager,
rewardToken: rewardToken,
numberOfPeriods: numberOfPeriods,
endTimestamp: currentPeriod + ((numberOfPeriods + 1) * _WEEK),
maxRewardPerVote: maxRewardPerVote,
totalRewardAmount: totalRewardAmount,
blacklist: blacklist
});
emit BountyCreated(
newBountyId,
gauge,
manager,
rewardToken,
numberOfPeriods,
maxRewardPerVote,
rewardPerPeriod,
totalRewardAmount,
upgradeable
);
// Set Upgradeable status.
isUpgradeable[newBountyId] = upgradeable;
// Starting from next period.
activePeriod[newBountyId] = Period(0, currentPeriod + _WEEK, rewardPerPeriod);
// Add the addresses to the blacklist.
uint256 length = blacklist.length;
for (uint256 i = 0; i < length;) {
isBlacklisted[newBountyId][blacklist[i]] = true;
unchecked {
++i;
}
}
}
/// @notice Claim rewards for a given bounty.
/// @param bountyId ID of the bounty.
/// @return Amount of rewards claimed.
function claim(uint256 bountyId) external returns (uint256) {
return _claim(msg.sender, msg.sender, bountyId);
}
/// @notice Claim rewards for a given bounty.
/// @param bountyId ID of the bounty.
/// @return Amount of rewards claimed.
function claim(uint256 bountyId, address _recipient) external returns (uint256) {
return _claim(msg.sender, _recipient, bountyId);
}
/// @notice Claim rewards for a given bounty.
/// @param bountyId ID of the bounty.
/// @return Amount of rewards claimed.
function claimFor(address user, uint256 bountyId) external returns (uint256) {
address _recipient = recipient[user];
return _claim(user, _recipient != address(0) ? _recipient : user, bountyId);
}
/// @notice Claim all rewards for multiple bounties.
/// @param ids Array of bounty IDs to claim.
function claimAll(uint256[] calldata ids) external {
uint256 length = ids.length;
for (uint256 i = 0; i < length;) {
uint256 id = ids[i];
_claim(msg.sender, msg.sender, id);
unchecked {
++i;
}
}
}
/// @notice Claim all rewards for multiple bounties to a given recipient.
/// @param ids Array of bounty IDs to claim.
/// @param _recipient Address to send the rewards to.
function claimAll(uint256[] calldata ids, address _recipient) external {
uint256 length = ids.length;
for (uint256 i = 0; i < length;) {
uint256 id = ids[i];
_claim(msg.sender, _recipient, id);
unchecked {
++i;
}
}
}
/// @notice Claim all rewards for multiple bounties on behalf of a user.
/// @param ids Array of bounty IDs to claim.
/// @param _user Address to claim the rewards for.
function claimAllFor(address _user, uint256[] calldata ids) external {
address _recipient = recipient[_user];
uint256 length = ids.length;
for (uint256 i = 0; i < length;) {
uint256 id = ids[i];
_claim(_user, _recipient != address(0) ? _recipient : _user, id);
unchecked {
++i;
}
}
}
/// @notice Update Bounty for a given id.
/// @param bountyId ID of the bounty.
function updateBountyPeriod(uint256 bountyId) external {
_updateBountyPeriod(bountyId);
}
/// @notice Update multiple bounties for given ids.
/// @param ids Array of Bounty IDs.
function updateBountyPeriods(uint256[] calldata ids) external {
uint256 length = ids.length;
for (uint256 i = 0; i < length;) {
_updateBountyPeriod(ids[i]);
unchecked {
++i;
}
}
}
/// @notice Set a recipient address for calling user.
/// @param _recipient Address of the recipient.
/// @dev Recipient are used when calling claimFor functions. Regular claim functions will use msg.sender as recipient,
/// or recipient parameter provided if called by msg.sender.
function setRecipient(address _recipient) external {
recipient[msg.sender] = _recipient;
emit RecipientSet(msg.sender, _recipient);
}
////////////////////////////////////////////////////////////////
/// --- INTERNAL LOGIC
///////////////////////////////////////////////////////////////
/// @notice Claim rewards for a given bounty.
/// @param _user Address of the user.
/// @param _recipient Address of the recipient.
/// @param _bountyId ID of the bounty.
/// @return amount of rewards claimed.
function _claim(address _user, address _recipient, uint256 _bountyId)
internal
nonReentrant
notKilled
returns (uint256 amount)
{
if (isBlacklisted[_bountyId][_user]) return 0;
// Update if needed the current period.
uint256 currentPeriod = _updateBountyPeriod(_bountyId);
Bounty storage bounty = bounties[_bountyId];
// Get the last_vote timestamp.
uint256 lastVote = gaugeController.last_user_vote(_user, bounty.gauge);
GaugeController.VotedSlope memory userSlope = gaugeController.vote_user_slopes(_user, bounty.gauge);
if (
userSlope.slope == 0 || lastUserClaim[_user][_bountyId] >= currentPeriod || currentPeriod >= userSlope.end
|| currentPeriod <= lastVote || currentPeriod >= bounty.endTimestamp || currentPeriod != getCurrentPeriod()
|| amountClaimed[_bountyId] == bounty.totalRewardAmount
) return 0;
// Update User last claim period.
lastUserClaim[_user][_bountyId] = currentPeriod;
// Voting Power = userSlope * dt
// with dt = lock_end - period.
uint256 _bias = _getAddrBias(userSlope.slope, userSlope.end, currentPeriod);
// Compute the reward amount based on
// Reward / Total Votes.
amount = _bias.mulWad(rewardPerVote[_bountyId]);
// Compute the reward amount based on
// the max price to pay.
uint256 _amountWithMaxPrice = _bias.mulWad(bounty.maxRewardPerVote);
// Distribute the _min between the amount based on votes, and price.
amount = FixedPointMathLib.min(amount, _amountWithMaxPrice);
// Update the amount claimed.
uint256 _amountClaimed = amountClaimed[_bountyId];
if (amount + _amountClaimed > bounty.totalRewardAmount) {
amount = bounty.totalRewardAmount - _amountClaimed;
}
amountClaimed[_bountyId] += amount;
uint256 feeAmount;
if (fee != 0) {
feeAmount = amount.mulWad(fee);
amount -= feeAmount;
feeAccrued[bounty.rewardToken] += feeAmount;
}
// Transfer to user.
SafeTransferLib.safeTransfer(bounty.rewardToken, _recipient, amount);
emit Claimed(_user, bounty.rewardToken, _bountyId, amount, feeAmount, currentPeriod);
}
/// @notice Update the current period for a given bounty.
/// @param bountyId Bounty ID.
/// @return current/updated period.
function _updateBountyPeriod(uint256 bountyId) internal returns (uint256) {
Period storage _activePeriod = activePeriod[bountyId];
uint256 currentPeriod = getCurrentPeriod();
if (_activePeriod.id == 0 && currentPeriod == _activePeriod.timestamp) {
// Check if there is an upgrade in queue and update the bounty.
_checkForUpgrade(bountyId);
// Initialize reward per token.
// Only for the first period, and if not already initialized.
_updateRewardPerToken(bountyId, currentPeriod);
}
// Increase Period
if (block.timestamp >= _activePeriod.timestamp + _WEEK) {
// Checkpoint gauge to have up to date gauge weight.
gaugeController.checkpoint_gauge(bounties[bountyId].gauge);
// Check if there is an upgrade in queue and update the bounty.
_checkForUpgrade(bountyId);
// Roll to next period.
_rollOverToNextPeriod(bountyId, currentPeriod);
return currentPeriod;
}
return _activePeriod.timestamp;
}
/// @notice Checks for an upgrade and update the bounty.
function _checkForUpgrade(uint256 bountyId) internal {
Upgrade storage upgradedBounty = upgradeBountyQueue[bountyId];
// Check if there is an upgrade in queue.
if (upgradedBounty.totalRewardAmount != 0) {
// Save new values.
bounties[bountyId].endTimestamp = upgradedBounty.endTimestamp;
bounties[bountyId].numberOfPeriods = upgradedBounty.numberOfPeriods;
bounties[bountyId].maxRewardPerVote = upgradedBounty.maxRewardPerVote;
bounties[bountyId].totalRewardAmount = upgradedBounty.totalRewardAmount;
if (activePeriod[bountyId].id == 0) {
activePeriod[bountyId].rewardPerPeriod =
upgradedBounty.totalRewardAmount.mulDiv(1, upgradedBounty.numberOfPeriods);
}
emit BountyDurationIncrease(
bountyId,
upgradedBounty.numberOfPeriods,
upgradedBounty.totalRewardAmount,
upgradedBounty.maxRewardPerVote
);
// Reset the next values.
delete upgradeBountyQueue[bountyId];
}
}
/// @notice Roll over to next period.
/// @param bountyId Bounty ID.
/// @param currentPeriod Next period timestamp.
function _rollOverToNextPeriod(uint256 bountyId, uint256 currentPeriod) internal {
uint8 index = getActivePeriodPerBounty(bountyId);
Bounty storage bounty = bounties[bountyId];
uint256 periodsLeft = getPeriodsLeft(bountyId);
uint256 rewardPerPeriod;
rewardPerPeriod = bounty.totalRewardAmount - amountClaimed[bountyId];
if (bounty.endTimestamp > currentPeriod + _WEEK && periodsLeft > 1) {
rewardPerPeriod = rewardPerPeriod.mulDiv(1, periodsLeft);
}
// Get adjusted slope without blacklisted addresses.
uint256 gaugeBias = _getAdjustedBias(bounty.gauge, bounty.blacklist, currentPeriod);
rewardPerVote[bountyId] = rewardPerPeriod.mulDiv(_BASE_UNIT, gaugeBias);
activePeriod[bountyId] = Period(index, currentPeriod, rewardPerPeriod);
emit PeriodRolledOver(bountyId, index, currentPeriod, rewardPerPeriod);
}
/// @notice Update the amount of reward per token for a given bounty.
/// @dev This function is only called once per Bounty.
function _updateRewardPerToken(uint256 bountyId, uint256 currentPeriod) internal {
if (rewardPerVote[bountyId] == 0) {
Bounty storage bounty = bounties[bountyId];
// Checkpoint gauge to have up to date gauge weight.
gaugeController.checkpoint_gauge(bounty.gauge);
uint256 gaugeBias = _getAdjustedBias(bounty.gauge, bounty.blacklist, currentPeriod);
if (gaugeBias != 0) {
rewardPerVote[bountyId] = activePeriod[bountyId].rewardPerPeriod.mulDiv(_BASE_UNIT, gaugeBias);
}
}
}
////////////////////////////////////////////////////////////////
/// --- VIEWS
///////////////////////////////////////////////////////////////
/// @notice Get an estimate of the reward amount for a given user.
/// @param user Address of the user.
/// @param bountyId ID of the bounty.
/// @return amount of rewards.
/// Mainly used for UI.
function claimable(address user, uint256 bountyId) external view returns (uint256 amount) {
if (isBlacklisted[bountyId][user]) return 0;
Bounty memory bounty = bounties[bountyId];
// If there is an upgrade in progress but period hasn't been rolled over yet.
Upgrade storage upgradedBounty = upgradeBountyQueue[bountyId];
// Update if needed the current period.
uint256 currentPeriod = getCurrentPeriod();
// End timestamp of the bounty.
uint256 endTimestamp = FixedPointMathLib.max(bounty.endTimestamp, upgradedBounty.endTimestamp);
// Get the last_vote timestamp.
uint256 lastVote = gaugeController.last_user_vote(user, bounty.gauge);
GaugeController.VotedSlope memory userSlope = gaugeController.vote_user_slopes(user, bounty.gauge);
if (
userSlope.slope == 0 || lastUserClaim[user][bountyId] >= currentPeriod || currentPeriod >= userSlope.end
|| currentPeriod <= lastVote || currentPeriod >= endTimestamp
|| currentPeriod < getActivePeriod(bountyId).timestamp
|| amountClaimed[bountyId] >= bounty.totalRewardAmount
) return 0;
uint256 _rewardPerVote = rewardPerVote[bountyId];
// If period updated.
if (_rewardPerVote == 0 || (_rewardPerVote > 0 && getActivePeriod(bountyId).timestamp != currentPeriod)) {
uint256 _rewardPerPeriod;
if (upgradedBounty.numberOfPeriods != 0) {
// Update max reward per vote.
bounty.maxRewardPerVote = upgradedBounty.maxRewardPerVote;
bounty.totalRewardAmount = upgradedBounty.totalRewardAmount;
}
uint256 periodsLeft = endTimestamp > currentPeriod ? (endTimestamp - currentPeriod) / _WEEK : 0;
_rewardPerPeriod = bounty.totalRewardAmount - amountClaimed[bountyId];
if (endTimestamp > currentPeriod + _WEEK && periodsLeft > 1) {
_rewardPerPeriod = _rewardPerPeriod.mulDiv(1, periodsLeft);
}
// Get Adjusted Slope without blacklisted addresses weight.
uint256 gaugeBias = _getAdjustedBias(bounty.gauge, bounty.blacklist, currentPeriod);
_rewardPerVote = _rewardPerPeriod.mulDiv(_BASE_UNIT, gaugeBias);
}
// Get user voting power.
uint256 _bias = _getAddrBias(userSlope.slope, userSlope.end, currentPeriod);
// Estimation of the amount of rewards.
amount = _bias.mulWad(_rewardPerVote);
// Compute the reward amount based on
// the max price to pay.
uint256 _amountWithMaxPrice = _bias.mulWad(bounty.maxRewardPerVote);
// Distribute the _min between the amount based on votes, and price.
amount = FixedPointMathLib.min(amount, _amountWithMaxPrice);
uint256 _amountClaimed = amountClaimed[bountyId];
// Update the amount claimed.
if (amount + _amountClaimed > bounty.totalRewardAmount) {
amount = bounty.totalRewardAmount - _amountClaimed;
}
// Substract fees.
if (fee != 0) {
amount = amount.mulWad(_BASE_UNIT - fee);
}
}
////////////////////////////////////////////////////////////////
/// --- INTERNAL VIEWS
///////////////////////////////////////////////////////////////
/// @notice Get adjusted slope from Gauge Controller for a given gauge address.
/// Remove the weight of blacklisted addresses.
/// @param gauge Address of the gauge.
/// @param _addressesBlacklisted Array of blacklisted addresses.
/// @param period Timestamp to check vote weight.
function _getAdjustedBias(address gauge, address[] memory _addressesBlacklisted, uint256 period)
internal
view
returns (uint256 gaugeBias)
{
// Cache the user slope.
GaugeController.VotedSlope memory userSlope;
// Bias
uint256 _bias;
// Last Vote
uint256 _lastVote;
// Cache the length of the array.
uint256 length = _addressesBlacklisted.length;
// Cache blacklist.
// Get the gauge slope.
gaugeBias = gaugeController.points_weight(gauge, period).bias;
for (uint256 i = 0; i < length;) {
// Get the user slope.
userSlope = gaugeController.vote_user_slopes(_addressesBlacklisted[i], gauge);
_lastVote = gaugeController.last_user_vote(_addressesBlacklisted[i], gauge);
if (period > _lastVote) {
_bias = _getAddrBias(userSlope.slope, userSlope.end, period);
gaugeBias -= _bias;
}
// Increment i.
unchecked {
++i;
}
}
}
////////////////////////////////////////////////////////////////
/// --- MANAGEMENT LOGIC
///////////////////////////////////////////////////////////////
/// @notice Increase Bounty duration.
/// @param _bountyId ID of the bounty.
/// @param _additionnalPeriods Number of periods to add.
/// @param _increasedAmount Total reward amount to add.
/// @param _newMaxPricePerVote Total reward amount to add.
function increaseBountyDuration(
uint256 _bountyId,
uint8 _additionnalPeriods,
uint256 _increasedAmount,
uint256 _newMaxPricePerVote
) external nonReentrant notKilled onlyManager(_bountyId) {
if (!isUpgradeable[_bountyId]) revert NOT_UPGRADEABLE();
if (getPeriodsLeft(_bountyId) < 1) revert NO_PERIODS_LEFT();
if (_increasedAmount == 0 || _newMaxPricePerVote == 0) {
revert WRONG_INPUT();
}
Bounty storage bounty = bounties[_bountyId];
Upgrade memory upgradedBounty = upgradeBountyQueue[_bountyId];
SafeTransferLib.safeTransferFrom(bounty.rewardToken, msg.sender, address(this), _increasedAmount);
if (upgradedBounty.totalRewardAmount != 0) {
upgradedBounty = Upgrade({
numberOfPeriods: upgradedBounty.numberOfPeriods + _additionnalPeriods,
totalRewardAmount: upgradedBounty.totalRewardAmount + _increasedAmount,
maxRewardPerVote: _newMaxPricePerVote,
endTimestamp: upgradedBounty.endTimestamp + (_additionnalPeriods * _WEEK)
});
} else {
upgradedBounty = Upgrade({
numberOfPeriods: bounty.numberOfPeriods + _additionnalPeriods,
totalRewardAmount: bounty.totalRewardAmount + _increasedAmount,
maxRewardPerVote: _newMaxPricePerVote,
endTimestamp: bounty.endTimestamp + (_additionnalPeriods * _WEEK)
});
}
upgradeBountyQueue[_bountyId] = upgradedBounty;
emit BountyDurationIncreaseQueued(
_bountyId, upgradedBounty.numberOfPeriods, upgradedBounty.totalRewardAmount, _newMaxPricePerVote
);
}
/// @notice Close Bounty if there is remaining.
/// @param bountyId ID of the bounty to close.
function closeBounty(uint256 bountyId) external nonReentrant {
// Check if the currentPeriod is the last one.
// If not, we can increase the duration.
Bounty storage bounty = bounties[bountyId];
if (bounty.manager == address(0)) revert ALREADY_CLOSED();
if (getCurrentPeriod() >= bounty.endTimestamp || isKilled) {
uint256 leftOver;
Upgrade memory upgradedBounty = upgradeBountyQueue[bountyId];
if (upgradedBounty.totalRewardAmount != 0) {
leftOver = upgradedBounty.totalRewardAmount - amountClaimed[bountyId];
delete upgradeBountyQueue[bountyId];
} else {
leftOver = bounties[bountyId].totalRewardAmount - amountClaimed[bountyId];
}
// Transfer the left over to the owner.
SafeTransferLib.safeTransfer(bounty.rewardToken, bounty.manager, leftOver);
delete bounties[bountyId].manager;
emit BountyClosed(bountyId, leftOver);
}
}
/// @notice Update Bounty Manager.
/// @param bountyId ID of the bounty.
/// @param newManager Address of the new manager.
function updateManager(uint256 bountyId, address newManager) external onlyManager(bountyId) {
emit ManagerUpdated(bountyId, bounties[bountyId].manager = newManager);
}
////////////////////////////////////////////////////////////////
/// --- ONLY OWNER FUNCTIONS
///////////////////////////////////////////////////////////////
/// @notice Claim fees.
/// @param rewardTokens Array of reward tokens.
function claimFees(address[] calldata rewardTokens) external nonReentrant {
uint256 _feeAccrued;
uint256 length = rewardTokens.length;
for (uint256 i = 0; i < length;) {
address rewardToken = rewardTokens[i];
_feeAccrued = feeAccrued[rewardToken];
delete feeAccrued[rewardToken];
emit FeesCollected(rewardToken, _feeAccrued);
SafeTransferLib.safeTransfer(rewardToken, feeCollector, _feeAccrued);
unchecked {
i++;
}
}
}
/// @notice Set the platform fee.
/// @param _platformFee Platform fee.
function setPlatformFee(uint256 _platformFee) external onlyOwner {
fee = _platformFee;
emit FeeUpdated(_platformFee);
}
/// @notice Set the fee collector.
/// @param _feeCollector Address of the fee collector.
function setFeeCollector(address _feeCollector) external onlyOwner {
feeCollector = _feeCollector;
emit FeeCollectorUpdated(_feeCollector);
}
/// @notice Set the recipient for a given address.
/// @param _for Address to set the recipient for.
/// @param _recipient Address of the recipient.
function setRecipientFor(address _for, address _recipient) external onlyOwner {
recipient[_for] = _recipient;
emit RecipientSet(_for, _recipient);
}
function kill() external onlyOwner {
isKilled = true;
}
////////////////////////////////////////////////////////////////
/// --- UTILS FUNCTIONS
///////////////////////////////////////////////////////////////
/// @notice Returns the number of periods left for a given bounty.
/// @param bountyId ID of the bounty.
function getPeriodsLeft(uint256 bountyId) public view returns (uint256 periodsLeft) {
Bounty storage bounty = bounties[bountyId];
uint256 currentPeriod = getCurrentPeriod();
periodsLeft = bounty.endTimestamp > currentPeriod ? (bounty.endTimestamp - currentPeriod) / _WEEK : 0;
}
/// @notice Return the bounty object for a given ID.
/// @param bountyId ID of the bounty.
function getBounty(uint256 bountyId) external view returns (Bounty memory) {
return bounties[bountyId];
}
/// @notice Return the bounty in queue for a given ID.
/// @dev Can return an empty bounty if there is no upgrade.
/// @param bountyId ID of the bounty.
function getUpgradedBountyQueued(uint256 bountyId) external view returns (Upgrade memory) {
return upgradeBountyQueue[bountyId];
}
/// @notice Return the blacklisted addresses of a bounty for a given ID.
/// @param bountyId ID of the bounty.
function getBlacklistedAddressesPerBounty(uint256 bountyId) external view returns (address[] memory) {
return bounties[bountyId].blacklist;
}
/// @notice Return the active period running of bounty given an ID.
/// @param bountyId ID of the bounty.
function getActivePeriod(uint256 bountyId) public view returns (Period memory) {
return activePeriod[bountyId];
}
/// @notice Return the expected current period id.
/// @param bountyId ID of the bounty.
function getActivePeriodPerBounty(uint256 bountyId) public view returns (uint8) {
Bounty storage bounty = bounties[bountyId];
uint256 currentPeriod = getCurrentPeriod();
uint256 periodsLeft = bounty.endTimestamp > currentPeriod ? (bounty.endTimestamp - currentPeriod) / _WEEK : 0;
// If periodsLeft is superior, then the bounty didn't start yet.
return uint8(periodsLeft > bounty.numberOfPeriods ? 0 : bounty.numberOfPeriods - periodsLeft);
}
/// @notice Return the current period based on Gauge Controller rounding.
function getCurrentPeriod() public view returns (uint256) {
return (block.timestamp / _WEEK) * _WEEK;
}
/// @notice Return the bias of a given address based on its lock end date and the current period.
/// @param userSlope User slope.
/// @param endLockTime Lock end date of the address.
/// @param currentPeriod Current period.
function _getAddrBias(uint256 userSlope, uint256 endLockTime, uint256 currentPeriod)
internal
pure
returns (uint256)
{
if (currentPeriod + _WEEK >= endLockTime) return 0;
return userSlope * (endLockTime - currentPeriod);
}
function getVersion() external pure returns (string memory) {
return "2.1.0";
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
uint256 private locked = 1;
modifier nonReentrant() virtual {
require(locked == 1, "REENTRANCY");
locked = 2;
_;
locked = 1;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Caution! This library won't check that a token has code, responsibility is delegated to the caller.
library SafeTransferLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ETH transfer has failed.
error ETHTransferFailed();
/// @dev The ERC20 `transferFrom` has failed.
error TransferFromFailed();
/// @dev The ERC20 `transfer` has failed.
error TransferFailed();
/// @dev The ERC20 `approve` has failed.
error ApproveFailed();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Suggested gas stipend for contract receiving ETH
/// that disallows any storage writes.
uint256 internal constant _GAS_STIPEND_NO_STORAGE_WRITES = 2300;
/// @dev Suggested gas stipend for contract receiving ETH to perform a few
/// storage reads and writes, but low enough to prevent griefing.
/// Multiply by a small constant (e.g. 2), if needed.
uint256 internal constant _GAS_STIPEND_NO_GRIEF = 100000;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ETH OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sends `amount` (in wei) ETH to `to`.
/// Reverts upon failure.
function safeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
// Transfer the ETH and check if it succeeded or not.
if iszero(call(gas(), to, amount, 0, 0, 0, 0)) {
// Store the function selector of `ETHTransferFailed()`.
mstore(0x00, 0xb12d13eb)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
/// The `gasStipend` can be set to a low enough value to prevent
/// storage writes or gas griefing.
///
/// If sending via the normal procedure fails, force sends the ETH by
/// creating a temporary contract which uses `SELFDESTRUCT` to force send the ETH.
///
/// Reverts if the current contract has insufficient balance.
function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
// If insufficient balance, revert.
if lt(selfbalance(), amount) {
// Store the function selector of `ETHTransferFailed()`.
mstore(0x00, 0xb12d13eb)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// Transfer the ETH and check if it succeeded or not.
if iszero(call(gasStipend, to, amount, 0, 0, 0, 0)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
// We can directly use `SELFDESTRUCT` in the contract creation.
// Compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758
pop(create(amount, 0x0b, 0x16))
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with a gas stipend
/// equal to `_GAS_STIPEND_NO_GRIEF`. This gas stipend is a reasonable default
/// for 99% of cases and can be overriden with the three-argument version of this
/// function if necessary.
///
/// If sending via the normal procedure fails, force sends the ETH by
/// creating a temporary contract which uses `SELFDESTRUCT` to force send the ETH.
///
/// Reverts if the current contract has insufficient balance.
function forceSafeTransferETH(address to, uint256 amount) internal {
// Manually inlined because the compiler doesn't inline functions with branches.
/// @solidity memory-safe-assembly
assembly {
// If insufficient balance, revert.
if lt(selfbalance(), amount) {
// Store the function selector of `ETHTransferFailed()`.
mstore(0x00, 0xb12d13eb)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// Transfer the ETH and check if it succeeded or not.
if iszero(call(_GAS_STIPEND_NO_GRIEF, to, amount, 0, 0, 0, 0)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
// We can directly use `SELFDESTRUCT` in the contract creation.
// Compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758
pop(create(amount, 0x0b, 0x16))
}
}
}
/// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
/// The `gasStipend` can be set to a low enough value to prevent
/// storage writes or gas griefing.
///
/// Simply use `gasleft()` for `gasStipend` if you don't need a gas stipend.
///
/// Note: Does NOT revert upon failure.
/// Returns whether the transfer of ETH is successful instead.
function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
// Transfer the ETH and check if it succeeded or not.
success := call(gasStipend, to, amount, 0, 0, 0, 0)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for
/// the current contract to manage.
function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
// Store the function selector of `transferFrom(address,address,uint256)`.
mstore(0x00, 0x23b872dd)
mstore(0x20, from) // Store the `from` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x60, amount) // Store the `amount` argument.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(eq(mload(0x00), 1), iszero(returndatasize())),
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
// Store the function selector of `TransferFromFailed()`.
mstore(0x00, 0x7939f424)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends all of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for
/// the current contract to manage.
function safeTransferAllFrom(address token, address from, address to)
internal
returns (uint256 amount)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
mstore(0x20, from) // Store the `from` argument.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
)
) {
// Store the function selector of `TransferFromFailed()`.
mstore(0x00, 0x7939f424)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// Store the function selector of `transferFrom(address,address,uint256)`.
mstore(0x00, 0x23b872dd)
mstore(0x40, to) // Store the `to` argument.
// The `amount` argument is already written to the memory word at 0x6a.
amount := mload(0x60)
if iszero(
and( // The arguments of `and` are evaluated from right to left.
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(eq(mload(0x00), 1), iszero(returndatasize())),
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
// Store the function selector of `TransferFromFailed()`.
mstore(0x00, 0x7939f424)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransfer(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x1a, to) // Store the `to` argument.
mstore(0x3a, amount) // Store the `amount` argument.
// Store the function selector of `transfer(address,uint256)`,
// left by 6 bytes (enough for 8tb of memory represented by the free memory pointer).
// We waste 6-3 = 3 bytes to save on 6 runtime gas (PUSH1 0x224 SHL).
mstore(0x00, 0xa9059cbb000000000000)
if iszero(
and( // The arguments of `and` are evaluated from right to left.
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(eq(mload(0x00), 1), iszero(returndatasize())),
call(gas(), token, 0, 0x16, 0x44, 0x00, 0x20)
)
) {
// Store the function selector of `TransferFailed()`.
mstore(0x00, 0x90b8ec18)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// Restore the part of the free memory pointer that was overwritten,
// which is guaranteed to be zero, if less than 8tb of memory is used.
mstore(0x3a, 0)
}
}
/// @dev Sends all of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransferAll(address token, address to) internal returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
mstore(0x20, address()) // Store the address of the current contract.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x3a, 0x20)
)
) {
// Store the function selector of `TransferFailed()`.
mstore(0x00, 0x90b8ec18)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
mstore(0x1a, to) // Store the `to` argument.
// The `amount` argument is already written to the memory word at 0x3a.
amount := mload(0x3a)
// Store the function selector of `transfer(address,uint256)`,
// left by 6 bytes (enough for 8tb of memory represented by the free memory pointer).
// We waste 6-3 = 3 bytes to save on 6 runtime gas (PUSH1 0x224 SHL).
mstore(0x00, 0xa9059cbb000000000000)
if iszero(
and( // The arguments of `and` are evaluated from right to left.
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(eq(mload(0x00), 1), iszero(returndatasize())),
call(gas(), token, 0, 0x16, 0x44, 0x00, 0x20)
)
) {
// Store the function selector of `TransferFailed()`.
mstore(0x00, 0x90b8ec18)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// Restore the part of the free memory pointer that was overwritten,
// which is guaranteed to be zero, if less than 8tb of memory is used.
mstore(0x3a, 0)
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// Reverts upon failure.
function safeApprove(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x1a, to) // Store the `to` argument.
mstore(0x3a, amount) // Store the `amount` argument.
// Store the function selector of `approve(address,uint256)`,
// left by 6 bytes (enough for 8tb of memory represented by the free memory pointer).
// We waste 6-3 = 3 bytes to save on 6 runtime gas (PUSH1 0x224 SHL).
mstore(0x00, 0x095ea7b3000000000000)
if iszero(
and( // The arguments of `and` are evaluated from right to left.
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(eq(mload(0x00), 1), iszero(returndatasize())),
call(gas(), token, 0, 0x16, 0x44, 0x00, 0x20)
)
) {
// Store the function selector of `ApproveFailed()`.
mstore(0x00, 0x3e3f8f73)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// Restore the part of the free memory pointer that was overwritten,
// which is guaranteed to be zero, if less than 8tb of memory is used.
mstore(0x3a, 0)
}
}
/// @dev Returns the amount of ERC20 `token` owned by `account`.
/// Returns zero if the `token` does not exist.
function balanceOf(address token, address account) internal view returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
mstore(0x20, account) // Store the `account` argument.
amount :=
mul(
mload(0x20),
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x20, 0x20)
)
)
}
}
}
{
"compilationTarget": {
"src/Platform.sol": "Platform"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 99999999
},
"remappings": [
":ds-test/=lib/forge-std/lib/ds-test/src/",
":forge-std/=lib/forge-std/src/",
":solady/=lib/solady/src/",
":solmate/=lib/solmate/src/"
],
"viaIR": true
}
[{"inputs":[{"internalType":"address","name":"_gaugeController","type":"address"},{"internalType":"address","name":"_feeCollector","type":"address"},{"internalType":"address","name":"_owner","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ALREADY_CLOSED","type":"error"},{"inputs":[],"name":"AUTH_MANAGER_ONLY","type":"error"},{"inputs":[],"name":"INVALID_NUMBER_OF_PERIODS","type":"error"},{"inputs":[],"name":"KILLED","type":"error"},{"inputs":[],"name":"NOT_UPGRADEABLE","type":"error"},{"inputs":[],"name":"NO_PERIODS_LEFT","type":"error"},{"inputs":[],"name":"WRONG_INPUT","type":"error"},{"inputs":[],"name":"ZERO_ADDRESS","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"remainingReward","type":"uint256"}],"name":"BountyClosed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"gauge","type":"address"},{"indexed":false,"internalType":"address","name":"manager","type":"address"},{"indexed":false,"internalType":"address","name":"rewardToken","type":"address"},{"indexed":false,"internalType":"uint8","name":"numberOfPeriods","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"maxRewardPerVote","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"rewardPerPeriod","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalRewardAmount","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isUpgradeable","type":"bool"}],"name":"BountyCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"numberOfPeriods","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"totalRewardAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"maxRewardPerVote","type":"uint256"}],"name":"BountyDurationIncrease","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"numberOfPeriods","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"totalRewardAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"maxRewardPerVote","type":"uint256"}],"name":"BountyDurationIncreaseQueued","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"address","name":"rewardToken","type":"address"},{"indexed":true,"internalType":"uint256","name":"bountyId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"protocolFees","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"period","type":"uint256"}],"name":"Claimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"feeCollector","type":"address"}],"name":"FeeCollectorUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"FeeUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rewardToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FeesCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"manager","type":"address"}],"name":"ManagerUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"periodId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"rewardPerPeriod","type":"uint256"}],"name":"PeriodRolledOver","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"}],"name":"RecipientSet","type":"event"},{"inputs":[],"name":"MINIMUM_PERIOD","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"activePeriod","outputs":[{"internalType":"uint8","name":"id","type":"uint8"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"uint256","name":"rewardPerPeriod","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"amountClaimed","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"bounties","outputs":[{"internalType":"address","name":"gauge","type":"address"},{"internalType":"address","name":"manager","type":"address"},{"internalType":"address","name":"rewardToken","type":"address"},{"internalType":"uint8","name":"numberOfPeriods","type":"uint8"},{"internalType":"uint256","name":"endTimestamp","type":"uint256"},{"internalType":"uint256","name":"maxRewardPerVote","type":"uint256"},{"internalType":"uint256","name":"totalRewardAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"bountyId","type":"uint256"}],"name":"claim","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"bountyId","type":"uint256"},{"internalType":"address","name":"_recipient","type":"address"}],"name":"claim","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"ids","type":"uint256[]"}],"name":"claimAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"address","name":"_recipient","type":"address"}],"name":"claimAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"}],"name":"claimAllFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"rewardTokens","type":"address[]"}],"name":"claimFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"bountyId","type":"uint256"}],"name":"claimFor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"bountyId","type":"uint256"}],"name":"claimable","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"bountyId","type":"uint256"}],"name":"closeBounty","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"gauge","type":"address"},{"internalType":"address","name":"manager","type":"address"},{"internalType":"address","name":"rewardToken","type":"address"},{"internalType":"uint8","name":"numberOfPeriods","type":"uint8"},{"internalType":"uint256","name":"maxRewardPerVote","type":"uint256"},{"internalType":"uint256","name":"totalRewardAmount","type":"uint256"},{"internalType":"address[]","name":"blacklist","type":"address[]"},{"internalType":"bool","name":"upgradeable","type":"bool"}],"name":"createBounty","outputs":[{"internalType":"uint256","name":"newBountyId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"fee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"feeAccrued","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeCollector","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gaugeController","outputs":[{"internalType":"contract GaugeController","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"bountyId","type":"uint256"}],"name":"getActivePeriod","outputs":[{"components":[{"internalType":"uint8","name":"id","type":"uint8"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"uint256","name":"rewardPerPeriod","type":"uint256"}],"internalType":"struct Platform.Period","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"bountyId","type":"uint256"}],"name":"getActivePeriodPerBounty","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"bountyId","type":"uint256"}],"name":"getBlacklistedAddressesPerBounty","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"bountyId","type":"uint256"}],"name":"getBounty","outputs":[{"components":[{"internalType":"address","name":"gauge","type":"address"},{"internalType":"address","name":"manager","type":"address"},{"internalType":"address","name":"rewardToken","type":"address"},{"internalType":"uint8","name":"numberOfPeriods","type":"uint8"},{"internalType":"uint256","name":"endTimestamp","type":"uint256"},{"internalType":"uint256","name":"maxRewardPerVote","type":"uint256"},{"internalType":"uint256","name":"totalRewardAmount","type":"uint256"},{"internalType":"address[]","name":"blacklist","type":"address[]"}],"internalType":"struct Platform.Bounty","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"bountyId","type":"uint256"}],"name":"getPeriodsLeft","outputs":[{"internalType":"uint256","name":"periodsLeft","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"bountyId","type":"uint256"}],"name":"getUpgradedBountyQueued","outputs":[{"components":[{"internalType":"uint8","name":"numberOfPeriods","type":"uint8"},{"internalType":"uint256","name":"totalRewardAmount","type":"uint256"},{"internalType":"uint256","name":"maxRewardPerVote","type":"uint256"},{"internalType":"uint256","name":"endTimestamp","type":"uint256"}],"internalType":"struct Platform.Upgrade","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVersion","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"_bountyId","type":"uint256"},{"internalType":"uint8","name":"_additionnalPeriods","type":"uint8"},{"internalType":"uint256","name":"_increasedAmount","type":"uint256"},{"internalType":"uint256","name":"_newMaxPricePerVote","type":"uint256"}],"name":"increaseBountyDuration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"isBlacklisted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isKilled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"isUpgradeable","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"kill","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"lastUserClaim","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"recipient","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"rewardPerVote","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_feeCollector","type":"address"}],"name":"setFeeCollector","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_platformFee","type":"uint256"}],"name":"setPlatformFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_recipient","type":"address"}],"name":"setRecipient","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_for","type":"address"},{"internalType":"address","name":"_recipient","type":"address"}],"name":"setRecipientFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"bountyId","type":"uint256"}],"name":"updateBountyPeriod","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"ids","type":"uint256[]"}],"name":"updateBountyPeriods","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"bountyId","type":"uint256"},{"internalType":"address","name":"newManager","type":"address"}],"name":"updateManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"upgradeBountyQueue","outputs":[{"internalType":"uint8","name":"numberOfPeriods","type":"uint8"},{"internalType":"uint256","name":"totalRewardAmount","type":"uint256"},{"internalType":"uint256","name":"maxRewardPerVote","type":"uint256"},{"internalType":"uint256","name":"endTimestamp","type":"uint256"}],"stateMutability":"view","type":"function"}]