文件 1 的 1:UnipumpStaking.sol
pragma solidity ^0.7.0;
interface IUnipumpContest
{
}
interface IUnipumpStaking
{
event Stake(address indexed _staker, uint256 _amount, uint256 _epochCount);
event Reward(address indexed _staker, uint256 _reward);
event RewardPotIncrease(uint256 _amount);
function stakingRewardPot() external view returns (uint256);
function currentEpoch() external view returns (uint256);
function nextEpochTimestamp() external view returns (uint256);
function isActivated() external view returns (bool);
function secondsUntilCanActivate() external view returns (uint256);
function totalStaked() external view returns (uint256);
function increaseRewardsPot() external;
function activate() external;
function claimRewardsAt(uint256 index) external;
function claimRewards() external;
function updateEpoch() external returns (bool);
function stakeForProfit(uint256 epochCount) external;
}
interface IUnipumpDrain
{
function drain(address token) external;
}
interface IUnipumpEscrow is IUnipumpDrain
{
function start() external;
function available() external view returns (uint256);
}
interface IUnipumpTradingGroup
{
function leader() external view returns (address);
function close() external;
function closeWithNonzeroTokenBalances() external;
function anyNonzeroTokenBalances() external view returns (bool);
function tokenList() external view returns (IUnipumpTokenList);
function maxSecondsRemaining() external view returns (uint256);
function group() external view returns (IUnipumpGroup);
function externalBalanceChanges(address token) external view returns (bool);
function startTime() external view returns (uint256);
function endTime() external view returns (uint256);
function maxEndTime() external view returns (uint256);
function startingWethBalance() external view returns (uint256);
function finalWethBalance() external view returns (uint256);
function leaderWethProfitPayout() external view returns (uint256);
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
uint256 deadline
)
external
returns (uint256[] memory amounts);
function swapTokensForExactTokens(
uint256 amountOut,
uint256 amountInMax,
address[] calldata path,
uint256 deadline
)
external
returns (uint256[] memory amounts);
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
uint256 deadline
)
external;
function withdraw(address token) external;
}
interface IUnipumpTokenList
{
function parentList() external view returns (IUnipumpTokenList);
function isLocked() external view returns (bool);
function tokens(uint256 index) external view returns (address);
function exists(address token) external view returns (bool);
function tokenCount() external view returns (uint256);
function lock() external;
function add(address token) external;
function addMany(address[] calldata _tokens) external;
function remove(address token) external;
}
interface IUnipumpGroup
{
function contribute() external payable;
function abort() external;
function startPumping() external;
function isActive() external view returns (bool);
function withdraw() external;
function leader() external view returns (address);
function tokenList() external view returns (IUnipumpTokenList);
function leaderUppCollateral() external view returns (uint256);
function requiredMemberUppFee() external view returns (uint256);
function minEthToJoin() external view returns (uint256);
function minEthToStart() external view returns (uint256);
function maxEthAcceptable() external view returns (uint256);
function maxRunTimeSeconds() external view returns (uint256);
function leaderProfitShareOutOf10000() external view returns (uint256);
function memberCount() external view returns (uint256);
function members(uint256 at) external view returns (address);
function contributions(address member) external view returns (uint256);
function totalContributions() external view returns (uint256);
function aborted() external view returns (bool);
function tradingGroup() external view returns (IUnipumpTradingGroup);
}
interface IUnipumpGroupFactory
{
function createGroup(
address leader,
IUnipumpTokenList unipumpTokenList,
uint256 uppCollateral,
uint256 requiredMemberUppFee,
uint256 minEthToJoin,
uint256 minEthToStart,
uint256 startTimeout,
uint256 maxEthAcceptable,
uint256 maxRunTimeSeconds,
uint256 leaderProfitShareOutOf10000
)
external
returns (IUnipumpGroup unipumpGroup);
}
interface IUnipumpGroupManager
{
function groupLeaders(uint256 at) external view returns (address);
function groupLeaderCount() external view returns (uint256);
function groups(uint256 at) external view returns (IUnipumpGroup);
function groupCount() external view returns (uint256);
function groupCountByLeader(address leader) external view returns (uint256);
function groupsByLeader(address leader, uint256 at) external view returns (IUnipumpGroup);
function createGroup(
IUnipumpTokenList tokenList,
uint256 uppCollateral,
uint256 requiredMemberUppFee,
uint256 minEthToJoin,
uint256 minEthToStart,
uint256 startTimeout,
uint256 maxEthAcceptable,
uint256 maxRunTimeSeconds,
uint256 leaderProfitShareOutOf10000
)
external
returns (IUnipumpGroup group);
}
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, 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 sender, address recipient, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
interface IUnipump is IERC20 {
event Sale(bool indexed _saleActive);
event LiquidityCrisis();
function WETH() external view returns (address);
function groupManager() external view returns (IUnipumpGroupManager);
function escrow() external view returns (IUnipumpEscrow);
function staking() external view returns (IUnipumpStaking);
function contest() external view returns (IUnipumpContest);
function init(
IUnipumpEscrow _escrow,
IUnipumpStaking _staking) external;
function startUnipumpSale(uint256 _tokensPerEth, uint256 _maxSoldEth) external;
function start(
IUnipumpGroupManager _groupManager,
IUnipumpContest _contest) external;
function isSaleActive() external view returns (bool);
function tokensPerEth() external view returns (uint256);
function maxSoldEth() external view returns (uint256);
function soldEth() external view returns (uint256);
function buy() external payable;
function minSecondsUntilLiquidityCrisis() external view returns (uint256);
function createLiquidityCrisis() external payable;
}
abstract contract UnipumpErc20Helper
{
function transferMax(address token, address from, address to)
internal
returns (uint256 amountTransferred)
{
uint256 balance = IERC20(token).balanceOf(from);
if (balance == 0) { return 0; }
uint256 allowed = IERC20(token).allowance(from, to);
amountTransferred = allowed > balance ? balance : allowed;
if (amountTransferred == 0) { return 0; }
require (IERC20(token).transferFrom(from, to, amountTransferred), "Transfer failed");
}
}
abstract contract UnipumpDrain is IUnipumpDrain
{
address payable immutable drainTarget;
constructor()
{
drainTarget = msg.sender;
}
function drain(address token)
public
override
{
uint256 amount;
if (token == address(0))
{
require (address(this).balance > 0, "Nothing to send");
amount = _drainAmount(token, address(this).balance);
require (amount > 0, "Nothing allowed to send");
(bool success,) = drainTarget.call{ value: amount }("");
require (success, "Transfer failed");
return;
}
amount = IERC20(token).balanceOf(address(this));
require (amount > 0, "Nothing to send");
amount = _drainAmount(token, amount);
require (amount > 0, "Nothing allowed to send");
require (IERC20(token).transfer(drainTarget, amount), "Transfer failed");
}
function _drainAmount(address token, uint256 available) internal virtual returns (uint256 amount);
}
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
uint256 c = a / b;
return c;
}
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}
contract UnipumpStaking is IUnipumpStaking, UnipumpDrain, UnipumpErc20Helper
{
using SafeMath for uint256;
struct StakedCoinInfo
{
uint256 amount;
uint256 fractionalFirstEpochAmount;
uint256 stakeUntilEpoch;
uint256 stakingEpoch;
uint256 epochRewardsClaimed;
}
struct EpochRewardInfo
{
uint256 totalReward;
uint256 totalStaked;
}
IUnipump immutable unipump;
mapping (address => StakedCoinInfo[]) stakedTokens;
uint256 public override currentEpoch;
uint256 public override nextEpochTimestamp;
uint256 constant epochSeconds = 60 * 60 * 24;
EpochRewardInfo[] epochRewards;
uint256 public override totalStaked;
uint256 totalStakedFractionalFirstEpoch;
uint256 public override stakingRewardPot;
uint256 minStakingActivationTime;
constructor (
IUnipump _unipump,
uint256 _seconds
)
{
require (address(_unipump) != address(0));
unipump = _unipump;
minStakingActivationTime = block.timestamp + _seconds;
}
receive()
external
payable
{
}
modifier epochUpToDate() { while (!updateEpoch()) { } _; }
modifier stakingActivated() { require (nextEpochTimestamp != 0, "Staking is not yet available"); _; }
function activate()
public
override
{
require (nextEpochTimestamp == 0, "Staking is already activated");
require (minStakingActivationTime > 0 && block.timestamp >= minStakingActivationTime, "Staking is not yet available");
nextEpochTimestamp = block.timestamp + epochSeconds;
}
function increaseRewardsPot()
public
override
{
uint256 amount = transferMax(address(unipump), msg.sender, address(this));
stakingRewardPot += amount;
emit RewardPotIncrease(amount);
}
function secondsUntilCanActivate()
public
view
override
returns (uint256)
{
uint256 min = minStakingActivationTime;
if (block.timestamp >= min) { return 0; }
return min - block.timestamp;
}
function isActivated()
public
view
override
returns (bool)
{
return nextEpochTimestamp != 0;
}
function updateEpoch()
public
override
stakingActivated()
returns (bool upToDate)
{
uint256 next = nextEpochTimestamp;
if (block.timestamp < next) { return true; }
uint256 epoch = currentEpoch++;
next += epochSeconds;
nextEpochTimestamp = next;
uint256 pot = stakingRewardPot;
uint256 reward =
epoch < 20 ? pot * 3 / 100 :
epoch < 40 ? pot * 2 / 100 :
pot / 100;
epochRewards.push();
epochRewards[epoch].totalReward = reward;
epochRewards[epoch].totalStaked = totalStakedFractionalFirstEpoch;
stakingRewardPot = pot - reward;
totalStakedFractionalFirstEpoch = totalStaked;
return block.timestamp < next;
}
function stakeForProfit(uint256 epochCount)
public
override
stakingActivated()
epochUpToDate()
{
require (epochCount > 0, "Tokens must be staked until at least the next epoch");
require (epochCount <= 3650, "Tokens cannot be staked this long");
uint256 amount = transferMax(address(unipump), msg.sender, address(this));
require (amount > 0, "No UPP tokens have been authorized for transfer");
uint256 len = stakedTokens[msg.sender].length;
uint256 epoch = currentEpoch;
uint256 fractional = amount.mul(nextEpochTimestamp - block.timestamp) / epochSeconds;
stakedTokens[msg.sender].push();
stakedTokens[msg.sender][len].amount = amount;
stakedTokens[msg.sender][len].fractionalFirstEpochAmount = fractional;
stakedTokens[msg.sender][len].stakeUntilEpoch = epoch + epochCount;
stakedTokens[msg.sender][len].stakingEpoch = epoch;
totalStaked += amount;
totalStakedFractionalFirstEpoch += fractional;
emit Stake(msg.sender, amount, epochCount);
}
function claimRewardsAt(uint256 index)
public
override
epochUpToDate()
{
uint256 len = stakedTokens[msg.sender].length;
require (index < len, "There are no staked tokens");
uint256 claimCount = stakedTokens[msg.sender][index].epochRewardsClaimed;
uint256 firstEpoch = stakedTokens[msg.sender][index].stakingEpoch;
uint256 epoch = currentEpoch;
uint256 claimingEpoch = firstEpoch + claimCount;
require (epoch > claimingEpoch, "Rewards are not available until the end of the epoch");
uint256 amountStaked = stakedTokens[msg.sender][index].amount;
bool expired = epoch >= stakedTokens[msg.sender][index].stakeUntilEpoch;
uint256 reward = claimCount == 0 ? stakedTokens[msg.sender][index].fractionalFirstEpochAmount : amountStaked;
reward = reward.mul(epochRewards[claimingEpoch].totalReward) / epochRewards[claimingEpoch].totalStaked;
if (expired) {
reward += amountStaked;
if (len - 1 != index) {
stakedTokens[msg.sender][index] = stakedTokens[msg.sender][len - 1];
}
stakedTokens[msg.sender].pop();
totalStaked -= amountStaked;
totalStakedFractionalFirstEpoch -= amountStaked;
}
else {
stakedTokens[msg.sender][index].epochRewardsClaimed = claimCount + 1;
}
unipump.transfer(msg.sender, reward);
emit Reward(msg.sender, reward);
}
function claimRewards()
public
override
epochUpToDate()
{
uint256 len = stakedTokens[msg.sender].length;
require (len > 0, "There are no staked tokens");
uint256 epoch = currentEpoch;
uint256 index = len;
uint256 removed = 0;
uint256 totalReward = 0;
while (index-- > 0)
{
uint256 claimCount = stakedTokens[msg.sender][index].epochRewardsClaimed;
uint256 firstEpoch = stakedTokens[msg.sender][index].stakingEpoch;
uint256 claimingEpoch = firstEpoch + claimCount;
if (claimingEpoch >= epoch) { continue; }
uint256 amountStaked = stakedTokens[msg.sender][index].amount;
bool expired = epoch >= stakedTokens[msg.sender][index].stakeUntilEpoch;
for (; claimingEpoch < epoch; ++claimingEpoch) {
uint256 reward = claimCount++ == 0 ? stakedTokens[msg.sender][index].fractionalFirstEpochAmount : amountStaked;
reward = reward.mul(epochRewards[claimingEpoch].totalReward) / epochRewards[claimingEpoch].totalStaked;
if (expired) {
reward += amountStaked;
if (len - 1 != index) {
stakedTokens[msg.sender][index] = stakedTokens[msg.sender][len - 1];
}
stakedTokens[msg.sender].pop();
removed += amountStaked;
removed += amountStaked;
}
totalReward += reward;
}
if (!expired) { stakedTokens[msg.sender][index].epochRewardsClaimed = claimCount; }
}
totalStaked -= removed;
totalStakedFractionalFirstEpoch -= removed;
unipump.transfer(msg.sender, totalReward);
emit Reward(msg.sender, totalReward);
}
function _drainAmount(address token, uint256 available)
internal
override
view
returns (uint256 amount)
{
amount = token == address(unipump) ? 0 : available;
}
}