编译器
0.8.24+commit.e11b9ed9
文件 1 的 13: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 的 13: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 的 13: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 的 13: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 的 13:IStaking.sol
pragma solidity ^0.8.24;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IUniswapV2Router02} from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
interface IStaking {
event RewardPoolIncreased(uint256 increaseAmount, uint256 newRewardPoolValue);
event RewardPoolDecreased(uint256 decreaseAmount, uint256 newRewardPoolValue);
event StakingPoolUpserted(uint256 indexed index, StakingPool pool);
event Staked(
uint256 indexed stakeIndex,
address indexed staker,
uint256 indexed stakingPoolIndex,
uint256 amount,
uint256 duration,
uint256 cancelPeriod,
uint256 rewardX18,
uint256 bonus
);
event StakeCanceled(
uint256 indexed stakeIndex,
address indexed staker,
uint256 indexed stakingPoolIndex,
uint256 unstakeAmount,
uint256 newStakeAmount,
uint256 newExpectedRewards,
uint256 prevBonus
);
event EmergencyWithdrawal(
uint256 indexed stakeIndex,
address indexed staker,
uint256 indexed stakingPoolIndex,
uint256 unstakeAmount,
uint256 newStakeAmount,
uint256 newExpectedRewards,
uint256 rewards,
uint256 rewardPoolIncreaseValue
);
event Withdrawal(
uint256 indexed stakeIndex,
address indexed staker,
uint256 indexed stakingPoolIndex,
uint256 rewards
);
error AddressIsZero();
error StakingPoolNotFound(uint256 requestedStakingPoolIndex, uint256 stakingPoolsCount);
error StakingPoolIsDisabled(uint256 poolIndex);
error MaxStakingAmountExceeded(uint256 stakingAmount, uint256 usdtEquivalent);
error RewardPoolIsEmpty(uint256 expectedRewards, uint256 availableRewards);
error StakingNotFound(uint256 requestedStakingIndex, uint256 stakesCount);
error UnauthorizedAccount(address caller, address staker);
error StakingNotFinished(uint256 currentTimestamp, uint256 stakingEndsAt);
error StakingIsFinished(uint256 currentTimestamp, uint256 stakingEndedAt);
error StakingIsClosed(uint256 stakeIndex);
error UnstakeAmountGTStakeAmount(uint256 unstakeAmount, uint256 stakeAmount);
error InsufficientRestakeAmount(uint256 restakeAmount, uint256 totalWithdrawalAmount);
error CancelPeriodIsClosed(uint256 currentTimestamp, uint256 stakedAt, uint256 cancelPeriod);
error StakingPoolCancelPeriodGTEDuration(uint256 cancelPeriod, uint256 duration);
struct StakingPool {
bool enabled;
uint256 duration;
uint256 rewardX18;
uint256 cancelPeriod;
}
struct Stake {
bool inProgress;
address account;
uint256 amount;
uint256 stakingPoolIndex;
uint256 stakedAt;
uint256 duration;
uint256 endsAt;
uint256 cancelPeriod;
uint256 rewardX18;
uint256 expectedRewards;
uint256 bonus;
}
function MAX_STAKE_AMOUNT() external pure returns (uint256);
function MAX_STAKE_AMOUNT_USDT() external pure returns (uint256);
function deployedAt() external view returns (uint256);
function bonusPeriod() external view returns (uint256);
function usdt() external view returns (address);
function token() external view returns (IERC20);
function uniV2Router() external view returns (IUniswapV2Router02);
function rewards() external view returns (uint256);
function stakingPools(uint256 poolIndex) external view returns (StakingPool memory stakingPool);
function stakingPoolsList() external view returns (StakingPool[] memory);
function stakingPoolsLength() external view returns (uint256);
function stakes(uint256 stakeIndex) external view returns (Stake memory stake);
function stakesLength() external view returns (uint256);
function bonusIsUsed(address account) external view returns (bool bonusUsed);
function replenishRewards(uint256 amount) external returns (uint256 availableRewards);
function decreaseRewardPool(uint256 amount) external returns (uint256 availableRewards);
function createStakingPool(StakingPool memory stakingPool) external returns (uint256 poolIndex);
function updateStakingPool(uint256 poolIndex, StakingPool memory newParameters) external;
function stake(uint256 poolIndex, uint256 amount) external returns (Stake memory stake_);
function cancelStaking(
uint256 stakeIndex,
uint256 unstakeAmount
) external returns (uint256 newStakeAmount, uint256 unstakedAmount);
function emergencyWithdrawal(
uint256 stakeIndex,
uint256 unstakeAmount
) external returns (uint256 penalty, uint256 rewards_);
function withdraw(uint256 stakeIndex) external returns (uint256 rewards_);
function restake(
uint256 stakeIndex,
uint256 restakePoolIndex,
uint256 restakeAmount
) external returns (Stake memory restake_);
}
文件 6 的 13:IUniswapV2Factory.sol
pragma solidity >=0.5.0;
interface IUniswapV2Factory {
event PairCreated(address indexed token0, address indexed token1, address pair, uint);
function feeTo() external view returns (address);
function feeToSetter() external view returns (address);
function getPair(address tokenA, address tokenB) external view returns (address pair);
function allPairs(uint) external view returns (address pair);
function allPairsLength() external view returns (uint);
function createPair(address tokenA, address tokenB) external returns (address pair);
function setFeeTo(address) external;
function setFeeToSetter(address) external;
}
文件 7 的 13:IUniswapV2Router01.sol
pragma solidity >=0.6.2;
interface IUniswapV2Router01 {
function factory() external pure returns (address);
function WETH() external pure returns (address);
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB, uint liquidity);
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external payable returns (uint amountToken, uint amountETH, uint liquidity);
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB);
function removeLiquidityETH(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external returns (uint amountToken, uint amountETH);
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountA, uint amountB);
function removeLiquidityETHWithPermit(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountToken, uint amountETH);
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
external
payable
returns (uint[] memory amounts);
function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
external
returns (uint[] memory amounts);
function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
external
returns (uint[] memory amounts);
function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
external
payable
returns (uint[] memory amounts);
function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}
文件 8 的 13:IUniswapV2Router02.sol
pragma solidity >=0.6.2;
import './IUniswapV2Router01.sol';
interface IUniswapV2Router02 is IUniswapV2Router01 {
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external returns (uint amountETH);
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountETH);
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external payable;
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
}
文件 9 的 13: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);
}
}
文件 10 的 13:Ownable2Step.sol
pragma solidity ^0.8.20;
import {Ownable} from "./Ownable.sol";
abstract contract Ownable2Step is Ownable {
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}
function acceptOwnership() public virtual {
address sender = _msgSender();
if (pendingOwner() != sender) {
revert OwnableUnauthorizedAccount(sender);
}
_transferOwnership(sender);
}
}
文件 11 的 13: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;
}
}
文件 12 的 13:Staking.sol
pragma solidity ^0.8.24;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable, Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {IUniswapV2Factory} from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
import {IUniswapV2Router02} from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import {IStaking} from "./interfaces/IStaking.sol";
import {UnsafeMath} from "./libraries/UnsafeMath.sol";
contract Staking is IStaking, Ownable2Step {
using UnsafeMath for uint256;
using SafeERC20 for IERC20;
uint256 public constant MAX_STAKE_AMOUNT = 40e6 ether;
uint256 public constant MAX_STAKE_AMOUNT_USDT = 100_000e6;
uint256 public immutable deployedAt;
uint256 public immutable bonusPeriod;
address public immutable usdt;
IERC20 public immutable token;
IUniswapV2Router02 public immutable uniV2Router;
uint256 public rewards;
StakingPool[] private _stakingPools;
function stakingPools(uint256 poolIndex) external view returns (StakingPool memory stakingPool) {
return _stakingPools[poolIndex];
}
function stakingPoolsList() external view returns (StakingPool[] memory) {
return _stakingPools;
}
function stakingPoolsLength() external view returns (uint256) {
return _stakingPools.length;
}
Stake[] private _stakes;
function stakes(uint256 stakeIndex) external view returns (Stake memory stake_) {
return _stakes[stakeIndex];
}
function stakesLength() external view returns (uint256) {
return _stakes.length;
}
mapping(address account => bool bonusUsed) public bonusIsUsed;
constructor(
address owner_,
uint256 bonusPeriod_,
address usdt_,
IERC20 token_,
IUniswapV2Router02 uniV2Router_,
StakingPool[] memory stakingPools_
) Ownable(owner_) {
if (usdt_ == address(0)) revert AddressIsZero();
if (address(token_) == address(0)) revert AddressIsZero();
bonusPeriod = bonusPeriod_;
usdt = usdt_;
token = token_;
uniV2Router = uniV2Router_;
IUniswapV2Factory uniV2Factory = IUniswapV2Factory(uniV2Router.factory());
if (uniV2Factory.getPair(address(token_), usdt_) == address(0)) uniV2Factory.createPair(address(token_), usdt_);
deployedAt = block.timestamp;
for (uint256 i = 0; i < stakingPools_.length; i++) _createStakingPool(stakingPools_[i]);
}
function replenishRewards(uint256 amount) external onlyOwner returns (uint256 availableRewards) {
rewards = availableRewards = rewards + amount;
emit RewardPoolIncreased(amount, availableRewards);
token.safeTransferFrom(msg.sender, address(this), amount);
}
function decreaseRewardPool(uint256 amount) external onlyOwner returns (uint256 availableRewards) {
uint256 currentRewardPool = rewards;
if (currentRewardPool < amount) revert RewardPoolIsEmpty(amount, currentRewardPool);
rewards = availableRewards = currentRewardPool.unsafeSub(amount);
emit RewardPoolDecreased(amount, availableRewards);
token.safeTransfer(msg.sender, amount);
}
function createStakingPool(StakingPool memory stakingPool) external onlyOwner returns (uint256 poolIndex) {
return _createStakingPool(stakingPool);
}
function updateStakingPool(uint256 poolIndex, StakingPool memory newParameters) external onlyOwner {
if (poolIndex >= _stakingPools.length) revert StakingPoolNotFound(poolIndex, _stakingPools.length);
_validateStakingPoolParameters(newParameters);
_stakingPools[poolIndex] = newParameters;
emit StakingPoolUpserted(poolIndex, newParameters);
}
function stake(uint256 poolIndex, uint256 amount) external returns (Stake memory stake_) {
address caller = msg.sender;
stake_ = _stakeNoTransfer(caller, poolIndex, amount);
token.safeTransferFrom(caller, address(this), amount);
}
function cancelStaking(
uint256 stakeIndex,
uint256 unstakeAmount
) external returns (uint256 newStakeAmount, uint256 newExpectedRewards) {
address caller = msg.sender;
Stake storage stake_ = _getStake(caller, stakeIndex);
if (block.timestamp > stake_.stakedAt + stake_.cancelPeriod) {
revert CancelPeriodIsClosed(block.timestamp, stake_.stakedAt, stake_.cancelPeriod);
}
uint256 prevBonus = _cancelStakeBonus(stake_);
uint256 rewardDiff;
(newStakeAmount, newExpectedRewards, rewardDiff) = _decreaseStakeAmount(
stake_,
unstakeAmount,
stake_.rewardX18
);
rewards += rewardDiff.unsafeAdd(prevBonus);
emit StakeCanceled(
stakeIndex,
caller,
stake_.stakingPoolIndex,
unstakeAmount,
newStakeAmount,
newExpectedRewards,
prevBonus
);
token.safeTransfer(caller, unstakeAmount);
}
function emergencyWithdrawal(
uint256 stakeIndex,
uint256 unstakeAmount
) external returns (uint256 penalty, uint256 rewards_) {
address caller = msg.sender;
Stake storage stake_ = _getStake(caller, stakeIndex);
uint256 rewardX18 = stake_.rewardX18;
penalty = unstakeAmount.unsafeDivCeil(10);
rewards_ = _calculateIntermediateReward(unstakeAmount, rewardX18, stake_);
(uint256 newStakeAmount, uint256 newExpectedRewards, uint256 rewardDiff) = _decreaseStakeAmount(
stake_,
unstakeAmount,
rewardX18
);
uint256 rewardPoolIncreaseValue = penalty.unsafeAdd(rewardDiff.unsafeSub(rewards_)).unsafeAdd(
_cancelStakeBonus(stake_)
);
rewards += rewardPoolIncreaseValue;
emit EmergencyWithdrawal(
stakeIndex,
caller,
stake_.stakingPoolIndex,
unstakeAmount,
newStakeAmount,
newExpectedRewards,
rewards_,
rewardPoolIncreaseValue
);
token.safeTransfer(caller, unstakeAmount.unsafeSub(penalty).unsafeAdd(rewards_));
}
function withdraw(uint256 stakeIndex) external returns (uint256 rewards_) {
address caller = msg.sender;
Stake storage stake_ = _getStake(caller, stakeIndex);
uint256 totalWithdrawalAmount;
(rewards_, totalWithdrawalAmount) = _withdrawNoTransfer(stakeIndex, caller, stake_);
token.safeTransfer(stake_.account, totalWithdrawalAmount);
}
function restake(
uint256 stakeIndex,
uint256 restakePoolIndex,
uint256 restakeAmount
) external returns (Stake memory restake_) {
address caller = msg.sender;
Stake storage stake_ = _getStake(caller, stakeIndex);
(, uint256 totalWithdrawalAmount) = _withdrawNoTransfer(stakeIndex, caller, stake_);
if (restakeAmount > totalWithdrawalAmount) {
revert InsufficientRestakeAmount(restakeAmount, totalWithdrawalAmount);
}
restake_ = _stakeNoTransfer(caller, restakePoolIndex, restakeAmount);
if (totalWithdrawalAmount > restakeAmount) {
token.safeTransfer(caller, totalWithdrawalAmount.unsafeSub(restakeAmount));
}
}
function _stakeNoTransfer(
address account,
uint256 poolIndex,
uint256 amount
) private returns (Stake memory stake_) {
uint256 timestamp = block.timestamp;
_validateStakingAmount(amount);
if (poolIndex >= _stakingPools.length) revert StakingPoolNotFound(poolIndex, _stakingPools.length);
StakingPool storage pool = _stakingPools[poolIndex];
if (!pool.enabled) revert StakingPoolIsDisabled(poolIndex);
uint256 rewardX18 = pool.rewardX18;
uint256 duration = pool.duration;
uint256 cancelPeriod = pool.cancelPeriod;
uint256 expectedRewards = (amount * rewardX18) / 1e18;
bool useBonus = timestamp <= deployedAt + bonusPeriod && !bonusIsUsed[account];
uint256 bonus = useBonus ? amount.unsafeDiv(20) : 0;
uint256 totalRewards = expectedRewards + bonus;
if (totalRewards > rewards) revert RewardPoolIsEmpty(totalRewards, rewards);
if (useBonus) bonusIsUsed[account] = true;
rewards = rewards.unsafeSub(totalRewards);
uint256 stakeIndex = _stakes.length;
stake_ = Stake({
inProgress: true,
account: account,
amount: amount,
stakingPoolIndex: poolIndex,
stakedAt: timestamp,
duration: duration,
endsAt: timestamp + duration,
cancelPeriod: cancelPeriod,
rewardX18: rewardX18,
expectedRewards: expectedRewards,
bonus: bonus
});
amount + totalRewards;
_stakes.push(stake_);
emit Staked(stakeIndex, account, poolIndex, amount, duration, cancelPeriod, rewardX18, bonus);
}
function _createStakingPool(StakingPool memory pool) private returns (uint256 poolIndex) {
_validateStakingPoolParameters(pool);
poolIndex = _stakingPools.length;
_stakingPools.push(pool);
emit StakingPoolUpserted(poolIndex, pool);
}
function _withdrawNoTransfer(
uint256 stakeIndex,
address staker,
Stake storage stake_
) private returns (uint256 rewards_, uint256 totalWithdrawalAmount) {
uint256 stakeAmount = stake_.amount;
if (block.timestamp < stake_.endsAt) revert StakingNotFinished(block.timestamp, stake_.endsAt);
rewards_ = stake_.expectedRewards + stake_.bonus;
stake_.inProgress = false;
totalWithdrawalAmount = stakeAmount.unsafeAdd(rewards_);
emit Withdrawal(stakeIndex, staker, stake_.stakingPoolIndex, rewards_);
}
function _decreaseStakeAmount(
Stake storage stake_,
uint256 unstakeAmount,
uint256 rewardX18
) private returns (uint256 newStakeAmount, uint256 newExpectedRewards, uint256 rewardDiff) {
uint256 prevStakedAmount = stake_.amount;
if (unstakeAmount > prevStakedAmount) revert UnstakeAmountGTStakeAmount(unstakeAmount, prevStakedAmount);
newStakeAmount = prevStakedAmount.unsafeSub(unstakeAmount);
if (newStakeAmount == 0) stake_.inProgress = false;
stake_.amount = newStakeAmount;
uint256 prevExpectedRewards = stake_.expectedRewards;
stake_.expectedRewards = newExpectedRewards = newStakeAmount.unsafeMul(rewardX18) / 1e18;
rewardDiff = prevExpectedRewards.unsafeSub(newExpectedRewards);
}
function _cancelStakeBonus(Stake storage stake_) private returns (uint256 prevBonus) {
prevBonus = stake_.bonus;
if (prevBonus > 0) stake_.bonus = 0;
}
function _validateStakingAmount(uint256 amount) private view {
if (amount <= MAX_STAKE_AMOUNT) return;
(bool usdtEquivalentCalculated, uint256 usdtEquivalent) = _tryGetUSDTAmountOut(amount);
if (!usdtEquivalentCalculated || usdtEquivalent > MAX_STAKE_AMOUNT_USDT) {
revert MaxStakingAmountExceeded(amount, usdtEquivalent);
}
}
function _getStake(address caller, uint256 stakeIndex) private view returns (Stake storage stake_) {
if (stakeIndex >= _stakes.length) revert StakingNotFound(stakeIndex, _stakes.length);
stake_ = _stakes[stakeIndex];
if (caller != stake_.account) revert UnauthorizedAccount(caller, stake_.account);
if (!stake_.inProgress) revert StakingIsClosed(stakeIndex);
}
function _tryGetUSDTAmountOut(uint256 tokenAmountIn) private view returns (bool success, uint256 amountOut) {
address[] memory path = new address[](2);
path[0] = address(token);
path[1] = address(usdt);
try uniV2Router.getAmountsOut(tokenAmountIn, path) returns (uint256[] memory result) {
return (true, result[1]);
} catch {
return (false, 0);
}
}
function _validateStakingPoolParameters(StakingPool memory pool) private pure {
if (pool.cancelPeriod >= pool.duration) {
revert StakingPoolCancelPeriodGTEDuration(pool.cancelPeriod, pool.duration);
}
}
function _calculateIntermediateReward(
uint256 amount,
uint256 rewardX18,
Stake storage stake_
) private view returns (uint256) {
uint256 timestamp = block.timestamp;
if (timestamp >= stake_.endsAt) revert StakingIsFinished(timestamp, stake_.endsAt);
return
((amount.unsafeMul(rewardX18) * (timestamp.unsafeSub(stake_.stakedAt))) / 1e18).unsafeDiv(stake_.duration);
}
}
文件 13 的 13:UnsafeMath.sol
pragma solidity =0.8.24;
library UnsafeMath {
function unsafeAdd(uint256 a, uint256 b) internal pure returns (uint256) {
unchecked {
return a + b;
}
}
function unsafeSub(uint256 a, uint256 b) internal pure returns (uint256) {
unchecked {
return a - b;
}
}
function unsafeMul(uint256 a, uint256 b) internal pure returns (uint256) {
unchecked {
return a * b;
}
}
function unsafeDiv(uint256 a, uint256 b) internal pure returns (uint256) {
unchecked {
return a / b;
}
}
function unsafeDivCeil(uint256 a, uint256 b) internal pure returns (uint256) {
unchecked {
uint256 result = a / b;
return a % b > 0 ? result + 1 : result;
}
}
}
{
"compilationTarget": {
"contracts/Staking.sol": "Staking"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs",
"useLiteralContent": true
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"owner_","type":"address"},{"internalType":"uint256","name":"bonusPeriod_","type":"uint256"},{"internalType":"address","name":"usdt_","type":"address"},{"internalType":"contract IERC20","name":"token_","type":"address"},{"internalType":"contract IUniswapV2Router02","name":"uniV2Router_","type":"address"},{"components":[{"internalType":"bool","name":"enabled","type":"bool"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"uint256","name":"rewardX18","type":"uint256"},{"internalType":"uint256","name":"cancelPeriod","type":"uint256"}],"internalType":"struct IStaking.StakingPool[]","name":"stakingPools_","type":"tuple[]"}],"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":"AddressIsZero","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentTimestamp","type":"uint256"},{"internalType":"uint256","name":"stakedAt","type":"uint256"},{"internalType":"uint256","name":"cancelPeriod","type":"uint256"}],"name":"CancelPeriodIsClosed","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[{"internalType":"uint256","name":"restakeAmount","type":"uint256"},{"internalType":"uint256","name":"totalWithdrawalAmount","type":"uint256"}],"name":"InsufficientRestakeAmount","type":"error"},{"inputs":[{"internalType":"uint256","name":"stakingAmount","type":"uint256"},{"internalType":"uint256","name":"usdtEquivalent","type":"uint256"}],"name":"MaxStakingAmountExceeded","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":[{"internalType":"uint256","name":"expectedRewards","type":"uint256"},{"internalType":"uint256","name":"availableRewards","type":"uint256"}],"name":"RewardPoolIsEmpty","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[{"internalType":"uint256","name":"stakeIndex","type":"uint256"}],"name":"StakingIsClosed","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentTimestamp","type":"uint256"},{"internalType":"uint256","name":"stakingEndedAt","type":"uint256"}],"name":"StakingIsFinished","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentTimestamp","type":"uint256"},{"internalType":"uint256","name":"stakingEndsAt","type":"uint256"}],"name":"StakingNotFinished","type":"error"},{"inputs":[{"internalType":"uint256","name":"requestedStakingIndex","type":"uint256"},{"internalType":"uint256","name":"stakesCount","type":"uint256"}],"name":"StakingNotFound","type":"error"},{"inputs":[{"internalType":"uint256","name":"cancelPeriod","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"}],"name":"StakingPoolCancelPeriodGTEDuration","type":"error"},{"inputs":[{"internalType":"uint256","name":"poolIndex","type":"uint256"}],"name":"StakingPoolIsDisabled","type":"error"},{"inputs":[{"internalType":"uint256","name":"requestedStakingPoolIndex","type":"uint256"},{"internalType":"uint256","name":"stakingPoolsCount","type":"uint256"}],"name":"StakingPoolNotFound","type":"error"},{"inputs":[{"internalType":"address","name":"caller","type":"address"},{"internalType":"address","name":"staker","type":"address"}],"name":"UnauthorizedAccount","type":"error"},{"inputs":[{"internalType":"uint256","name":"unstakeAmount","type":"uint256"},{"internalType":"uint256","name":"stakeAmount","type":"uint256"}],"name":"UnstakeAmountGTStakeAmount","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakeIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"staker","type":"address"},{"indexed":true,"internalType":"uint256","name":"stakingPoolIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unstakeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newStakeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newExpectedRewards","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"rewards","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"rewardPoolIncreaseValue","type":"uint256"}],"name":"EmergencyWithdrawal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","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":"decreaseAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newRewardPoolValue","type":"uint256"}],"name":"RewardPoolDecreased","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"increaseAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newRewardPoolValue","type":"uint256"}],"name":"RewardPoolIncreased","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakeIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"staker","type":"address"},{"indexed":true,"internalType":"uint256","name":"stakingPoolIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unstakeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newStakeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newExpectedRewards","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"prevBonus","type":"uint256"}],"name":"StakeCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakeIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"staker","type":"address"},{"indexed":true,"internalType":"uint256","name":"stakingPoolIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"duration","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"cancelPeriod","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"rewardX18","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"bonus","type":"uint256"}],"name":"Staked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"index","type":"uint256"},{"components":[{"internalType":"bool","name":"enabled","type":"bool"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"uint256","name":"rewardX18","type":"uint256"},{"internalType":"uint256","name":"cancelPeriod","type":"uint256"}],"indexed":false,"internalType":"struct IStaking.StakingPool","name":"pool","type":"tuple"}],"name":"StakingPoolUpserted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakeIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"staker","type":"address"},{"indexed":true,"internalType":"uint256","name":"stakingPoolIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"rewards","type":"uint256"}],"name":"Withdrawal","type":"event"},{"inputs":[],"name":"MAX_STAKE_AMOUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKE_AMOUNT_USDT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"bonusIsUsed","outputs":[{"internalType":"bool","name":"bonusUsed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bonusPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"stakeIndex","type":"uint256"},{"internalType":"uint256","name":"unstakeAmount","type":"uint256"}],"name":"cancelStaking","outputs":[{"internalType":"uint256","name":"newStakeAmount","type":"uint256"},{"internalType":"uint256","name":"newExpectedRewards","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bool","name":"enabled","type":"bool"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"uint256","name":"rewardX18","type":"uint256"},{"internalType":"uint256","name":"cancelPeriod","type":"uint256"}],"internalType":"struct IStaking.StakingPool","name":"stakingPool","type":"tuple"}],"name":"createStakingPool","outputs":[{"internalType":"uint256","name":"poolIndex","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"decreaseRewardPool","outputs":[{"internalType":"uint256","name":"availableRewards","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"deployedAt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"stakeIndex","type":"uint256"},{"internalType":"uint256","name":"unstakeAmount","type":"uint256"}],"name":"emergencyWithdrawal","outputs":[{"internalType":"uint256","name":"penalty","type":"uint256"},{"internalType":"uint256","name":"rewards_","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"replenishRewards","outputs":[{"internalType":"uint256","name":"availableRewards","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"stakeIndex","type":"uint256"},{"internalType":"uint256","name":"restakePoolIndex","type":"uint256"},{"internalType":"uint256","name":"restakeAmount","type":"uint256"}],"name":"restake","outputs":[{"components":[{"internalType":"bool","name":"inProgress","type":"bool"},{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"stakingPoolIndex","type":"uint256"},{"internalType":"uint256","name":"stakedAt","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"uint256","name":"endsAt","type":"uint256"},{"internalType":"uint256","name":"cancelPeriod","type":"uint256"},{"internalType":"uint256","name":"rewardX18","type":"uint256"},{"internalType":"uint256","name":"expectedRewards","type":"uint256"},{"internalType":"uint256","name":"bonus","type":"uint256"}],"internalType":"struct IStaking.Stake","name":"restake_","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolIndex","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"stake","outputs":[{"components":[{"internalType":"bool","name":"inProgress","type":"bool"},{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"stakingPoolIndex","type":"uint256"},{"internalType":"uint256","name":"stakedAt","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"uint256","name":"endsAt","type":"uint256"},{"internalType":"uint256","name":"cancelPeriod","type":"uint256"},{"internalType":"uint256","name":"rewardX18","type":"uint256"},{"internalType":"uint256","name":"expectedRewards","type":"uint256"},{"internalType":"uint256","name":"bonus","type":"uint256"}],"internalType":"struct IStaking.Stake","name":"stake_","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"stakeIndex","type":"uint256"}],"name":"stakes","outputs":[{"components":[{"internalType":"bool","name":"inProgress","type":"bool"},{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"stakingPoolIndex","type":"uint256"},{"internalType":"uint256","name":"stakedAt","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"uint256","name":"endsAt","type":"uint256"},{"internalType":"uint256","name":"cancelPeriod","type":"uint256"},{"internalType":"uint256","name":"rewardX18","type":"uint256"},{"internalType":"uint256","name":"expectedRewards","type":"uint256"},{"internalType":"uint256","name":"bonus","type":"uint256"}],"internalType":"struct IStaking.Stake","name":"stake_","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stakesLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolIndex","type":"uint256"}],"name":"stakingPools","outputs":[{"components":[{"internalType":"bool","name":"enabled","type":"bool"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"uint256","name":"rewardX18","type":"uint256"},{"internalType":"uint256","name":"cancelPeriod","type":"uint256"}],"internalType":"struct IStaking.StakingPool","name":"stakingPool","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stakingPoolsLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stakingPoolsList","outputs":[{"components":[{"internalType":"bool","name":"enabled","type":"bool"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"uint256","name":"rewardX18","type":"uint256"},{"internalType":"uint256","name":"cancelPeriod","type":"uint256"}],"internalType":"struct IStaking.StakingPool[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"uniV2Router","outputs":[{"internalType":"contract IUniswapV2Router02","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolIndex","type":"uint256"},{"components":[{"internalType":"bool","name":"enabled","type":"bool"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"uint256","name":"rewardX18","type":"uint256"},{"internalType":"uint256","name":"cancelPeriod","type":"uint256"}],"internalType":"struct IStaking.StakingPool","name":"newParameters","type":"tuple"}],"name":"updateStakingPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"usdt","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"stakeIndex","type":"uint256"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"rewards_","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]