//SPDX-License-Identifier: MITpragmasolidity 0.8.14;interfaceIERC20{
functiontotalSupply() externalviewreturns (uint256);
functionsymbol() externalviewreturns(stringmemory);
functionname() externalviewreturns(stringmemory);
/**
* @dev Returns the amount of tokens owned by `account`.
*/functionbalanceOf(address account) externalviewreturns (uint256);
/**
* @dev Returns the number of decimal places
*/functiondecimals() externalviewreturns (uint8);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/functiontransfer(address recipient, uint256 amount) externalreturns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/functionallowance(address owner, address spender) externalviewreturns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/functionapprove(address spender, uint256 amount) externalreturns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/functiontransferFrom(address sender, address recipient, uint256 amount) externalreturns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/eventTransfer(addressindexedfrom, addressindexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/eventApproval(addressindexed owner, addressindexed spender, uint256 value);
}
//SPDX-License-Identifier: MITpragmasolidity 0.8.14;/**
* @title Owner
* @dev Set & change owner
*/contractOwnable{
addressprivate owner;
// event for EVM loggingeventOwnerSet(addressindexed oldOwner, addressindexed newOwner);
// modifier to check if caller is ownermodifieronlyOwner() {
// If the first argument of 'require' evaluates to 'false', execution terminates and all// changes to the state and to Ether balances are reverted.// This used to consume all gas in old EVM versions, but not anymore.// It is often a good idea to use 'require' to check if functions are called correctly.// As a second argument, you can also provide an explanation about what went wrong.require(msg.sender== owner, "Caller is not owner");
_;
}
/**
* @dev Set contract deployer as owner
*/constructor() {
owner =msg.sender; // 'msg.sender' is sender of current call, contract deployer for a constructoremit OwnerSet(address(0), owner);
}
/**
* @dev Change owner
* @param newOwner address of new owner
*/functionchangeOwner(address newOwner) publiconlyOwner{
emit OwnerSet(owner, newOwner);
owner = newOwner;
}
/**
* @dev Return owner address
* @return address of owner
*/functiongetOwner() externalviewreturns (address) {
return owner;
}
}
//SPDX-License-Identifier: MITpragmasolidity 0.8.14;import"./IERC20.sol";
import"./Ownable.sol";
import"./ReentrantGuard.sol";
import"./IUniswapV2Router02.sol";
interfaceIGovernance{
functionmint(address account, uint256 amount) externalreturns (bool);
}
/**
GOVERNANCE TAO Staking Contract
*/contractGOVERNANCETAOisOwnable, IERC20, ReentrancyGuard{
// name and symbol for tokenized contractstringprivateconstant _name ='EtherNexus STAKE';
stringprivateconstant _symbol ='SENXS';
uint8privateconstant _decimals =9;
// constantsuint256privateconstant precision =10**24;
uint256publicconstant minLockTimeMultiplier =10**6;
addressprivateconstant burnAddress =0x000000000000000000000000000000000000dEaD;
addresspublicconstant token =0x18a3563c21062897950BB09339C82b9686a35667;
// Router for reinvesting
IUniswapV2Router02 public router = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
// maximum lock time in blocksuint256public maxLockTime =730days;
uint256public minLockTime =7days;
uint256public maxLockTimeMultiplier =25* minLockTimeMultiplier /10;
// maximum leave early fee multiplieruint256public minEarlyFee =2*10**16;
uint256public maxEarlyFee =15*10**16;
// Lock InfostructLockInfo {
uint256 lockAmount;
uint256 unlockTime;
uint256 lockDuration;
uint256 rewardPointsAssigned;
uint256 index;
address locker;
}
// Nonce For Lock Infouint256public lockInfoNonce;
// Nonce => LockInfomapping(uint256=> LockInfo) public lockInfo;
// User InfostructUserInfo {
uint256 totalAmountStaked;
uint256 rewardPoints;
uint256[] lockIds;
uint256 totalRewardsClaimed;
}
// Address => UserInfomapping(address=> UserInfo) public userInfo;
// list of all users for airdrop functionalityaddress[] public allUsers;
// Tracks Dividendsuint256public totalStaked;
uint256public totalRewardPoints;
// Average Lock Timesuint256public totalTimeLocked;
// Reward tracking infouint256public totalRewards;
uint256private dividendsPerPoint;
mapping ( address=>uint256 ) private totalExcluded;
// Governance Token
IGovernance public GENXS;
// EventseventSetMaxLockTime(uint256 newMaxLockTime);
eventSetMinLockTime(uint256 newMinLockTime);
eventSetMaxLeaveEarlyFee(uint256 newMaxFee);
eventSetMinLeaveEarlyFee(uint256 newMinFee);
eventSetMaxLockTimeMultiplier(uint256 newMaxLockTimeMultiplier);
constructor() {
emit Transfer(address(0), msg.sender, 0);
}
/** Returns the total number of tokens in existence */functiontotalSupply() externalviewoverridereturns (uint256) {
return totalStaked;
}
/** Returns the number of tokens owned by `account` */functionbalanceOf(address account) publicviewoverridereturns (uint256) {
return userInfo[account].totalAmountStaked;
}
/** Returns the number of tokens `spender` can transfer from `holder` */functionallowance(address, address)
externalpureoverridereturns (uint256)
{
return0;
}
/** Token Name */functionname() publicpureoverridereturns (stringmemory) {
return _name;
}
/** Token Ticker Symbol */functionsymbol() publicpureoverridereturns (stringmemory) {
return _symbol;
}
/** Tokens decimals */functiondecimals() publicpureoverridereturns (uint8) {
return _decimals;
}
/** Approves `spender` to transfer `amount` tokens from caller */functionapprove(address spender, uint256) publicoverridereturns (bool) {
emit Approval(msg.sender, spender, 0);
returntrue;
}
/** Transfer Function */functiontransfer(address recipient, uint256)
externaloverridenonReentrantreturns (bool)
{
_claimReward(msg.sender, false);
emit Transfer(msg.sender, recipient, 0);
returntrue;
}
/** Transfer Function */functiontransferFrom(address,
address recipient,
uint256) externaloverridenonReentrantreturns (bool) {
_claimReward(msg.sender, false);
emit Transfer(msg.sender, recipient, 0);
returntrue;
}
/////////////////////////////////////////////////// OWNER FUNCTIONS /////////////////////////////////////////////////////**
Sets The Governance Token, Can Only Be Set Once
*/functionsetGENXS(address GENXS_) externalonlyOwner{
require(
GENXS_ !=address(0) &&address(GENXS) ==address(0),
'Already Set'
);
GENXS = IGovernance(GENXS_);
}
/**
Sets the router for reinvesting
*/functionsetRouter(address newRouter) externalonlyOwner{
router = IUniswapV2Router02(newRouter);
}
/**
Sets The Minimum Allowed Lock Time That Users Can Stake
Requirements:
- newMinLockTime must be less than the maxLockTime
*/functionsetMinLockTime(uint256 newMinLockTime) externalonlyOwner{
require(
newMinLockTime < maxLockTime,
"Min Lock Time Cannot Exceed Max Lock Time"
);
minLockTime = newMinLockTime;
emit SetMinLockTime(newMinLockTime);
}
/**
Sets The Maximum Allowed Lock Time That Users Can Stake
Requirements:
- newMaxLockTime must be greater than the minLockTime
*/functionsetMaxLockTime(uint256 newMaxLockTime) externalonlyOwner{
require(
newMaxLockTime > minLockTime,
"Max Lock Time Must Exceed Min Lock Time"
);
maxLockTime = newMaxLockTime;
emit SetMaxLockTime(newMaxLockTime);
}
/**
Sets The Minimum Penalty For Unstaking Before Stake Unlocks
Requirements:
- newMinLeaveEarlyFee must be less than the maxEarlyFee
*/functionsetMinLeaveEarlyFee(uint256 newMinLeaveEarlyFee) externalonlyOwner{
require(
newMinLeaveEarlyFee < maxEarlyFee,
"Min Lock Time Cannot Exceed Max Lock Time"
);
minEarlyFee = newMinLeaveEarlyFee;
emit SetMinLeaveEarlyFee(newMinLeaveEarlyFee);
}
/**
Sets The Maximum Penalty For Unstaking Before Stake Unlocks
Requirements:
- newMaxLeaveEarlyFee must be less than the minEarlyFee
*/functionsetMaxLeaveEarlyFee(uint256 newMaxLeaveEarlyFee) externalonlyOwner{
require(
newMaxLeaveEarlyFee > minEarlyFee,
"Max Lock Time Must Exceed Min Lock Time"
);
maxEarlyFee = newMaxLeaveEarlyFee;
emit SetMaxLeaveEarlyFee(newMaxLeaveEarlyFee);
}
/**
Sets The Multiplier For Maximum Lock Time
A Multiplier Of 4 * 10^18 Would Make A Max Lock Time Stake
Gain 4x The Rewards Of A Min Lock Time Stake For The Same Amount Of Tokens Staked
Requirements:
- newMaxLockTimeMultiplier MUST Be Greater Than Or Equal To 10^18
*/functionsetMaxLockTimeMultiplier(uint256 newMaxLockTimeMultiplier)
externalonlyOwner{
require(
newMaxLockTimeMultiplier >= minLockTimeMultiplier,
"Max Lock Time Multiplier Too Small"
);
maxLockTimeMultiplier = newMaxLockTimeMultiplier;
emit SetMaxLockTimeMultiplier(newMaxLockTimeMultiplier);
}
/**
Withdraws Any Token That Is Not MTAO
NOTE: Withdrawing Reward Tokens Will Mess Up The Math Associated With Rewarding
The Contract will still function as desired, but the last users to claim
Will not receive their full amount, or any, of the reward token
*/functionwithdrawForeignToken(address token_) externalonlyOwner{
require(token != token_, "Cannot Withdraw Staked Token");
_send(token_, msg.sender, balanceOfToken(token_));
}
/**
Withdraws The Native Chain Token To Owner's Address
*/functionwithdrawNative() externalonlyOwner{
(bool s, ) =payable(msg.sender).call{value: address(this).balance}("");
require(s, "Failure To Withdraw Native");
}
/**
Stakes `amount` of MTAO for the specified `lockTime`
Increasing the user's rewardPoints and overall share of the pool
Also claims the current pending rewards for the user
Requirements:
- `amount` is greater than zero
- lock time is within bounds for min and max lock time, lock time is in blocks
- emergencyWithdraw has not been enabled by contract owner
*/functionstakeFor(address user, uint256 amount, uint256 lockTime) externalnonReentrantonlyOwner{
require(amount >0, "Zero Amount");
require(user !=address(0), 'Zero Address');
require(lockTime <= maxLockTime, "Lock Time Exceeds Maximum");
require(lockTime >= minLockTime, "Lock Time Preceeds Minimum");
// attempt to claim rewardsif (userInfo[user].totalAmountStaked >0) {
_claimReward(user, false);
}
// transfer in tokensuint256 received = _transferIn(token, amount);
// total reward multiplieruint256 multiplier = calculateRewardPoints(received, lockTime);
// update reward multiplier dataunchecked {
userInfo[user].rewardPoints += multiplier;
totalRewardPoints += multiplier;
}
// update staked dataunchecked {
totalStaked += received;
userInfo[user].totalAmountStaked += received;
}
// update reward data for each reward token
totalExcluded[user] = getCumulativeDividends(userInfo[user].rewardPoints);
// Map Lock Nonce To Lock Info
lockInfo[lockInfoNonce] = LockInfo({
lockAmount: received,
unlockTime: block.timestamp+ lockTime,
lockDuration: lockTime,
rewardPointsAssigned: multiplier,
index: userInfo[user].lockIds.length,
locker: user
});
// Push Lock Nonce To User's Lock IDs
userInfo[user].lockIds.push(lockInfoNonce);
unchecked {
// Increment Global Lock Nonce
lockInfoNonce++;
// Increment Total Time Locked
totalTimeLocked += lockTime;
}
// show transfer for Staking Tokenemit Transfer(address(0), user, received);
// mint governance tokens
GENXS.mint(user, multiplier);
}
/////////////////////////////////////////////////// PUBLIC FUNCTIONS /////////////////////////////////////////////////////**
Claims All The Rewards Associated With `msg.sender` in ETH
*/functionclaimRewards() externalnonReentrant{
_claimReward(msg.sender, false);
}
/**
Claims All The Rewards Associated With `msg.sender` in MTAO
*/functionclaimRewardsAsMTAO() externalnonReentrant{
_claimReward(msg.sender, true);
}
/**
Stakes `amount` of MTAO for the specified `lockTime`
Increasing the user's rewardPoints and overall share of the pool
Also claims the current pending rewards for the user
Requirements:
- `amount` is greater than zero
- lock time is within bounds for min and max lock time, lock time is in blocks
- emergencyWithdraw has not been enabled by contract owner
*/functionstake(uint256 amount, uint256 lockTime) externalnonReentrant{
require(amount >0, "Zero Amount");
require(lockTime <= maxLockTime, "Lock Time Exceeds Maximum");
require(lockTime >= minLockTime, "Lock Time Preceeds Minimum");
// gas savingsaddress user =msg.sender;
// attempt to claim rewardsif (userInfo[user].totalAmountStaked >0) {
_claimReward(user, false);
}
// transfer in tokensuint256 received = _transferIn(token, amount);
// total reward multiplieruint256 multiplier = calculateRewardPoints(received, lockTime);
// update reward multiplier dataunchecked {
userInfo[user].rewardPoints += multiplier;
totalRewardPoints += multiplier;
}
// update staked dataunchecked {
totalStaked += received;
userInfo[user].totalAmountStaked += received;
}
// update reward data for each reward token
totalExcluded[user] = getCumulativeDividends(userInfo[user].rewardPoints);
// Map Lock Nonce To Lock Info
lockInfo[lockInfoNonce] = LockInfo({
lockAmount: received,
unlockTime: block.timestamp+ lockTime,
lockDuration: lockTime,
rewardPointsAssigned: multiplier,
index: userInfo[user].lockIds.length,
locker: user
});
// Push Lock Nonce To User's Lock IDs
userInfo[user].lockIds.push(lockInfoNonce);
unchecked {
// Increment Global Lock Nonce
lockInfoNonce++;
// Increment Total Time Locked
totalTimeLocked += lockTime;
}
// show transfer for Staking Tokenemit Transfer(address(0), user, received);
// mint governance tokens
GENXS.mint(user, multiplier);
}
/**
Withdraws `amount` of MTAO Associated With `lockId`
Claims All Pending Rewards For The User
Requirements:
- `lockId` is a valid lock ID
- locker of `lockId` is msg.sender
- lock amount for `lockId` is greater than zero
- the time left until unlock for `lockId` is zero
- Emergency Withdraw is disabled
*/functionwithdraw(uint256 lockId, uint256 amount) externalnonReentrant{
// gas savingsaddress user =msg.sender;
uint256 lockIdAmount = lockInfo[lockId].lockAmount;
// Require Input Data Is Correctrequire(lockId < lockInfoNonce, "Invalid LockID");
require(lockInfo[lockId].locker == user, "Not Owner Of LockID");
require(lockIdAmount >0&& amount >0, "Insufficient Amount");
// claim reward for userif (userInfo[user].totalAmountStaked >0) {
_claimReward(user, false);
}
// ensure we are not trying to unlock more than we ownif (amount > lockIdAmount) {
amount = lockIdAmount;
}
// update amount staked
totalStaked -= amount;
userInfo[user].totalAmountStaked -= amount;
// see if early fee should be applieduint earlyFee = timeUntilUnlock(lockId) ==0 ? 0 : amount * getEarlyFee(lockId) /10**18;
// if withdrawing full amount, remove lock IDif (amount == lockIdAmount) {
// reduce reward points assigneduint256 rewardPointsAssigned = lockInfo[lockId].rewardPointsAssigned;
userInfo[user].rewardPoints -= rewardPointsAssigned;
totalRewardPoints -= rewardPointsAssigned;
// remove all lockId data
_removeLock(lockId);
} else {
// reduce rewardPoints by rewardPoints * ( amount / lockAmount )uint256 rewardPointsToRemove = (amount *
lockInfo[lockId].rewardPointsAssigned) / lockIdAmount;
// decrement reward points
userInfo[user].rewardPoints -= rewardPointsToRemove;
totalRewardPoints -= rewardPointsToRemove;
// update lock data
lockInfo[lockId].lockAmount -= amount;
lockInfo[lockId].rewardPointsAssigned -= rewardPointsToRemove;
}
// update reward data for each reward token
totalExcluded[user] = getCumulativeDividends(userInfo[user].rewardPoints);
// remove user from list if unstaked completelyif (userInfo[user].totalAmountStaked ==0) {
delete userInfo[user];
}
// burn early fee if applicableif (earlyFee >0) {
_send(token, burnAddress, earlyFee);
}
// send rest of amount to user
_send(token, user, amount - earlyFee);
// emit token transferemit Transfer(user, address(0), amount);
}
/**
Allows Contract To Receive Native Currency
*/receive() externalpayablenonReentrant{
// update rewardsunchecked {
totalRewards +=msg.value;
}
if (totalRewardPoints >0) {
dividendsPerPoint += ( precision *msg.value ) / totalRewardPoints;
}
}
/////////////////////////////////////////////////// INTERNAL FUNCTIONS ////////////////////////////////////////////////////function_claimReward(address user, bool asMTAO) internal{
// exit if zero value lockedif (userInfo[user].totalAmountStaked ==0) {
return;
}
// get pending rewardsuint256 pending = pendingRewards(user);
// reset total excluded
totalExcluded[user] = getCumulativeDividends(userInfo[user].rewardPoints);
// increment total rewards claimedunchecked {
userInfo[user].totalRewardsClaimed += pending;
}
if (asMTAO) {
// amount of MTAO in contract before swapuint256 amountBefore = IERC20(token).balanceOf(address(this));
// define swap pathaddress[] memory path =newaddress[](2);
path[0] = router.WETH();
path[1] = token;
// swap ETH for MTAO
router.swapExactETHForTokensSupportingFeeOnTransferTokens{value: pending}(
0, path, address(this), block.timestamp+10000
);
// amount of MTAO in contract after swapuint256 amountAfter = IERC20(token).balanceOf(address(this));
require(
amountAfter > amountBefore,
'No Received'
);
// calculate amount of MTAO purchaseduint256 received = amountAfter - amountBefore;
// send MTAO to user
_send(token, user, received);
// save memory, refund gasdelete path;
} else {
// send ETH value to user
_send(address(0), user, pending);
}
}
function_transferIn(address _token, uint256 amount)
internalreturns (uint256)
{
require(
IERC20(_token).allowance(msg.sender, address(this)) >= amount,
'Insufficient Allowance'
);
uint256 before = balanceOfToken(_token);
IERC20(_token).transferFrom(msg.sender, address(this), amount);
uint256 After = balanceOfToken(_token);
require(After > before, "Error On Transfer From");
return After - before;
}
function_send(address _token,
address to,
uint256 amount
) internal{
if (to ==address(0)) {
return;
}
// fetch and validate contract owns necessary balanceuint256 bal = _token ==address(0) ? address(this).balance : balanceOfToken(_token);
if (amount > bal) {
amount = bal;
}
// return if amount is zeroif (amount ==0) {
return;
}
if (_token ==address(0)) {
(bool s,) =payable(to).call{value: amount}("");
require(s, 'Failure On Eth Transfer');
} else {
// ensure transfer succeedsrequire(
IERC20(_token).transfer(to, amount),
"Failure On Token Transfer"
);
}
}
function_removeLock(uint256 id) internal{
// fetch elements to make function more readableaddress user = lockInfo[id].locker;
uint256 rmIndex = lockInfo[id].index;
uint256 lastElement = userInfo[user].lockIds[
userInfo[user].lockIds.length-1
];
// set last element's index to be removed index
lockInfo[lastElement].index = rmIndex;
// set removed index's position to be the last element
userInfo[user].lockIds[rmIndex] = lastElement;
// pop last element off the user array
userInfo[user].lockIds.pop();
// delete lock datadelete lockInfo[id];
}
/////////////////////////////////////////////////// READ FUNCTIONS ////////////////////////////////////////////////////functiongetCumulativeDividends(uint256 share) publicviewreturns (uint256) {
return ( share * dividendsPerPoint ) / precision;
}
functiongetEarlyFee(uint lockId) publicviewreturns (uint256) {
return calculateLeaveEarlyFee(lockInfo[lockId].lockDuration);
}
functionbalanceOfToken(address _token) publicviewreturns (uint256) {
return IERC20(_token).balanceOf(address(this));
}
functiontokenBalanceOf(address user) externalviewreturns (uint256) {
return userInfo[user].totalAmountStaked;
}
functioncalculateRewardPoints(uint256 lockAmount, uint256 lockTime)
publicviewreturns (uint256)
{
return
lockAmount *
(minLockTimeMultiplier +
(((lockTime - minLockTime) *
(maxLockTimeMultiplier - minLockTimeMultiplier)) /
(maxLockTime - minLockTime)));
}
functioncalculateLeaveEarlyFee(uint256 lockTime)
publicviewreturns (uint256)
{
return
minEarlyFee +
(((lockTime - minLockTime) *
(maxEarlyFee - minEarlyFee)) /
(maxLockTime - minLockTime));
}
functiontimeUntilUnlock(uint256 lockId) publicviewreturns (uint256) {
return
lockInfo[lockId].unlockTime <=block.timestamp
? 0
: lockInfo[lockId].unlockTime -block.timestamp;
}
functionpendingRewards(address user)
publicviewreturns (uint256)
{
if (userInfo[user].totalAmountStaked ==0) {
return0;
}
uint256 holderTotalDividends = getCumulativeDividends(userInfo[user].rewardPoints);
uint256 holderTotalExcluded = totalExcluded[user];
return
holderTotalDividends > holderTotalExcluded
? holderTotalDividends - holderTotalExcluded
: 0;
}
functionaverageTimeLocked() externalviewreturns (uint256) {
return totalTimeLocked / lockInfoNonce;
}
functiongetAllLockIDsForUser(address user)
externalviewreturns (uint256[] memory)
{
return userInfo[user].lockIds;
}
functiongetNumberOfLockIDsForUser(address user)
externalviewreturns (uint256)
{
return userInfo[user].lockIds.length;
}
functiongetTotalRewardsClaimedForUser(address user)
externalviewreturns (uint256)
{
return userInfo[user].totalRewardsClaimed;
}
functionfetchLockData(address user)
externalviewreturns (uint256[] memory,
uint256[] memory,
uint256[] memory,
uint256[] memory,
uint256[] memory)
{
uint256 len = userInfo[user].lockIds.length;
uint256[] memory amounts =newuint256[](len);
uint256[] memory durations =newuint256[](len);
uint256[] memory timeRemaining =newuint256[](len);
uint256[] memory earlyFees =newuint256[](len);
for (uint256 i =0; i < len; ) {
amounts[i] = lockInfo[userInfo[user].lockIds[i]].lockAmount;
durations[i] = lockInfo[userInfo[user].lockIds[i]].lockDuration;
timeRemaining[i] = timeUntilUnlock(userInfo[user].lockIds[i]);
earlyFees[i] = getEarlyFee(userInfo[user].lockIds[i]);
unchecked {
++i;
}
}
return (userInfo[user].lockIds, amounts, durations, timeRemaining, earlyFees);
}
}