Contract Name:
StakingDrift2
Contract Source Code:
File 1 of 1 : StakingDrift2
// File: @openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.20;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/**
* @dev The contract is not initializing.
*/
error NotInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
* production.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reininitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$._initialized = 1;
if (isTopLevelCall) {
$._initializing = true;
}
_;
if (isTopLevelCall) {
$._initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing || $._initialized >= version) {
revert InvalidInitialization();
}
$._initialized = version;
$._initializing = true;
_;
$._initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
_checkInitializing();
_;
}
/**
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_isInitializing()) {
revert NotInitializing();
}
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing) {
revert InvalidInitialization();
}
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _getInitializableStorage()._initializing;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
assembly {
$.slot := INITIALIZABLE_STORAGE
}
}
}
// File: @openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
/**
* @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 ReentrancyGuardUpgradeable is Initializable {
// 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;
function __ReentrancyGuard_init() internal onlyInitializing {
__ReentrancyGuard_init_unchained();
}
function __ReentrancyGuard_init_unchained() internal onlyInitializing {
_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
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// 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;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}
// File: @openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol
// 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 ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
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;
}
}
// File: @openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Ownable
struct OwnableStorage {
address _owner;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;
function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
assembly {
$.slot := OwnableStorageLocation
}
}
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
function __Ownable_init(address initialOwner) internal onlyInitializing {
__Ownable_init_unchained(initialOwner);
}
function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
OwnableStorage storage $ = _getOwnableStorage();
return $._owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
OwnableStorage storage $ = _getOwnableStorage();
address oldOwner = $._owner;
$._owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// File: @openzeppelin/contracts/utils/structs/EnumerableSet.sol
// 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;
}
}
// File: utils/AllowableAddressUpgradeable.sol
pragma solidity ^0.8.4;
contract AllowableAddress is OwnableUpgradeable {
using EnumerableSet for EnumerableSet.AddressSet;
// @dev the allowed minter addresses
EnumerableSet.AddressSet internal s_minters;
// @dev the allowed burner addresses
EnumerableSet.AddressSet internal s_burners;
/******* Variables *******/
// blacklist address => bool
mapping(address => bool) internal blacklistAddress;
/******* Modifiers *******/
modifier blacklistCheck(address _from, address _to) {
require(!blacklistAddress[_from] && !blacklistAddress[_to], "Address is blacklisted");
_;
}
modifier onlyMinter() {
if (!isMinter(msg.sender)) revert SenderNotMinter(msg.sender);
_;
}
modifier onlyBurner() {
if (!isBurner(msg.sender)) revert SenderNotBurner(msg.sender);
_;
}
/******* Events *******/
error SenderNotMinter(address sender);
error SenderNotBurner(address sender);
error MaxSupplyExceeded(uint256 supplyAfterMint);
event MintAccessGranted(address indexed minter);
event BurnAccessGranted(address indexed burner);
event MintAccessRevoked(address indexed minter);
event BurnAccessRevoked(address indexed burner);
event BlacklistUpdated(bool status);
/******* Constructor *******/
function __AllowableAddress_init(address _ownerAddress) internal {
__Ownable_init(_ownerAddress);
}
/*
* @notice Manage blacklisting to restrict addresses
* @param addresses Array of addresses
* @param status If blacklist passing true, false otherwise.
*/
function manageBlacklist(address[] calldata addresses, bool status)
external
virtual
onlyOwner
{
for (uint256 i; i < addresses.length; ++i) {
blacklistAddress[addresses[i]] = status;
}
emit BlacklistUpdated(status);
}
/*
* @notice grants both mint and burn roles to `burnAndMinter`.
* @notice Accessable to only owner.
* @param burnAndMinter Any address for mint and burn access
*/
function grantMintAndBurnRoles(address burnAndMinter) external onlyOwner {
grantMintRole(burnAndMinter);
grantBurnRole(burnAndMinter);
}
/*
* @notice Grants mint role to the given address.
* @notice Accessable to only owner.
* @param minter Any address for mint access
*/
function grantMintRole(address minter) public onlyOwner {
if (s_minters.add(minter)) {
emit MintAccessGranted(minter);
}
}
/*
* @notice Grants burn role to the given address.
* @notice Accessable to only owner.
* @param burner Any address for burn access
*/
function grantBurnRole(address burner) public onlyOwner {
if (s_burners.add(burner)) {
emit BurnAccessGranted(burner);
}
}
/*
* @notice Revokes mint role for the given address.
* @notice Accessable to only owner.
* @param minter Any address for remove mint access
*/
function revokeMintRole(address minter) public onlyOwner {
if (s_minters.remove(minter)) {
emit MintAccessRevoked(minter);
}
}
/*
* @notice Revokes burn role from the given address.
* @notice Accessable to only owner
* @param burner Any address for remove burn access
*/
function revokeBurnRole(address burner) public onlyOwner {
if (s_burners.remove(burner)) {
emit BurnAccessRevoked(burner);
}
}
/*
* @param _address Any address
* @return {the address is blacklisted or not}.
*/
function isBlacklisted(address _address) public view returns (bool) {
return blacklistAddress[_address];
}
/*
* @return {all permissioned minters}
*/
function getMinters() public view returns (address[] memory) {
return s_minters.values();
}
/*
* @return {all permissioned burners}
*/
function getBurners() public view returns (address[] memory) {
return s_burners.values();
}
/*
* @notice Checks whether a given address is a minter for this token.
* @return {true if the address is allowed to mint}.
*/
function isMinter(address minter) public view returns (bool) {
return s_minters.contains(minter);
}
/*
* @notice Checks whether a given address is a burner for this token.
* @return {true if the address is allowed to burn}.
*/
function isBurner(address burner) public view returns (bool) {
return s_burners.contains(burner);
}
}
// File: @openzeppelin/contracts/utils/math/SafeMath.sol
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/SafeMath.sol)
pragma solidity ^0.8.0;
// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.
/**
* @dev Wrappers over Solidity's arithmetic operations.
*
* NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
* now has built in overflow checking.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
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.
*
* _Available since v3.4._
*/
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.
*
* _Available since v3.4._
*/
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.
*
* _Available since v3.4._
*/
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.
*
* _Available since v3.4._
*/
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 addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return a - b;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
return a * b;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator.
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return a % b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {trySub}.
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
unchecked {
require(b <= a, errorMessage);
return a - b;
}
}
/**
* @dev Returns the integer division of two unsigned integers, reverting with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
unchecked {
require(b > 0, errorMessage);
return a / b;
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting with custom message when dividing by zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryMod}.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
unchecked {
require(b > 0, errorMessage);
return a % b;
}
}
}
// File: @openzeppelin/contracts/token/ERC20/IERC20.sol
// 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);
}
// File: StakingDrift2.sol
/**
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2024, DRIFTToken.
*
* DRIFT is the studio token behind the Drift studio. Drift is a Web3 game studio, which already has a ready-to-play Beta version of its game, Payout Pursuit.
*
* The DRIFT token has utility for gamers and non-gamers. Gamers that hold DRIFT will be able to customize their gaming experience with NFT skins and other features.
* Non-gamers who stake DRIFT receive a percentage of game revenue, and the token also receives Liquidity from a percentage of game revenue.
*
* The Drift project has been developed by a highly experienced, fully doxxed Web3 team, with multiple successes in the Web3 space.
*
* Drift:
* https://drifttoken.io/
*
* Influ3nce:
* https://influ3nce.me/
*
* Amba$$ador:
* https://influ3nce.me/ambassador/
*
* Twitter / X:
* https://twitter.com/TheDriftToken
*
* Telegram:
* https://t.me/driftportal
*
*/
pragma solidity 0.8.20;
// Interface of ERC20
interface IERC20_EXT is IERC20 {
function mint(address to, uint256 amount) external;
function burn(uint256 value) external;
function burnFrom(address account, uint256 value) external;
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
function decimals() external view returns (uint8);
}
// Entry of Staking Drift Pool Contract
contract StakingDrift is AllowableAddress, ReentrancyGuardUpgradeable {
using SafeMath for uint256;
struct User {
uint256 stakedBalance;
uint256 lastStakedTime;
uint256 lastUpdateTime;
uint256 rewardsBalance;
uint256 rewardRate;
}
struct StakeInfo {
uint256 minimumStake;
uint256 maximumStake;
uint256 minStakePeriod;
uint256 fixedAPY; // 18 decimals
uint256 lockedStake; // Days
bool fixedRewardRate;
}
/* Variables */
bool public initialized = false;
string public version;
IERC20_EXT public stDriftToken; // 1:1 Stake token to be issued equals to staked drift
IERC20_EXT public driftToken; // Drift token to be stake
IERC20_EXT public rewardToken; // Reward Token
address public factory; // Deployer factory
address payable public feeWallet; // Fee wallet address where the fees will be sent to.
StakeInfo public stakeInfo; // Info of this Stake Pool
bool private isNative = false; // Is Reward is Native Gas Token
bool private openStake = false; // Is stake pool open
uint256 public totalStaked; // Total Stake Amount
uint256 public unstakeFee; // Percentage multiplication with 1000.
uint256 public rewardRate; // Reward rate per token per day (required if Fixed APY enabled)
uint256 public stakeStartTimestamp = 0; // Stake Start TimeStamp for rewardRate
uint256 public stakeEndDeadline = 0; // Stake End deadline for rewardRate and/or pool. If zero, no end period.
uint256 public BONUS_MULTIPLIER; // e.g. Rewards (2x, 3x etc)
mapping(address => User) private users; // Mapping User address => User info
/* Modifiers */
modifier checkAmount(uint256 _amount) {
require(
_amount >= stakeInfo.minimumStake && _amount <= stakeInfo.maximumStake,
"StakingDrift: invalid stake amount"
);
_;
}
modifier isOpen() {
require(isOpenStake(), "StakingDrift: stake not opened");
_;
}
/* Events */
event Staked(address indexed user, uint256 amount);
event Unstaked(address indexed user, uint256 amount);
event Claimed(address indexed user, uint256 amount);
event UpdatedDriftToken(address driftToken);
event UpdatedStakeDriftToken(address stDriftToken);
event UpdatedFeeWallet(address feeWallet);
event UpdatedBonusMultiplier(uint8 _multiplier);
event RemoveFixed(uint256 timestamp);
event EnableFixed(uint256 timestamp);
event StakeOpen(uint256 stakeEndDeadlineInDays, uint256 unstakeFeeRate, uint256 timestamp);
event StakeClosed(uint256 timestamp);
event UpdateUnstakeFeesAndLockedDays(uint256 unstakeFeeRate, uint256 lockedStakeDays);
event UpdateLimits(uint256 minimumStake, uint256 maximumStake, uint256 minStakePeriod);
/* Constructor */
/**
* @notice Initialize Construction called from proxy initialization
* @param _factory Deployer factory address
* @param _driftToken Drift Token Address
* @param _stDriftToken Stake Drift Token Address
* @param _rewardToken Reward Token Address
* @param _feeWallet Fee Wallet Address
* @param _isRewardNativeToken True if Reward is Native, false otherwise.
* @param _fixedAPY Percentage in wei if enable Fixed APY, zero otherwise.
* @param _rewardRate Reward rate is required if Fixed APY enabled. Reward rate is a per token per day.
* @param _lockedStake For Locked Stake, value in days greater than zero. Zero otherwise.
*/
function initialize(
address _factory,
address _driftToken,
address _stDriftToken,
address _rewardToken,
address _feeWallet,
bool _isRewardNativeToken,
uint256 _fixedAPY, // in wei
uint256 _rewardRate, // in wei
uint256 _lockedStake
) public initializer {
require(!initialized, "Already initialized");
__AllowableAddress_init(_msgSender());
factory = _factory;
feeWallet = payable(_feeWallet);
driftToken = IERC20_EXT(_driftToken);
stDriftToken = IERC20_EXT(_stDriftToken);
rewardToken = IERC20_EXT(_rewardToken);
isNative = _isRewardNativeToken;
stakeInfo.fixedAPY = _fixedAPY;
if(_fixedAPY > 0) {
require(_rewardRate > 0, "StakingDrift: reward rate per token per day required for fixed APY");
}
if(_rewardRate > 0) {
rewardRate = _rewardRate;
stakeInfo.fixedRewardRate = true;
}
stakeInfo.maximumStake = type(uint256).max;
stakeInfo.minStakePeriod = 30;
stakeInfo.lockedStake = _lockedStake;
BONUS_MULTIPLIER = 1;
version = "1";
initialized = true;
}
/*
* @notice fallback functions
*/
receive() external payable {}
fallback() external payable {}
/**
* @notice Update Bonus Multiplier
* @notice Accessable to only owner
* @param _multiplier Multiplier in number (i.e. 2 = 2x, 3 = 3x, etc)
*/
function updateBonusMultiplier(uint8 _multiplier) external onlyOwner {
require(
_multiplier >= 1,
"StakingDrift: bonus multiplier should be greater than or equals to 1"
);
BONUS_MULTIPLIER = _multiplier;
emit UpdatedBonusMultiplier(_multiplier);
}
/**
* @notice Update Drift Token Address
* @notice Accessable to only owner
* @param _newDriftToken New Drift Token Address
*/
function updateDriftToken(address _newDriftToken) external onlyOwner {
require(_newDriftToken != address(0), "StakingDrift: newDriftToken is a zero address");
driftToken = IERC20_EXT(_newDriftToken);
emit UpdatedDriftToken(_newDriftToken);
}
/**
* @notice Update Fee Wallet
* @notice Accessable to only owner
* @param _newStDriftToken New Stake Drift Token Address
*/
function updateStakeDriftToken(address _newStDriftToken) external onlyOwner {
require(_newStDriftToken != address(0), "StakingDrift: newStDriftToken is a zero address");
stDriftToken = IERC20_EXT(_newStDriftToken);
emit UpdatedStakeDriftToken(_newStDriftToken);
}
/**
* @notice Update Fee Wallet
* @notice Accessable to only owner
* @param _newFeeWallet New Fee Wallet Address
*/
function updateFeeWallet(address _newFeeWallet) external onlyOwner {
require(_newFeeWallet != address(0), "StakingDrift: newFeeWallet is a zero address");
feeWallet = payable(_newFeeWallet);
emit UpdatedFeeWallet(_newFeeWallet);
}
/**
* @notice Remove fixed APY, reward rate
* @notice Accessable to only owner
*/
function removeFixed() external onlyOwner {
stakeInfo.fixedAPY = 0;
stakeInfo.fixedRewardRate = false;
calculateRewardRate();
emit RemoveFixed(block.timestamp);
}
/**
* @notice Enable fixed APY, reward rate if not enabled
* @notice Accessable to only owner
* @param _apyPercent APY percentage in wei
* @param _rewardRate Reward rate per token per day in wei
*/
function enableFixed(uint256 _apyPercent, uint256 _rewardRate) external onlyOwner {
require(rewardRate > 0, "StakingDrift: reward rate per token per day required for fixed APY");
stakeInfo.fixedAPY = _apyPercent;
stakeInfo.fixedRewardRate = true;
rewardRate = _rewardRate;
emit EnableFixed(block.timestamp);
}
/**
* @notice Stake with permit
* @param _amount Amount in wei
* @param deadline Future deadline in timestamp
* @param v secp256k1 signature from `owner`
* @param r secp256k1 signature from `owner`
* @param s secp256k1 signature from `owner`
*/
function stakeWithPermit(
uint256 _amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external isOpen checkAmount(_amount) nonReentrant {
driftToken.permit(_msgSender(), address(this), _amount, deadline, v, r, s);
revert("StakingDrift: Stake not allowed for this pool");
// _stake(_amount, _msgSender());
// updateReward(_msgSender());
}
/**
* @notice Stake on behalf. Owner of tokens stakes for someone.
* @param _amount Amount in wei
* @param _userAddress Address of user to stake for
*/
function stakeOnBehalf(uint256 _amount, address _userAddress)
external
isOpen
checkAmount(_amount)
nonReentrant
{
require(behalfStakeAllowedAddress[_msgSender()] == true || owner() == _msgSender(), "StakingDrift: Stake not allowed");
_stake(_amount, _userAddress, _msgSender());
updateReward(_userAddress);
}
/**
* @notice Stake Drift tokens.
* @param _amount Amount in wei.
*/
function stake(uint256 _amount) external isOpen checkAmount(_amount) nonReentrant {
revert("StakingDrift: Stake not allowed for this pool");
// _stake(_amount, _msgSender());
// updateReward(_msgSender());
}
/**
* @notice Unstake tokens with rewards if available.
* @param _amount Amount in wei.
*/
function unstake(uint256 _amount) external nonReentrant {
require(_amount > 0, "StakingDrift: amount must be greater than zero");
updateReward(_msgSender());
User storage $ = users[_msgSender()];
require($.stakedBalance >= _amount, "StakingDrift: insufficient staked balance");
if(stakeInfo.lockedStake > 0) {
require(_getDays($.lastStakedTime, block.timestamp) >= stakeInfo.lockedStake, "StakingDrift: your staked token");
}
uint256 _unstakeFee = _getDays($.lastStakedTime, block.timestamp) < stakeInfo.minStakePeriod
? (_amount * unstakeFee) / 10**5
: 0;
uint256 amountAfterFee = _amount - _unstakeFee;
$.stakedBalance -= _amount;
totalStaked -= _amount;
distributeReward(_msgSender());
// Burn stDRIFT tokens
stDriftToken.burnFrom(_msgSender(), _amount);
if (_unstakeFee > 0) {
driftToken.transfer(feeWallet, _unstakeFee);
}
driftToken.transfer(_msgSender(), amountAfterFee);
}
/**
* @notice Claim rewards of staked tokens if available.
*/
function claimRewards() external nonReentrant {
updateReward(_msgSender()); // Update rewards for the user
User storage $ = users[_msgSender()];
require($.rewardsBalance > 0, "StakingDrift: no rewards to claim");
distributeReward(_msgSender());
}
/**
* @notice Add liquidity or Rewards in pool.
* @notice Accessable to only owner
* @param amount Amount in wei.
*/
function addLiquidty(uint256 amount) external payable onlyOwner {
require(amount > 0, "StakingDrift: Amount must be greater than zero");
if (isNative) {
require(msg.value == amount, "StakingDrift: incorrect amount sent");
} else {
rewardToken.transferFrom(_msgSender(), address(this), amount);
}
calculateRewardRate();
}
/**
* @notice Start stake if not already open.
* @notice Accessable to only owner
* @param _stakeEndDeadlineInDays Stake pool deadline in days or zero otherwise for infinite
* @param _unstakeFeeRate Unstake fees percentage multiplied by 1000
*/
function startStake(uint256 _stakeEndDeadlineInDays, uint256 _unstakeFeeRate)
external
onlyOwner
{
require(isOpenStake() == false, "StakingDrift : stake is already open.");
openStake = true;
stakeStartTimestamp = block.timestamp;
stakeEndDeadline = _stakeEndDeadlineInDays > 0 ? block.timestamp + (_stakeEndDeadlineInDays * 1 days) : 0;
unstakeFee = _unstakeFeeRate;
calculateRewardRate();
emit StakeOpen(_stakeEndDeadlineInDays, _unstakeFeeRate, block.timestamp);
}
/**
* @notice Stop stake if not already closed.
* @notice Accessable to only owner
*/
function stopStake() external onlyOwner {
require(isOpenStake() == true, "StakingDrift : stake is already closed.");
openStake = false;
stakeEndDeadline = block.timestamp;
emit StakeClosed(block.timestamp);
}
/**
* @notice Update unstake fees percentage and/or locked days of stake
* @notice Accessable to only owner
* @param _newUnstakeFeeRate Unstake fees percentage multiplied by 1000
* @param _lockedStakeDays Locked stake in days if enable locked pool
*/
function updateUnstakeFeesAndLockedDays(uint256 _newUnstakeFeeRate, uint256 _lockedStakeDays) external onlyOwner {
unstakeFee = _newUnstakeFeeRate;
stakeInfo.lockedStake = _lockedStakeDays;
emit UpdateUnstakeFeesAndLockedDays(_newUnstakeFeeRate, _lockedStakeDays);
}
/**
* @notice Update limits and enable or disable feature that users stake (max / min) tokens and minimum stake period
* @notice Accessable to only owner
* @param _newMinimumStake Amount of token user min hold.
* @param _newMaximumStake Amount of token user max hold.
* @param _newMinStakePeriod Minimum stake period in days
*/
function updateLimits(
uint256 _newMinimumStake,
uint256 _newMaximumStake,
uint256 _newMinStakePeriod
) external onlyOwner {
stakeInfo.minimumStake = _newMinimumStake;
stakeInfo.maximumStake = _newMaximumStake;
stakeInfo.minStakePeriod = _newMinStakePeriod;
emit UpdateLimits(_newMinimumStake, _newMaximumStake, _newMinStakePeriod);
}
/**
* @return {APY}
*/
function calculateAPY() external view returns (uint256) {
return _calculateAPY();
}
/**
* @return stakedBalance Staked balance
* @return lastStakedTime Last staked timestamp
* @return lastUpdateTime Last updated timestamp
* @return rewardsBalance Reward balance
*/
function getUserInfo(address _userAddress)
external
view
returns (
uint256 stakedBalance,
uint256 lastStakedTime,
uint256 lastUpdateTime,
uint256 rewardsBalance
)
{
User storage user = users[_userAddress];
(uint256 _pendingRewards, ) = getPendingRewards(_userAddress);
return (
user.stakedBalance,
stakeStartTimestamp,
user.lastUpdateTime > stakeStartTimestamp ? user.lastUpdateTime : stakeStartTimestamp,
_pendingRewards
);
}
/**
* @notice Withdraw reward liquidity from contract.
* @notice Accessable to only owner
* @param _amount Amount in wei
*/
function withdrawLiquidity(uint256 _amount) public onlyOwner {
if (isNative) {
require(
address(this).balance >= _amount,
"StakingDrift: amount is greater than balance"
);
(bool os, ) = payable(owner()).call{value: address(this).balance}("");
require(os);
} else {
require(
rewardToken.balanceOf(address(this)) >= _amount,
"StakingDrift: amount is greater than balance"
);
rewardToken.transfer(owner(), _amount);
}
}
/**
* @notice Withdraw Native Gas Token from contract.
* @notice Accessable to only owner
*/
function withdrawNativeFunds() public onlyOwner {
if (address(this).balance > 0) {
(bool os, ) = payable(owner()).call{value: address(this).balance}("");
require(os);
} else {
revert("StakingDrift: no funds");
}
}
/**
* @notice Withdraw token from contract.
* @notice Accessable to only owner
* @param _tokenAddress Token address
*/
function withdrawOtherTokenFunds(address _tokenAddress) public onlyOwner {
if (IERC20(_tokenAddress).balanceOf(address(this)) > 0) {
IERC20(_tokenAddress).transfer(owner(), IERC20(_tokenAddress).balanceOf(address(this)));
} else {
revert("StakingDrift: no funds");
}
}
/**
* @return {true if pool open, false otherwise}
*/
function isOpenStake() public view returns (bool) {
return openStake == true && (block.timestamp < stakeEndDeadline || stakeEndDeadline == 0);
}
/**
* @notice Helper function to get pending rewards
* @param _userAddress Address of staker
* @return pendingRewards Claimable pending rewards
* @return rewardDays Days of Unclaimable rewards
*/
function getPendingRewards(address _userAddress) public view returns (uint256 pendingRewards, uint256 rewardDays) {
User storage $ = users[_userAddress];
rewardDays = rewardAccumulateStartTimestamp > 0 ? _getDays(
$.lastUpdateTime > rewardAccumulateStartTimestamp ? $.lastUpdateTime : rewardAccumulateStartTimestamp,
stakeEndDeadline > 0 ? min(block.timestamp, stakeEndDeadline) : block.timestamp
) : 0;
uint256 pendingRewardOnSubscribedRewardRateOneDay = 0;
uint256 pendingRewardsRemains = 0;
if (rewardDays > 0) {
pendingRewardOnSubscribedRewardRateOneDay = $.stakedBalance.mul($.rewardRate).div(
10**(stakeInfo.fixedRewardRate == true ? 18 : 20)
);
pendingRewardsRemains = $.stakedBalance.mul(rewardRate.mul(rewardDays - 1)).div(
10**(stakeInfo.fixedRewardRate == true ? 18 : 20)
);
}
pendingRewards = $.rewardsBalance.add(pendingRewardsRemains.add(pendingRewardOnSubscribedRewardRateOneDay).mul(BONUS_MULTIPLIER));
}
/**
* @return {Total pending rewards}
*/
function getTotalPendingRewards() public view returns (uint256) {
if (isNative) {
return address(this).balance;
}
return rewardToken.balanceOf(address(this));
}
/**
* @notice Internal callable function when stake tokens
* @param _amount Amount in wei.
* @param _sender Sender address who stakes.
*/
function _stake(uint256 _amount, address _sender) internal {
_stake(_amount, _sender, _sender);
}
/**
* @notice Internal callable function when stake tokens
* @param _amount Amount in wei.
* @param _userAddress Address of user to stake for.
* @param _sender Sender address who stakes.
*/
function _stake(
uint256 _amount,
address _userAddress,
address _sender
) internal {
User storage $ = users[_userAddress];
driftToken.transferFrom(_sender, address(this), _amount);
totalStaked += _amount;
$.stakedBalance += _amount;
$.lastStakedTime = block.timestamp;
stDriftToken.mint(_userAddress, _amount);
emit Staked(_userAddress, _amount);
}
/**
* @notice Internal callable function when claim reward calls
* @param _userAddress Address of user
*/
function distributeReward(address _userAddress) internal {
User storage $ = users[_userAddress];
uint256 amount = $.rewardsBalance;
if (amount > 0) {
$.rewardsBalance = 0; // Reset user rewards
if (isNative) {
amount = min(amount, address(this).balance);
_transferNative(_userAddress, amount);
} else {
amount = min(calcDriftDigitsToOther(amount), rewardToken.balanceOf(address(this)));
rewardToken.transfer(_msgSender(), amount);
}
emit Claimed(_userAddress, amount);
}
}
/**
* @notice Internal callable function to transfer Native Gas Token.
* @param _recipient Address of recipient
* @param _amount Amount in wei
*/
function _transferNative(address _recipient, uint256 _amount) internal {
(bool sent, ) = payable(_recipient).call{value: _amount}("");
require(sent);
}
/**
* @notice Internal callable function to update rewards of staker when user stakes or unstakes
* @param _userAddress Address of staker
*/
function updateReward(address _userAddress) internal {
User storage $ = users[_userAddress];
calculateRewardRate(); // Calculate dynamic reward rate
if (totalStaked > 0) {
($.rewardsBalance, ) = getPendingRewards(_userAddress); // Accumulate pending rewards
}
$.lastUpdateTime = block.timestamp; // Update last update time
$.rewardRate = rewardRate;
}
/**
* @notice Internal callable function to update and calculate reward rate if Fixed Rate not enabled.
*/
function calculateRewardRate() internal {
if(stakeInfo.fixedRewardRate == true) {
return;
}
rewardRate = _calculateAPY().div(365 * 10**2);
}
/**
* @notice Helper function to convert Drift decimals to other token decimals
* @param amount Amount of tokens in Drift decimals
*/
function calcDriftDigitsToOther(uint256 amount) internal view returns (uint256 calcAmount) {
calcAmount =
(amount * 10**(isNative ? 18 : rewardToken.decimals())) /
10**driftToken.decimals();
return calcAmount;
}
/**
* @notice Helper function to convert other token decimals to Drift decimals
* @param amount Amount of tokens in other token decimals
*/
function calcOtherToDriftDigits(uint256 amount) internal view returns (uint256 calcAmount) {
calcAmount =
(amount * 10**driftToken.decimals()) /
10**(isNative ? 18 : rewardToken.decimals());
return calcAmount;
}
/**
* @notice Helper function to get APY
* @return {Fixed APY or Calculated if Fixed not enabled}
*/
function _calculateAPY() internal view returns (uint256) {
if (getTotalPendingRewards() == 0 || totalStaked == 0) {
return 0;
}
if (stakeInfo.fixedAPY > 0) {
return stakeInfo.fixedAPY * 10**2;
}
uint256 apy = ((calcOtherToDriftDigits(getTotalPendingRewards()) * 10**20) / totalStaked);
return apy;
}
/**
* @notice Helper function to find the minimum of two values.
* @param a Number 1
* @param b Number 2
* @return {Minimum number}
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @notice Helper function to get days
* @param _timestampBefore Before time
* @param _timestampAfter After time
* @return {DAYS}
*/
function _getDays(uint256 _timestampBefore, uint256 _timestampAfter)
internal
pure
returns (uint256)
{
if (_timestampBefore >= _timestampAfter || _timestampBefore == 0 || _timestampAfter == 0) {
return 0;
}
uint256 timestamp = _timestampAfter - _timestampBefore;
uint256 timestampDays = timestamp.div(60).div(60).div(24);
return timestampDays;
}
mapping(address => bool) public behalfStakeAllowedAddress;
uint256 public rewardAccumulateStartTimestamp = 0;
function updateRewardAccumulateStartTimestamp(uint256 _newTimestamp) external onlyOwner {
rewardAccumulateStartTimestamp = _newTimestamp;
}
}
contract StakingDrift2 is StakingDrift {
bool initialized_v2 = false;
function initializeV2(
address claimAddress,
uint256 _rewardAccumulateStartTimestamp
) external {
require(!initialized_v2, "Already initialized");
behalfStakeAllowedAddress[claimAddress] = true;
rewardAccumulateStartTimestamp = _rewardAccumulateStartTimestamp;
version = "2";
initialized_v2 = true;
}
}