Contract Name:
LockboxSonic
Contract Source Code:
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {ERC165} from "../utils/introspection/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
mapping(bytes32 role => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with an {AccessControlUnauthorizedAccount} error including the required role.
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual returns (bool) {
return _roles[role].hasRole[account];
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
* is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
* is missing `role`.
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address callerConfirmation) public virtual {
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
if (!hasRole(role, account)) {
_roles[role].hasRole[account] = true;
emit RoleGranted(role, account, _msgSender());
return true;
} else {
return false;
}
}
/**
* @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
if (hasRole(role, account)) {
_roles[role].hasRole[account] = false;
emit RoleRevoked(role, account, _msgSender());
return true;
} else {
return false;
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/extensions/AccessControlEnumerable.sol)
pragma solidity ^0.8.20;
import {IAccessControlEnumerable} from "./IAccessControlEnumerable.sol";
import {AccessControl} from "../AccessControl.sol";
import {EnumerableSet} from "../../utils/structs/EnumerableSet.sol";
/**
* @dev Extension of {AccessControl} that allows enumerating the members of each role.
*/
abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {
using EnumerableSet for EnumerableSet.AddressSet;
mapping(bytes32 role => EnumerableSet.AddressSet) private _roleMembers;
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) public view virtual returns (address) {
return _roleMembers[role].at(index);
}
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) public view virtual returns (uint256) {
return _roleMembers[role].length();
}
/**
* @dev Overload {AccessControl-_grantRole} to track enumerable memberships
*/
function _grantRole(bytes32 role, address account) internal virtual override returns (bool) {
bool granted = super._grantRole(role, account);
if (granted) {
_roleMembers[role].add(account);
}
return granted;
}
/**
* @dev Overload {AccessControl-_revokeRole} to track enumerable memberships
*/
function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) {
bool revoked = super._revokeRole(role, account);
if (revoked) {
_roleMembers[role].remove(account);
}
return revoked;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/extensions/IAccessControlEnumerable.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "../IAccessControl.sol";
/**
* @dev External interface of AccessControlEnumerable declared to support ERC165 detection.
*/
interface IAccessControlEnumerable is IAccessControl {
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) external view returns (address);
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) external view returns (uint256);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/IAccessControl.sol)
pragma solidity ^0.8.20;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev The `account` is missing a role.
*/
error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);
/**
* @dev The caller of a function is not the expected one.
*
* NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
*/
error AccessControlBadConfirmation();
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*/
function renounceRole(bytes32 role, address callerConfirmation) external;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Muldiv operation overflow.
*/
error MathOverflowedMulDiv();
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an overflow flag.
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
return a / b;
}
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0 = x * y; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
if (denominator <= prod1) {
revert MathOverflowedMulDiv();
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
// works in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256 of a positive value rounded towards zero.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
}
}
/**
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
*/
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.20;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes32 value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity 0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
pragma solidity 0.8.20;
/**
* @dev Standard ERC20 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.
*/
interface IERC20Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC20InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC20InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
* @param spender Address that may be allowed to operate on tokens without being their owner.
* @param allowance Amount of tokens a `spender` is allowed to operate with.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC20InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `spender` to be approved. Used in approvals.
* @param spender Address that may be allowed to operate on tokens without being their owner.
*/
error ERC20InvalidSpender(address spender);
}
/**
* @dev Standard ERC721 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
*/
interface IERC721Errors {
/**
* @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.
* Used in balance queries.
* @param owner Address of the current owner of a token.
*/
error ERC721InvalidOwner(address owner);
/**
* @dev Indicates a `tokenId` whose `owner` is the zero address.
* @param tokenId Identifier number of a token.
*/
error ERC721NonexistentToken(uint256 tokenId);
/**
* @dev Indicates an error related to the ownership over a particular token. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param tokenId Identifier number of a token.
* @param owner Address of the current owner of a token.
*/
error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC721InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC721InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param tokenId Identifier number of a token.
*/
error ERC721InsufficientApproval(address operator, uint256 tokenId);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC721InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC721InvalidOperator(address operator);
}
/**
* @dev Standard ERC1155 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.
*/
interface IERC1155Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
* @param tokenId Identifier number of a token.
*/
error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC1155InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC1155InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param owner Address of the current owner of a token.
*/
error ERC1155MissingApprovalForAll(address operator, address owner);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC1155InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC1155InvalidOperator(address operator);
/**
* @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
* Used in batch transfers.
* @param idsLength Length of the array of token identifiers
* @param valuesLength Length of the array of token amounts
*/
error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)
pragma solidity 0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./IERC20Metadata.sol";
import {Context} from "./Context.sol";
import {IERC20Errors} from "./draft-IERC6093.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*/
abstract contract ERC20 is IERC20, IERC20Metadata, IERC20Errors {
error TransferDenied();
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
revert TransferDenied();
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = msg.sender;
_approve(owner, spender, value);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
revert TransferDenied();
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
_totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
* true using the following override:
* ```
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity 0.8.20;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity 0.8.20;
import {IERC20} from "./IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
interface IGauge {
function deposit(uint amount) external;
function withdraw(uint amount) external;
function claim_rewards() external;
function balanceOf(address user) external view returns (uint);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import {IGauge} from "./interfaces/IGauge.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {IERC20} from "./ERC20/IERC20.sol";
import {ERC20} from "./ERC20/ERC20NonTransferable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {AccessControlEnumerable} from "@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol";
contract LockboxSonic is ERC20, ReentrancyGuard, AccessControlEnumerable {
error ZeroAmount();
error InvalidTokenOrAddress();
error Paused();
error NotPaused();
error UnderTimeLock();
error InvalidLockDuration();
error NoLock();
error NoVest();
error NotAllowed();
error Expired();
struct Reward {
uint rewardRate;
uint periodFinish;
uint lastUpdateTime;
uint rewardPerTokenStored;
}
struct UserInfo {
bool isLocked;
bool isVested;
uint lockedFor;
uint vestedFor;
uint lockedAmount;
uint vestedAmount;
}
bytes32 internal constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
address public immutable stakingToken;
address public immutable gauge;
address public immutable beets;
address public immutable multisig;
address public immutable treasury;
address public rewardVester;
uint internal constant MINLOCK = 4 * 7 * 86400;
uint internal constant MAXLOCK = 26 * 7 * 86400;
uint internal constant MAXVEST = 6 * 7 * 86400;
uint internal constant DURATION = 7 days;
uint internal constant PRECISION = 10 ** 18;
uint internal constant PENALTYDIV = 1000;
// 60% penalty, 40% to lockers, 10% to treasury
uint internal constant EARLYPENALTY = 600;
// Used to calculate lockerRetained based on pre-calculated penalty amount. 40% of Total amount = 83.33% of the 60% penalty amount
// As it does not return a whole number, we have allowed it to be marginally over the 40% by rounding up to 83.4%
uint internal constant LOCKRETAINED = 834;
uint internal unsyncedBeets;
uint public lastBeetsHarvest;
bool public paused;
bool public sonicMigration;
address[] internal rewards;
mapping(address token => Reward) internal _rewardData;
mapping(address token => bool) public isReward;
mapping(address user => mapping(address token => uint rewardPerToken)) public userRewardPerTokenStored;
mapping(address user => mapping(address token => uint reward)) public storedRewardsPerUser;
mapping(address user => UserInfo) public userInfo;
event LockCreated(address indexed from, uint amount, uint lockEnd);
event LockAmountIncreased(address indexed from, uint amount);
event LockExtended(address indexed user, uint amount);
event LockTransfered(address indexed from, address indexed to, uint amount);
event UnlockedEarly(address indexed user, uint received, uint retained);
event LockWithdrawn(address indexed user, uint amount);
event LockBroken(address indexed user, uint lockAmount);
event VestCreated(address indexed user, uint amount, uint vestEnd);
event AddedToVest(address indexed user, uint amount);
event EarlyUnvest(address indexed user, uint amountReceived, uint amountRetained);
event VestWithdrawn(address indexed user, uint amount);
event VestBroken(address indexed user, uint vestAmount);
event NotifyReward(address indexed from, address indexed reward, uint amount);
event ClaimRewards(address indexed from, address indexed reward, uint amount);
event EmergencyWithdraw(uint amount);
event RewardVesterSet(address indexed newVester);
event WasPaused(uint amount);
event UnPaused(uint amount);
event ShutDown(bool state);
constructor(
address[3] memory _operators,
address _admin,
address _treasury,
address _stakingtoken,
address _gauge,
address _rewardVester,
address _beets,
string memory _name,
string memory _symbol
) ERC20(_name, _symbol) ReentrancyGuard() {
_grantRole(DEFAULT_ADMIN_ROLE, _admin);
_grantRole(OPERATOR_ROLE, _admin);
_grantRole(OPERATOR_ROLE, _operators[0]);
_grantRole(OPERATOR_ROLE, _operators[1]);
_grantRole(OPERATOR_ROLE, _operators[2]);
multisig = _admin;
treasury = _treasury;
stakingToken = _stakingtoken;
gauge = _gauge;
rewardVester = _rewardVester;
beets = _beets;
rewards.push(_beets);
isReward[_beets] = true;
IERC20(stakingToken).approve(_gauge, type(uint).max);
}
modifier updateReward(address account) {
_updateReward(account);
_;
}
/// @dev compiled with via-ir, caching is less efficient
function _updateReward(address account) internal {
for (uint i; i < rewards.length; i++) {
_rewardData[rewards[i]].rewardPerTokenStored = rewardPerToken(
rewards[i]
);
_rewardData[rewards[i]].lastUpdateTime = lastTimeRewardApplicable(
rewards[i]
);
if (account != address(0)) {
storedRewardsPerUser[account][rewards[i]] = earned(
rewards[i],
account
);
userRewardPerTokenStored[account][rewards[i]] = _rewardData[
rewards[i]
].rewardPerTokenStored;
}
}
}
// Returns current reward list
function rewardsList() external view returns (address[] memory _rewards) {
_rewards = rewards;
}
function rewardsListLength() external view returns (uint _length) {
_length = rewards.length;
}
/// @notice returns the last time the reward was modified or periodFinish if the reward has ended
function lastTimeRewardApplicable(address token) public view returns (uint) {
return Math.min(_getTimestamp(), _rewardData[token].periodFinish);
}
// Returns struct with all stored info regarding a reward token.
function rewardData(address token) external view returns (Reward memory data) {
data = _rewardData[token];
}
// Returns depositor's accrued rewards
function earned(address token,address account) public view returns (uint _reward) {
uint userRewardRate = _getUserRewardRate();
_reward =
(((balanceOf(account) *
(rewardPerToken(token) -
userRewardPerTokenStored[account][token])) / PRECISION) *
userRewardRate) / 100 +
storedRewardsPerUser[account][token];
}
/// @notice claims all pending locked and non locked rewards for depositor
function getReward() public nonReentrant updateReward(msg.sender) {
address user = msg.sender;
UserInfo storage account = userInfo[user];
for (uint i; i < rewards.length; i++) {
uint reward = storedRewardsPerUser[user][rewards[i]];
if (reward > 0) {
storedRewardsPerUser[user][rewards[i]] = 0;
if(rewards[i] == address(this)){
if(!account.isLocked && !account.isVested){_unlockReward(reward);
} else {
// If reward is locked LP receipt, sort if amt is assinged to vest or lock.
if (!account.isLocked && account.isVested) { // Only Vested
account.vestedAmount += reward;
} else if (account.isLocked && !account.isVested) { // Only locked
account.lockedAmount += reward;
} else if (account.isLocked && account.isVested) {
if (account.lockedFor >= account.vestedFor) {
account.lockedAmount += reward;
} else {
account.vestedAmount += reward;
}
}
_mint(user, reward);
}
} else {
_safeTransfer(rewards[i], user, reward);
}
emit ClaimRewards(user, rewards[i], reward);
}
}
}
// Returns rewardToken amount
function rewardPerToken(address token) public view returns (uint) {
if (totalSupply() == 0) {
return _rewardData[token].rewardPerTokenStored;
}
return
_rewardData[token].rewardPerTokenStored +
((lastTimeRewardApplicable(token) -
_rewardData[token].lastUpdateTime) *
_rewardData[token].rewardRate *
PRECISION) /
totalSupply();
}
function _getUserRewardRate() internal view returns (uint) {
address user = msg.sender;
UserInfo memory account = userInfo[user];
uint baseRate = 70;
uint additionalRate;
uint timeLeft;
if(account.vestedAmount < account.lockedAmount){
timeLeft = lockLeft(user);
} else {timeLeft = vestLeft(user);}
if (timeLeft > 5 * MINLOCK) {
additionalRate = 30; // 5 to 6 months: 100%
} else if (timeLeft > 4 * MINLOCK) {
additionalRate = 24; // 4 to 5 months: 94%
} else if (timeLeft > 3 * MINLOCK) {
additionalRate = 18; // 3 to 4 months: 88%
} else if (timeLeft > 2 * MINLOCK) {
additionalRate = 12; // 2 to 3 months: 82%
} else if (timeLeft > MINLOCK) {
additionalRate = 6; // 1 to 2 months: 76%
}
return baseRate + additionalRate;
}
/// @notice User created Lock. 1-6M duration range.
function createLock(uint amount, uint duration) external nonReentrant updateReward(msg.sender){
if(paused){revert Paused();}
if(amount == 0) {revert ZeroAmount();}
address user = msg.sender;
UserInfo storage account = userInfo[user];
if(account.isLocked){revert UnderTimeLock();} // Check if already locked.
uint timestamp = _getTimestamp();
uint unlockTime = timestamp + duration;
if(unlockTime <= timestamp + MINLOCK){unlockTime = timestamp + MINLOCK;}
if(unlockTime >= timestamp + MAXLOCK){unlockTime = timestamp + MAXLOCK;}
account.isLocked = true;
account.lockedFor = unlockTime;
_safeTransferFrom(stakingToken, user, address(this), amount);
IGauge(gauge).deposit(amount);
_mint(user, amount);
account.lockedAmount += amount;
emit LockCreated(user, amount, unlockTime);
}
// Adds an amount to an active lock.
function increaseLockAmount(uint amount) external nonReentrant updateReward(msg.sender) {
if(paused){revert Paused();}
if(amount == 0) {revert ZeroAmount();}
address user = msg.sender;
UserInfo storage account = userInfo[user];
if(!account.isLocked){revert NoLock();}
if(_getTimestamp() >= account.lockedFor){revert Expired();}
_safeTransferFrom(stakingToken, user, address(this), amount);
IGauge(gauge).deposit(amount);
_mint(user, amount);
account.lockedAmount += amount;
emit LockAmountIncreased(user, amount);
}
// Extends duration of an ongoing lock
function extendLock(uint duration) external nonReentrant {
if(paused){revert Paused();}
address user = msg.sender;
UserInfo storage account = userInfo[user];
if(!account.isLocked){revert NoLock();}
uint timestamp = _getTimestamp();
uint unlockTime = timestamp + duration;
if(unlockTime <= account.lockedFor){revert InvalidLockDuration();} // Can only increase lock duration
if(unlockTime < timestamp + MINLOCK){revert InvalidLockDuration();} // Below 1M min
if(unlockTime >= timestamp + MAXLOCK){unlockTime = timestamp + MAXLOCK;} // 26 weeks max lock
account.lockedFor = unlockTime;
emit LockExtended(user, unlockTime);
}
function withdrawLock() public nonReentrant updateReward(msg.sender){
address user = msg.sender;
UserInfo storage account = userInfo[user];
if(!account.isLocked){revert NoLock();}
if(_getTimestamp() < account.lockedFor){revert UnderTimeLock();}
account.isLocked = false;
uint userLockBal = account.lockedAmount;
account.lockedFor = 0;
account.lockedAmount = 0;
_burn(user, userLockBal);
uint gaugeBal = _gaugeBalance();
if(gaugeBal >= userLockBal){
IGauge(gauge).withdraw(userLockBal);
}
_safeTransfer(stakingToken, user, userLockBal);
emit LockWithdrawn(user, userLockBal);
}
// Allows locker to exit full or partial position with a 40% penalty.
function earlyUnlock(uint amount) external nonReentrant updateReward(msg.sender) updateReward(treasury){
address user = msg.sender;
UserInfo storage account = userInfo[user];
if(!account.isLocked){revert NoLock();}
if(_getTimestamp() >= account.lockedFor){revert Expired();}
if(amount == 0) {revert ZeroAmount();}
if(amount > account.lockedAmount){amount = account.lockedAmount;}
account.lockedAmount -= amount;
if(account.lockedAmount == 0){account.isLocked = false; account.lockedFor = 0;}
UserInfo storage protocol = userInfo[treasury];
uint earlyPenalty = amount * EARLYPENALTY / PENALTYDIV;
uint userReceived = amount - earlyPenalty;
uint lockerRetained = earlyPenalty * LOCKRETAINED / PENALTYDIV;
uint treasuryRetained = earlyPenalty - lockerRetained;
_burn(user, amount);
_mint(treasury, treasuryRetained);
if(!protocol.isLocked){
protocol.isLocked = true;
protocol.lockedFor = block.timestamp + MAXLOCK;
protocol.lockedAmount += treasuryRetained;
} else {
protocol.lockedAmount += treasuryRetained;
}
_distroEarlyPenalty(lockerRetained);
uint gaugeBal = _gaugeBalance();
if(gaugeBal >= userReceived){
IGauge(gauge).withdraw(userReceived);
}
_safeTransfer(stakingToken, user, userReceived);
emit UnlockedEarly(user, userReceived, lockerRetained + treasuryRetained);
}
// Partial or complete transfer of a lock to new or existing lock. If receiver is locked, lockedFor must >= msg.sender's
// If receiver isn't locked, creates one with the same lockedFor as msg.sender
function transferLock(address receiver, uint amount) external nonReentrant updateReward(msg.sender) updateReward(receiver){
address user = msg.sender;
if(receiver == user){revert InvalidTokenOrAddress();}
UserInfo storage senderAccount = userInfo[user];
if(!senderAccount.isLocked){revert NoLock();}
if(_getTimestamp() >= senderAccount.lockedFor){revert Expired();}
if(amount == 0){revert NotAllowed();}
if(amount > senderAccount.lockedAmount){amount = senderAccount.lockedAmount;}
UserInfo storage receiverAccount = userInfo[receiver];
if(receiverAccount.isLocked){
if(receiverAccount.lockedFor < senderAccount.lockedFor){revert NotAllowed();} // Can't transfer to shorter lock.
}
if(!receiverAccount.isLocked){
receiverAccount.isLocked = true;
receiverAccount.lockedFor = senderAccount.lockedFor;
emit LockCreated(receiver, amount, receiverAccount.lockedFor);
}
senderAccount.lockedAmount -= amount;
if(senderAccount.lockedAmount == 0){
senderAccount.isLocked = false;
senderAccount.lockedFor = 0;
}
_burn(user, amount);
_mint(receiver, amount);
receiverAccount.lockedAmount += amount;
emit LockTransfered(user, receiver, amount);
}
// Creates vestLock when calling earlyClaim in RewardVester contract.
function createVest(address user, uint amount) external nonReentrant updateReward(user){
if(msg.sender != rewardVester){revert NotAllowed();}
uint timestamp = _getTimestamp();
UserInfo storage account = userInfo[user];
if(account.isVested){
if(timestamp >= account.vestedFor){revert Expired();}
}
if(!account.isVested){
uint unlockTime = timestamp + MAXVEST;
account.isVested = true;
account.vestedFor = unlockTime;
emit VestCreated(user, amount, unlockTime);
} else {
emit AddedToVest(user, amount);
}
_safeTransferFrom(stakingToken, rewardVester, address(this), amount);
account.vestedAmount += amount;
IGauge(gauge).deposit(amount);
_mint(user, amount);
}
function withdrawVest() public nonReentrant updateReward(msg.sender) {
address user = msg.sender;
UserInfo storage account = userInfo[user];
if(!account.isVested){revert NoVest();}
if(_getTimestamp() < account.vestedFor){revert UnderTimeLock();}
uint amount = account.vestedAmount;
account.vestedAmount = 0;
account.vestedFor = 0;
account.isVested = false;
_burn(user, amount);
uint gaugeBal = _gaugeBalance();
if(gaugeBal >= amount){
IGauge(gauge).withdraw(amount);
}
_safeTransfer(stakingToken, user, amount);
emit VestWithdrawn(user, amount);
}
function earlyUnvest(uint amount) external nonReentrant updateReward(msg.sender) updateReward(treasury){
address user = msg.sender;
UserInfo storage account = userInfo[user];
if(!account.isVested){revert NoVest();}
if(_getTimestamp() >= account.vestedFor){revert Expired();}
if(amount == 0) {revert ZeroAmount();}
if(amount > account.vestedAmount){amount = account.vestedAmount;}
account.vestedAmount -= amount;
if(account.vestedAmount == 0){account.isVested = false;}
uint earlyPenalty = amount * EARLYPENALTY / PENALTYDIV;
uint userReceived = amount - earlyPenalty;
uint lockerRetained = earlyPenalty * LOCKRETAINED / PENALTYDIV;
uint treasuryRetained = earlyPenalty - lockerRetained;
UserInfo storage protocol = userInfo[treasury];
_burn(user, amount);
_mint(treasury, treasuryRetained);
if(!protocol.isLocked){
protocol.isLocked = true;
protocol.lockedFor = block.timestamp + MAXLOCK;
protocol.lockedAmount += treasuryRetained;
} else {
protocol.lockedAmount += treasuryRetained;
}
_distroEarlyPenalty(lockerRetained);
uint gaugeBal = _gaugeBalance();
if(gaugeBal >= userReceived){
IGauge(gauge).withdraw(userReceived);
}
_safeTransfer(stakingToken, user, userReceived);
emit UnlockedEarly(user, userReceived, lockerRetained + treasuryRetained);
}
/// @notice Transfers vested amount into a new or existing non expired but longer lock
function vestToLock(uint amount, uint duration) external nonReentrant{
address user = msg.sender;
UserInfo storage account = userInfo[user];
if(!account.isVested){revert NoVest();}
if(amount == 0) {revert ZeroAmount();}
if(amount > account.vestedAmount){amount = account.vestedAmount;}
uint timestamp = _getTimestamp();
if(timestamp >= account.vestedFor){revert Expired();}
account.vestedAmount -= amount;
if(!account.isLocked){
uint unlockTime = timestamp + duration;
if(unlockTime <= account.vestedFor){revert InvalidLockDuration();} // Lock shorter than vest
if(unlockTime <= timestamp + MINLOCK){unlockTime = timestamp + MINLOCK;}
if(unlockTime >= timestamp + MAXLOCK){unlockTime = timestamp + MAXLOCK;}
account.isLocked = true;
account.lockedFor = unlockTime;
account.lockedAmount += amount;
emit LockCreated(user, amount, unlockTime);
} else {
if(account.vestedFor >= account.lockedFor){revert InvalidLockDuration();} // Lock shorter than vest
if(timestamp >= account.lockedFor){revert Expired();}
account.lockedAmount += amount;
emit LockAmountIncreased(user, amount);
}
if(account.vestedAmount == 0){account.isVested = false; account.vestedFor = 0;}
}
// Our protocol is slated to migrate from Fantom Opera to sonic. This function enables lockers to jailBreak if
// fMoney Staker is paused in order to bridge their fBUX and lock on Sonic.
function breakerOfLocks() external {
address user = msg.sender;
UserInfo storage account = userInfo[user];
if(!paused){revert NotPaused();}
if(!sonicMigration){revert NotAllowed();}
if(account.isLocked){
account.lockedFor = 0;
emit LockBroken(user, account.lockedAmount);
withdrawLock();
}
if(account.isVested){
account.vestedFor = 0;
emit VestBroken(user, account.vestedAmount);
withdrawVest();
}
}
// Returns, in seconds, how much time is left on a lock.
function lockLeft(address user) public view returns(uint){
UserInfo memory account = userInfo[user];
uint timestamp = _getTimestamp();
uint lockEnd = account.lockedFor;
if(lockEnd == 0){return 0;}
if(timestamp >= lockEnd){return 0;}
return lockEnd - timestamp;
}
// Returns, in seconds, how much time is left on a vest.
function vestLeft(address user) public view returns(uint){
UserInfo memory account = userInfo[user];
uint timestamp = _getTimestamp();
uint vestEnd = account.vestedFor;
if(vestEnd == 0){return 0;}
if(timestamp >= vestEnd){return 0;}
return vestEnd - timestamp;
}
// Returns reward duration
function left(address token) public view returns (uint) {
uint timestamp = _getTimestamp();
if (timestamp >= _rewardData[token].periodFinish) return 0;
uint _remaining = _rewardData[token].periodFinish - timestamp;
return _remaining * _rewardData[token].rewardRate;
}
/// @notice Tops up reward pool for a token
function notifyRewardAmount(address token, uint amount) external updateReward(address(0)) onlyRole(DEFAULT_ADMIN_ROLE) {
if (amount == 0) {revert ZeroAmount();}
if (!isReward[token]) {
rewards.push(token);
isReward[token] = true;
}
uint timestamp = _getTimestamp();
address thisContract = address(this);
uint periodFinish = _rewardData[token].periodFinish;
_rewardData[token].rewardPerTokenStored = rewardPerToken(token);
// Check actual amount transferred for compatibility with fee on transfer tokens.
uint balanceBefore = _balanceOf(token, thisContract);
_safeTransferFrom(token, msg.sender, thisContract, amount);
uint balanceAfter = _balanceOf(token, thisContract);
amount = balanceAfter - balanceBefore;
if (timestamp >= periodFinish) {
_rewardData[token].rewardRate = amount / DURATION;
} else {
uint remaining = periodFinish - timestamp;
uint _left = remaining * _rewardData[token].rewardRate;
_rewardData[token].rewardRate = (amount + _left) / DURATION;
}
_rewardData[token].lastUpdateTime = timestamp;
_rewardData[token].periodFinish = timestamp + DURATION;
emit NotifyReward(msg.sender, token, amount);
}
// Harvests beets rewards & adds amount to reward pool
function harvestBeets() external nonReentrant updateReward(address(0)) {
uint timestamp = _getTimestamp();
address thisContract = address(this);
if(timestamp < lastBeetsHarvest + 5 days){revert UnderTimeLock();}
lastBeetsHarvest = timestamp;
uint beetsBalance = _balanceOf(beets, thisContract);
IGauge(gauge).claim_rewards();
uint beetsBalanceAfter = _balanceOf(beets, thisContract);
uint _unsyncedBeets = beetsBalanceAfter - beetsBalance;
_unsyncedBeets += unsyncedBeets;
if(_unsyncedBeets == 0){revert ZeroAmount();}
unsyncedBeets = 0;
_rewardData[beets].rewardPerTokenStored = rewardPerToken(beets);
if (timestamp >= _rewardData[beets].periodFinish) {
_rewardData[beets].rewardRate = _unsyncedBeets / DURATION;
} else {
uint remaining = _rewardData[beets].periodFinish - timestamp;
uint _left = remaining * _rewardData[beets].rewardRate;
_rewardData[beets].rewardRate = (_unsyncedBeets + _left) / DURATION;
}
_rewardData[beets].lastUpdateTime = timestamp;
_rewardData[beets].periodFinish = timestamp + DURATION;
emit NotifyReward(msg.sender, beets, _unsyncedBeets);
}
/// @notice Distributes penalty from early unlocks as locked rewards to Lockers.
function _distroEarlyPenalty(uint amount) internal updateReward(address(0)) {
address lockReceipt = address(this);
if (!isReward[lockReceipt]) {
rewards.push(lockReceipt);
isReward[lockReceipt] = true;
}
uint timestamp = _getTimestamp();
_rewardData[lockReceipt].rewardPerTokenStored = rewardPerToken(lockReceipt);
if (timestamp >= _rewardData[lockReceipt].periodFinish) {
_rewardData[lockReceipt].rewardRate = amount / DURATION;
} else {
uint remaining = _rewardData[lockReceipt].periodFinish - timestamp;
uint _left = remaining * _rewardData[lockReceipt].rewardRate;
_rewardData[lockReceipt].rewardRate = (amount + _left) / DURATION;
}
_rewardData[lockReceipt].lastUpdateTime = timestamp;
_rewardData[lockReceipt].periodFinish = timestamp + DURATION;
emit NotifyReward(msg.sender, lockReceipt, amount);
}
//If user has exited lock or vest, getReward calls this func to unlock pending locked Reward and send to user.
function _unlockReward(uint amount) internal{
uint gaugeBal = _gaugeBalance();
if(gaugeBal >= amount){
IGauge(gauge).withdraw(amount);
}
_safeTransfer(stakingToken, msg.sender, amount);
}
/// @notice Emergency withdraw from chef to staking contract & pause deposits.
function emergencyWithdrawFromGauge() external onlyRole(OPERATOR_ROLE){
if(paused){revert Paused();}
uint gaugeBal = IGauge(gauge).balanceOf(address(this));
IGauge(gauge).withdraw(gaugeBal);
paused = true;
uint stakingBal = _balanceOf(stakingToken, address(this));
emit EmergencyWithdraw(stakingBal);
}
// Unpauses deposits and stakes LP in chef if there's balance in the contract
function unpause() external onlyRole(OPERATOR_ROLE){
if(!paused){revert NotPaused();}
paused = false;
uint stakingBal = _balanceOf(stakingToken, address(this));
if(stakingBal != 0){IGauge(gauge).deposit(stakingBal);}
emit UnPaused(stakingBal);
}
// Recovers token mistakenly sent to the contract if not a protected token.
function recoverTokens(address token, address to, uint amount) external onlyRole(DEFAULT_ADMIN_ROLE) {
if(token == stakingToken || isReward[token] || token == gauge){revert InvalidTokenOrAddress();}
_safeTransfer(token, to, amount);
}
//To cover a migration/shutdown, rewards can be sent to multisig.
function recoverRewards(address token, uint amount) external onlyRole(OPERATOR_ROLE){
if(!paused){revert NotPaused();}
if(token == stakingToken){revert InvalidTokenOrAddress();}
_safeTransfer(token, multisig, amount);
}
// To cover a pool migration/contract shutdown, this
// admin gated function enables users to dissolve locks if the staker has been emergency withdrawn and is paused.
function setShutdown(bool state) external onlyRole(DEFAULT_ADMIN_ROLE){
if(!paused){revert NotPaused();}
sonicMigration = state;
emit ShutDown(state);
}
// In the event of a rewardVester change.
function setRewardVester(address vester) external onlyRole(DEFAULT_ADMIN_ROLE){
if(vester == address(0)){revert InvalidTokenOrAddress();}
rewardVester = vester;
emit RewardVesterSet(vester);
}
// Approval refresh for contract longevity
function renewApprovals() external onlyRole(OPERATOR_ROLE){
IERC20(stakingToken).approve(gauge, 0);
IERC20(stakingToken).approve(gauge, type(uint).max);
}
// Returns contract's stakingToken amount deposited in chef & rewardDebt
function _gaugeBalance() internal view returns(uint lpAmount){
return IGauge(gauge).balanceOf(address(this));
}
function _getTimestamp() internal view returns (uint){
return block.timestamp;
}
// Internal update function, adds or removes reward shares for a depositor.
function _update(address from, address to, uint value) internal override {
// if burn or mint
if (from == address(0) || to == address(0)) {
super._update(from, to, value);
} else {
_updateReward(from);
_updateReward(to);
super._update(from, to, value);
}
}
// ERC20 handling
function _safeTransfer(address token, address to, uint value) internal {
(bool success, bytes memory data) = token.call(
abi.encodeCall(IERC20.transfer, (to, value))
);
require(success && (data.length == 0 || abi.decode(data, (bool))));
}
function _safeTransferFrom(address token, address from, address to, uint value) internal {
(bool success, bytes memory data) = token.call(
abi.encodeCall(IERC20.transferFrom, (from, to, value))
);
require(success && (data.length == 0 || abi.decode(data, (bool))));
}
function _balanceOf(address token, address account) internal view returns (uint) {
(bool success, bytes memory data) = token.staticcall(
abi.encodeCall(IERC20.balanceOf, (account))
);
require(success && data.length >= 32);
return abi.decode(data, (uint));
}
}