// SPDX-License-Identifier: -- 💰 --import'./Token.sol';
pragmasolidity ^0.7.3;contractFEYTokenisToken{
usingSafeMathforuint256;
/**
* @notice returns the interest rate by getting the global variable
* YEARLY_INTEREST and subtracting the percentage passed.
* Lowers the default interest rate as amount staked rises towards the totalSupply.
* Used to record the rates for snapshots + to calculate stake interest
* @param _percentage any uint256 figure
* @return interestRate
*/functiongetInterestRateYearly(uint256 _percentage
)
publicpurereturns (uint256 interestRate)
{
return _percentage >100
? uint256(YEARLY_INTEREST).mul(uint256(10000)).div(_percentage)
: YEARLY_INTEREST.mul(100);
}
/**
* @notice a no args function used to get current APY
* @dev _precision in getPercent is fixed to 4
* @return percentage -- totalStaked on a particular day out of totalSupply
* @return interestRateYearly -- APY based on relative size of current total stakes
*/functiongetYearlyInterestLatest()
publicviewreturns (uint256 percentage,
uint256 interestRateYearly
)
{
percentage = getPercent(
globals.totalStakedAmount,
totalSupply,
4
);
interestRateYearly = getInterestRateYearly(
percentage
);
}
/**
* @notice function used to get APY of a specific day
* @param _day integer for the target day, starting @ 0
* @dev _precision in getPercent is fixed to 4
* @return percentage -- totalStaked on a particular day out of totalSupply
* @return interestRateYearly -- APY based on relative size of stake
*/functiongetYearlyInterestHistorical(uint256 _day
)
publicviewreturns (uint256 percentage,
uint256 interestRateYearly
)
{
SnapShot memory s = snapshots[_day];
if (s.totalSupply ==0) {
return getYearlyInterestLatest();
}
percentage = getPercent(
s.totalStakedAmount,
s.totalSupply,
4
);
interestRateYearly = getInterestRateYearly(
percentage
);
}
/**
* @notice calculates amount of interest earned per second
* @param _stakedAmount principal amount
* @param _totalStakedAmount summation of principal amount staked by everyone
* @param _seconds time spent earning interest on a particular day
* _seconds will be passed as the full SECONDS_IN_DAY for full days that we staked
* _seconds will be the seconds that have passed by the time getInterest is called on the last day
* @dev _precision in getPercent is fixed to 4
* @return durationInterestAmt -- totalStaked on a particular day out of totalSupply
*/functiongetInterest(uint256 _stakedAmount,
uint256 _totalStakedAmount,
uint256 _seconds
)
publicviewreturns (uint256 durationInterestAmt)
{
uint256 percentage = getPercent(
_totalStakedAmount,
totalSupply,
4
);
uint256 interestRateYearly = getInterestRateYearly(
percentage
);
uint256 yearFullAmount = _stakedAmount
.mul(interestRateYearly)
.div(100);
uint256 dailyInterestAmt = getPercent(
yearFullAmount,
31556952,
0
);
durationInterestAmt = dailyInterestAmt
.mul(_seconds)
.div(100);
}
/**
* @notice admin function to close a matured stake OBO the staker
* @param _stakingId ID of the stake, used as the Key from the stakeList mapping
* @dev can only close after all of the seconds of the last day have passed
*/functioncloseGhostStake(uint256 _stakingId
)
externalonlyOwner{
(uint256 daysOld, uint256 secondsOld) =
getStakeAge(
_stakingId
);
require(
daysOld == MAX_STAKE_DAYS &&
secondsOld == SECONDS_IN_DAY,
'FEYToken: not old enough'
);
_closeStake(
stakeList[_stakingId].userAddress,
_stakingId
);
emit ClosedGhostStake(
daysOld,
secondsOld,
_stakingId
);
}
/**
* @notice calculates number of days and remaining seconds on current day that a stake is open
* @param _stakingId ID of the stake, used as the Key from the stakeList mapping
* @return daysTotal -- number of complete days that the stake has been open
* @return secondsToday -- number of seconds the stake has been open on the current day
*/functiongetStakeAge(uint256 _stakingId
)
publicviewreturns (uint256 daysTotal,
uint256 secondsToday
)
{
StakeElement memory _stakeElement = stakeList[_stakingId];
uint256 secondsTotal = getNow()
.sub(_stakeElement.stakedAt);
daysTotal = secondsTotal
.div(SECONDS_IN_DAY);
if (daysTotal > MAX_STAKE_DAYS) {
daysTotal = MAX_STAKE_DAYS;
secondsToday = SECONDS_IN_DAY;
} else {
secondsToday = secondsTotal
.mod(SECONDS_IN_DAY);
}
}
/**
* @notice calculates amount of interest due to be credited to the staker based on:
* number of days and remaining seconds on current day that a stake is open
* @param _stakingId ID of the stake, used as the Key from the stakeList mapping
* @return stakeInterest -- total interest per second the stake was open on each day
*/functiongetStakeInterest(uint256 _stakingId
)
publicviewreturns (uint256 stakeInterest
)
{
StakeElement memory _stakeElement = stakeList[_stakingId];
if (_stakeElement.isActive ==false) {
stakeInterest = _stakeElement.interestAmount;
} else {
(
uint256 daysTotal,
uint256 secondsToday
) = getStakeAge(_stakingId);
uint256 finalDay = _currentFeyDay();
uint256 startDay = finalDay.sub(daysTotal);
for (uint256 _day = startDay; _day < finalDay; _day++) {
stakeInterest += getInterest(
_stakeElement.stakedAmount,
snapshots[_day].totalStakedAmount,
SECONDS_IN_DAY
);
}
stakeInterest += getInterest(
_stakeElement.stakedAmount,
globals.totalStakedAmount,
secondsToday
);
}
}
/**
* @notice penalties are taken if you close a stake before the completion of the 4th day
* if closed before the end of the 15th day: 7.5% of staked amount is penalized
* if closed before the end of the 30th day: 5% of staked amount is penalized
* if closed before the end of the 45th day: 2.5% of staked amount is penalized
* @param _stakingId ID of the stake, used as the Key from the stakeList mapping
* @return penaltyAmount -- amount that will be debited from the stakers principal when they close their stake
*/functiongetStakePenalty(uint256 _stakingId
)
publicviewreturns (uint256 penaltyAmount)
{
StakeElement memory _stakeElement = stakeList[_stakingId];
uint256 daysDifference = getNow()
.sub(_stakeElement.stakedAt)
.div(SECONDS_IN_DAY);
if (daysDifference <15) {
penaltyAmount = percentCalculator(
_stakeElement.stakedAmount,
750
);
} elseif (daysDifference <30) {
penaltyAmount = percentCalculator(
_stakeElement.stakedAmount,
500
);
} elseif (daysDifference <45) {
penaltyAmount = percentCalculator(
_stakeElement.stakedAmount,
250
);
}
}
/**
* @notice calculates principal + interest - penalty (if applicable)
* Note: this does not calculate a return rate, only what the sum would be if the stake was closed at that moment
* @param _stakingId ID of the stake, used as the Key from the stakeList mapping
* @dev the calculated value is only in memory
* @return uint256 -- principal + interest - penalty
*/functionestimateReturn(uint256 _stakingId
)
publicviewreturns (uint256)
{
StakeElement memory _stakeElement = stakeList[_stakingId];
if (_stakeElement.isActive ==false) {
return _stakeElement.returnAmount;
}
return _stakeElement.stakedAmount
.add(getStakeInterest(_stakingId))
.sub(getStakePenalty(_stakingId));
}
/**
* @notice close a stake older than 1 full day to:
* 1) credit principal + interest - penalty to the balance of the staker
* 2) update totalStakedAmount in globals
* 3) take snapshot of current FEY status before the stake closes
* No interest is accrued unless the stake is at least on its 4th day
* Updates global variables to reflect the closed stake
* @param _stakingId ID of the stake, used as the Key from the stakeList mapping
* @return stakedAmount -- represents the total calculated by: principal + interest - penalty
* @return penaltyAmount -- amount that will be debited from the stakers principal when they close their stake
* @return interestAmount -- amount that will be debited from the stakers principal when they close their stake
*/functioncloseStake(uint256 _stakingId
)
publicsnapshotTriggerOnClosereturns (uint256 stakedAmount,
uint256 penaltyAmount,
uint256 interestAmount
)
{
return _closeStake(
msg.sender,
_stakingId
);
}
function_closeStake(address _staker,
uint256 _stakingId
)
internalreturns (uint256 stakedAmount,
uint256 penaltyAmount,
uint256 interestAmount
)
{
StakeElement memory _stakeElement = stakeList[_stakingId];
uint256 daysDifference = getNow()
.sub(_stakeElement.stakedAt)
.div(SECONDS_IN_DAY);
require(
daysDifference >=3,
'FEYToken: immature stake'
);
require(
_stakeElement.userAddress == _staker,
'FEYToken: wrong stake owner'
);
require(
_stakeElement.isActive,
'FEYToken: stake not active'
);
_stakeElement.isActive =false;
stakedAmount = _stakeElement.stakedAmount;
if (daysDifference >=45) {
interestAmount = getStakeInterest(
_stakingId
);
}
penaltyAmount = getStakePenalty(
_stakingId
);
totalSupply = totalSupply
.add(interestAmount)
.sub(penaltyAmount);
_stakeElement.interestAmount = interestAmount;
_stakeElement.returnAmount = stakedAmount
.add(interestAmount)
.sub(penaltyAmount);
stakeList[_stakingId] = _stakeElement;
balances[_staker] =
balances[_staker].add(_stakeElement.returnAmount);
globals.totalStakedAmount =
globals.totalStakedAmount.sub(stakedAmount);
emit StakeEnd(
_stakingId,
_staker,
_stakeElement.returnAmount
);
emit Transfer(
address(0x0),
_staker,
_stakeElement.returnAmount
);
}
/**
* @notice open a stake:
* 1) must be greater than MINIMUM_STAKE in Declarations
* 2) address opening the stake must have the amount of funds that they wish to stake in their balances[0xaddress]
* 3) increment the global incrementId that is used to set the stakingId
* 3) take snapshot of current FEY status before the stake is opened
* Updates global variables to reflect the new stake
* @param _amount the amount that you want to stake, will become your principal amount
* @return true if no revert or error occurs
*/functionopenStake(uint256 _amount
)
externalincrementIdsnapshotTriggerOnOpenreturns (bool)
{
require(
_transferCheck(
msg.sender,
address(0x0),
_amount,
true
),
'FEYToken: _transferCheck failed'
);
require(
_amount >= MINIMUM_STAKE,
'FEYToken: stake below minimum'
);
balances[msg.sender] =
balances[msg.sender].sub(_amount);
stakeList[globals.stakingId] = StakeElement(
msg.sender,
_amount,
0,
0,
getNow(),
true
);
globals.totalStakedAmount =
globals.totalStakedAmount.add(_amount);
emit StakeStart(
globals.stakingId,
msg.sender,
_amount
);
emit Transfer(
msg.sender,
address(0),
_amount
);
returntrue;
}
/**
* @notice getter for the data of a specific stake
* @param _stakingId ID of the stake, used as the Key from the stakeList mapping
* @return _stakedAmount -- represents the total calculated by: principal + interest - penalty
* @return _userAddress -- address that was used to open the stake
* @return _returnAmount -- principal + interest - penalty
* @return interestAmount -- amount of interest accrued after closing the stake
* @return _stakedAt -- timestamp of when stake was opened
* @return _isActive -- boolean for if the stake is open and accruing interest
*/functiongetStaking(uint256 _stakingId
)
externalviewreturns (uint256 _stakedAmount,
address _userAddress,
uint256 _returnAmount,
uint256 interestAmount,
uint256 _stakedAt,
bool _isActive
)
{
StakeElement memory _stakeElement = stakeList[_stakingId];
return (
_stakeElement.stakedAmount,
_stakeElement.userAddress,
_stakeElement.returnAmount,
_stakeElement.interestAmount,
_stakeElement.stakedAt,
_stakeElement.isActive
);
}
}
Contract Source Code
File 4 of 9: Helper.sol
// SPDX-License-Identifier: -- 💰 --pragmasolidity ^0.7.3;import'./Timing.sol';
import'./Ownable.sol';
import'./Events.sol';
import'./SafeMath.sol';
contractHelperisOwnable, Timing, Events{
usingSafeMathforuint256;
/**
* @notice burns set amount of tokens
* @dev currently unused based on changing requirements
* @param _amount -- amount to be burned
* @return true if burn() succeeds
*/functionburn(uint256 _amount
)
externalonlyOwnerreturns (bool)
{
require(
balances[msg.sender].sub(_amount) >=0,
'FEYToken: exceeding balance'
);
totalSupply =
totalSupply.sub(_amount);
balances[msg.sender] =
balances[msg.sender].sub(_amount);
emit Transfer(
msg.sender,
address(0x0),
_amount
);
returntrue;
}
/**
* @notice Groups common requirements in global, internal function
* @dev Used by Transfer(), TransferFrom(), OpenStake()
* @param _sender -- msg.sender of the functions listed above
* @param _recipient -- recipient of amount
* @param _amount -- amount that is transferred
* @param _allowBurnAddress -- boolean to allow burning tokens
* @return balance[] value of the input address
*/function_transferCheck(address _sender,
address _recipient,
uint256 _amount,
bool _allowBurnAddress
)
internalviewreturns (bool)
{
if (_allowBurnAddress ==false) {
require(
_recipient !=address(0x0),
'FEYToken: cannot send to burn address'
);
}
require(
balances[_sender] >= _amount,
'FEYToken: exceeding balance'
);
require(
balances[_recipient].add(_amount) >= balances[_recipient],
'FEYToken: overflow detected'
);
returntrue;
}
/**
* @notice Used to calculate % that is staked out of the totalSupply
* @dev Used by getYearlyInterestLatest(), getYearlyInterestHistorical(), + twice in getInterest()
* @param _numerator -- numerator, typically globals.totalStakedAmount
* @param _denominator -- denominator, typically totalSupply
* @param _precision -- number of decimal points, fixed at 4
* @return quotient -- calculated value
*/functiongetPercent(uint256 _numerator,
uint256 _denominator,
uint256 _precision
)
publicpurereturns(uint256 quotient)
{
uint256 numerator = _numerator *10** (_precision +1);
quotient = ((numerator / _denominator) +5) /10;
}
/**
* @notice Used to reduce value by a set percentage amount
* @dev Used to calculate penaltyAmount
* @param _value -- initial value, typically _stakeElement.stakedAmount
* @param _perc -- percentage reduction that will be applied
* @return percentageValue -- value reduced by the input percentage
*/functionpercentCalculator(uint256 _value,
uint256 _perc
)
publicpurereturns (uint256 percentageValue)
{
percentageValue = _value
.mul(_perc)
.div(10000);
}
}
Contract Source Code
File 5 of 9: Ownable.sol
// SPDX-License-Identifier: -- 💰 --pragmasolidity ^0.7.3;contractOwnable{
addresspublic owner;
eventownershipChanged(addressindexed _invoker,
addressindexed _newOwner
);
constructor() {
owner =msg.sender;
}
modifieronlyOwner() {
require(
msg.sender== owner,
'Ownable: must be the owner'
);
_;
}
functionchangeOwner(address _newOwner
)
externalonlyOwnerreturns (bool)
{
require(
_newOwner !=address(0),
'Ownable: new owner must not be the blackhole address'
);
owner = _newOwner;
emit ownershipChanged(
msg.sender,
_newOwner
);
returntrue;
}
}
Contract Source Code
File 6 of 9: SafeMath.sol
// SPDX-License-Identifier: -- 🎲 --pragmasolidity ^0.7.0;librarySafeMath{
functionadd(uint256 a, uint256 b) internalpurereturns (uint256) {
uint256 c = a + b;
require(c >= a, 'SafeMath: addition overflow');
return c;
}
functionsub(uint256 a, uint256 b) internalpurereturns (uint256) {
require(b <= a, 'SafeMath: subtraction overflow');
uint256 c = a - b;
return c;
}
functionmul(uint256 a, uint256 b) internalpurereturns (uint256) {
if (a ==0) {
return0;
}
uint256 c = a * b;
require(c / a == b, 'SafeMath: multiplication overflow');
return c;
}
functiondiv(uint256 a, uint256 b) internalpurereturns (uint256) {
require(b >0, 'SafeMath: division by zero');
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't holdreturn c;
}
functionmod(uint256 a, uint256 b) internalpurereturns (uint256) {
require(b !=0, 'SafeMath: modulo by zero');
return a % b;
}
}
Contract Source Code
File 7 of 9: Snapshot.sol
// SPDX-License-Identifier: -- 💰 --pragmasolidity ^0.7.3;import"./Helper.sol";
abstractcontractSnapshotisHelper{
usingSafeMathforuint;
/**
* @notice modifier to capture snapshots when a stake is opened
* @dev used in OpenStake() in FeyToken
*/modifiersnapshotTriggerOnOpen()
{
_;
_dailySnapshotPoint(
_currentFeyDay()
);
}
/**
* @notice modifier to capture snapshots when a stake is closed
* @dev used in CloseStake() in FeyToken
*/modifiersnapshotTriggerOnClose()
{
_dailySnapshotPoint(
_currentFeyDay()
);
_;
}
/**
* @notice Manually capture snapshot
*/functionmanualDailySnapshot()
external{
_dailySnapshotPoint(
_currentFeyDay()
);
}
/**
* @notice takes in todays feyday + updates all missing snapshot days with todays data
* @param _updateDay -- current FeyDay as outputted from timing's _currentFeyDay() function
* Emits SnapshotCaptured event
*/function_dailySnapshotPoint(uint64 _updateDay
)
private{
for (uint256 _day = globals.currentFeyDay; _day < _updateDay; _day++) {
SnapShot memory s = snapshots[_day];
s.totalSupply = totalSupply;
s.totalStakedAmount = globals.totalStakedAmount;
snapshots[_day] = s;
globals.currentFeyDay++;
}
emit SnapshotCaptured(
totalSupply,
globals.totalStakedAmount,
_updateDay
);
}
}
Contract Source Code
File 8 of 9: Timing.sol
// SPDX-License-Identifier: -- 💰 --pragmasolidity ^0.7.3;import'./Declaration.sol';
abstractcontractTimingisDeclaration{
/**
* @notice external view function to get current FeyDay, unless called at LAUNCH_TIME, in which case it will return 0 to save gas
* @dev called by _currentFeyDay
* @return current FeyDay
*/functioncurrentFeyDay()
publicviewreturns (uint64)
{
return getNow() >= LAUNCH_TIME
? _currentFeyDay()
: 0;
}
/**
* @notice internal view function to calculate current FeyDay by using _feyDayFromStamp()
* @dev called by snapshotTrigger(), manualDailySnapshot(), + getStakeInterest()
* @return current FeyDay
*/function_currentFeyDay()
internalviewreturns (uint64)
{
return _feyDayFromStamp(getNow());
}
/**
* @notice calculates difference between passed timestamp + original LAUNCH_TIME, set when contract was deployed
* @dev called by _currentFeyDay
* @param _timestamp -- timestamp to use for difference
* @return number of days between timestamp param + LAUNCH_TIME
*/function_feyDayFromStamp(uint256 _timestamp
)
internalviewreturns (uint64)
{
returnuint64((_timestamp - LAUNCH_TIME) / SECONDS_IN_DAY);
}
/**
* @dev called by getStakeAge(), getStakePenalty, closeStake(), openStake(), + _currentFeyDay
* @return current block.timestamp
*/functiongetNow()
publicviewreturns (uint256)
{
returnblock.timestamp;
}
}
Contract Source Code
File 9 of 9: Token.sol
// SPDX-License-Identifier: -- 💰 --pragmasolidity ^0.7.3;import"./Snapshot.sol";
contractTokenisSnapshot{
usingSafeMathforuint256;
/**
* @notice Moves amount tokens from the caller’s account to recipient.
* Returns a boolean value indicating whether the operation succeeded.
* @dev See {IERC20-transfer}.
* Emits an {Transfer} event indicating a successful transfer.
* @param _receiver -- recipient of amount
* @param _amount -- amount that is transferred
* @return true if transfer() succeeds
*/functiontransfer(address _receiver,
uint256 _amount
)
externalreturns (bool)
{
require(
_transferCheck(
msg.sender,
_receiver,
_amount,
false
),
'Token: _transferCheck failed'
);
balances[msg.sender] =
balances[msg.sender].sub(_amount);
balances[_receiver] =
balances[_receiver].add(_amount);
emit Transfer(
msg.sender,
_receiver,
_amount
);
returntrue;
}
/**
* @notice 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.
* @dev See {IERC20-transferFrom}.
* Emits an {Transfer} event indicating a successful transfer.
* @param _owner -- address who is sending the transfer amount
* @param _receiver -- recipient of amount
* @param _amount -- amount that is transferred
* @return true if transferFrom() succeeds
*/functiontransferFrom(address _owner,
address _receiver,
uint256 _amount
)
externalreturns (bool)
{
require(
_transferCheck(
_owner,
_receiver,
_amount,
false
),
'Token: _transferCheck failed'
);
require(
allowances[_owner][msg.sender] >= _amount,
'Token: exceeding allowance'
);
allowances[_owner][msg.sender] =
allowances[_owner][msg.sender].sub(_amount);
balances[_owner] =
balances[_owner].sub(_amount);
balances[_receiver] =
balances[_receiver].add(_amount);
emit Transfer(
_owner,
_receiver,
_amount
);
returntrue;
}
/**
* @notice Sets amount as the allowance of spender over the caller’s tokens.
* @dev See {IERC20-approve}.
* Emits an {Approval} event indicating how much was approved and whom is the spender
* @param _spender -- approved address
* @param _amount -- amount that they are approved to spend
* @return true if Approve() succeeds
*/functionapprove(address _spender,
uint256 _amount
)
externalreturns (bool)
{
allowances[msg.sender][_spender] = _amount;
emit Approval(
msg.sender,
_spender,
_amount
);
returntrue;
}
/**
* @notice Returns the amount of tokens owned by account.
* @dev See {IERC20-approve}.
* @param _address -- address whose balance will be returned
* @return balance[] value of the input address
*/functionbalanceOf(address _address
)
externalviewreturns (uint256)
{
return balances[_address];
}
/**
* @notice 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.
* @dev See {IERC20-allowance}.
* @param _owner -- owner address
* @param _spender -- address that is approved to spend tokens
* @return allowances[] value of the input addresses to reflect the value mapped to the _spender's address
*/functionallowance(address _owner,
address _spender
)
externalviewreturns (uint256)
{
return allowances[_owner][_spender];
}
}