编译器
0.8.26+commit.8a97fa7a
文件 1 的 8:Address.sol
pragma solidity ^0.8.20;
library Address {
error AddressInsufficientBalance(address account);
error AddressEmptyCode(address target);
error FailedInnerCall();
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
function _revert(bytes memory returndata) private pure {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}
文件 2 的 8:Context.sol
pragma solidity ^0.8.20;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
文件 3 的 8:IERC20.sol
pragma solidity ^0.8.20;
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 value) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
文件 4 的 8:IERC20Permit.sol
pragma solidity ^0.8.20;
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);
}
文件 5 的 8:IStakingV2.sol
pragma solidity >=0.8.25;
error InvalidAmount();
error NoRewardsToClaim();
error LockPeriodNotComplete();
error InsufficientRewardsInRewardPool();
error RewardDurationNotFinished();
error RewardRateZero();
error InvalidDuration();
error StakindAndDepositTokenMismatch();
interface IStakingV2 {
event Deposited(address indexed user, uint256 amount);
event Claimed(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event RewardsAdded(uint256 amount);
event RewardsDurationUpdated(uint256 newDuration);
event RewardsDeposited(address indexed user, uint256 amount);
event LockPeriodUpdated(uint256 newLockPeriod);
}
文件 6 的 8:Ownable.sol
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
error OwnableUnauthorizedAccount(address account);
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view virtual returns (address) {
return _owner;
}
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 7 的 8:SafeERC20.sol
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";
library SafeERC20 {
using Address for address;
error SafeERC20FailedOperation(address token);
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
function _callOptionalReturn(IERC20 token, bytes memory data) private {
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}
文件 8 的 8:StakingV2.sol
pragma solidity >=0.8.25;
import "./interfaces/IStakingV2.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
contract StakingV2 is IStakingV2, Ownable {
using SafeERC20 for IERC20;
IERC20 public immutable depositToken;
IERC20 public immutable rewardsToken;
uint256 public duration;
uint256 public finishAt;
uint256 public updatedAt;
uint256 public rewardRate;
uint256 public rewardPerTokenStored;
uint256 public lockPeriod;
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewards;
mapping(address => uint256) public userDepositTime;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
constructor(
address _owner,
address _stakingToken,
address _rewardsToken,
uint256 _rewardsDuration,
uint256 _lockPeriod
) Ownable(_owner) {
require(_stakingToken == _rewardsToken, StakindAndDepositTokenMismatch());
depositToken = IERC20(_stakingToken);
rewardsToken = IERC20(_rewardsToken);
duration = _rewardsDuration;
lockPeriod = _lockPeriod;
}
modifier updateReward(address _account) {
rewardPerTokenStored = rewardPerToken();
updatedAt = lastTimeRewardApplicable();
if (_account != address(0)) {
rewards[_account] = rewardsOf(_account);
userRewardPerTokenPaid[_account] = rewardPerTokenStored;
}
_;
}
modifier isUnlocked() {
if (userDepositTime[msg.sender] + lockPeriod > block.timestamp) revert LockPeriodNotComplete();
_;
}
function _min(uint256 x, uint256 y) private pure returns (uint256) {
return x <= y ? x : y;
}
function lastTimeRewardApplicable() public view returns (uint256) {
return _min(finishAt, block.timestamp);
}
function rewardPerToken() public view returns (uint256) {
if (totalSupply == 0) {
return rewardPerTokenStored;
}
return rewardPerTokenStored + (rewardRate * (lastTimeRewardApplicable() - updatedAt) * 1e18) / totalSupply;
}
function rewardsOf(address _account) public view returns (uint256) {
return ((balanceOf[_account] * (rewardPerToken() - userRewardPerTokenPaid[_account])) / 1e18) + rewards[_account];
}
function dailyRewardsOf(address _account) public view returns (uint256) {
uint256 _balance = balanceOf[_account];
if (_balance == 0) return 0;
uint256 dailyRewardPerToken = (rewardRate * 1 days * 1e18) / totalSupply;
return (_balance * dailyRewardPerToken) / 1e18;
}
function getAccountDetails(
address _account
) external view returns (uint256 dailyRewards, uint256 rewardsBalance, uint256 depositedBalance, uint256 unlockTime) {
dailyRewards = dailyRewardsOf(_account);
rewardsBalance = rewardsOf(_account);
depositedBalance = balanceOf[_account];
unlockTime = userDepositTime[_account] + lockPeriod;
}
function getPoolDetails() external view returns (uint256 totalDeposited, uint256 yearlyRewardsRate, address token) {
totalDeposited = totalSupply;
yearlyRewardsRate = rewardRate * 365 days;
token = address(depositToken);
}
function deposit(uint256 amount) external updateReward(msg.sender) {
if (amount == 0) revert InvalidAmount();
uint256 _rewards = rewards[msg.sender];
uint256 _amount = amount + _rewards;
depositToken.safeTransferFrom(msg.sender, address(this), amount);
balanceOf[msg.sender] += _amount;
userDepositTime[msg.sender] = block.timestamp;
totalSupply += _amount;
rewards[msg.sender] = 0;
emit Deposited(msg.sender, _amount);
}
function depositRewards() external updateReward(msg.sender) {
uint256 _rewards = rewards[msg.sender];
if (_rewards == 0) revert NoRewardsToClaim();
rewards[msg.sender] = 0;
balanceOf[msg.sender] += _rewards;
userDepositTime[msg.sender] = block.timestamp;
totalSupply += _rewards;
emit Deposited(msg.sender, _rewards);
}
function withdraw(uint256 _amount) public updateReward(msg.sender) isUnlocked {
if (_amount == 0) revert InvalidAmount();
uint256 _rewards = rewards[msg.sender];
rewards[msg.sender] = 0;
balanceOf[msg.sender] -= _amount;
totalSupply -= _amount;
depositToken.safeTransfer(msg.sender, _amount + _rewards);
emit Withdrawn(msg.sender, _amount);
}
function claim() public updateReward(msg.sender) isUnlocked {
uint256 _rewards = rewards[msg.sender];
if (_rewards == 0) revert NoRewardsToClaim();
rewards[msg.sender] = 0;
rewardsToken.safeTransfer(msg.sender, _rewards);
emit Claimed(msg.sender, _rewards);
}
function setRewardsDuration(uint256 _duration) external onlyOwner {
if (_duration == 0) revert InvalidDuration();
if (finishAt >= block.timestamp) revert RewardDurationNotFinished();
duration = _duration;
}
function addRewards(uint256 _amount) external onlyOwner updateReward(address(0)) {
rewardsToken.safeTransferFrom(msg.sender, address(this), _amount);
if (block.timestamp >= finishAt) {
rewardRate = _amount / duration;
} else {
uint256 remainingRewards = (finishAt - block.timestamp) * rewardRate;
rewardRate = (_amount + remainingRewards) / duration;
}
if (rewardRate == 0) revert RewardRateZero();
if (rewardRate * duration > rewardsToken.balanceOf(address(this))) revert InsufficientRewardsInRewardPool();
finishAt = block.timestamp + duration;
updatedAt = block.timestamp;
emit RewardsAdded(_amount);
}
function setLockPeriod(uint256 timeInSeconds) external onlyOwner {
lockPeriod = timeInSeconds;
emit LockPeriodUpdated(timeInSeconds);
}
}
{
"compilationTarget": {
"src/StakingV2.sol": "StakingV2"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "none"
},
"optimizer": {
"enabled": true,
"runs": 10000
},
"remappings": [
":@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/",
":ds-test/=node_modules/ds-test/src/",
":forge-std/=node_modules/forge-std/",
":solmate/=node_modules/solmate/"
],
"viaIR": true
}
[{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_stakingToken","type":"address"},{"internalType":"address","name":"_rewardsToken","type":"address"},{"internalType":"uint256","name":"_rewardsDuration","type":"uint256"},{"internalType":"uint256","name":"_lockPeriod","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"InsufficientRewardsInRewardPool","type":"error"},{"inputs":[],"name":"InvalidAmount","type":"error"},{"inputs":[],"name":"InvalidDuration","type":"error"},{"inputs":[],"name":"LockPeriodNotComplete","type":"error"},{"inputs":[],"name":"NoRewardsToClaim","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"RewardDurationNotFinished","type":"error"},{"inputs":[],"name":"RewardRateZero","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[],"name":"StakindAndDepositTokenMismatch","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Claimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Deposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newLockPeriod","type":"uint256"}],"name":"LockPeriodUpdated","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":"uint256","name":"amount","type":"uint256"}],"name":"RewardsAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"RewardsDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newDuration","type":"uint256"}],"name":"RewardsDurationUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdrawn","type":"event"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"addRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"dailyRewardsOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"depositRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"depositToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"duration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"finishAt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"getAccountDetails","outputs":[{"internalType":"uint256","name":"dailyRewards","type":"uint256"},{"internalType":"uint256","name":"rewardsBalance","type":"uint256"},{"internalType":"uint256","name":"depositedBalance","type":"uint256"},{"internalType":"uint256","name":"unlockTime","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPoolDetails","outputs":[{"internalType":"uint256","name":"totalDeposited","type":"uint256"},{"internalType":"uint256","name":"yearlyRewardsRate","type":"uint256"},{"internalType":"address","name":"token","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastTimeRewardApplicable","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lockPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rewardPerToken","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardPerTokenStored","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"rewardsOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardsToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"timeInSeconds","type":"uint256"}],"name":"setLockPeriod","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_duration","type":"uint256"}],"name":"setRewardsDuration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"updatedAt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"userDepositTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"userRewardPerTokenPaid","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]