编译器
0.8.16+commit.07a7930e
文件 1 的 14: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 的 14: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 的 14:ERC20.sol
pragma solidity ^0.8.0;
import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";
contract ERC20 is Context, IERC20, IERC20Metadata {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
function name() public view virtual override returns (string memory) {
return _name;
}
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
function decimals() public view virtual override returns (uint8) {
return 18;
}
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
function transfer(address to, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
return true;
}
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
function approve(address spender, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue);
return true;
}
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
address owner = _msgSender();
uint256 currentAllowance = allowance(owner, spender);
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue);
}
return true;
}
function _transfer(
address from,
address to,
uint256 amount
) internal virtual {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(from, to, amount);
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
_balances[to] += amount;
}
emit Transfer(from, to, amount);
_afterTokenTransfer(from, to, amount);
}
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
unchecked {
_balances[account] += amount;
}
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
_totalSupply -= amount;
}
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
function _approve(
address owner,
address spender,
uint256 amount
) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
function _spendAllowance(
address owner,
address spender,
uint256 amount
) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
function _afterTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
}
文件 4 的 14:Errors.sol
pragma solidity 0.8.16;
library Errors {
error ZeroAddress();
error ZeroValue();
error DifferentSizeArrays(uint256 size1, uint256 size2);
error EmptyArray();
error AlreadySet();
error SameAddress();
error InvalidParameter();
error CannotBeOwner();
error CallerNotPendingOwner();
error CallerNotAllowed();
error AllowanceUnderflow();
error ListedLocker();
error ListedFarmer();
error InvalidFeeRatio();
error HarvestNotAllowed();
error NoWarLocker();
error LockerShutdown();
error MismatchingLocker(address expected, address actual);
error MintAmountBiggerThanSupply();
error NotListedLocker();
error InvalidIndex();
error CannotRedeemYet();
error AlreadyRedeemed();
error InvalidWeightSum();
error AlreadyListedDepositor();
error NotListedDepositor();
error MismatchingFarmer();
error ZeroMintAmount();
error SupplyAlreadySet();
error RatioAlreadySet();
error NotRewardToken();
error IncorrectToken();
error UnstakingMoreThanBalance();
error NumberExceed128Bits();
error SlippageTooHigh();
error RecoverForbidden();
error DelegationRequiresLock();
}
文件 5 的 14: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);
}
文件 6 的 14:IERC20Metadata.sol
pragma solidity ^0.8.0;
import "../IERC20.sol";
interface IERC20Metadata is IERC20 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
}
文件 7 的 14:IFarmer.sol
pragma solidity 0.8.16;
interface IFarmer {
function getCurrentIndex() external view returns (uint256);
function sendTokens(address receiver, uint256 amount) external;
function stake(address token, uint256 amount) external;
function token() external view returns (address);
}
文件 8 的 14: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);
}
}
文件 9 的 14:Owner.sol
pragma solidity 0.8.16;
import "openzeppelin/access/Ownable.sol";
contract Owner is Ownable {
address public pendingOwner;
event NewPendingOwner(address indexed previousPendingOwner, address indexed newPendingOwner);
error CannotBeOwner();
error CallerNotPendingOwner();
error OwnerAddressZero();
function transferOwnership(address newOwner) public virtual override onlyOwner {
if (newOwner == address(0)) revert OwnerAddressZero();
if (newOwner == owner()) revert CannotBeOwner();
address oldPendingOwner = pendingOwner;
pendingOwner = newOwner;
emit NewPendingOwner(oldPendingOwner, newOwner);
}
function acceptOwnership() public virtual {
if (msg.sender != pendingOwner) revert CallerNotPendingOwner();
address newOwner = pendingOwner;
_transferOwnership(pendingOwner);
pendingOwner = address(0);
emit NewPendingOwner(newOwner, address(0));
}
}
文件 10 的 14: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());
}
}
文件 11 的 14:ReentrancyGuard.sol
pragma solidity ^0.8.0;
abstract contract ReentrancyGuard {
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
_status = _ENTERED;
}
function _nonReentrantAfter() private {
_status = _NOT_ENTERED;
}
}
文件 12 的 14: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");
}
}
}
文件 13 的 14:Staker.sol
pragma solidity 0.8.16;
import {IFarmer} from "interfaces/IFarmer.sol";
import {Owner} from "utils/Owner.sol";
import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol";
import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol";
import {ERC20} from "openzeppelin/token/ERC20/ERC20.sol";
import {Pausable} from "openzeppelin/security/Pausable.sol";
import {ReentrancyGuard} from "openzeppelin/security/ReentrancyGuard.sol";
import {Errors} from "utils/Errors.sol";
contract WarStaker is ERC20, ReentrancyGuard, Pausable, Owner {
using SafeERC20 for IERC20;
uint256 private constant UNIT = 1e18;
uint256 private constant MAX_BPS = 10_000;
uint256 private constant MAX_UINT256 = 2 ** 256 - 1;
uint256 private constant DISTRIBUTION_DURATION = 604_800;
uint256 private constant UPDATE_REWARD_RATIO = 8500;
struct UserRewardState {
uint256 lastRewardPerToken;
uint256 accruedRewards;
}
struct RewardState {
uint256 rewardPerToken;
uint128 lastUpdate;
uint128 distributionEndTimestamp;
uint256 ratePerSecond;
uint256 currentRewardAmount;
uint256 queuedRewardAmount;
mapping(address => UserRewardState) userStates;
}
struct UserClaimableRewards {
address reward;
uint256 claimableAmount;
}
struct UserClaimedRewards {
address reward;
uint256 amount;
}
address public immutable warToken;
address[] public rewardTokens;
mapping(address => RewardState) public rewardStates;
mapping(address => address) public rewardFarmers;
mapping(address => uint256) public farmerLastIndex;
mapping(address => bool) public rewardDepositors;
event Staked(address indexed caller, address indexed receiver, uint256 amount);
event Unstaked(address indexed owner, address indexed receiver, uint256 amount);
event ClaimedRewards(address indexed reward, address indexed user, address indexed receiver, uint256 amount);
event SetUserAllowedClaimer(address indexed user, address indexed claimer);
event NewRewards(address indexed rewardToken, uint256 amount, uint256 endTimestamp);
event AddedRewardDepositor(address indexed depositor);
event RemovedRewardDepositor(address indexed depositor);
event SetRewardFarmer(address indexed rewardToken, address indexed farmer);
modifier onlyRewardDepositors() {
if (!rewardDepositors[msg.sender]) revert Errors.CallerNotAllowed();
_;
}
constructor(address _warToken) ERC20("Staked Warlord token", "stkWAR") {
if (_warToken == address(0)) revert Errors.ZeroAddress();
warToken = _warToken;
}
function lastRewardUpdateTimestamp(address reward) public view returns (uint256) {
uint256 rewardEndTimestamp = rewardStates[reward].distributionEndTimestamp;
return block.timestamp > rewardEndTimestamp ? rewardEndTimestamp : block.timestamp;
}
function getRewardTokens() external view returns (address[] memory) {
return rewardTokens;
}
function getUserRewardState(address reward, address user) external view returns (UserRewardState memory) {
return rewardStates[reward].userStates[user];
}
function getUserAccruedRewards(address reward, address user) external view returns (uint256) {
return rewardStates[reward].userStates[user].accruedRewards
+ _getUserEarnedRewards(reward, user, _getNewRewardPerToken(reward));
}
function getUserTotalClaimableRewards(address user) external view returns (UserClaimableRewards[] memory) {
address[] memory rewards = rewardTokens;
uint256 rewardsLength = rewards.length;
UserClaimableRewards[] memory rewardAmounts = new UserClaimableRewards[](rewardsLength);
for (uint256 i; i < rewardsLength;) {
rewardAmounts[i].reward = rewards[i];
rewardAmounts[i].claimableAmount = rewardStates[rewards[i]].userStates[user].accruedRewards
+ _getUserEarnedRewards(rewards[i], user, _getNewRewardPerToken(rewards[i]));
unchecked {
++i;
}
}
return rewardAmounts;
}
function stake(uint256 amount, address receiver) external nonReentrant whenNotPaused returns (uint256) {
if (amount == MAX_UINT256) amount = IERC20(warToken).balanceOf(msg.sender);
if (amount == 0) revert Errors.ZeroValue();
if (receiver == address(0)) revert Errors.ZeroAddress();
IERC20(warToken).safeTransferFrom(msg.sender, address(this), amount);
_mint(receiver, amount);
emit Staked(msg.sender, receiver, amount);
return amount;
}
function unstake(uint256 amount, address receiver) external nonReentrant returns (uint256) {
if (amount == MAX_UINT256) amount = balanceOf(msg.sender);
if (amount == 0) revert Errors.ZeroValue();
if (receiver == address(0)) revert Errors.ZeroAddress();
_burn(msg.sender, amount);
IERC20(warToken).safeTransfer(receiver, amount);
emit Unstaked(msg.sender, receiver, amount);
return amount;
}
function claimRewards(address reward, address receiver) external nonReentrant whenNotPaused returns (uint256) {
if (reward == address(0)) revert Errors.ZeroAddress();
if (receiver == address(0)) revert Errors.ZeroAddress();
return _claimRewards(reward, msg.sender, receiver);
}
function claimAllRewards(address receiver) external nonReentrant whenNotPaused returns (UserClaimedRewards[] memory) {
if (receiver == address(0)) revert Errors.ZeroAddress();
return _claimAllRewards(msg.sender, receiver);
}
function updateRewardState(address reward) external nonReentrant whenNotPaused {
if (reward == address(0)) revert Errors.ZeroAddress();
_updateRewardState(reward);
}
function updateAllRewardStates() external nonReentrant whenNotPaused {
address[] memory _rewards = rewardTokens;
uint256 length = _rewards.length;
for (uint256 i; i < length;) {
_updateRewardState(_rewards[i]);
unchecked {
++i;
}
}
}
function queueRewards(address rewardToken, uint256 amount)
external
nonReentrant
whenNotPaused
onlyRewardDepositors
returns (bool)
{
if (amount == 0) revert Errors.ZeroValue();
if (rewardToken == address(0)) revert Errors.ZeroAddress();
RewardState storage state = rewardStates[rewardToken];
if (state.lastUpdate == 0) {
rewardTokens.push(rewardToken);
}
_updateRewardState(rewardToken);
uint256 totalQueued = amount + state.queuedRewardAmount;
if (block.timestamp >= state.distributionEndTimestamp) {
_updateRewardDistribution(rewardToken, state, totalQueued);
state.queuedRewardAmount = 0;
return true;
}
uint256 currentRemainingAmount = state.ratePerSecond * (state.distributionEndTimestamp - block.timestamp);
uint256 queuedAmountRatio = (totalQueued * MAX_BPS) / (totalQueued + currentRemainingAmount);
if (queuedAmountRatio >= UPDATE_REWARD_RATIO) {
_updateRewardDistribution(rewardToken, state, totalQueued);
state.queuedRewardAmount = 0;
} else {
state.queuedRewardAmount = totalQueued;
}
return true;
}
function _updateRewardDistribution(address rewardToken, RewardState storage state, uint256 rewardAmount) internal {
if (block.timestamp < state.distributionEndTimestamp) {
uint256 remainingRewards = state.ratePerSecond * (state.distributionEndTimestamp - block.timestamp);
rewardAmount += remainingRewards;
}
state.ratePerSecond = rewardAmount / DISTRIBUTION_DURATION;
state.currentRewardAmount = rewardAmount;
state.lastUpdate = safe128(block.timestamp);
uint256 distributionEnd = block.timestamp + DISTRIBUTION_DURATION;
state.distributionEndTimestamp = safe128(distributionEnd);
emit NewRewards(rewardToken, rewardAmount, distributionEnd);
}
function _beforeTokenTransfer(address from, address to, uint256 ) internal override {
if (from != address(0)) {
_updateAllUserRewardStates(from);
}
if (to != address(0)) {
_updateAllUserRewardStates(to);
}
}
function _getNewRewardPerToken(address reward) internal view returns (uint256) {
RewardState storage state = rewardStates[reward];
uint256 totalStakedAmount = totalSupply();
if (totalStakedAmount == 0) return state.rewardPerToken;
uint256 totalAccruedAmount;
if (rewardFarmers[reward] == address(0)) {
uint256 lastRewardTimestamp = lastRewardUpdateTimestamp(reward);
if (state.lastUpdate == lastRewardTimestamp) return state.rewardPerToken;
totalAccruedAmount = (lastRewardTimestamp - state.lastUpdate) * state.ratePerSecond;
} else {
uint256 currentFarmerIndex = IFarmer(rewardFarmers[reward]).getCurrentIndex();
totalAccruedAmount = currentFarmerIndex - farmerLastIndex[reward];
}
return state.rewardPerToken + ((totalAccruedAmount * UNIT) / totalStakedAmount);
}
function _getUserEarnedRewards(address reward, address user, uint256 currentRewardPerToken)
internal
view
returns (uint256)
{
UserRewardState storage userState = rewardStates[reward].userStates[user];
uint256 userStakedAmount = balanceOf(user);
if (userStakedAmount == 0) return 0;
return (userStakedAmount * (currentRewardPerToken - userState.lastRewardPerToken)) / UNIT;
}
function _updateRewardState(address reward) internal {
RewardState storage state = rewardStates[reward];
state.rewardPerToken = _getNewRewardPerToken(reward);
state.lastUpdate = safe128(lastRewardUpdateTimestamp(reward));
if (rewardFarmers[reward] != address(0)) {
farmerLastIndex[reward] = IFarmer(rewardFarmers[reward]).getCurrentIndex();
}
}
function _updateUserRewardState(address reward, address user) internal {
_updateRewardState(reward);
UserRewardState storage userState = rewardStates[reward].userStates[user];
uint256 currentRewardPerToken = rewardStates[reward].rewardPerToken;
userState.accruedRewards += _getUserEarnedRewards(reward, user, currentRewardPerToken);
userState.lastRewardPerToken = currentRewardPerToken;
}
function _updateAllUserRewardStates(address user) internal {
address[] memory _rewards = rewardTokens;
uint256 length = _rewards.length;
for (uint256 i; i < length;) {
_updateUserRewardState(_rewards[i], user);
unchecked {
++i;
}
}
}
function _claimRewards(address reward, address user, address receiver) internal returns (uint256) {
_updateUserRewardState(reward, user);
UserRewardState storage userState = rewardStates[reward].userStates[user];
uint256 rewardAmount = userState.accruedRewards;
if (rewardAmount == 0) return 0;
userState.accruedRewards = 0;
_sendRewards(reward, receiver, rewardAmount);
emit ClaimedRewards(reward, user, receiver, rewardAmount);
return rewardAmount;
}
function _claimAllRewards(address user, address receiver) internal returns (UserClaimedRewards[] memory) {
address[] memory rewards = rewardTokens;
uint256 rewardsLength = rewards.length;
UserClaimedRewards[] memory rewardAmounts = new UserClaimedRewards[](rewardsLength);
_updateAllUserRewardStates(user);
for (uint256 i; i < rewardsLength;) {
UserRewardState storage userState = rewardStates[rewards[i]].userStates[user];
uint256 rewardAmount = userState.accruedRewards;
rewardAmounts[i].reward = rewards[i];
rewardAmounts[i].amount = rewardAmount;
if (rewardAmount == 0) {
unchecked {
++i;
}
continue;
}
userState.accruedRewards = 0;
_sendRewards(rewards[i], receiver, rewardAmount);
emit ClaimedRewards(rewards[i], user, receiver, rewardAmounts[i].amount);
unchecked {
++i;
}
}
return rewardAmounts;
}
function _sendRewards(address token, address receiver, uint256 amount) internal {
if (rewardFarmers[token] == address(0)) {
IERC20(token).safeTransfer(receiver, amount);
} else {
IFarmer(rewardFarmers[token]).sendTokens(receiver, amount);
}
}
function pause() external onlyOwner {
_pause();
}
function unpause() external onlyOwner {
_unpause();
}
function addRewardDepositor(address depositor) external onlyOwner {
if (depositor == address(0)) revert Errors.ZeroAddress();
if (rewardDepositors[depositor]) revert Errors.AlreadyListedDepositor();
rewardDepositors[depositor] = true;
emit AddedRewardDepositor(depositor);
}
function removeRewardDepositor(address depositor) external onlyOwner {
if (depositor == address(0)) revert Errors.ZeroAddress();
if (!rewardDepositors[depositor]) revert Errors.NotListedDepositor();
rewardDepositors[depositor] = false;
emit RemovedRewardDepositor(depositor);
}
function setRewardFarmer(address rewardToken, address farmer) external onlyOwner {
if (rewardToken == address(0) || farmer == address(0)) revert Errors.ZeroAddress();
address expectedToken = IFarmer(farmer).token();
if (rewardToken != expectedToken) revert Errors.MismatchingFarmer();
rewardFarmers[rewardToken] = farmer;
rewardTokens.push(rewardToken);
emit SetRewardFarmer(rewardToken, farmer);
}
function safe128(uint256 n) internal pure returns (uint128) {
if (n > type(uint128).max) revert Errors.NumberExceed128Bits();
return uint128(n);
}
}
文件 14 的 14: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": {
"src/Staker.sol": "WarStaker"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":ds-test/=lib/forge-std/lib/ds-test/src/",
":forge-std/=lib/forge-std/src/",
":interfaces/=src/interfaces/",
":mocks/=test/mocks/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/",
":openzeppelin/=lib/openzeppelin-contracts/contracts/",
":solgen/=lib/solidity-generators/src/",
":solidity-generators/=lib/solidity-generators/src/",
":solmate/=lib/solmate/src/",
":utils/=src/utils/"
]
}
[{"inputs":[{"internalType":"address","name":"_warToken","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyListedDepositor","type":"error"},{"inputs":[],"name":"CallerNotAllowed","type":"error"},{"inputs":[],"name":"CallerNotPendingOwner","type":"error"},{"inputs":[],"name":"CannotBeOwner","type":"error"},{"inputs":[],"name":"MismatchingFarmer","type":"error"},{"inputs":[],"name":"NotListedDepositor","type":"error"},{"inputs":[],"name":"NumberExceed128Bits","type":"error"},{"inputs":[],"name":"OwnerAddressZero","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"inputs":[],"name":"ZeroValue","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"depositor","type":"address"}],"name":"AddedRewardDepositor","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"reward","type":"address"},{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ClaimedRewards","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousPendingOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newPendingOwner","type":"address"}],"name":"NewPendingOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rewardToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"endTimestamp","type":"uint256"}],"name":"NewRewards","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":true,"internalType":"address","name":"depositor","type":"address"}],"name":"RemovedRewardDepositor","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rewardToken","type":"address"},{"indexed":true,"internalType":"address","name":"farmer","type":"address"}],"name":"SetRewardFarmer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"claimer","type":"address"}],"name":"SetUserAllowedClaimer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Staked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Unstaked","type":"event"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"depositor","type":"address"}],"name":"addRewardDepositor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"claimAllRewards","outputs":[{"components":[{"internalType":"address","name":"reward","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct WarStaker.UserClaimedRewards[]","name":"","type":"tuple[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"reward","type":"address"},{"internalType":"address","name":"receiver","type":"address"}],"name":"claimRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"farmerLastIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRewardTokens","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"reward","type":"address"},{"internalType":"address","name":"user","type":"address"}],"name":"getUserAccruedRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"reward","type":"address"},{"internalType":"address","name":"user","type":"address"}],"name":"getUserRewardState","outputs":[{"components":[{"internalType":"uint256","name":"lastRewardPerToken","type":"uint256"},{"internalType":"uint256","name":"accruedRewards","type":"uint256"}],"internalType":"struct WarStaker.UserRewardState","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getUserTotalClaimableRewards","outputs":[{"components":[{"internalType":"address","name":"reward","type":"address"},{"internalType":"uint256","name":"claimableAmount","type":"uint256"}],"internalType":"struct WarStaker.UserClaimableRewards[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"reward","type":"address"}],"name":"lastRewardUpdateTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"rewardToken","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"queueRewards","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"depositor","type":"address"}],"name":"removeRewardDepositor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewardDepositors","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewardFarmers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewardStates","outputs":[{"internalType":"uint256","name":"rewardPerToken","type":"uint256"},{"internalType":"uint128","name":"lastUpdate","type":"uint128"},{"internalType":"uint128","name":"distributionEndTimestamp","type":"uint128"},{"internalType":"uint256","name":"ratePerSecond","type":"uint256"},{"internalType":"uint256","name":"currentRewardAmount","type":"uint256"},{"internalType":"uint256","name":"queuedRewardAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"rewardTokens","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"rewardToken","type":"address"},{"internalType":"address","name":"farmer","type":"address"}],"name":"setRewardFarmer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"stake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"unstake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"updateAllRewardStates","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"reward","type":"address"}],"name":"updateRewardState","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"warToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]