编译器
0.8.18+commit.87f61d96
文件 1 的 19:Address.sol
pragma solidity ^0.8.1;
library Address {
function isContract(address account) internal view returns (bool) {
return account.code.length > 0;
}
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
文件 2 的 19:Context.sol
pragma solidity ^0.8.0;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
文件 3 的 19:IERC165.sol
pragma solidity ^0.8.0;
import "../utils/introspection/IERC165.sol";
文件 4 的 19:IERC20.sol
pragma solidity ^0.8.0;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}
文件 5 的 19:IMigratable.sol
pragma solidity ^0.8.18;
interface IMigratable {
event MigrationTargetProposed(address migrationTarget);
event MigrationTargetAccepted(address migrationTarget);
event Migrated(address staker, uint256 principal, uint256 baseReward, uint256 delegationReward, bytes data);
error InvalidMigrationTarget();
function getMigrationTarget() external view returns (address);
function proposeMigrationTarget(address migrationTarget) external;
function acceptMigrationTarget() external;
function migrate(bytes calldata data) external;
}
文件 6 的 19:IMigrationTarget.sol
pragma solidity ^0.8.18;
interface IMigrationTarget {
function migrateFrom(uint256 amount, bytes calldata data) external;
}
文件 7 的 19:INodeStaking.sol
pragma solidity ^0.8.18;
interface INodeStaking {
event Locked(address staker, uint256 newLock);
event Unlocked(address staker, uint256 newUnlock);
event DelegationRewardSlashed(address staker, uint256 amount);
error InadequateOperatorLockedStakingAmount(uint256 currentLockedStakingAmount);
function lock(address staker, uint256 amount) external;
function unlock(address staker, uint256 amount) external;
function slashDelegationReward(address staker, uint256 amount) external;
function getLockedAmount(address staker) external view returns (uint256);
}
文件 8 的 19:IStaking.sol
pragma solidity ^0.8.18;
interface IStaking {
event ControllerSet(address controller);
event Staked(address staker, uint256 newStake, uint256 totalStake);
event Unstaked(address staker, uint256 principal, uint256 baseReward, uint256 delegationReward);
event RewardClaimed(address staker, uint256 baseReward);
event FrozenPrincipalClaimed(address staker, uint256 principal);
error AccessForbidden();
error InvalidZeroAddress();
error SenderNotController();
function stake(uint256 amount) external;
function unstake(uint256 amount) external;
function claim() external;
function claimReward() external;
function claimFrozenPrincipal() external;
function getArpaToken() external view returns (address);
function getStake(address staker) external view returns (uint256);
function isOperator(address staker) external view returns (bool);
function isActive() external view returns (bool);
function getMaxPoolSize() external view returns (uint256);
function getCommunityStakerLimits() external view returns (uint256, uint256);
function getOperatorLimit() external view returns (uint256);
function getRewardTimestamps() external view returns (uint256, uint256);
function getRewardRate() external view returns (uint256);
function getDelegationRateDenominator() external view returns (uint256);
function getAvailableReward() external view returns (uint256);
function getBaseReward(address) external view returns (uint256);
function getDelegationReward(address) external view returns (uint256);
function getTotalDelegatedAmount() external view returns (uint256);
function getDelegatesCount() external view returns (uint256);
function getCommunityStakersCount() external view returns (uint256);
function getTotalStakedAmount() external view returns (uint256);
function getTotalCommunityStakedAmount() external view returns (uint256);
function getTotalFrozenAmount() external view returns (uint256);
function getFrozenPrincipal(address)
external
view
returns (uint96[] memory amounts, uint256[] memory unlockTimestamps);
function getClaimablePrincipalAmount(address) external view returns (uint256);
function getController() external view returns (address);
}
文件 9 的 19:IStakingOwner.sol
pragma solidity ^0.8.18;
interface IStakingOwner {
error InvalidDelegationRate();
error InvalidOperatorStakeAmount();
error InvalidMinCommunityStakeAmount();
error AlreadyInitialized();
function addOperators(address[] calldata operators) external;
function newReward(uint256 amount, uint256 rewardDuration) external;
function addReward(uint256 amount, uint256 rewardDuration) external;
function setPoolConfig(uint256 maxPoolSize, uint256 maxCommunityStakeAmount) external;
function setController(address controller) external;
function start(uint256 amount, uint256 rewardDuration) external;
function emergencyPause() external;
function emergencyUnpause() external;
}
文件 10 的 19:Math.sol
pragma solidity ^0.8.0;
library Math {
enum Rounding {
Down,
Up,
Zero
}
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
function average(uint256 a, uint256 b) internal pure returns (uint256) {
return (a & b) + (a ^ b) / 2;
}
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
return a == 0 ? 0 : (a - 1) / b + 1;
}
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
uint256 prod0;
uint256 prod1;
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
if (prod1 == 0) {
return prod0 / denominator;
}
require(denominator > prod1);
uint256 remainder;
assembly {
remainder := mulmod(x, y, denominator)
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
uint256 twos = denominator & (~denominator + 1);
assembly {
denominator := div(denominator, twos)
prod0 := div(prod0, twos)
twos := add(div(sub(0, twos), twos), 1)
}
prod0 |= prod1 * twos;
uint256 inverse = (3 * denominator) ^ 2;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
result = prod0 * inverse;
return result;
}
}
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator,
Rounding rounding
) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 result = 1 << (log2(a) >> 1);
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10**64) {
value /= 10**64;
result += 64;
}
if (value >= 10**32) {
value /= 10**32;
result += 32;
}
if (value >= 10**16) {
value /= 10**16;
result += 16;
}
if (value >= 10**8) {
value /= 10**8;
result += 8;
}
if (value >= 10**4) {
value /= 10**4;
result += 4;
}
if (value >= 10**2) {
value /= 10**2;
result += 2;
}
if (value >= 10**1) {
result += 1;
}
}
return result;
}
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
}
}
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
}
}
}
文件 11 的 19:Ownable.sol
pragma solidity ^0.8.0;
import "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
_transferOwnership(_msgSender());
}
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view virtual returns (address) {
return _owner;
}
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 12 的 19:Pausable.sol
pragma solidity ^0.8.0;
import "../utils/Context.sol";
abstract contract Pausable is Context {
event Paused(address account);
event Unpaused(address account);
bool private _paused;
constructor() {
_paused = false;
}
modifier whenNotPaused() {
_requireNotPaused();
_;
}
modifier whenPaused() {
_requirePaused();
_;
}
function paused() public view virtual returns (bool) {
return _paused;
}
function _requireNotPaused() internal view virtual {
require(!paused(), "Pausable: paused");
}
function _requirePaused() internal view virtual {
require(paused(), "Pausable: not paused");
}
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
文件 13 的 19:RewardLib.sol
pragma solidity ^0.8.18;
import {SafeCast} from "./SafeCast.sol";
import {StakingPoolLib} from "./StakingPoolLib.sol";
import {Math} from "openzeppelin-contracts/contracts/utils/math/Math.sol";
library RewardLib {
using SafeCast for uint256;
event RewardInitialized(uint256 available, uint256 startTimestamp, uint256 endTimestamp);
event RewardAdded(uint256 amountAdded, uint256 endTimestamp);
event RewardWithdrawn(uint256 amount);
event RewardSlashed(address[] operator, uint256[] slashedDelegatedRewards);
error RewardDurationTooShort();
uint256 internal constant REWARD_PRECISION = 1e12;
struct DelegatedRewards {
uint8 delegatesCount;
uint96 cumulativePerDelegate;
uint32 lastAccumulateTimestamp;
}
struct BaseRewards {
uint32 communityStakersCount;
uint96 cumulativePerShare;
uint32 lastAccumulateTimestamp;
}
struct MissedRewards {
uint96 base;
uint96 delegated;
}
struct Reward {
mapping(address => MissedRewards) missed;
DelegatedRewards delegated;
BaseRewards base;
uint80 rate;
uint32 endTimestamp;
uint32 startTimestamp;
}
function _initialize(Reward storage reward, uint256 minRewardDuration, uint256 newReward, uint256 rewardDuration)
internal
{
uint32 blockTimestamp = block.timestamp._toUint32();
reward.startTimestamp = blockTimestamp;
reward.delegated.lastAccumulateTimestamp = blockTimestamp;
reward.base.lastAccumulateTimestamp = blockTimestamp;
_updateReward(reward, newReward, rewardDuration, minRewardDuration);
emit RewardInitialized(newReward, reward.startTimestamp, reward.endTimestamp);
}
function _isDepleted(Reward storage reward) internal view returns (bool) {
return reward.endTimestamp <= block.timestamp;
}
function _accumulateBaseRewards(Reward storage reward, uint256 totalStakedAmount) internal {
reward.base.cumulativePerShare = _calculateCumulativeBaseRewards(reward, totalStakedAmount)._toUint96();
reward.base.lastAccumulateTimestamp = _getCappedTimestamp(reward)._toUint32();
}
function _accumulateDelegationRewards(
Reward storage reward,
uint256 totalDelegatedAmount,
uint256 totalStakedAmount
) internal {
reward.delegated.cumulativePerDelegate =
_calculateCumulativeDelegatedRewards(reward, totalDelegatedAmount, totalStakedAmount)._toUint96();
reward.delegated.lastAccumulateTimestamp = _getCappedTimestamp(reward)._toUint32();
}
function _calculateCumulativeBaseRewards(Reward storage reward, uint256 totalStakedAmount)
internal
view
returns (uint256)
{
if (totalStakedAmount == 0) return reward.base.cumulativePerShare;
uint256 elapsedDurationSinceLastAccumulate = _isDepleted(reward)
? (uint256(reward.endTimestamp) - uint256(reward.base.lastAccumulateTimestamp))
: block.timestamp - uint256(reward.base.lastAccumulateTimestamp);
return reward.base.cumulativePerShare
+ (uint256(reward.rate) * elapsedDurationSinceLastAccumulate * REWARD_PRECISION / totalStakedAmount)._toUint96();
}
function _calculateCumulativeDelegatedRewards(
Reward storage reward,
uint256 totalDelegatedAmount,
uint256 totalStakedAmount
) internal view returns (uint256) {
if (totalStakedAmount == 0) return reward.delegated.cumulativePerDelegate;
uint256 elapsedDurationSinceLastAccumulate = _isDepleted(reward)
? uint256(reward.endTimestamp) - uint256(reward.delegated.lastAccumulateTimestamp)
: block.timestamp - uint256(reward.delegated.lastAccumulateTimestamp);
return reward.delegated.cumulativePerDelegate
+ (
uint256(reward.rate) * elapsedDurationSinceLastAccumulate * totalDelegatedAmount / totalStakedAmount
/ Math.max(uint256(reward.delegated.delegatesCount), 1)
)._toUint96();
}
function _calculateAccruedDelegatedRewards(
Reward storage reward,
uint256 totalDelegatedAmount,
uint256 totalStakedAmount
) internal view returns (uint256) {
return _calculateCumulativeDelegatedRewards(reward, totalDelegatedAmount, totalStakedAmount);
}
function _calculateAccruedBaseRewards(Reward storage reward, uint256 amount, uint256 totalStakedAmount)
internal
view
returns (uint256)
{
return amount * _calculateCumulativeBaseRewards(reward, totalStakedAmount) / REWARD_PRECISION;
}
function _getDelegatedAmount(uint256 amount, uint256 delegationRateDenominator) internal pure returns (uint256) {
return amount / delegationRateDenominator;
}
function _getNonDelegatedAmount(uint256 amount, uint256 delegationRateDenominator)
internal
pure
returns (uint256)
{
return amount - _getDelegatedAmount(amount, delegationRateDenominator);
}
function _updateReward(Reward storage reward, uint256 newReward, uint256 rewardDuration, uint256 minRewardDuration)
internal
{
uint256 remainingRewards =
(_isDepleted(reward) ? 0 : (reward.rate * (uint256(reward.endTimestamp) - block.timestamp))) + newReward;
if (rewardDuration < minRewardDuration) {
revert RewardDurationTooShort();
}
reward.endTimestamp = (block.timestamp + rewardDuration)._toUint32();
reward.rate = (remainingRewards / rewardDuration)._toUint80();
}
function _getOperatorEarnedDelegatedRewards(
Reward storage reward,
address operator,
uint256 totalDelegatedAmount,
uint256 totalStakedAmount
) internal view returns (uint256) {
return _calculateAccruedDelegatedRewards(reward, totalDelegatedAmount, totalStakedAmount)
- uint256(reward.missed[operator].delegated);
}
function _getCappedTimestamp(Reward storage reward) internal view returns (uint256) {
return Math.min(uint256(reward.endTimestamp), block.timestamp);
}
}
文件 14 的 19:SafeCast.sol
pragma solidity ^0.8.18;
library SafeCast {
error CastError();
uint256 private constant MAX_UINT_8 = type(uint8).max;
uint256 private constant MAX_UINT_32 = type(uint32).max;
uint256 private constant MAX_UINT_80 = type(uint80).max;
uint256 private constant MAX_UINT_96 = type(uint96).max;
function _toUint8(uint256 value) internal pure returns (uint8) {
if (value > MAX_UINT_8) revert CastError();
return uint8(value);
}
function _toUint32(uint256 value) internal pure returns (uint32) {
if (value > MAX_UINT_32) revert CastError();
return uint32(value);
}
function _toUint80(uint256 value) internal pure returns (uint80) {
if (value > MAX_UINT_80) revert CastError();
return uint80(value);
}
function _toUint96(uint256 value) internal pure returns (uint96) {
if (value > MAX_UINT_96) revert CastError();
return uint96(value);
}
}
文件 15 的 19:SafeERC20.sol
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../utils/Address.sol";
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
function _callOptionalReturn(IERC20 token, bytes memory data) private {
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
文件 16 的 19:Staking.sol
pragma solidity ^0.8.18;
import {IERC20, SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {TypeAndVersionInterface} from "./interfaces/TypeAndVersionInterface.sol";
import {Pausable} from "openzeppelin-contracts/contracts/security/Pausable.sol";
import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol";
import {IERC165} from "openzeppelin-contracts/contracts/interfaces/IERC165.sol";
import {Math} from "openzeppelin-contracts/contracts/utils/math/Math.sol";
import {IStaking} from "./interfaces/IStaking.sol";
import {IStakingOwner} from "./interfaces/IStakingOwner.sol";
import {INodeStaking} from "./interfaces/INodeStaking.sol";
import {IMigratable} from "./interfaces/IMigratable.sol";
import {StakingPoolLib} from "./libraries/StakingPoolLib.sol";
import {RewardLib, SafeCast} from "./libraries/RewardLib.sol";
import {IMigrationTarget} from "./interfaces/IMigrationTarget.sol";
contract Staking is IStaking, IStakingOwner, INodeStaking, IMigratable, Ownable, TypeAndVersionInterface, Pausable {
using StakingPoolLib for StakingPoolLib.Pool;
using RewardLib for RewardLib.Reward;
using SafeCast for uint256;
using SafeERC20 for IERC20;
struct PoolConstructorParams {
IERC20 arpa;
uint256 initialMaxPoolSize;
uint256 initialMaxCommunityStakeAmount;
uint256 minCommunityStakeAmount;
uint256 operatorStakeAmount;
uint256 minInitialOperatorCount;
uint256 minRewardDuration;
uint256 delegationRateDenominator;
uint256 unstakeFreezingDuration;
}
IERC20 internal immutable _arpa;
StakingPoolLib.Pool internal _pool;
RewardLib.Reward internal _reward;
address internal _controller;
address internal _proposedMigrationTarget;
uint256 internal _proposedMigrationTargetAt;
address internal _migrationTarget;
uint256 internal immutable _operatorStakeAmount;
uint256 internal immutable _minCommunityStakeAmount;
uint256 internal immutable _minInitialOperatorCount;
uint256 internal immutable _minRewardDuration;
uint256 internal immutable _delegationRateDenominator;
uint256 internal immutable _unstakeFreezingDuration;
event StakingConfigSet(
address arpaAddress,
uint256 initialMaxPoolSize,
uint256 initialMaxCommunityStakeAmount,
uint256 minCommunityStakeAmount,
uint256 operatorStakeAmount,
uint256 minInitialOperatorCount,
uint256 minRewardDuration,
uint256 delegationRateDenominator,
uint256 unstakeFreezingDuration
);
constructor(PoolConstructorParams memory params) {
if (address(params.arpa) == address(0)) revert InvalidZeroAddress();
if (params.delegationRateDenominator == 0) revert InvalidDelegationRate();
if (RewardLib.REWARD_PRECISION % params.delegationRateDenominator > 0) {
revert InvalidDelegationRate();
}
if (params.operatorStakeAmount == 0) {
revert InvalidOperatorStakeAmount();
}
if (params.minCommunityStakeAmount > params.initialMaxCommunityStakeAmount) {
revert InvalidMinCommunityStakeAmount();
}
_pool._setConfig(params.initialMaxPoolSize, params.initialMaxCommunityStakeAmount);
_arpa = params.arpa;
_operatorStakeAmount = params.operatorStakeAmount;
_minCommunityStakeAmount = params.minCommunityStakeAmount;
_minInitialOperatorCount = params.minInitialOperatorCount;
_minRewardDuration = params.minRewardDuration;
_delegationRateDenominator = params.delegationRateDenominator;
_unstakeFreezingDuration = params.unstakeFreezingDuration;
emit StakingConfigSet(
address(params.arpa),
params.initialMaxPoolSize,
params.initialMaxCommunityStakeAmount,
params.minCommunityStakeAmount,
params.operatorStakeAmount,
params.minInitialOperatorCount,
params.minRewardDuration,
params.delegationRateDenominator,
params.unstakeFreezingDuration
);
}
function typeAndVersion() external pure override returns (string memory) {
return "Staking 0.1.0";
}
function setController(address controller) external override(IStakingOwner) onlyOwner {
if (controller == address(0)) revert InvalidZeroAddress();
_controller = controller;
emit ControllerSet(controller);
}
function setPoolConfig(uint256 maxPoolSize, uint256 maxCommunityStakeAmount)
external
override(IStakingOwner)
onlyOwner
whenActive
{
_pool._setConfig(maxPoolSize, maxCommunityStakeAmount);
}
function start(uint256 amount, uint256 rewardDuration) external override(IStakingOwner) onlyOwner {
if (_reward.startTimestamp != 0) revert AlreadyInitialized();
_pool._open(_minInitialOperatorCount);
_arpa.safeTransferFrom(msg.sender, address(this), amount);
_reward._initialize(_minRewardDuration, amount, rewardDuration);
}
function newReward(uint256 amount, uint256 rewardDuration)
external
override(IStakingOwner)
onlyOwner
whenInactive
{
_reward._accumulateBaseRewards(getTotalCommunityStakedAmount());
_reward._accumulateDelegationRewards(getTotalDelegatedAmount(), getTotalCommunityStakedAmount());
_arpa.safeTransferFrom(msg.sender, address(this), amount);
_reward._initialize(_minRewardDuration, amount, rewardDuration);
}
function addReward(uint256 amount, uint256 rewardDuration) external override(IStakingOwner) onlyOwner whenActive {
_reward._accumulateBaseRewards(getTotalCommunityStakedAmount());
_reward._accumulateDelegationRewards(getTotalDelegatedAmount(), getTotalCommunityStakedAmount());
_arpa.safeTransferFrom(msg.sender, address(this), amount);
_reward._updateReward(amount, rewardDuration, _minRewardDuration);
emit RewardLib.RewardAdded(amount, block.timestamp + rewardDuration);
}
function addOperators(address[] calldata operators) external override(IStakingOwner) onlyOwner {
if (_reward.startTimestamp > 0 && !isActive()) {
revert StakingPoolLib.InvalidPoolStatus(false, true);
}
_pool._addOperators(operators);
}
function emergencyPause() external override(IStakingOwner) onlyOwner {
_pause();
}
function emergencyUnpause() external override(IStakingOwner) onlyOwner {
_unpause();
}
function getMigrationTarget() external view override(IMigratable) returns (address) {
return _migrationTarget;
}
function proposeMigrationTarget(address migrationTarget) external override(IMigratable) onlyOwner {
if (
migrationTarget.code.length == 0 || migrationTarget == address(this)
|| _proposedMigrationTarget == migrationTarget || _migrationTarget == migrationTarget
|| !IERC165(migrationTarget).supportsInterface(IMigrationTarget.migrateFrom.selector)
) {
revert InvalidMigrationTarget();
}
_migrationTarget = address(0);
_proposedMigrationTarget = migrationTarget;
_proposedMigrationTargetAt = block.timestamp;
emit MigrationTargetProposed(migrationTarget);
}
function acceptMigrationTarget() external override(IMigratable) onlyOwner {
if (_proposedMigrationTarget == address(0)) {
revert InvalidMigrationTarget();
}
if (block.timestamp < (uint256(_proposedMigrationTargetAt) + 7 days)) {
revert AccessForbidden();
}
_migrationTarget = _proposedMigrationTarget;
_proposedMigrationTarget = address(0);
emit MigrationTargetAccepted(_migrationTarget);
}
function migrate(bytes calldata data) external override(IMigratable) whenInactive {
if (_migrationTarget == address(0)) revert InvalidMigrationTarget();
(uint256 amount, uint256 baseReward, uint256 delegationReward) = _exit(msg.sender);
_arpa.safeTransfer(_migrationTarget, uint256(amount + baseReward + delegationReward));
IMigrationTarget(_migrationTarget).migrateFrom(
uint256(amount + baseReward + delegationReward), abi.encode(msg.sender, data)
);
emit Migrated(msg.sender, amount, baseReward, delegationReward, data);
}
function lock(address staker, uint256 amount) external override(INodeStaking) onlyController {
StakingPoolLib.Staker storage stakerAccount = _pool.stakers[staker];
if (!stakerAccount.isOperator) {
revert StakingPoolLib.OperatorDoesNotExist(staker);
}
if (stakerAccount.stakedAmount < amount) {
revert StakingPoolLib.InsufficientStakeAmount(amount);
}
stakerAccount.lockedStakeAmount += amount._toUint96();
emit Locked(staker, amount);
}
function unlock(address staker, uint256 amount) external override(INodeStaking) onlyController {
StakingPoolLib.Staker storage stakerAccount = _pool.stakers[staker];
if (!stakerAccount.isOperator) {
revert StakingPoolLib.OperatorDoesNotExist(staker);
}
if (stakerAccount.lockedStakeAmount < amount) {
revert INodeStaking.InadequateOperatorLockedStakingAmount(stakerAccount.lockedStakeAmount);
}
stakerAccount.lockedStakeAmount -= amount._toUint96();
emit Unlocked(staker, amount);
}
function slashDelegationReward(address staker, uint256 amount) external override(INodeStaking) onlyController {
StakingPoolLib.Staker memory stakerAccount = _pool.stakers[staker];
if (!stakerAccount.isOperator) {
revert StakingPoolLib.OperatorDoesNotExist(staker);
}
uint256 earnedRewards = _reward._getOperatorEarnedDelegatedRewards(
staker, getTotalDelegatedAmount(), getTotalCommunityStakedAmount()
);
uint256 slashedRewards = Math.min(amount, earnedRewards);
_reward.missed[staker].delegated += slashedRewards._toUint96();
_arpa.safeTransfer(owner(), slashedRewards);
emit DelegationRewardSlashed(staker, slashedRewards);
}
function getLockedAmount(address staker) external view override(INodeStaking) returns (uint256) {
return _pool.stakers[staker].lockedStakeAmount;
}
function stake(uint256 amount) external override(IStaking) whenNotPaused {
if (amount < RewardLib.REWARD_PRECISION) {
revert StakingPoolLib.InsufficientStakeAmount(RewardLib.REWARD_PRECISION);
}
uint256 remainder = amount % RewardLib.REWARD_PRECISION;
if (remainder > 0) {
amount -= remainder;
}
if (_pool._isOperator(msg.sender)) {
_stakeAsOperator(msg.sender, amount);
} else {
_stakeAsCommunityStaker(msg.sender, amount);
}
_arpa.safeTransferFrom(msg.sender, address(this), amount);
}
function unstake(uint256 amount) external override(IStaking) whenNotPaused {
uint256 remainder = amount % RewardLib.REWARD_PRECISION;
if (remainder > 0) {
amount -= remainder;
}
(uint256 baseReward, uint256 delegationReward) = _exit(msg.sender, amount, false);
_arpa.safeTransfer(msg.sender, baseReward + delegationReward);
emit Unstaked(msg.sender, amount, baseReward, delegationReward);
}
function claim() external override(IStaking) whenNotPaused {
claimReward();
if (_pool.stakers[msg.sender].frozenPrincipals.length > 0) {
claimFrozenPrincipal();
}
}
function claimReward() public override(IStaking) whenNotPaused {
StakingPoolLib.Staker memory stakerAccount = _pool.stakers[msg.sender];
if (stakerAccount.isOperator) {
revert StakingPoolLib.NoBaseRewardForOperator();
}
uint256 accruedReward = _reward._calculateAccruedBaseRewards(
RewardLib._getNonDelegatedAmount(stakerAccount.stakedAmount, _delegationRateDenominator),
getTotalCommunityStakedAmount()
);
uint256 claimingReward = accruedReward - uint256(_reward.missed[msg.sender].base);
_reward.missed[msg.sender].base = accruedReward._toUint96();
_arpa.safeTransfer(msg.sender, claimingReward);
emit RewardClaimed(msg.sender, claimingReward);
}
function claimFrozenPrincipal() public override(IStaking) whenNotPaused {
StakingPoolLib.FrozenPrincipal[] storage frozenPrincipals = _pool.stakers[msg.sender].frozenPrincipals;
if (frozenPrincipals.length == 0) revert StakingPoolLib.FrozenPrincipalDoesNotExist(msg.sender);
uint256 claimingPrincipal = 0;
uint256 popCount = 0;
for (uint256 i = 0; i < frozenPrincipals.length; i++) {
StakingPoolLib.FrozenPrincipal storage frozenPrincipal = frozenPrincipals[i];
if (frozenPrincipals[i].unlockTimestamp <= block.timestamp) {
claimingPrincipal += frozenPrincipal.amount;
_pool.totalFrozenAmount -= frozenPrincipal.amount;
popCount++;
} else {
break;
}
}
if (popCount > 0) {
for (uint256 i = 0; i < frozenPrincipals.length - popCount; i++) {
frozenPrincipals[i] = frozenPrincipals[i + popCount];
}
for (uint256 i = 0; i < popCount; i++) {
frozenPrincipals.pop();
}
}
if (claimingPrincipal > 0) {
_arpa.safeTransfer(msg.sender, claimingPrincipal);
}
emit FrozenPrincipalClaimed(msg.sender, claimingPrincipal);
}
function getStake(address staker) public view override(IStaking) returns (uint256) {
return _pool.stakers[staker].stakedAmount;
}
function isOperator(address staker) external view override(IStaking) returns (bool) {
return _pool._isOperator(staker);
}
function isActive() public view override(IStaking) returns (bool) {
return _pool.state.isOpen && !_reward._isDepleted();
}
function getMaxPoolSize() external view override(IStaking) returns (uint256) {
return uint256(_pool.limits.maxPoolSize);
}
function getCommunityStakerLimits() external view override(IStaking) returns (uint256, uint256) {
return (_minCommunityStakeAmount, uint256(_pool.limits.maxCommunityStakeAmount));
}
function getOperatorLimit() external view override(IStaking) returns (uint256) {
return _operatorStakeAmount;
}
function getRewardTimestamps() external view override(IStaking) returns (uint256, uint256) {
return (uint256(_reward.startTimestamp), uint256(_reward.endTimestamp));
}
function getRewardRate() external view override(IStaking) returns (uint256) {
return uint256(_reward.rate);
}
function getDelegationRateDenominator() external view override(IStaking) returns (uint256) {
return _delegationRateDenominator;
}
function getAvailableReward() public view override(IStaking) returns (uint256) {
return _arpa.balanceOf(address(this)) - getTotalStakedAmount() - _pool.totalFrozenAmount;
}
function getBaseReward(address staker) public view override(IStaking) returns (uint256) {
uint256 stakedAmount = _pool.stakers[staker].stakedAmount;
if (stakedAmount == 0) return 0;
if (_pool._isOperator(staker)) {
return 0;
}
return _reward._calculateAccruedBaseRewards(
RewardLib._getNonDelegatedAmount(stakedAmount, _delegationRateDenominator), getTotalCommunityStakedAmount()
) - uint256(_reward.missed[staker].base);
}
function getDelegationReward(address staker) public view override(IStaking) returns (uint256) {
StakingPoolLib.Staker memory stakerAccount = _pool.stakers[staker];
if (!stakerAccount.isOperator) return 0;
if (stakerAccount.stakedAmount == 0) return 0;
return _reward._getOperatorEarnedDelegatedRewards(
staker, getTotalDelegatedAmount(), getTotalCommunityStakedAmount()
);
}
function getTotalDelegatedAmount() public view override(IStaking) returns (uint256) {
return RewardLib._getDelegatedAmount(_pool.state.totalCommunityStakedAmount, _delegationRateDenominator);
}
function getDelegatesCount() external view override(IStaking) returns (uint256) {
return uint256(_reward.delegated.delegatesCount);
}
function getCommunityStakersCount() external view returns (uint256) {
return uint256(_reward.base.communityStakersCount);
}
function getTotalStakedAmount() public view override(IStaking) returns (uint256) {
return _pool._getTotalStakedAmount();
}
function getTotalCommunityStakedAmount() public view override(IStaking) returns (uint256) {
return _pool.state.totalCommunityStakedAmount;
}
function getTotalFrozenAmount() external view override(IStaking) returns (uint256) {
return _pool.totalFrozenAmount;
}
function getFrozenPrincipal(address staker)
external
view
override(IStaking)
returns (uint96[] memory amounts, uint256[] memory unlockTimestamps)
{
StakingPoolLib.FrozenPrincipal[] memory frozenPrincipals = _pool.stakers[staker].frozenPrincipals;
amounts = new uint96[](frozenPrincipals.length);
unlockTimestamps = new uint256[](frozenPrincipals.length);
for (uint256 i = 0; i < frozenPrincipals.length; i++) {
amounts[i] = frozenPrincipals[i].amount;
unlockTimestamps[i] = frozenPrincipals[i].unlockTimestamp;
}
}
function getClaimablePrincipalAmount(address) external view returns (uint256 claimingPrincipal) {
StakingPoolLib.FrozenPrincipal[] storage frozenPrincipals = _pool.stakers[msg.sender].frozenPrincipals;
if (frozenPrincipals.length == 0) return 0;
for (uint256 i = 0; i < frozenPrincipals.length; i++) {
StakingPoolLib.FrozenPrincipal storage frozenPrincipal = frozenPrincipals[i];
if (frozenPrincipals[i].unlockTimestamp <= block.timestamp) {
claimingPrincipal += frozenPrincipal.amount;
} else {
break;
}
}
}
function getArpaToken() public view override(IStaking) returns (address) {
return address(_arpa);
}
function getController() external view override(IStaking) returns (address) {
return _controller;
}
function _stakeAsCommunityStaker(address staker, uint256 amount) internal whenActive {
uint256 currentStakedAmount = _pool.stakers[staker].stakedAmount;
uint256 newStakedAmount = currentStakedAmount + amount;
if (newStakedAmount < _minCommunityStakeAmount) {
revert StakingPoolLib.InsufficientStakeAmount(_minCommunityStakeAmount);
}
uint256 maxCommunityStakeAmount = uint256(_pool.limits.maxCommunityStakeAmount);
if (newStakedAmount > maxCommunityStakeAmount) {
revert StakingPoolLib.ExcessiveStakeAmount(maxCommunityStakeAmount - currentStakedAmount);
}
uint256 remainingPoolSpace = _pool._getRemainingPoolSpace();
if (amount > remainingPoolSpace) {
revert StakingPoolLib.ExcessiveStakeAmount(remainingPoolSpace);
}
_reward._accumulateBaseRewards(getTotalCommunityStakedAmount());
_reward._accumulateDelegationRewards(getTotalDelegatedAmount(), getTotalCommunityStakedAmount());
if (currentStakedAmount == 0) {
_reward.base.communityStakersCount += 1;
}
uint256 extraNonDelegatedAmount = RewardLib._getNonDelegatedAmount(amount, _delegationRateDenominator);
_reward.missed[staker].base +=
_reward._calculateAccruedBaseRewards(extraNonDelegatedAmount, getTotalCommunityStakedAmount())._toUint96();
_pool.state.totalCommunityStakedAmount += amount._toUint96();
_pool.stakers[staker].stakedAmount = newStakedAmount._toUint96();
emit Staked(staker, amount, newStakedAmount);
}
function _stakeAsOperator(address staker, uint256 amount) internal {
StakingPoolLib.Staker storage operator = _pool.stakers[staker];
uint256 currentStakedAmount = operator.stakedAmount;
uint256 newStakedAmount = currentStakedAmount + amount;
if (newStakedAmount < _operatorStakeAmount) {
revert StakingPoolLib.InsufficientStakeAmount(_operatorStakeAmount);
}
if (newStakedAmount > _operatorStakeAmount) {
revert StakingPoolLib.ExcessiveStakeAmount(newStakedAmount - _operatorStakeAmount);
}
if (currentStakedAmount == 0) {
_reward._accumulateDelegationRewards(getTotalDelegatedAmount(), getTotalCommunityStakedAmount());
uint8 delegatesCount = _reward.delegated.delegatesCount;
if (delegatesCount == 0) {
delete _reward.delegated.cumulativePerDelegate;
}
_reward.delegated.delegatesCount = delegatesCount + 1;
_reward.missed[staker].delegated = _reward.delegated.cumulativePerDelegate;
}
_pool.state.totalOperatorStakedAmount += amount._toUint96();
_pool.stakers[staker].stakedAmount = newStakedAmount._toUint96();
emit Staked(staker, amount, newStakedAmount);
}
function _exit(address staker) internal returns (uint256, uint256, uint256) {
StakingPoolLib.Staker memory stakerAccount = _pool.stakers[staker];
if (stakerAccount.stakedAmount == 0) {
revert StakingPoolLib.StakeNotFound(staker);
}
if (stakerAccount.lockedStakeAmount > 0) {
revert StakingPoolLib.ExistingLockedStakeFound(staker);
}
(uint256 baseReward, uint256 delegationReward) = _exit(staker, stakerAccount.stakedAmount, true);
return (stakerAccount.stakedAmount, baseReward, delegationReward);
}
function _exit(address staker, uint256 amount, bool isMigrate) internal returns (uint256, uint256) {
StakingPoolLib.Staker memory stakerAccount = _pool.stakers[staker];
if (amount == 0) {
revert StakingPoolLib.UnstakeWithZeroAmount(staker);
}
if (stakerAccount.stakedAmount < amount) {
revert StakingPoolLib.InadequateStakingAmount(stakerAccount.stakedAmount);
}
_reward._accumulateBaseRewards(getTotalCommunityStakedAmount());
_reward._accumulateDelegationRewards(getTotalDelegatedAmount(), getTotalCommunityStakedAmount());
if (stakerAccount.isOperator) {
if (amount != _operatorStakeAmount) {
revert StakingPoolLib.UnstakeOperatorWithPartialAmount(staker);
}
if (stakerAccount.lockedStakeAmount > 0) {
revert StakingPoolLib.ExistingLockedStakeFound(staker);
}
uint256 delegationReward = _reward._getOperatorEarnedDelegatedRewards(
staker, getTotalDelegatedAmount(), getTotalCommunityStakedAmount()
);
_pool.state.totalOperatorStakedAmount -= amount._toUint96();
_pool.stakers[staker].stakedAmount -= amount._toUint96();
if (!isMigrate) {
_pool.totalFrozenAmount += amount._toUint96();
_pool.stakers[staker].frozenPrincipals.push(
StakingPoolLib.FrozenPrincipal(amount._toUint96(), block.timestamp + _unstakeFreezingDuration)
);
}
_reward.delegated.delegatesCount -= 1;
_reward.missed[staker].delegated = _reward.delegated.cumulativePerDelegate;
return (0, delegationReward);
} else {
uint256 baseReward = _reward._calculateAccruedBaseRewards(
RewardLib._getNonDelegatedAmount(stakerAccount.stakedAmount, _delegationRateDenominator),
getTotalCommunityStakedAmount()
) - uint256(_reward.missed[staker].base);
_pool.state.totalCommunityStakedAmount -= amount._toUint96();
_pool.stakers[staker].stakedAmount -= amount._toUint96();
if (_pool.stakers[staker].stakedAmount == 0) {
_reward.base.communityStakersCount -= 1;
}
if (!isMigrate) {
_pool.totalFrozenAmount += amount._toUint96();
_pool.stakers[staker].frozenPrincipals.push(
StakingPoolLib.FrozenPrincipal(amount._toUint96(), block.timestamp + _unstakeFreezingDuration)
);
}
_reward.missed[staker].base = _reward._calculateAccruedBaseRewards(
RewardLib._getNonDelegatedAmount(_pool.stakers[staker].stakedAmount, _delegationRateDenominator),
getTotalCommunityStakedAmount()
)._toUint96();
return (baseReward, 0);
}
}
function _isActive() private view {
if (!isActive()) revert StakingPoolLib.InvalidPoolStatus(false, true);
}
modifier whenActive() {
_isActive();
_;
}
modifier whenInactive() {
if (isActive()) revert StakingPoolLib.InvalidPoolStatus(true, false);
_;
}
modifier onlyController() {
if (msg.sender != _controller) revert SenderNotController();
_;
}
}
文件 17 的 19:StakingPoolLib.sol
pragma solidity ^0.8.18;
import {SafeCast} from "./SafeCast.sol";
library StakingPoolLib {
using SafeCast for uint256;
event PoolOpened();
event PoolSizeIncreased(uint256 maxPoolSize);
event MaxCommunityStakeAmountIncreased(uint256 maxStakeAmount);
event OperatorAdded(address operator);
error InvalidPoolStatus(bool currentStatus, bool requiredStatus);
error InvalidPoolSize(uint256 maxPoolSize);
error InvalidMaxStakeAmount(uint256 maxStakeAmount);
error InsufficientStakeAmount(uint256 requiredAmount);
error ExcessiveStakeAmount(uint256 remainingAmount);
error StakeNotFound(address staker);
error ExistingStakeFound(address staker);
error OperatorAlreadyExists(address operator);
error OperatorDoesNotExist(address operator);
error NoBaseRewardForOperator();
error InadequateInitialOperatorsCount(uint256 currentOperatorsCount, uint256 minInitialOperatorsCount);
error InadequateStakingAmount(uint256 currentStakingAmount);
error FrozenPrincipalDoesNotExist(address staker);
error UnstakeWithZeroAmount(address staker);
error UnstakeOperatorWithPartialAmount(address operator);
error ExistingLockedStakeFound(address operator);
struct PoolLimits {
uint96 maxPoolSize;
uint96 maxCommunityStakeAmount;
}
struct PoolState {
bool isOpen;
uint8 operatorsCount;
uint96 totalCommunityStakedAmount;
uint96 totalOperatorStakedAmount;
}
struct FrozenPrincipal {
uint96 amount;
uint256 unlockTimestamp;
}
struct Staker {
bool isOperator;
uint96 stakedAmount;
FrozenPrincipal[] frozenPrincipals;
uint96 lockedStakeAmount;
}
struct Pool {
mapping(address => Staker) stakers;
PoolState state;
PoolLimits limits;
uint256 totalFrozenAmount;
}
function _setConfig(Pool storage pool, uint256 maxPoolSize, uint256 maxCommunityStakeAmount) internal {
if (pool.limits.maxPoolSize > maxPoolSize) {
revert InvalidPoolSize(maxPoolSize);
}
if (pool.limits.maxCommunityStakeAmount > maxCommunityStakeAmount) {
revert InvalidMaxStakeAmount(maxCommunityStakeAmount);
}
if (pool.limits.maxPoolSize != maxPoolSize) {
pool.limits.maxPoolSize = maxPoolSize._toUint96();
emit PoolSizeIncreased(maxPoolSize);
}
if (pool.limits.maxCommunityStakeAmount != maxCommunityStakeAmount) {
pool.limits.maxCommunityStakeAmount = maxCommunityStakeAmount._toUint96();
emit MaxCommunityStakeAmountIncreased(maxCommunityStakeAmount);
}
}
function _open(Pool storage pool, uint256 minInitialOperatorCount) internal {
if (uint256(pool.state.operatorsCount) < minInitialOperatorCount) {
revert InadequateInitialOperatorsCount(pool.state.operatorsCount, minInitialOperatorCount);
}
pool.state.isOpen = true;
emit PoolOpened();
}
function _isOperator(Pool storage pool, address staker) internal view returns (bool) {
return pool.stakers[staker].isOperator;
}
function _getTotalStakedAmount(Pool storage pool) internal view returns (uint256) {
StakingPoolLib.PoolState memory poolState = pool.state;
return uint256(poolState.totalCommunityStakedAmount) + uint256(poolState.totalOperatorStakedAmount);
}
function _getRemainingPoolSpace(Pool storage pool) internal view returns (uint256) {
StakingPoolLib.PoolState memory poolState = pool.state;
return uint256(pool.limits.maxPoolSize) - uint256(poolState.totalCommunityStakedAmount);
}
function _addOperators(Pool storage pool, address[] calldata operators) internal {
for (uint256 i; i < operators.length; i++) {
if (pool.stakers[operators[i]].isOperator) {
revert OperatorAlreadyExists(operators[i]);
}
if (pool.stakers[operators[i]].stakedAmount > 0) {
revert ExistingStakeFound(operators[i]);
}
pool.stakers[operators[i]].isOperator = true;
emit OperatorAdded(operators[i]);
}
pool.state.operatorsCount = pool.state.operatorsCount + operators.length._toUint8();
}
}
文件 18 的 19:TypeAndVersionInterface.sol
pragma solidity ^0.8.18;
abstract contract TypeAndVersionInterface {
function typeAndVersion() external pure virtual returns (string memory);
}
文件 19 的 19:draft-IERC20Permit.sol
pragma solidity ^0.8.0;
interface IERC20Permit {
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
function nonces(address owner) external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
{
"compilationTarget": {
"lib/Staking-v0.1/src/Staking.sol": "Staking"
},
"evmVersion": "london",
"libraries": {
"src/libraries/BLS.sol:BLS": "0x25e627ed5c1102c4a130e8b846aa24867898eb78",
"src/libraries/GroupLib.sol:GroupLib": "0xda08c1be1519c3adf6a71b4b0634208e02eccdc9"
},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 500
},
"remappings": [
":Staking-v0.1/=lib/Staking-v0.1/src/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":forge-std/=lib/forge-std/src/",
":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/"
]
}
[{"inputs":[{"components":[{"internalType":"contract IERC20","name":"arpa","type":"address"},{"internalType":"uint256","name":"initialMaxPoolSize","type":"uint256"},{"internalType":"uint256","name":"initialMaxCommunityStakeAmount","type":"uint256"},{"internalType":"uint256","name":"minCommunityStakeAmount","type":"uint256"},{"internalType":"uint256","name":"operatorStakeAmount","type":"uint256"},{"internalType":"uint256","name":"minInitialOperatorCount","type":"uint256"},{"internalType":"uint256","name":"minRewardDuration","type":"uint256"},{"internalType":"uint256","name":"delegationRateDenominator","type":"uint256"},{"internalType":"uint256","name":"unstakeFreezingDuration","type":"uint256"}],"internalType":"struct Staking.PoolConstructorParams","name":"params","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AccessForbidden","type":"error"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"CastError","type":"error"},{"inputs":[{"internalType":"uint256","name":"remainingAmount","type":"uint256"}],"name":"ExcessiveStakeAmount","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"ExistingLockedStakeFound","type":"error"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"ExistingStakeFound","type":"error"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"FrozenPrincipalDoesNotExist","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"minInitialOperatorsCount","type":"uint256"}],"name":"InadequateInitialOperatorsCount","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentLockedStakingAmount","type":"uint256"}],"name":"InadequateOperatorLockedStakingAmount","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentStakingAmount","type":"uint256"}],"name":"InadequateStakingAmount","type":"error"},{"inputs":[{"internalType":"uint256","name":"requiredAmount","type":"uint256"}],"name":"InsufficientStakeAmount","type":"error"},{"inputs":[],"name":"InvalidDelegationRate","type":"error"},{"inputs":[{"internalType":"uint256","name":"maxStakeAmount","type":"uint256"}],"name":"InvalidMaxStakeAmount","type":"error"},{"inputs":[],"name":"InvalidMigrationTarget","type":"error"},{"inputs":[],"name":"InvalidMinCommunityStakeAmount","type":"error"},{"inputs":[],"name":"InvalidOperatorStakeAmount","type":"error"},{"inputs":[{"internalType":"uint256","name":"maxPoolSize","type":"uint256"}],"name":"InvalidPoolSize","type":"error"},{"inputs":[{"internalType":"bool","name":"currentStatus","type":"bool"},{"internalType":"bool","name":"requiredStatus","type":"bool"}],"name":"InvalidPoolStatus","type":"error"},{"inputs":[],"name":"InvalidZeroAddress","type":"error"},{"inputs":[],"name":"NoBaseRewardForOperator","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"OperatorAlreadyExists","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"OperatorDoesNotExist","type":"error"},{"inputs":[],"name":"RewardDurationTooShort","type":"error"},{"inputs":[],"name":"SenderNotController","type":"error"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"StakeNotFound","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"UnstakeOperatorWithPartialAmount","type":"error"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"UnstakeWithZeroAmount","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"controller","type":"address"}],"name":"ControllerSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"DelegationRewardSlashed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"principal","type":"uint256"}],"name":"FrozenPrincipalClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"newLock","type":"uint256"}],"name":"Locked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"principal","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"baseReward","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"delegationReward","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"Migrated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"migrationTarget","type":"address"}],"name":"MigrationTargetAccepted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"migrationTarget","type":"address"}],"name":"MigrationTargetProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"baseReward","type":"uint256"}],"name":"RewardClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"newStake","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalStake","type":"uint256"}],"name":"Staked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"arpaAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"initialMaxPoolSize","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"initialMaxCommunityStakeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minCommunityStakeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"operatorStakeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minInitialOperatorCount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minRewardDuration","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"delegationRateDenominator","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unstakeFreezingDuration","type":"uint256"}],"name":"StakingConfigSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"newUnlock","type":"uint256"}],"name":"Unlocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"principal","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"baseReward","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"delegationReward","type":"uint256"}],"name":"Unstaked","type":"event"},{"inputs":[],"name":"acceptMigrationTarget","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"operators","type":"address[]"}],"name":"addOperators","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"rewardDuration","type":"uint256"}],"name":"addReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimFrozenPrincipal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"emergencyPause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"emergencyUnpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getArpaToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAvailableReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"getBaseReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"getClaimablePrincipalAmount","outputs":[{"internalType":"uint256","name":"claimingPrincipal","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCommunityStakerLimits","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCommunityStakersCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getController","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getDelegatesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getDelegationRateDenominator","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"getDelegationReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"getFrozenPrincipal","outputs":[{"internalType":"uint96[]","name":"amounts","type":"uint96[]"},{"internalType":"uint256[]","name":"unlockTimestamps","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"getLockedAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMaxPoolSize","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMigrationTarget","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getOperatorLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRewardRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRewardTimestamps","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"getStake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalCommunityStakedAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalDelegatedAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalFrozenAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalStakedAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"isOperator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"lock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"migrate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"rewardDuration","type":"uint256"}],"name":"newReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"migrationTarget","type":"address"}],"name":"proposeMigrationTarget","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"}],"name":"setController","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"maxPoolSize","type":"uint256"},{"internalType":"uint256","name":"maxCommunityStakeAmount","type":"uint256"}],"name":"setPoolConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"slashDelegationReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"rewardDuration","type":"uint256"}],"name":"start","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"typeAndVersion","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"unlock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"unstake","outputs":[],"stateMutability":"nonpayable","type":"function"}]