Contract Source Code:
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @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);
}
}
// SPDX-License-Identifier: MIT
// 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
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (proxy/utils/UUPSUpgradeable.sol)
pragma solidity ^0.8.20;
import {IERC1822Proxiable} from "@openzeppelin/contracts/interfaces/draft-IERC1822.sol";
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import {Initializable} from "./Initializable.sol";
/**
* @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
* {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
*
* A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
* reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
* `UUPSUpgradeable` with a custom implementation of upgrades.
*
* The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
*/
abstract contract UUPSUpgradeable is Initializable, IERC1822Proxiable {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address private immutable __self = address(this);
/**
* @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
* and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
* while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
* If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
* be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
* during an upgrade.
*/
string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";
/**
* @dev The call is from an unauthorized context.
*/
error UUPSUnauthorizedCallContext();
/**
* @dev The storage `slot` is unsupported as a UUID.
*/
error UUPSUnsupportedProxiableUUID(bytes32 slot);
/**
* @dev Check that the execution is being performed through a delegatecall call and that the execution context is
* a proxy contract with an implementation (as defined in ERC-1967) pointing to self. This should only be the case
* for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
* function through ERC-1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
* fail.
*/
modifier onlyProxy() {
_checkProxy();
_;
}
/**
* @dev Check that the execution is not being performed through a delegate call. This allows a function to be
* callable on the implementing contract but not through proxies.
*/
modifier notDelegated() {
_checkNotDelegated();
_;
}
function __UUPSUpgradeable_init() internal onlyInitializing {
}
function __UUPSUpgradeable_init_unchained() internal onlyInitializing {
}
/**
* @dev Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the
* implementation. It is used to validate the implementation's compatibility when performing an upgrade.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
*/
function proxiableUUID() external view virtual notDelegated returns (bytes32) {
return ERC1967Utils.IMPLEMENTATION_SLOT;
}
/**
* @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
* encoded in `data`.
*
* Calls {_authorizeUpgrade}.
*
* Emits an {Upgraded} event.
*
* @custom:oz-upgrades-unsafe-allow-reachable delegatecall
*/
function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallUUPS(newImplementation, data);
}
/**
* @dev Reverts if the execution is not performed via delegatecall or the execution
* context is not of a proxy with an ERC-1967 compliant implementation pointing to self.
* See {_onlyProxy}.
*/
function _checkProxy() internal view virtual {
if (
address(this) == __self || // Must be called through delegatecall
ERC1967Utils.getImplementation() != __self // Must be called through an active proxy
) {
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Reverts if the execution is performed via delegatecall.
* See {notDelegated}.
*/
function _checkNotDelegated() internal view virtual {
if (address(this) != __self) {
// Must not be called through delegatecall
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
* {upgradeToAndCall}.
*
* Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
*
* ```solidity
* function _authorizeUpgrade(address) internal onlyOwner {}
* ```
*/
function _authorizeUpgrade(address newImplementation) internal virtual;
/**
* @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.
*
* As a security check, {proxiableUUID} is invoked in the new implementation, and the return value
* is expected to be the implementation slot in ERC-1967.
*
* Emits an {IERC1967-Upgraded} event.
*/
function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
revert UUPSUnsupportedProxiableUUID(slot);
}
ERC1967Utils.upgradeToAndCall(newImplementation, data);
} catch {
// The implementation is not UUPS
revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @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;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @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 Ownable is Context {
address private _owner;
/**
* @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.
*/
constructor(address initialOwner) {
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) {
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 {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/draft-IERC1822.sol)
pragma solidity ^0.8.20;
/**
* @dev ERC-1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
* proxy whose upgrades are fully controlled by the current implementation.
*/
interface IERC1822Proxiable {
/**
* @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
* address.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy.
*/
function proxiableUUID() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1967.sol)
pragma solidity ^0.8.20;
/**
* @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
*/
interface IERC1967 {
/**
* @dev Emitted when the implementation is upgraded.
*/
event Upgraded(address indexed implementation);
/**
* @dev Emitted when the admin account has changed.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Emitted when the beacon is changed.
*/
event BeaconUpgraded(address indexed beacon);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/IBeacon.sol)
pragma solidity ^0.8.20;
/**
* @dev This is the interface that {BeaconProxy} expects of its beacon.
*/
interface IBeacon {
/**
* @dev Must return an address that can be used as a delegate call target.
*
* {UpgradeableBeacon} will check that this address is a contract.
*/
function implementation() external view returns (address);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (proxy/ERC1967/ERC1967Utils.sol)
pragma solidity ^0.8.21;
import {IBeacon} from "../beacon/IBeacon.sol";
import {IERC1967} from "../../interfaces/IERC1967.sol";
import {Address} from "../../utils/Address.sol";
import {StorageSlot} from "../../utils/StorageSlot.sol";
/**
* @dev This library provides getters and event emitting update functions for
* https://eips.ethereum.org/EIPS/eip-1967[ERC-1967] slots.
*/
library ERC1967Utils {
/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @dev The `implementation` of the proxy is invalid.
*/
error ERC1967InvalidImplementation(address implementation);
/**
* @dev The `admin` of the proxy is invalid.
*/
error ERC1967InvalidAdmin(address admin);
/**
* @dev The `beacon` of the proxy is invalid.
*/
error ERC1967InvalidBeacon(address beacon);
/**
* @dev An upgrade function sees `msg.value > 0` that may be lost.
*/
error ERC1967NonPayable();
/**
* @dev Returns the current implementation address.
*/
function getImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
}
/**
* @dev Stores a new address in the ERC-1967 implementation slot.
*/
function _setImplementation(address newImplementation) private {
if (newImplementation.code.length == 0) {
revert ERC1967InvalidImplementation(newImplementation);
}
StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation;
}
/**
* @dev Performs implementation upgrade with additional setup call if data is nonempty.
* This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
* to avoid stuck value in the contract.
*
* Emits an {IERC1967-Upgraded} event.
*/
function upgradeToAndCall(address newImplementation, bytes memory data) internal {
_setImplementation(newImplementation);
emit IERC1967.Upgraded(newImplementation);
if (data.length > 0) {
Address.functionDelegateCall(newImplementation, data);
} else {
_checkNonPayable();
}
}
/**
* @dev Storage slot with the admin of the contract.
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Returns the current admin.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using
* the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
*/
function getAdmin() internal view returns (address) {
return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
}
/**
* @dev Stores a new address in the ERC-1967 admin slot.
*/
function _setAdmin(address newAdmin) private {
if (newAdmin == address(0)) {
revert ERC1967InvalidAdmin(address(0));
}
StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin;
}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {IERC1967-AdminChanged} event.
*/
function changeAdmin(address newAdmin) internal {
emit IERC1967.AdminChanged(getAdmin(), newAdmin);
_setAdmin(newAdmin);
}
/**
* @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
* This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
/**
* @dev Returns the current beacon.
*/
function getBeacon() internal view returns (address) {
return StorageSlot.getAddressSlot(BEACON_SLOT).value;
}
/**
* @dev Stores a new beacon in the ERC-1967 beacon slot.
*/
function _setBeacon(address newBeacon) private {
if (newBeacon.code.length == 0) {
revert ERC1967InvalidBeacon(newBeacon);
}
StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon;
address beaconImplementation = IBeacon(newBeacon).implementation();
if (beaconImplementation.code.length == 0) {
revert ERC1967InvalidImplementation(beaconImplementation);
}
}
/**
* @dev Change the beacon and trigger a setup call if data is nonempty.
* This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
* to avoid stuck value in the contract.
*
* Emits an {IERC1967-BeaconUpgraded} event.
*
* CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since
* it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for
* efficiency.
*/
function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal {
_setBeacon(newBeacon);
emit IERC1967.BeaconUpgraded(newBeacon);
if (data.length > 0) {
Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
} else {
_checkNonPayable();
}
}
/**
* @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract
* if an upgrade doesn't perform an initialization call.
*/
function _checkNonPayable() private {
if (msg.value > 0) {
revert ERC1967NonPayable();
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Address.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert Errors.FailedCall();
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {Errors.FailedCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case
* of an unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {Errors.FailedCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly ("memory-safe") {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert Errors.FailedCall();
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of common custom errors used in multiple contracts
*
* IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
* It is recommended to avoid relying on the error API for critical functionality.
*
* _Available since v5.1._
*/
library Errors {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error InsufficientBalance(uint256 balance, uint256 needed);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedCall();
/**
* @dev The deployment failed.
*/
error FailedDeployment();
/**
* @dev A necessary precompile is missing.
*/
error MissingPrecompile(address);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
pragma solidity ^0.8.20;
/**
* @dev Library for reading and writing primitive types to specific storage slots.
*
* Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
*
* Example usage to set ERC-1967 implementation slot:
* ```solidity
* contract ERC1967 {
* // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
* return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
* }
*
* function _setImplementation(address newImplementation) internal {
* require(newImplementation.code.length > 0);
* StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
* }
* }
* ```
*
* TIP: Consider using this library along with {SlotDerivation}.
*/
library StorageSlot {
struct AddressSlot {
address value;
}
struct BooleanSlot {
bool value;
}
struct Bytes32Slot {
bytes32 value;
}
struct Uint256Slot {
uint256 value;
}
struct Int256Slot {
int256 value;
}
struct StringSlot {
string value;
}
struct BytesSlot {
bytes value;
}
/**
* @dev Returns an `AddressSlot` with member `value` located at `slot`.
*/
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `BooleanSlot` with member `value` located at `slot`.
*/
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Bytes32Slot` with member `value` located at `slot`.
*/
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Uint256Slot` with member `value` located at `slot`.
*/
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Int256Slot` with member `value` located at `slot`.
*/
function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `StringSlot` with member `value` located at `slot`.
*/
function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` representation of the string storage pointer `store`.
*/
function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
assembly ("memory-safe") {
r.slot := store.slot
}
}
/**
* @dev Returns a `BytesSlot` with member `value` located at `slot`.
*/
function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
*/
function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
assembly ("memory-safe") {
r.slot := store.slot
}
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.27;
/**
* @custom:security-contact [email protected]
*/
library Decimal {
// unit is used for decimals, e.g. 0.123456
function unit() internal pure returns (uint256) {
return 1e18;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.27;
/**
* @title EVM Writer Interface
* @notice Allows the NodeDriver contract to write into the state database.
* @dev Implemented in the native code of the Sonic client.
* @custom:security-contact [email protected]
*/
interface IEVMWriter {
function setBalance(address acc, uint256 value) external;
function copyCode(address acc, address from) external;
function swapCode(address acc, address where) external;
function setStorage(address acc, bytes32 key, bytes32 value) external;
function incNonce(address acc, uint256 diff) external;
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.27;
/**
* @title Node Driver Contract Interface
* @notice Ensures interaction of on-chain contracts with the Sonic client itself.
* @dev Methods with onlyNode modifier are called by Sonic internal txs during epoch sealing.
* @custom:security-contact [email protected]
*/
interface INodeDriver {
/// Set an initial validator. Called only as part of network initialization/genesis file generating.
function setGenesisValidator(
address auth,
uint256 validatorID,
bytes calldata pubkey,
uint256 createdTime
) external;
/// Set an initial delegation. Called only as part of network initialization/genesis file generating.
function setGenesisDelegation(address delegator, uint256 toValidatorID, uint256 stake) external;
/// Deactivate a validator. Called by network node when a double-sign of the given validator is registered.
/// Is called before sealEpoch() call.
function deactivateValidator(uint256 validatorID, uint256 status) external;
/// Seal epoch. Called BEFORE epoch sealing made by the client itself.
function sealEpoch(
uint256[] calldata offlineTimes,
uint256[] calldata offlineBlocks,
uint256[] calldata uptimes,
uint256[] calldata originatedTxsFee
) external;
/// Seal epoch. Called AFTER epoch sealing made by the client itself.
function sealEpochValidators(uint256[] calldata nextValidatorIDs) external;
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.27;
/**
* @title Node Driver Executable
* @notice A batch of operations to be executed with NodeDriver permissions.
* @notice Contracts implementing this interface should be executed using NodeDriverAuth.execute() or mutExecute().
* @custom:security-contact [email protected]
*/
interface INodeDriverExecutable {
function execute() external;
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.27;
/**
* @title Special Fee Contract for Sonic network
* @notice The SFC maintains a list of validators and delegators and distributes rewards to them.
* @custom:security-contact [email protected]
*/
interface ISFC {
event CreatedValidator(
uint256 indexed validatorID,
address indexed auth,
uint256 createdEpoch,
uint256 createdTime
);
event Delegated(address indexed delegator, uint256 indexed toValidatorID, uint256 amount);
event Undelegated(address indexed delegator, uint256 indexed toValidatorID, uint256 indexed wrID, uint256 amount);
event Withdrawn(
address indexed delegator,
uint256 indexed toValidatorID,
uint256 indexed wrID,
uint256 amount,
uint256 penalty
);
event ClaimedRewards(address indexed delegator, uint256 indexed toValidatorID, uint256 rewards);
event RestakedRewards(address indexed delegator, uint256 indexed toValidatorID, uint256 rewards);
event BurntFTM(uint256 amount);
event UpdatedSlashingRefundRatio(uint256 indexed validatorID, uint256 refundRatio);
event RefundedSlashedLegacyDelegation(address indexed delegator, uint256 indexed validatorID, uint256 amount);
event DeactivatedValidator(uint256 indexed validatorID, uint256 deactivatedEpoch, uint256 deactivatedTime);
event ChangedValidatorStatus(uint256 indexed validatorID, uint256 status);
event AnnouncedRedirection(address indexed from, address indexed to);
function currentSealedEpoch() external view returns (uint256);
function getEpochSnapshot(
uint256
)
external
view
returns (
uint256 endTime,
uint256 endBlock,
uint256 epochFee,
uint256 baseRewardPerSecond,
uint256 totalStake,
uint256 totalSupply
);
function getStake(address, uint256) external view returns (uint256);
function getValidator(
uint256
)
external
view
returns (
uint256 status,
uint256 receivedStake,
address auth,
uint256 createdEpoch,
uint256 createdTime,
uint256 deactivatedTime,
uint256 deactivatedEpoch
);
function getValidatorID(address) external view returns (uint256);
function getValidatorPubkey(uint256) external view returns (bytes memory);
function pubkeyAddressToValidatorID(address pubkeyAddress) external view returns (uint256);
function getWithdrawalRequest(
address,
uint256,
uint256
) external view returns (uint256 epoch, uint256 time, uint256 amount);
function isOwner() external view returns (bool);
function lastValidatorID() external view returns (uint256);
function minGasPrice() external view returns (uint256);
function owner() external view returns (address);
function renounceOwnership() external;
function slashingRefundRatio(uint256) external view returns (uint256);
function stashedRewardsUntilEpoch(address, uint256) external view returns (uint256);
function totalActiveStake() external view returns (uint256);
function totalStake() external view returns (uint256);
function totalSupply() external view returns (uint256);
function transferOwnership(address newOwner) external;
function treasuryAddress() external view returns (address);
function version() external pure returns (bytes3);
function currentEpoch() external view returns (uint256);
function updateConstsAddress(address v) external;
function constsAddress() external view returns (address);
function getEpochValidatorIDs(uint256 epoch) external view returns (uint256[] memory);
function getEpochReceivedStake(uint256 epoch, uint256 validatorID) external view returns (uint256);
function getEpochAccumulatedRewardPerToken(uint256 epoch, uint256 validatorID) external view returns (uint256);
function getEpochAccumulatedUptime(uint256 epoch, uint256 validatorID) external view returns (uint256);
function getEpochAverageUptime(uint256 epoch, uint256 validatorID) external view returns (uint32);
function getEpochAccumulatedOriginatedTxsFee(uint256 epoch, uint256 validatorID) external view returns (uint256);
function getEpochOfflineTime(uint256 epoch, uint256 validatorID) external view returns (uint256);
function getEpochOfflineBlocks(uint256 epoch, uint256 validatorID) external view returns (uint256);
function getEpochEndBlock(uint256 epoch) external view returns (uint256);
function rewardsStash(address delegator, uint256 validatorID) external view returns (uint256);
function createValidator(bytes calldata pubkey) external payable;
function getSelfStake(uint256 validatorID) external view returns (uint256);
function delegate(uint256 toValidatorID) external payable;
function undelegate(uint256 toValidatorID, uint256 wrID, uint256 amount) external;
function isSlashed(uint256 validatorID) external view returns (bool);
function withdraw(uint256 toValidatorID, uint256 wrID) external;
function deactivateValidator(uint256 validatorID, uint256 status) external;
function pendingRewards(address delegator, uint256 toValidatorID) external view returns (uint256);
function stashRewards(address delegator, uint256 toValidatorID) external;
function claimRewards(uint256 toValidatorID) external;
function restakeRewards(uint256 toValidatorID) external;
function updateSlashingRefundRatio(uint256 validatorID, uint256 refundRatio) external;
function updateTreasuryAddress(address v) external;
function burnFTM(uint256 amount) external;
function sealEpoch(
uint256[] calldata offlineTime,
uint256[] calldata offlineBlocks,
uint256[] calldata uptimes,
uint256[] calldata originatedTxsFee
) external;
function sealEpochValidators(uint256[] calldata nextValidatorIDs) external;
function initialize(
uint256 sealedEpoch,
uint256 _totalSupply,
address nodeDriver,
address consts,
address _owner
) external;
function setGenesisValidator(
address auth,
uint256 validatorID,
bytes calldata pubkey,
uint256 createdTime
) external;
function setGenesisDelegation(address delegator, uint256 toValidatorID, uint256 stake) external;
function updateStakeSubscriberAddress(address v) external;
function stakeSubscriberAddress() external view returns (address);
function setRedirectionAuthorizer(address v) external;
function announceRedirection(address to) external;
function initiateRedirection(address from, address to) external;
function redirect(address to) external;
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.27;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Decimal} from "../common/Decimal.sol";
/**
* @custom:security-contact [email protected]
*/
contract ConstantsManager is Ownable {
// Minimum amount of stake for a validator, i.e., 500000 FTM
uint256 public minSelfStake;
// Maximum ratio of delegations a validator can have, say, 15 times of self-stake
uint256 public maxDelegatedRatio;
// The commission fee in percentage a validator will get from a delegation, e.g., 15%
uint256 public validatorCommission;
// The percentage of fees to burn, e.g., 20%
uint256 public burntFeeShare;
// The percentage of fees to transfer to treasury address, e.g., 10%
uint256 public treasuryFeeShare;
// the number of epochs that undelegated stake is locked for
uint256 public withdrawalPeriodEpochs;
// the number of seconds that undelegated stake is locked for
uint256 public withdrawalPeriodTime;
uint256 public baseRewardPerSecond;
uint256 public offlinePenaltyThresholdBlocksNum;
uint256 public offlinePenaltyThresholdTime;
uint256 public targetGasPowerPerSecond;
uint256 public gasPriceBalancingCounterweight;
// The number of epochs to calculate the average uptime ratio from, acceptable bound [10, 87600].
// Is also the minimum number of epochs necessary for deactivation of offline validators.
uint32 public averageUptimeEpochWindow;
// Minimum average uptime ratio in fixed-point format; acceptable bounds [0,0.9].
// Zero to disable validators deactivation by this metric.
uint64 public minAverageUptime;
// The address of the recipient that receives issued tokens
// as a counterparty to the burnt FTM tokens
address public issuedTokensRecipient;
/**
* @dev Given value is too small
*/
error ValueTooSmall();
/**
* @dev Given value is too large
*/
error ValueTooLarge();
constructor(address owner) Ownable(owner) {}
function updateMinSelfStake(uint256 v) external virtual onlyOwner {
if (v < 100000 * 1e18) {
revert ValueTooSmall();
}
if (v > 10000000 * 1e18) {
revert ValueTooLarge();
}
minSelfStake = v;
}
function updateMaxDelegatedRatio(uint256 v) external virtual onlyOwner {
if (v < Decimal.unit()) {
revert ValueTooSmall();
}
if (v > 31 * Decimal.unit()) {
revert ValueTooLarge();
}
maxDelegatedRatio = v;
}
function updateValidatorCommission(uint256 v) external virtual onlyOwner {
if (v > Decimal.unit() / 2) {
revert ValueTooLarge();
}
validatorCommission = v;
}
function updateBurntFeeShare(uint256 v) external virtual onlyOwner {
if (v > Decimal.unit() / 2) {
revert ValueTooLarge();
}
burntFeeShare = v;
}
function updateTreasuryFeeShare(uint256 v) external virtual onlyOwner {
if (v > Decimal.unit() / 2) {
revert ValueTooLarge();
}
treasuryFeeShare = v;
}
function updateWithdrawalPeriodEpochs(uint256 v) external virtual onlyOwner {
if (v < 2) {
revert ValueTooSmall();
}
if (v > 100) {
revert ValueTooLarge();
}
withdrawalPeriodEpochs = v;
}
function updateWithdrawalPeriodTime(uint256 v) external virtual onlyOwner {
if (v < 86400) {
revert ValueTooSmall();
}
if (v > 30 * 86400) {
revert ValueTooLarge();
}
withdrawalPeriodTime = v;
}
function updateBaseRewardPerSecond(uint256 v) external virtual onlyOwner {
if (v < 0.5 * 1e18) {
revert ValueTooSmall();
}
if (v > 32 * 1e18) {
revert ValueTooLarge();
}
baseRewardPerSecond = v;
}
function updateOfflinePenaltyThresholdTime(uint256 v) external virtual onlyOwner {
if (v < 86400) {
revert ValueTooSmall();
}
if (v > 10 * 86400) {
revert ValueTooLarge();
}
offlinePenaltyThresholdTime = v;
}
function updateOfflinePenaltyThresholdBlocksNum(uint256 v) external virtual onlyOwner {
if (v < 100) {
revert ValueTooSmall();
}
if (v > 1000000) {
revert ValueTooLarge();
}
offlinePenaltyThresholdBlocksNum = v;
}
function updateTargetGasPowerPerSecond(uint256 v) external virtual onlyOwner {
if (v < 1000000) {
revert ValueTooSmall();
}
if (v > 500000000) {
revert ValueTooLarge();
}
targetGasPowerPerSecond = v;
}
function updateGasPriceBalancingCounterweight(uint256 v) external virtual onlyOwner {
if (v < 100) {
revert ValueTooSmall();
}
if (v > 10 * 86400) {
revert ValueTooLarge();
}
gasPriceBalancingCounterweight = v;
}
function updateAverageUptimeEpochWindow(uint32 v) external virtual onlyOwner {
if (v < 10) {
// needs to be long enough to allow permissible downtime for validators maintenance
revert ValueTooSmall();
}
if (v > 87600) {
revert ValueTooLarge();
}
averageUptimeEpochWindow = v;
}
function updateMinAverageUptime(uint64 v) external virtual onlyOwner {
if (v > ((Decimal.unit() * 9) / 10)) {
revert ValueTooLarge();
}
minAverageUptime = v;
}
function updateIssuedTokensRecipient(address v) external virtual onlyOwner {
issuedTokensRecipient = v;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.27;
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {NodeDriverAuth} from "./NodeDriverAuth.sol";
import {IEVMWriter} from "../interfaces/IEVMWriter.sol";
import {INodeDriver} from "../interfaces/INodeDriver.sol";
/**
* @title Node Driver Contract
* @notice Ensures interaction of on-chain contracts with the Sonic client itself.
* @dev Methods with onlyNode modifier are called by Sonic internal txs during epoch sealing.
* @custom:security-contact [email protected]
*/
contract NodeDriver is OwnableUpgradeable, UUPSUpgradeable, INodeDriver {
NodeDriverAuth internal backend;
IEVMWriter internal evmWriter;
error NotNode();
error NotBackend();
/// Callable only by NodeDriverAuth (which mediates calls from SFC and from admins)
modifier onlyBackend() {
if (msg.sender != address(backend)) {
revert NotBackend();
}
_;
}
event UpdateValidatorWeight(uint256 indexed validatorID, uint256 weight);
event UpdateValidatorPubkey(uint256 indexed validatorID, bytes pubkey);
event UpdateNetworkRules(bytes diff);
event UpdateNetworkVersion(uint256 version);
event AdvanceEpochs(uint256 num);
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/// Initialization is called only once, after the contract deployment.
/// Because the contract code is written directly into genesis, constructor cannot be used.
function initialize(address _backend, address _evmWriterAddress, address _owner) external initializer {
__Ownable_init(_owner);
__UUPSUpgradeable_init();
backend = NodeDriverAuth(_backend);
evmWriter = IEVMWriter(_evmWriterAddress);
}
/// Override the upgrade authorization check to allow upgrades only from the owner.
// solhint-disable-next-line no-empty-blocks
function _authorizeUpgrade(address) internal override onlyOwner {}
function setBalance(address acc, uint256 value) external onlyBackend {
evmWriter.setBalance(acc, value);
}
function copyCode(address acc, address from) external onlyBackend {
evmWriter.copyCode(acc, from);
}
function swapCode(address acc, address where) external onlyBackend {
evmWriter.swapCode(acc, where);
}
function setStorage(address acc, bytes32 key, bytes32 value) external onlyBackend {
evmWriter.setStorage(acc, key, value);
}
function incNonce(address acc, uint256 diff) external onlyBackend {
evmWriter.incNonce(acc, diff);
}
/// Update network rules configuring the chain.
/// Emitted event is being observed by Sonic client.
function updateNetworkRules(bytes calldata diff) external onlyBackend {
emit UpdateNetworkRules(diff);
}
/// Update advertised version of the network.
/// Emitted event is being observed by Sonic client.
function updateNetworkVersion(uint256 version) external onlyBackend {
emit UpdateNetworkVersion(version);
}
/// Enforce sealing given number of epochs.
/// Emitted event is being observed by Sonic client.
function advanceEpochs(uint256 num) external onlyBackend {
emit AdvanceEpochs(num);
}
/// Update weight of a validator. Used to propagate a stake change from SFC to the client.
/// Emitted event is being observed by Sonic client.
function updateValidatorWeight(uint256 validatorID, uint256 value) external onlyBackend {
emit UpdateValidatorWeight(validatorID, value);
}
/// Update public key of a validator. Used to propagate a change from SFC to the client.
/// Emitted event is being observed by Sonic client.
function updateValidatorPubkey(uint256 validatorID, bytes calldata pubkey) external onlyBackend {
emit UpdateValidatorPubkey(validatorID, pubkey);
}
/// Callable only from Sonic client itself as an internal tx.
/// Used for propagating network event (validator doublesign, epoch sealing) from node to SFC.
modifier onlyNode() {
if (msg.sender != address(0)) {
revert NotNode();
}
_;
}
// Methods which are called only by the node
/// Set an initial validator. Called only as part of network initialization/genesis file generating.
function setGenesisValidator(
address auth,
uint256 validatorID,
bytes calldata pubkey,
uint256 createdTime
) external onlyNode {
backend.setGenesisValidator(auth, validatorID, pubkey, createdTime);
}
/// Set an initial delegation. Called only as part of network initialization/genesis file generating.
function setGenesisDelegation(address delegator, uint256 toValidatorID, uint256 stake) external onlyNode {
backend.setGenesisDelegation(delegator, toValidatorID, stake);
}
/// Deactivate a validator. Called by network node when a double-sign of the given validator is registered.
/// Is called before sealEpoch() call.
function deactivateValidator(uint256 validatorID, uint256 status) external onlyNode {
backend.deactivateValidator(validatorID, status);
}
/// Seal epoch. Called BEFORE epoch sealing made by the client itself.
function sealEpoch(
uint256[] calldata offlineTimes,
uint256[] calldata offlineBlocks,
uint256[] calldata uptimes,
uint256[] calldata originatedTxsFee
) external onlyNode {
backend.sealEpoch(offlineTimes, offlineBlocks, uptimes, originatedTxsFee);
}
/// Seal epoch. Called AFTER epoch sealing made by the client itself.
function sealEpochValidators(uint256[] calldata nextValidatorIDs) external onlyNode {
backend.sealEpochValidators(nextValidatorIDs);
}
uint256[50] private __gap;
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.27;
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {ISFC} from "../interfaces/ISFC.sol";
import {NodeDriver} from "./NodeDriver.sol";
import {INodeDriverExecutable} from "../interfaces/INodeDriverExecutable.sol";
/**
* @custom:security-contact [email protected]
*/
contract NodeDriverAuth is OwnableUpgradeable, UUPSUpgradeable {
ISFC internal sfc;
NodeDriver internal driver;
error NotSFC();
error NotDriver();
error NotContract();
error SelfCodeHashMismatch();
error DriverCodeHashMismatch();
error RecipientNotSFC();
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
// Initialize NodeDriverAuth, NodeDriver and SFC in one call to allow fewer genesis transactions
function initialize(address payable _sfc, address _driver, address _owner) external initializer {
__Ownable_init(_owner);
__UUPSUpgradeable_init();
driver = NodeDriver(_driver);
sfc = ISFC(_sfc);
}
/// Override the upgrade authorization check to allow upgrades only from the owner.
// solhint-disable-next-line no-empty-blocks
function _authorizeUpgrade(address) internal override onlyOwner {}
/// Callable only by SFC contract.
modifier onlySFC() {
if (msg.sender != address(sfc)) {
revert NotSFC();
}
_;
}
/// Callable only by NodeDriver (mediates messages from the network client)
modifier onlyDriver() {
if (msg.sender != address(driver)) {
revert NotDriver();
}
_;
}
function _execute(address executable, address newOwner, bytes32 selfCodeHash, bytes32 driverCodeHash) internal {
_transferOwnership(executable);
INodeDriverExecutable(executable).execute();
_transferOwnership(newOwner);
//require(driver.backend() == address(this), "ownership of driver is lost");
if (_getCodeHash(address(this)) != selfCodeHash) {
revert SelfCodeHashMismatch();
}
if (_getCodeHash(address(driver)) != driverCodeHash) {
revert DriverCodeHashMismatch();
}
}
/// Execute a batch update of network configuration.
/// Run given contract with a permission of the NodeDriverAuth owner.
/// Does not allow changing NodeDriver and NodeDriverAuth code.
function execute(address executable) external onlyOwner {
_execute(executable, owner(), _getCodeHash(address(this)), _getCodeHash(address(driver)));
}
/// Execute a batch update of network configuration.
/// Run given contract with a permission of the NodeDriverAuth owner.
/// Allows changing NodeDriver and NodeDriverAuth code.
function mutExecute(
address executable,
address newOwner,
bytes32 selfCodeHash,
bytes32 driverCodeHash
) external onlyOwner {
_execute(executable, newOwner, selfCodeHash, driverCodeHash);
}
/// Mint native token. To be used by SFC for minting validators rewards.
function incBalance(address acc, uint256 diff) external onlySFC {
driver.setBalance(acc, address(acc).balance + diff);
}
/// Upgrade code of given contract by coping it from other deployed contract.
/// Avoids setting code to an external address.
function upgradeCode(address acc, address from) external onlyOwner {
if (!isContract(acc) || !isContract(from)) {
revert NotContract();
}
driver.copyCode(acc, from);
}
/// Upgrade code of given contract by coping it from other deployed contract.
/// Does not avoid setting code to an external address. (DANGEROUS!)
function copyCode(address acc, address from) external onlyOwner {
driver.copyCode(acc, from);
}
/// Increment nonce of the given account.
function incNonce(address acc, uint256 diff) external onlyOwner {
driver.incNonce(acc, diff);
}
/// Update network rules by providing a JSON patch.
function updateNetworkRules(bytes calldata diff) external onlyOwner {
driver.updateNetworkRules(diff);
}
/// Update advertised network version.
function updateNetworkVersion(uint256 version) external onlyOwner {
driver.updateNetworkVersion(version);
}
/// Enforce sealing given number of epochs.
function advanceEpochs(uint256 num) external onlyOwner {
driver.advanceEpochs(num);
}
/// Update weight of a validator. Used to propagate a stake change from SFC to the client.
function updateValidatorWeight(uint256 validatorID, uint256 value) external onlySFC {
driver.updateValidatorWeight(validatorID, value);
}
/// Update public key of a validator. Used to propagate a change from SFC to the client.
function updateValidatorPubkey(uint256 validatorID, bytes calldata pubkey) external onlySFC {
driver.updateValidatorPubkey(validatorID, pubkey);
}
/// Set an initial validator into SFC. Called only as part of network initialization/genesis file generating.
function setGenesisValidator(
address auth,
uint256 validatorID,
bytes calldata pubkey,
uint256 createdTime
) external onlyDriver {
sfc.setGenesisValidator(auth, validatorID, pubkey, createdTime);
}
/// Set an initial delegation. Called only as part of network initialization/genesis file generating.
function setGenesisDelegation(address delegator, uint256 toValidatorID, uint256 stake) external onlyDriver {
sfc.setGenesisDelegation(delegator, toValidatorID, stake);
}
/// Deactivate a validator. Called by network node when a double-sign of the given validator is registered.
/// Is called before sealEpoch() call.
function deactivateValidator(uint256 validatorID, uint256 status) external onlyDriver {
sfc.deactivateValidator(validatorID, status);
}
/// Seal epoch. Called BEFORE epoch sealing made by the client itself.
function sealEpoch(
uint256[] calldata offlineTimes,
uint256[] calldata offlineBlocks,
uint256[] calldata uptimes,
uint256[] calldata originatedTxsFee
) external onlyDriver {
sfc.sealEpoch(offlineTimes, offlineBlocks, uptimes, originatedTxsFee);
}
/// Seal epoch. Called AFTER epoch sealing made by the client itself.
function sealEpochValidators(uint256[] calldata nextValidatorIDs) external onlyDriver {
sfc.sealEpochValidators(nextValidatorIDs);
}
function isContract(address account) internal view returns (bool) {
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly {
size := extcodesize(account)
}
return size > 0;
}
function _getCodeHash(address addr) internal view returns (bytes32) {
bytes32 codeHash;
assembly {
codeHash := extcodehash(addr)
}
return codeHash;
}
uint256[50] private __gap;
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.27;
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {Decimal} from "../common/Decimal.sol";
import {NodeDriverAuth} from "./NodeDriverAuth.sol";
import {ConstantsManager} from "./ConstantsManager.sol";
import {Version} from "../version/Version.sol";
/**
* @title Special Fee Contract for Sonic network
* @notice The SFC maintains a list of validators and delegators and distributes rewards to them.
* @custom:security-contact [email protected]
*/
contract SFC is OwnableUpgradeable, UUPSUpgradeable, Version {
uint256 internal constant OK_STATUS = 0;
uint256 internal constant WITHDRAWN_BIT = 1;
uint256 internal constant OFFLINE_BIT = 1 << 3;
uint256 internal constant OFFLINE_AVG_BIT = 1 << 4;
uint256 internal constant DOUBLESIGN_BIT = 1 << 7;
uint256 internal constant CHEATER_MASK = DOUBLESIGN_BIT;
/**
* @dev The staking for validation
*/
struct Validator {
uint256 status;
uint256 receivedStake; // from all delegators (weight of the validator)
address auth; // self-stake delegator
uint256 createdEpoch;
uint256 createdTime;
uint256 deactivatedTime;
uint256 deactivatedEpoch;
}
NodeDriverAuth internal node;
// last sealed epoch (currentEpoch - 1)
uint256 public currentSealedEpoch;
mapping(uint256 validatorID => Validator) public getValidator;
mapping(address auth => uint256 validatorID) public getValidatorID;
mapping(uint256 validatorID => bytes pubkey) public getValidatorPubkey;
uint256 public lastValidatorID;
// total stake of all validators - includes slashed/offline validators
uint256 public totalStake;
// total stake of active (OK_STATUS) validators (total weight)
uint256 public totalActiveStake;
// unresolved fees that failed to be send to the treasury
uint256 public unresolvedTreasuryFees;
// delegator => validator ID => stashed rewards (to be claimed/restaked)
mapping(address delegator => mapping(uint256 validatorID => uint256 stashedRewards)) internal _rewardsStash;
// delegator => validator ID => last epoch number for which were rewards stashed
mapping(address delegator => mapping(uint256 validatorID => uint256 epoch)) public stashedRewardsUntilEpoch;
struct WithdrawalRequest {
uint256 epoch; // epoch where undelegated
uint256 time; // when undelegated
uint256 amount;
}
// delegator => validator ID => withdrawal ID => withdrawal request
mapping(address delegator => mapping(uint256 validatorID => mapping(uint256 wrID => WithdrawalRequest)))
public getWithdrawalRequest;
// delegator => validator ID => current stake
mapping(address delegator => mapping(uint256 validatorID => uint256 stake)) public getStake;
// data structure to compute average uptime for each active validator
struct AverageUptime {
// average uptime ratio as a value between 0 and 1e18
uint64 averageUptime;
// remainder from the division in the average calculation
uint32 remainder;
// number of epochs in the average (at most averageUptimeEpochsWindow)
uint32 epochs;
}
struct EpochSnapshot {
// validator ID => validator weight in the epoch
mapping(uint256 => uint256) receivedStake;
// validator ID => accumulated ( delegatorsReward * 1e18 / receivedStake )
mapping(uint256 => uint256) accumulatedRewardPerToken;
// validator ID => accumulated online time
mapping(uint256 => uint256) accumulatedUptime;
// validator ID => average uptime as a percentage
mapping(uint256 => AverageUptime) averageUptime;
// validator ID => gas fees from txs originated by the validator
mapping(uint256 => uint256) accumulatedOriginatedTxsFee;
mapping(uint256 => uint256) offlineTime;
mapping(uint256 => uint256) offlineBlocks;
uint256[] validatorIDs;
uint256 endTime;
uint256 endBlock;
uint256 epochFee; // gas fees from txs in the epoch
uint256 baseRewardPerSecond; // the base reward to divide among validators for each second of the epoch
uint256 totalStake; // total weight of all validators
uint256 totalSupply; // total supply of native tokens
}
// the total supply of native tokens in the chain
uint256 public totalSupply;
// epoch id => epoch snapshot
mapping(uint256 epoch => EpochSnapshot) public getEpochSnapshot;
// validator ID -> slashing refund ratio (allows to withdraw slashed stake)
mapping(uint256 validatorID => uint256 refundRatio) public slashingRefundRatio;
// the treasure contract (receives unlock penalties and a part of epoch fees)
address public treasuryAddress;
ConstantsManager internal c;
// the contract subscribed to stake changes notifications
address public stakeSubscriberAddress;
// address derived from the validator pubkey => validator id
mapping(address pubkeyAddress => uint256 validatorID) public pubkeyAddressToValidatorID;
// address authorized to initiate redirection
address public redirectionAuthorizer;
// delegator => withdrawals receiver
mapping(address delegator => address receiver) public getRedirectionRequest;
// delegator => withdrawals receiver
mapping(address delegator => address receiver) public getRedirection;
struct SealEpochRewardsCtx {
uint256[] baseRewardWeights;
uint256 totalBaseRewardWeight;
uint256[] txRewardWeights;
uint256 totalTxRewardWeight;
uint256 epochFee;
}
// auth
error NotDriverAuth();
error NotAuthorized();
// addresses
error ZeroAddress();
error SameAddress();
// values
error ZeroAmount();
error ZeroRewards();
error ValueTooLarge();
// pubkeys
error PubkeyUsedByOtherValidator();
error MalformedPubkey();
// redirections
error AlreadyRedirected();
error SameRedirectionAuthorizer();
error Redirected();
// validators
error ValidatorNotExists();
error ValidatorExists();
error ValidatorNotActive();
error ValidatorDelegationLimitExceeded();
error NotDeactivatedStatus();
// requests
error RequestExists();
error RequestNotExists();
// transfers
error TransfersNotAllowed();
error TransferFailed();
// stake changes subscriber
error StakeSubscriberFailed();
// staking
error InsufficientSelfStake();
error NotEnoughTimePassed();
error NotEnoughEpochsPassed();
error StakeIsFullySlashed();
// stashing
error NothingToStash();
// slashing
error ValidatorNotSlashed();
error RefundRatioTooHigh();
// treasury
error TreasuryNotSet();
error NoUnresolvedTreasuryFees();
event DeactivatedValidator(uint256 indexed validatorID, uint256 deactivatedEpoch, uint256 deactivatedTime);
event ChangedValidatorStatus(uint256 indexed validatorID, uint256 status);
event CreatedValidator(
uint256 indexed validatorID,
address indexed auth,
uint256 createdEpoch,
uint256 createdTime
);
event Delegated(address indexed delegator, uint256 indexed toValidatorID, uint256 amount);
event Undelegated(address indexed delegator, uint256 indexed toValidatorID, uint256 indexed wrID, uint256 amount);
event Withdrawn(
address indexed delegator,
uint256 indexed toValidatorID,
uint256 indexed wrID,
uint256 amount,
uint256 penalty
);
event ClaimedRewards(address indexed delegator, uint256 indexed toValidatorID, uint256 rewards);
event RestakedRewards(address indexed delegator, uint256 indexed toValidatorID, uint256 rewards);
event BurntNativeTokens(uint256 amount);
event UpdatedSlashingRefundRatio(uint256 indexed validatorID, uint256 refundRatio);
event RefundedSlashedLegacyDelegation(address indexed delegator, uint256 indexed validatorID, uint256 amount);
event AnnouncedRedirection(address indexed from, address indexed to);
event TreasuryFeesResolved(uint256 amount);
modifier onlyDriver() {
if (!isNode(msg.sender)) {
revert NotDriverAuth();
}
_;
}
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/// Initialization is called only once, after the contract deployment.
/// Because the contract code is written directly into genesis, constructor cannot be used.
function initialize(
uint256 sealedEpoch,
uint256 _totalSupply,
address nodeDriver,
address _c,
address owner
) external initializer {
__Ownable_init(owner);
__UUPSUpgradeable_init();
currentSealedEpoch = sealedEpoch;
node = NodeDriverAuth(nodeDriver);
c = ConstantsManager(_c);
totalSupply = _totalSupply;
getEpochSnapshot[sealedEpoch].endTime = _now();
}
/// Override the upgrade authorization check to allow upgrades only from the owner.
// solhint-disable-next-line no-empty-blocks
function _authorizeUpgrade(address) internal override onlyOwner {}
/// Receive fallback to revert transfers.
receive() external payable {
revert TransfersNotAllowed();
}
/// Set admin address responsible for initiating redirections.
function setRedirectionAuthorizer(address v) external onlyOwner {
if (redirectionAuthorizer == v) {
revert SameRedirectionAuthorizer();
}
redirectionAuthorizer = v;
}
/// Announce redirection of address to be called by validator whose auth key was compromised.
/// Produced events are used to notify redirect authorizer about redirection request.
/// Redirect authorizer then initiates creating of appropriate redirect by calling initiateRedirection().
function announceRedirection(address to) external {
emit AnnouncedRedirection(msg.sender, to);
}
/// Initiate redirection of withdrawals/claims for a compromised validator account.
/// Needs to be accepted by validator key holder before the redirect is active.
function initiateRedirection(address from, address to) external {
if (msg.sender != redirectionAuthorizer) {
revert NotAuthorized();
}
if (getRedirection[from] == to) {
revert AlreadyRedirected();
}
if (from == to) {
revert SameAddress();
}
getRedirectionRequest[from] = to;
}
/// Accept redirection proposal.
/// Redirection must by accepted by the validator key holder before it start to be applied.
function redirect(address to) external {
address from = msg.sender;
if (to == address(0)) {
revert ZeroAddress();
}
if (getRedirectionRequest[from] != to) {
revert RequestNotExists();
}
getRedirection[from] = to;
getRedirectionRequest[from] = address(0);
}
/// Seal current epoch - deactivate validators who were offline too long, create an epoch snapshot
/// for the current epoch (provides information for rewards calculation), calculate new minimal gas price.
/// This method is called BEFORE the epoch sealing made by the client itself.
function sealEpoch(
uint256[] calldata offlineTime,
uint256[] calldata offlineBlocks,
uint256[] calldata uptimes,
uint256[] calldata originatedTxsFee
) external onlyDriver {
EpochSnapshot storage snapshot = getEpochSnapshot[currentEpoch()];
uint256[] memory validatorIDs = snapshot.validatorIDs;
_sealEpochOffline(snapshot, validatorIDs, offlineTime, offlineBlocks);
{
EpochSnapshot storage prevSnapshot = getEpochSnapshot[currentSealedEpoch];
uint256 epochDuration = 1;
if (_now() > prevSnapshot.endTime) {
epochDuration = _now() - prevSnapshot.endTime;
}
_sealEpochRewards(epochDuration, snapshot, prevSnapshot, validatorIDs, uptimes, originatedTxsFee);
_sealEpochAverageUptime(epochDuration, snapshot, prevSnapshot, validatorIDs, uptimes);
}
currentSealedEpoch = currentEpoch();
snapshot.endTime = _now();
snapshot.endBlock = block.number;
snapshot.baseRewardPerSecond = c.baseRewardPerSecond();
snapshot.totalSupply = totalSupply;
}
/// Finish epoch sealing - store validators of the new epoch into a snapshot.
/// This method is called AFTER the epoch sealing made by the client itself.
function sealEpochValidators(uint256[] calldata nextValidatorIDs) external onlyDriver {
EpochSnapshot storage snapshot = getEpochSnapshot[currentEpoch()];
// fill data for the next snapshot
for (uint256 i = 0; i < nextValidatorIDs.length; i++) {
uint256 validatorID = nextValidatorIDs[i];
uint256 receivedStake = getValidator[validatorID].receivedStake;
snapshot.receivedStake[validatorID] = receivedStake;
snapshot.totalStake = snapshot.totalStake + receivedStake;
}
snapshot.validatorIDs = nextValidatorIDs;
}
/// Set an initial validator.
/// Called only as part of network initialization/genesis file generating.
function setGenesisValidator(
address auth,
uint256 validatorID,
bytes calldata pubkey,
uint256 createdTime
) external onlyDriver {
_rawCreateValidator(
auth,
validatorID,
pubkey,
OK_STATUS,
0, // createdEpoch
createdTime,
0, // deactivatedEpoch - not deactivated
0 // deactivatedTime - not deactivated
);
if (validatorID > lastValidatorID) {
lastValidatorID = validatorID;
}
}
/// Set an initial delegation.
/// Called only as part of network initialization/genesis file generating.
function setGenesisDelegation(address delegator, uint256 toValidatorID, uint256 stake) external onlyDriver {
_rawDelegate(delegator, toValidatorID, stake, false);
_mintNativeToken(stake);
}
/// Create a validator with a given public key while using attached value as the validator's self-stake.
function createValidator(bytes calldata pubkey) external payable {
if (msg.value < c.minSelfStake()) {
revert InsufficientSelfStake();
}
if (pubkey.length != 66 || pubkey[0] != 0xc0) {
revert MalformedPubkey();
}
if (pubkeyAddressToValidatorID[_pubkeyToAddress(pubkey)] != 0) {
revert PubkeyUsedByOtherValidator();
}
_createValidator(msg.sender, pubkey);
_delegate(msg.sender, lastValidatorID, msg.value);
}
/// Update slashing refund ratio for a validator.
/// The refund ratio is used to calculate the amount of stake that can be withdrawn after slashing.
function updateSlashingRefundRatio(uint256 validatorID, uint256 refundRatio) external onlyOwner {
if (!isSlashed(validatorID)) {
revert ValidatorNotSlashed();
}
if (refundRatio > Decimal.unit()) {
revert RefundRatioTooHigh();
}
slashingRefundRatio[validatorID] = refundRatio;
emit UpdatedSlashingRefundRatio(validatorID, refundRatio);
}
/// Delegate stake to a validator.
function delegate(uint256 toValidatorID) external payable {
_delegate(msg.sender, toValidatorID, msg.value);
}
/// Withdraw stake from a validator after its un-delegation.
/// Un-delegated stake is locked for a certain period of time.
function withdraw(uint256 toValidatorID, uint256 wrID) public {
_withdraw(msg.sender, toValidatorID, wrID, _receiverOf(msg.sender));
}
/// Deactivate a validator.
/// Called by the chain client when a client misbehavior is observed.
function deactivateValidator(uint256 validatorID, uint256 status) external onlyDriver {
if (status == OK_STATUS) {
revert NotDeactivatedStatus();
}
_setValidatorDeactivated(validatorID, status);
_syncValidator(validatorID, false);
address validatorAddr = getValidator[validatorID].auth;
_notifyStakeSubscriber(validatorAddr, validatorAddr, false);
}
/// Stash rewards for a delegator.
function stashRewards(address delegator, uint256 toValidatorID) external {
if (!_stashRewards(delegator, toValidatorID)) {
revert NothingToStash();
}
}
/// Resolve failed treasury transfers and send the unresolved fees to the treasury address.
function resolveTreasuryFees() external {
if (treasuryAddress == address(0)) {
revert TreasuryNotSet();
}
if (unresolvedTreasuryFees == 0) {
revert NoUnresolvedTreasuryFees();
}
// zero the fees before sending to prevent re-entrancy
uint256 fees = unresolvedTreasuryFees;
unresolvedTreasuryFees = 0;
(bool success, ) = treasuryAddress.call{value: fees, gas: 1000000}("");
if (!success) {
revert TransferFailed();
}
emit TreasuryFeesResolved(fees);
}
/// Burn native tokens by sending them to the SFC contract.
function burnNativeTokens() external payable {
if (msg.value == 0) {
revert ZeroAmount();
}
_burnNativeTokens(msg.value);
}
/// Issue tokens to the issued tokens recipient as a counterparty to the burnt FTM tokens.
function issueTokens(uint256 amount) external onlyOwner {
if (c.issuedTokensRecipient() == address(0)) {
revert ZeroAddress();
}
node.incBalance(c.issuedTokensRecipient(), amount);
totalSupply += amount;
}
/// Update treasury address.
function updateTreasuryAddress(address v) external onlyOwner {
treasuryAddress = v;
}
/// Update consts address.
function updateConstsAddress(address v) external onlyOwner {
c = ConstantsManager(v);
}
/// Update voteBook address.
function updateStakeSubscriberAddress(address v) external onlyOwner {
stakeSubscriberAddress = v;
}
/// Get consts address.
function constsAddress() external view returns (address) {
return address(c);
}
/// Claim rewards for stake delegated to a validator.
function claimRewards(uint256 toValidatorID) public {
address delegator = msg.sender;
uint256 rewards = _claimRewards(delegator, toValidatorID);
// It's important that we transfer after erasing (protection against Re-Entrancy)
(bool sent, ) = _receiverOf(delegator).call{value: rewards}("");
if (!sent) {
revert TransferFailed();
}
emit ClaimedRewards(delegator, toValidatorID, rewards);
}
/// Get amount of currently stashed rewards.
function rewardsStash(address delegator, uint256 validatorID) public view returns (uint256) {
return _rewardsStash[delegator][validatorID];
}
/// Un-delegate stake from a validator.
function undelegate(uint256 toValidatorID, uint256 wrID, uint256 amount) public {
address delegator = msg.sender;
_stashRewards(delegator, toValidatorID);
if (amount == 0) {
revert ZeroAmount();
}
if (getWithdrawalRequest[delegator][toValidatorID][wrID].amount != 0) {
revert RequestExists();
}
_rawUndelegate(delegator, toValidatorID, amount, true, false, true);
getWithdrawalRequest[delegator][toValidatorID][wrID].amount = amount;
getWithdrawalRequest[delegator][toValidatorID][wrID].epoch = currentEpoch();
getWithdrawalRequest[delegator][toValidatorID][wrID].time = _now();
_syncValidator(toValidatorID, false);
emit Undelegated(delegator, toValidatorID, wrID, amount);
}
/// Re-stake rewards - claim rewards for staking and delegate it immediately
/// to the same validator - add it to the current stake.
function restakeRewards(uint256 toValidatorID) public {
address delegator = msg.sender;
uint256 rewards = _claimRewards(delegator, toValidatorID);
_delegate(delegator, toValidatorID, rewards);
emit RestakedRewards(delegator, toValidatorID, rewards);
}
/// Get the current epoch number.
function currentEpoch() public view returns (uint256) {
return currentSealedEpoch + 1;
}
/// Get self-stake of a validator.
function getSelfStake(uint256 validatorID) public view returns (uint256) {
return getStake[getValidator[validatorID].auth][validatorID];
}
/// Get validator IDs for given epoch.
function getEpochValidatorIDs(uint256 epoch) public view returns (uint256[] memory) {
return getEpochSnapshot[epoch].validatorIDs;
}
/// Get received stake for a validator in a given epoch.
function getEpochReceivedStake(uint256 epoch, uint256 validatorID) public view returns (uint256) {
return getEpochSnapshot[epoch].receivedStake[validatorID];
}
/// Get accumulated reward per token for a validator in a given epoch.
function getEpochAccumulatedRewardPerToken(uint256 epoch, uint256 validatorID) public view returns (uint256) {
return getEpochSnapshot[epoch].accumulatedRewardPerToken[validatorID];
}
/// Get accumulated uptime for a validator in a given epoch.
function getEpochAccumulatedUptime(uint256 epoch, uint256 validatorID) public view returns (uint256) {
return getEpochSnapshot[epoch].accumulatedUptime[validatorID];
}
/// Get average uptime for a validator in a given epoch.
function getEpochAverageUptime(uint256 epoch, uint256 validatorID) public view returns (uint64) {
return getEpochSnapshot[epoch].averageUptime[validatorID].averageUptime;
}
/// Get accumulated originated txs fee for a validator in a given epoch.
function getEpochAccumulatedOriginatedTxsFee(uint256 epoch, uint256 validatorID) public view returns (uint256) {
return getEpochSnapshot[epoch].accumulatedOriginatedTxsFee[validatorID];
}
/// Get offline time for a validator in a given epoch.
function getEpochOfflineTime(uint256 epoch, uint256 validatorID) public view returns (uint256) {
return getEpochSnapshot[epoch].offlineTime[validatorID];
}
/// Get offline blocks for a validator in a given epoch.
function getEpochOfflineBlocks(uint256 epoch, uint256 validatorID) public view returns (uint256) {
return getEpochSnapshot[epoch].offlineBlocks[validatorID];
}
/// Get end block for a given epoch.
function getEpochEndBlock(uint256 epoch) public view returns (uint256) {
return getEpochSnapshot[epoch].endBlock;
}
/// Check whether the given validator is slashed - the stake (or its part) cannot
/// be withdrawn because of misbehavior (double-sign) of the validator.
function isSlashed(uint256 validatorID) public view returns (bool) {
return getValidator[validatorID].status & CHEATER_MASK != 0;
}
/// Get the amount of rewards which can be currently claimed by the given delegator for the given validator.
function pendingRewards(address delegator, uint256 toValidatorID) public view returns (uint256) {
uint256 reward = _newRewards(delegator, toValidatorID);
return _rewardsStash[delegator][toValidatorID] + reward;
}
/// Check whether the self-stake covers the required fraction of all delegations for the given validator.
function _checkDelegatedStakeLimit(uint256 validatorID) internal view returns (bool) {
return
getValidator[validatorID].receivedStake <=
(getSelfStake(validatorID) * c.maxDelegatedRatio()) / Decimal.unit();
}
/// Check if an address is the NodeDriverAuth contract.
function isNode(address addr) internal view virtual returns (bool) {
return addr == address(node);
}
/// Delegate stake to a validator.
function _delegate(address delegator, uint256 toValidatorID, uint256 amount) internal {
if (!_validatorExists(toValidatorID)) {
revert ValidatorNotExists();
}
if (getValidator[toValidatorID].status != OK_STATUS) {
revert ValidatorNotActive();
}
_rawDelegate(delegator, toValidatorID, amount, true);
if (!_checkDelegatedStakeLimit(toValidatorID)) {
revert ValidatorDelegationLimitExceeded();
}
}
/// Delegate stake to a validator without checking delegation limit.
function _rawDelegate(address delegator, uint256 toValidatorID, uint256 amount, bool strict) internal {
if (amount == 0) {
revert ZeroAmount();
}
_stashRewards(delegator, toValidatorID);
getStake[delegator][toValidatorID] = getStake[delegator][toValidatorID] + amount;
uint256 origStake = getValidator[toValidatorID].receivedStake;
getValidator[toValidatorID].receivedStake = origStake + amount;
totalStake = totalStake + amount;
if (getValidator[toValidatorID].status == OK_STATUS) {
totalActiveStake = totalActiveStake + amount;
}
_syncValidator(toValidatorID, origStake == 0);
emit Delegated(delegator, toValidatorID, amount);
_notifyStakeSubscriber(delegator, getValidator[toValidatorID].auth, strict);
}
/// Un-delegate stake from a validator.
function _rawUndelegate(
address delegator,
uint256 toValidatorID,
uint256 amount,
bool strict,
bool forceful,
bool checkDelegatedStake
) internal {
getStake[delegator][toValidatorID] -= amount;
getValidator[toValidatorID].receivedStake = getValidator[toValidatorID].receivedStake - amount;
totalStake = totalStake - amount;
if (getValidator[toValidatorID].status == OK_STATUS) {
totalActiveStake = totalActiveStake - amount;
}
uint256 selfStakeAfterwards = getSelfStake(toValidatorID);
if (selfStakeAfterwards != 0 && getValidator[toValidatorID].status == OK_STATUS) {
if (!(selfStakeAfterwards >= c.minSelfStake())) {
if (forceful) {
revert InsufficientSelfStake();
} else {
_setValidatorDeactivated(toValidatorID, WITHDRAWN_BIT);
}
}
if (checkDelegatedStake && !_checkDelegatedStakeLimit(toValidatorID)) {
revert ValidatorDelegationLimitExceeded();
}
} else {
_setValidatorDeactivated(toValidatorID, WITHDRAWN_BIT);
}
_notifyStakeSubscriber(delegator, getValidator[toValidatorID].auth, strict);
}
/// Get slashing penalty for a stake.
function getSlashingPenalty(
uint256 amount,
bool isCheater,
uint256 refundRatio
) internal pure returns (uint256 penalty) {
if (!isCheater || refundRatio >= Decimal.unit()) {
return 0;
}
// round penalty upwards (ceiling) to prevent dust amount attacks
penalty = (amount * (Decimal.unit() - refundRatio)) / Decimal.unit() + 1;
if (penalty > amount) {
return amount;
}
return penalty;
}
/// Withdraw stake from a validator.
/// The stake must be undelegated first.
function _withdraw(address delegator, uint256 toValidatorID, uint256 wrID, address payable receiver) private {
WithdrawalRequest memory request = getWithdrawalRequest[delegator][toValidatorID][wrID];
if (request.epoch == 0) {
revert RequestNotExists();
}
uint256 requestTime = request.time;
uint256 requestEpoch = request.epoch;
if (
getValidator[toValidatorID].deactivatedTime != 0 &&
getValidator[toValidatorID].deactivatedTime < requestTime
) {
requestTime = getValidator[toValidatorID].deactivatedTime;
requestEpoch = getValidator[toValidatorID].deactivatedEpoch;
}
if (_now() < requestTime + c.withdrawalPeriodTime()) {
revert NotEnoughTimePassed();
}
if (currentEpoch() < requestEpoch + c.withdrawalPeriodEpochs()) {
revert NotEnoughEpochsPassed();
}
uint256 amount = getWithdrawalRequest[delegator][toValidatorID][wrID].amount;
bool isCheater = isSlashed(toValidatorID);
uint256 penalty = getSlashingPenalty(amount, isCheater, slashingRefundRatio[toValidatorID]);
delete getWithdrawalRequest[delegator][toValidatorID][wrID];
if (amount <= penalty) {
revert StakeIsFullySlashed();
}
// It's important that we transfer after erasing (protection against Re-Entrancy)
(bool sent, ) = receiver.call{value: amount - penalty}("");
if (!sent) {
revert TransferFailed();
}
_burnNativeTokens(penalty);
emit Withdrawn(delegator, toValidatorID, wrID, amount - penalty, penalty);
}
/// Get highest epoch for which can be claimed rewards for the given validator.
// If the validator is deactivated, the highest payable epoch is the deactivation epoch
// or the current epoch, whichever is lower
function _highestPayableEpoch(uint256 validatorID) internal view returns (uint256) {
if (getValidator[validatorID].deactivatedEpoch != 0) {
if (currentSealedEpoch < getValidator[validatorID].deactivatedEpoch) {
return currentSealedEpoch;
}
return getValidator[validatorID].deactivatedEpoch;
}
return currentSealedEpoch;
}
/// Get new rewards for a delegator.
/// The rewards are calculated from the last stashed epoch until the highest payable epoch.
function _newRewards(address delegator, uint256 toValidatorID) internal view returns (uint256) {
uint256 stashedUntil = stashedRewardsUntilEpoch[delegator][toValidatorID];
uint256 payableUntil = _highestPayableEpoch(toValidatorID);
uint256 wholeStake = getStake[delegator][toValidatorID];
uint256 fullReward = _newRewardsOf(wholeStake, toValidatorID, stashedUntil, payableUntil);
return fullReward;
}
/// Get new rewards for a delegator for a given stake amount and epoch range.
function _newRewardsOf(
uint256 stakeAmount,
uint256 toValidatorID,
uint256 fromEpoch,
uint256 toEpoch
) internal view returns (uint256) {
if (fromEpoch >= toEpoch) {
return 0;
}
uint256 stashedRate = getEpochSnapshot[fromEpoch].accumulatedRewardPerToken[toValidatorID];
uint256 currentRate = getEpochSnapshot[toEpoch].accumulatedRewardPerToken[toValidatorID];
return ((currentRate - stashedRate) * stakeAmount) / Decimal.unit();
}
/// Stash rewards for a delegator.
function _stashRewards(address delegator, uint256 toValidatorID) internal returns (bool updated) {
uint256 nonStashedReward = _newRewards(delegator, toValidatorID);
stashedRewardsUntilEpoch[delegator][toValidatorID] = _highestPayableEpoch(toValidatorID);
_rewardsStash[delegator][toValidatorID] += nonStashedReward;
return nonStashedReward != 0;
}
/// Claim rewards for a delegator.
function _claimRewards(address delegator, uint256 toValidatorID) internal returns (uint256) {
_stashRewards(delegator, toValidatorID);
uint256 rewards = _rewardsStash[delegator][toValidatorID];
if (rewards == 0) {
revert ZeroRewards();
}
delete _rewardsStash[delegator][toValidatorID];
// It's important that we mint after erasing (protection against Re-Entrancy)
_mintNativeToken(rewards);
return rewards;
}
/// Burn native tokens.
/// The tokens are sent to the zero address.
function _burnNativeTokens(uint256 amount) internal {
if (amount != 0) {
if (amount > totalSupply) {
revert ValueTooLarge();
}
totalSupply -= amount;
payable(address(0)).transfer(amount);
emit BurntNativeTokens(amount);
}
}
/// Get epoch end time.
function epochEndTime(uint256 epoch) internal view returns (uint256) {
return getEpochSnapshot[epoch].endTime;
}
/// Check if an address is redirected.
function _redirected(address addr) internal view returns (bool) {
return getRedirection[addr] != address(0);
}
/// Get address which should receive rewards and withdrawn stake for the given delegator.
/// The delegator is usually the receiver, unless a redirection is created.
function _receiverOf(address addr) internal view returns (address payable) {
address to = getRedirection[addr];
if (to == address(0)) {
return payable(address(uint160(addr)));
}
return payable(address(uint160(to)));
}
/// Seal epoch - sync validators.
function _sealEpochOffline(
EpochSnapshot storage snapshot,
uint256[] memory validatorIDs,
uint256[] memory offlineTime,
uint256[] memory offlineBlocks
) internal {
// mark offline nodes
for (uint256 i = 0; i < validatorIDs.length; i++) {
if (
offlineBlocks[i] > c.offlinePenaltyThresholdBlocksNum() &&
offlineTime[i] >= c.offlinePenaltyThresholdTime()
) {
_setValidatorDeactivated(validatorIDs[i], OFFLINE_BIT);
_syncValidator(validatorIDs[i], false);
}
// log data
snapshot.offlineTime[validatorIDs[i]] = offlineTime[i];
snapshot.offlineBlocks[validatorIDs[i]] = offlineBlocks[i];
}
}
/// Seal epoch - calculate rewards.
function _sealEpochRewards(
uint256 epochDuration,
EpochSnapshot storage snapshot,
EpochSnapshot storage prevSnapshot,
uint256[] memory validatorIDs,
uint256[] memory uptimes,
uint256[] memory accumulatedOriginatedTxsFee
) internal {
SealEpochRewardsCtx memory ctx = SealEpochRewardsCtx(
new uint256[](validatorIDs.length),
0,
new uint256[](validatorIDs.length),
0,
0
);
for (uint256 i = 0; i < validatorIDs.length; i++) {
uint256 prevAccumulatedTxsFee = prevSnapshot.accumulatedOriginatedTxsFee[validatorIDs[i]];
uint256 originatedTxsFee = 0;
if (accumulatedOriginatedTxsFee[i] > prevAccumulatedTxsFee) {
originatedTxsFee = accumulatedOriginatedTxsFee[i] - prevAccumulatedTxsFee;
}
// txRewardWeight = {originatedTxsFee} * {uptime}
// originatedTxsFee is roughly proportional to {uptime} * {stake}, so the whole formula is roughly
// {stake} * {uptime} ^ 2
ctx.txRewardWeights[i] = (originatedTxsFee * uptimes[i]) / epochDuration;
ctx.totalTxRewardWeight = ctx.totalTxRewardWeight + ctx.txRewardWeights[i];
ctx.epochFee = ctx.epochFee + originatedTxsFee;
}
for (uint256 i = 0; i < validatorIDs.length; i++) {
// baseRewardWeight = {stake} * {uptime ^ 2}
ctx.baseRewardWeights[i] =
(((snapshot.receivedStake[validatorIDs[i]] * uptimes[i]) / epochDuration) * uptimes[i]) /
epochDuration;
ctx.totalBaseRewardWeight = ctx.totalBaseRewardWeight + ctx.baseRewardWeights[i];
}
for (uint256 i = 0; i < validatorIDs.length; i++) {
uint256 rawReward = _calcRawValidatorEpochBaseReward(
epochDuration,
c.baseRewardPerSecond(),
ctx.baseRewardWeights[i],
ctx.totalBaseRewardWeight
);
rawReward =
rawReward +
_calcRawValidatorEpochTxReward(ctx.epochFee, ctx.txRewardWeights[i], ctx.totalTxRewardWeight);
uint256 validatorID = validatorIDs[i];
address validatorAddr = getValidator[validatorID].auth;
// accounting validator's commission
uint256 commissionRewardFull = _calcValidatorCommission(rawReward, c.validatorCommission());
uint256 selfStake = getStake[validatorAddr][validatorID];
if (selfStake != 0) {
_rewardsStash[validatorAddr][validatorID] += commissionRewardFull;
}
// accounting reward per token for delegators
uint256 delegatorsReward = rawReward - commissionRewardFull;
// note: use latest stake for the sake of rewards distribution accuracy, not snapshot.receivedStake
uint256 receivedStake = getValidator[validatorID].receivedStake;
uint256 rewardPerToken = 0;
if (receivedStake != 0) {
rewardPerToken = (delegatorsReward * Decimal.unit()) / receivedStake;
}
snapshot.accumulatedRewardPerToken[validatorID] =
prevSnapshot.accumulatedRewardPerToken[validatorID] +
rewardPerToken;
snapshot.accumulatedOriginatedTxsFee[validatorID] = accumulatedOriginatedTxsFee[i];
snapshot.accumulatedUptime[validatorID] = prevSnapshot.accumulatedUptime[validatorID] + uptimes[i];
}
snapshot.epochFee = ctx.epochFee;
if (totalSupply > snapshot.epochFee) {
totalSupply -= snapshot.epochFee;
} else {
totalSupply = 0;
}
// transfer 10% of fees to treasury
if (treasuryAddress != address(0)) {
uint256 feeShare = (ctx.epochFee * c.treasuryFeeShare()) / Decimal.unit();
_mintNativeToken(feeShare);
(bool success, ) = treasuryAddress.call{value: feeShare, gas: 1000000}("");
// solhint-disable-next-line no-empty-blocks
if (!success) {
// ignore treasury transfer failure
// the treasury failure must not endanger the epoch sealing
// store the unresolved treasury fees to be resolved later
unresolvedTreasuryFees += feeShare;
}
}
}
/// Seal epoch - recalculate average uptime time of validators
function _sealEpochAverageUptime(
uint256 epochDuration,
EpochSnapshot storage snapshot,
EpochSnapshot storage prevSnapshot,
uint256[] memory validatorIDs,
uint256[] memory uptimes
) internal {
for (uint256 i = 0; i < validatorIDs.length; i++) {
uint256 validatorID = validatorIDs[i];
// compute normalised uptime as a percentage in the fixed-point format
uint256 normalisedUptime = (uptimes[i] * Decimal.unit()) / epochDuration;
if (normalisedUptime > Decimal.unit()) {
normalisedUptime = Decimal.unit();
}
AverageUptime memory previous = prevSnapshot.averageUptime[validatorID];
AverageUptime memory current = _addElementIntoAverageUptime(uint64(normalisedUptime), previous);
snapshot.averageUptime[validatorID] = current;
// remove validator if average uptime drops below min average uptime
// (by setting minAverageUptime to zero, this check is ignored)
if (current.averageUptime < c.minAverageUptime() && current.epochs >= c.averageUptimeEpochWindow()) {
_setValidatorDeactivated(validatorID, OFFLINE_AVG_BIT);
_syncValidator(validatorID, false);
}
}
}
function _addElementIntoAverageUptime(
uint64 newValue,
AverageUptime memory prev
) private view returns (AverageUptime memory) {
AverageUptime memory cur;
if (prev.epochs == 0) {
cur.averageUptime = newValue; // the only element for the average
cur.epochs = 1;
return cur;
}
// the number of elements the average is calculated from
uint128 n = prev.epochs + 1;
// add new value into the average
uint128 tmp = (n - 1) * uint128(prev.averageUptime) + uint128(newValue) + prev.remainder;
cur.averageUptime = uint64(tmp / n);
cur.remainder = uint32(tmp % n);
if (cur.averageUptime > Decimal.unit()) {
cur.averageUptime = uint64(Decimal.unit());
}
if (prev.epochs < c.averageUptimeEpochWindow()) {
cur.epochs = prev.epochs + 1;
} else {
cur.epochs = prev.epochs;
}
return cur;
}
/// Create a new validator.
function _createValidator(address auth, bytes calldata pubkey) internal {
uint256 validatorID = ++lastValidatorID;
_rawCreateValidator(auth, validatorID, pubkey, OK_STATUS, currentEpoch(), _now(), 0, 0);
}
/// Create a new validator without incrementing lastValidatorID.
function _rawCreateValidator(
address auth,
uint256 validatorID,
bytes calldata pubkey,
uint256 status,
uint256 createdEpoch,
uint256 createdTime,
uint256 deactivatedEpoch,
uint256 deactivatedTime
) internal {
if (getValidatorID[auth] != 0) {
revert ValidatorExists();
}
getValidatorID[auth] = validatorID;
getValidator[validatorID].status = status;
getValidator[validatorID].createdEpoch = createdEpoch;
getValidator[validatorID].createdTime = createdTime;
getValidator[validatorID].deactivatedTime = deactivatedTime;
getValidator[validatorID].deactivatedEpoch = deactivatedEpoch;
getValidator[validatorID].auth = auth;
getValidatorPubkey[validatorID] = pubkey;
pubkeyAddressToValidatorID[_pubkeyToAddress(pubkey)] = validatorID;
emit CreatedValidator(validatorID, auth, createdEpoch, createdTime);
if (deactivatedEpoch != 0) {
emit DeactivatedValidator(validatorID, deactivatedEpoch, deactivatedTime);
}
if (status != 0) {
emit ChangedValidatorStatus(validatorID, status);
}
}
/// Calculate raw validator epoch transaction reward.
function _calcRawValidatorEpochTxReward(
uint256 epochFee,
uint256 txRewardWeight,
uint256 totalTxRewardWeight
) internal view returns (uint256) {
if (txRewardWeight == 0) {
return 0;
}
uint256 txReward = (epochFee * txRewardWeight) / totalTxRewardWeight;
// fee reward except burntFeeShare and treasuryFeeShare
return (txReward * (Decimal.unit() - c.burntFeeShare() - c.treasuryFeeShare())) / Decimal.unit();
}
/// Calculate raw validator epoch base reward.
function _calcRawValidatorEpochBaseReward(
uint256 epochDuration,
uint256 _baseRewardPerSecond,
uint256 baseRewardWeight,
uint256 totalBaseRewardWeight
) internal pure returns (uint256) {
if (baseRewardWeight == 0) {
return 0;
}
uint256 totalReward = epochDuration * _baseRewardPerSecond;
return (totalReward * baseRewardWeight) / totalBaseRewardWeight;
}
/// Mint native token.
function _mintNativeToken(uint256 amount) internal {
// balance will be increased after the transaction is processed
node.incBalance(address(this), amount);
totalSupply = totalSupply + amount;
}
/// Notify stake subscriber about staking changes.
/// Used to recount votes from delegators in the governance contract.
function _notifyStakeSubscriber(address delegator, address validatorAuth, bool strict) internal {
if (stakeSubscriberAddress != address(0)) {
// Don't allow announceStakeChange to use up all the gas
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = stakeSubscriberAddress.call{gas: 8000000}(
abi.encodeWithSignature("announceStakeChange(address,address)", delegator, validatorAuth)
);
// Don't revert if announceStakeChange failed unless strict mode enabled
if (!success && strict) {
revert StakeSubscriberFailed();
}
}
}
/// Set validator deactivated status.
function _setValidatorDeactivated(uint256 validatorID, uint256 status) internal {
if (getValidator[validatorID].status == OK_STATUS && status != OK_STATUS) {
totalActiveStake = totalActiveStake - getValidator[validatorID].receivedStake;
}
// status as a number is proportional to severity
if (status > getValidator[validatorID].status) {
getValidator[validatorID].status = status;
if (getValidator[validatorID].deactivatedEpoch == 0) {
getValidator[validatorID].deactivatedEpoch = currentEpoch();
getValidator[validatorID].deactivatedTime = _now();
emit DeactivatedValidator(
validatorID,
getValidator[validatorID].deactivatedEpoch,
getValidator[validatorID].deactivatedTime
);
}
emit ChangedValidatorStatus(validatorID, status);
}
}
/// Sync validator with node.
function _syncValidator(uint256 validatorID, bool syncPubkey) public {
if (!_validatorExists(validatorID)) {
revert ValidatorNotExists();
}
// emit special log for node
uint256 weight = getValidator[validatorID].receivedStake;
if (getValidator[validatorID].status != OK_STATUS) {
weight = 0;
}
node.updateValidatorWeight(validatorID, weight);
if (syncPubkey && weight != 0) {
node.updateValidatorPubkey(validatorID, getValidatorPubkey[validatorID]);
}
}
/// Check if a validator exists.
function _validatorExists(uint256 validatorID) internal view returns (bool) {
return getValidator[validatorID].createdTime != 0;
}
/// Calculate validator commission.
function _calcValidatorCommission(uint256 rawReward, uint256 commission) internal pure returns (uint256) {
return (rawReward * commission) / Decimal.unit();
}
/// Derive address from validator private key
function _pubkeyToAddress(bytes calldata pubkey) private pure returns (address) {
return address(uint160(uint256(keccak256(pubkey[2:]))));
}
/// Get current time.
function _now() internal view virtual returns (uint256) {
return block.timestamp;
}
uint256[50] private __gap;
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.27;
/**
* @dev Version contract gives the versioning information of the implementation contract
*/
contract Version {
/**
* @dev Returns the version of the SFC contract
*/
function version() public pure returns (bytes3) {
return 0x040000; // version 4.0.0
}
}