Commit ce39a68a authored by XFT-dev's avatar XFT-dev
Browse files

initializing repo

parent 8605a110
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
contract Migrations {
address public owner = msg.sender;
uint256 public last_completed_migration;
modifier restricted() {
require(
msg.sender == owner,
"This function is restricted to the contract's owner"
);
_;
}
function setCompleted(uint256 completed) public restricted {
last_completed_migration = completed;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.7.6;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./interfaces/IStakingRewards.sol";
import "./interfaces/IMasterChef.sol";
contract StakingRewards is IStakingRewards, Ownable, Pausable, ReentrancyGuard {
using SafeMath for uint256;
using SafeERC20 for IERC20;
uint256 public immutable PID;
IERC20 public rewardsTokenXFT;
IERC20 public rewardsTokenSUSHI;
IERC20 public stakingToken;
IMasterChef public masterChef;
PoolInfo public poolInfo;
uint256 public periodFinish;
uint256 public rewardRate;
uint256 public rewardsDuration;
uint256 public lastUpdateTime;
uint256 public rewardPerTokenStored;
mapping(address => UserInfo) public userInfo;
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewardsXFT;
mapping(address => uint256) public rewardsSUSHI;
uint256 private _totalStaked;
modifier updateReward(address account) {
rewardPerTokenStored = rewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
if (account != address(0)) {
rewardsXFT[account] = earnedXFT(account);
userRewardPerTokenPaid[account] = rewardPerTokenStored;
updatePoolInfo();
if (userInfo[account].amount > 0) {
rewardsSUSHI[account] = earnedSushi(account);
}
}
_;
}
constructor(
address _rewardsTokenXFT,
address _rewardsTokenSUSHI,
address _stakingToken,
address _masterChef,
uint256 _pid
) Ownable() {
rewardsTokenXFT = IERC20(_rewardsTokenXFT);
rewardsTokenSUSHI = IERC20(_rewardsTokenSUSHI);
stakingToken = IERC20(_stakingToken);
masterChef = IMasterChef(_masterChef);
PID = _pid;
periodFinish = 0;
rewardRate = 0;
rewardsDuration = 30 days;
}
function totalStaked() external view override returns (uint256) {
return _totalStaked;
}
function balanceOf(address _account)
external
view
override
returns (uint256)
{
return userInfo[_account].amount;
}
function getRewardForDuration() external view override returns (uint256) {
return rewardRate.mul(rewardsDuration);
}
function exit() external override {
withdraw(userInfo[msg.sender].amount);
getReward();
}
function notifyRewardAmount(uint256 _reward)
external
onlyOwner
updateReward(address(0))
{
if (block.timestamp >= periodFinish) {
rewardRate = _reward.div(rewardsDuration);
} else {
uint256 remaining = periodFinish.sub(block.timestamp);
uint256 leftover = remaining.mul(rewardRate);
rewardRate = _reward.add(leftover).div(rewardsDuration);
}
uint256 balance = rewardsTokenXFT.balanceOf(address(this));
require(
rewardRate <= balance.div(rewardsDuration),
"Provided reward too high"
);
lastUpdateTime = block.timestamp;
periodFinish = block.timestamp.add(rewardsDuration);
emit RewardAdded(_reward);
}
function updatePeriodFinish(uint256 _timestamp)
external
onlyOwner
updateReward(address(0))
{
periodFinish = _timestamp;
}
function stake(uint256 _amount)
external
override
nonReentrant
whenNotPaused
updateReward(msg.sender)
{
require(_amount > 0, "Stake: cant stake 0");
_totalStaked = _totalStaked.add(_amount);
stakingToken.safeTransferFrom(msg.sender, address(this), _amount);
stakingToken.approve(address(masterChef), _amount);
masterChef.deposit(PID, _amount);
UserInfo storage user = userInfo[msg.sender];
user.amount = user.amount.add(_amount);
user.rewardDebt = user.amount
.mul(poolInfo.accSushiPerShare)
.div(1e12);
emit Staked(msg.sender, _amount);
}
function withdraw(uint256 _amount)
public
override
nonReentrant
updateReward(msg.sender)
{
require(_amount > 0, "Withdraw: cant withdraw 0");
UserInfo storage user = userInfo[msg.sender];
require(user.amount >= _amount, "Withdraw: insufficient funds");
_totalStaked = _totalStaked.sub(_amount);
masterChef.withdraw(PID, _amount);
user.amount = user.amount.sub(_amount);
user.rewardDebt = user.amount
.mul(poolInfo.accSushiPerShare)
.div(1e12);
stakingToken.safeTransfer(msg.sender, _amount);
emit Withdrawn(msg.sender, _amount);
}
function getReward() public override nonReentrant updateReward(msg.sender) {
uint256 rewardXFT = rewardsXFT[msg.sender];
uint256 rewardSUSHI = rewardsSUSHI[msg.sender];
if (rewardXFT > 0) {
rewardsXFT[msg.sender] = 0;
rewardsTokenXFT.safeTransfer(msg.sender, rewardXFT);
emit XFTRewardPaid(msg.sender, rewardXFT);
}
if (rewardSUSHI > 0) {
masterChef.deposit(PID, 0); // to get pending Sushi from onsen
rewardsSUSHI[msg.sender] = 0;
userInfo[msg.sender].rewardDebt = userInfo[msg.sender]
.amount
.mul(poolInfo.accSushiPerShare)
.div(1e12);
safeSushiTransfer(msg.sender, rewardSUSHI);
emit SUSHIRewardPaid(msg.sender, rewardSUSHI);
}
}
function lastTimeRewardApplicable() public view override returns (uint256) {
return Math.min(block.timestamp, periodFinish);
}
function rewardPerToken() public view override returns (uint256) {
if (_totalStaked == 0) {
return rewardPerTokenStored;
}
return
rewardPerTokenStored.add(
lastTimeRewardApplicable()
.sub(lastUpdateTime)
.mul(rewardRate)
.mul(1e18)
.div(_totalStaked)
);
}
function earnedXFT(address _account)
public
view
override
returns (uint256)
{
return
userInfo[_account]
.amount
.mul(rewardPerToken().sub(userRewardPerTokenPaid[_account]))
.div(1e18)
.add(rewardsXFT[_account]);
}
function earnedSushi(address _account)
public
view
override
returns (uint256)
{
(
address _lpToken,
uint256 _allocPoint,
uint256 _lastRewardBlock,
uint256 _accSushiPerShare
) = masterChef.poolInfo(PID);
UserInfo storage user = userInfo[_account];
if (user.amount == 0) return rewardsSUSHI[_account];
uint256 lpSupply = IERC20(_lpToken).balanceOf(address(masterChef));
if (block.number > _lastRewardBlock && lpSupply != 0) {
uint256 multiplier =
masterChef.getMultiplier(_lastRewardBlock, block.number);
uint256 sushiReward =
multiplier.mul(masterChef.sushiPerBlock()).mul(_allocPoint).div(
masterChef.totalAllocPoint()
);
_accSushiPerShare = _accSushiPerShare.add(
sushiReward.mul(1e12).div(lpSupply)
);
}
return
rewardsSUSHI[_account].add(
user.amount.mul(_accSushiPerShare).div(1e12).sub(
user.rewardDebt
)
);
}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
function safeSushiTransfer(address _to, uint256 _amount) internal {
uint256 sushiBal = rewardsTokenSUSHI.balanceOf(address(this));
if (_amount > sushiBal) {
rewardsTokenSUSHI.safeTransfer(_to, sushiBal);
} else {
rewardsTokenSUSHI.safeTransfer(_to, _amount);
}
}
function updatePoolInfo() internal {
masterChef.updatePool(PID);
(
address _lpToken,
uint256 _allocPoint,
uint256 _lastRewardBlock,
uint256 _accSushiPerShare
) = masterChef.poolInfo(PID);
poolInfo = PoolInfo(
IERC20(_lpToken),
_allocPoint,
_lastRewardBlock,
_accSushiPerShare
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.4;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
interface IMasterChef {
function poolInfo(uint256)
external
view
returns (
address lpToken,
uint256 allocPoint,
uint256 lastRewardBlock,
uint256 accSushiPerShare
);
function userInfo(uint256, address)
external
view
returns (uint256 amount, uint256 rewardDebt);
function deposit(uint256 _pid, uint256 _amount) external;
function withdraw(uint256 _pid, uint256 _amount) external;
function updatePool(uint256 _pid) external;
function getMultiplier(uint256 _from, uint256 _to)
external
view
returns (uint256);
function sushiPerBlock() external view returns (uint256);
function totalAllocPoint() external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.4;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IStakingRewards {
struct UserInfo {
uint256 amount; // How many LP tokens the user has provided.
uint256 rewardDebt; // Reward debt. See explanation below.
}
// Info of each pool.
struct PoolInfo {
IERC20 lpToken; // Address of LP token contract.
uint256 allocPoint; // How many allocation points assigned to this pool. SUSHIs to distribute per block.
uint256 lastRewardBlock; // Last block number that SUSHIs distribution occurs.
uint256 accSushiPerShare; // Accumulated SUSHIs per share, times 1e12. See below.
}
event RewardAdded(uint256 reward);
event Staked(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event XFTRewardPaid(address indexed user, uint256 reward);
event SUSHIRewardPaid(address indexed user, uint256 reward);
function totalStaked() external view returns (uint256);
function balanceOf(address _account) external view returns (uint256);
function getRewardForDuration() external view returns (uint256);
function lastTimeRewardApplicable() external view returns (uint256);
function rewardPerToken() external view returns (uint256);
function earnedXFT(address _account) external view returns (uint256);
function earnedSushi(address _account) external view returns (uint256);
function stake(uint256 _amount) external;
function withdraw(uint256 _amount) external;
function getReward() external;
function exit() external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.4;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract TokenMock is ERC20 {
constructor(
string memory name,
string memory symbol,
uint256 _amount
) ERC20(name, symbol) {
_mint(msg.sender, _amount);
}
function mint(address _to, uint256 _amount) public {
_mint(_to, _amount);
}
}
/**
*Submitted for verification at Etherscan.io on 2020-09-05
*/
// File: contracts/uniswapv2/interfaces/IUniswapV2Factory.sol
pragma solidity >=0.5.0;
interface IUniswapV2Factory {
event PairCreated(
address indexed token0,
address indexed token1,
address pair,
uint256
);
function feeTo() external view returns (address);
function feeToSetter() external view returns (address);
function migrator() external view returns (address);
function getPair(address tokenA, address tokenB)
external
view
returns (address pair);
function allPairs(uint256) external view returns (address pair);
function allPairsLength() external view returns (uint256);
function createPair(address tokenA, address tokenB)
external
returns (address pair);
function setFeeTo(address) external;
function setFeeToSetter(address) external;
function setMigrator(address) external;
}
// File: contracts/uniswapv2/libraries/SafeMath.sol
pragma solidity >=0.6.12;
// a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math)
library SafeMathUniswap {
function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
require((z = x + y) >= x, "ds-math-add-overflow");
}
function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
require((z = x - y) <= x, "ds-math-sub-underflow");
}
function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow");
}
}
// File: contracts/uniswapv2/UniswapV2ERC20.sol
pragma solidity >=0.6.12;
contract UniswapV2ERC20 {
using SafeMathUniswap for uint256;
string public constant name = "SushiSwap LP Token";
string public constant symbol = "SLP";
uint8 public constant decimals = 18;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
bytes32 public DOMAIN_SEPARATOR;
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant PERMIT_TYPEHASH =
0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
mapping(address => uint256) public nonces;
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
event Transfer(address indexed from, address indexed to, uint256 value);
constructor() public {
uint256 chainId;
assembly {
chainId := chainid()
}
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
),
keccak256(bytes(name)),
keccak256(bytes("1")),
chainId,
address(this)
)
);
}
function _mint(address to, uint256 value) internal {
totalSupply = totalSupply.add(value);
balanceOf[to] = balanceOf[to].add(value);
emit Transfer(address(0), to, value);
}
function _burn(address from, uint256 value) internal {
balanceOf[from] = balanceOf[from].sub(value);
totalSupply = totalSupply.sub(value);
emit Transfer(from, address(0), value);
}
function _approve(
address owner,
address spender,
uint256 value
) private {
allowance[owner][spender] = value;
emit Approval(owner, spender, value);
}
function _transfer(
address from,
address to,
uint256 value
) private {
balanceOf[from] = balanceOf[from].sub(value);
balanceOf[to] = balanceOf[to].add(value);
emit Transfer(from, to, value);
}
function approve(address spender, uint256 value) external returns (bool) {
_approve(msg.sender, spender, value);
return true;
}
function transfer(address to, uint256 value) external returns (bool) {
_transfer(msg.sender, to, value);
return true;
}
function transferFrom(
address from,
address to,
uint256 value
) external returns (bool) {
if (allowance[from][msg.sender] != uint256(-1)) {
allowance[from][msg.sender] = allowance[from][msg.sender].sub(
value
);
}
_transfer(from, to, value);
return true;
}
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
require(deadline >= block.timestamp, "UniswapV2: EXPIRED");
bytes32 digest =
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
keccak256(
abi.encode(
PERMIT_TYPEHASH,
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
require(
recoveredAddress != address(0) && recoveredAddress == owner,
"UniswapV2: INVALID_SIGNATURE"
);
_approve(owner, spender, value);
}
}
// File: contracts/uniswapv2/libraries/Math.sol
pragma solidity >=0.6.12;
// a library for performing various math operations
library MathS {
function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = x < y ? x : y;
}
// babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
function sqrt(uint256 y) internal pure returns (uint256 z) {
if (y > 3) {
z = y;
uint256 x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
}
}
// File: contracts/uniswapv2/libraries/UQ112x112.sol
pragma solidity >=0.6.12;
// a library for handling binary fixed point numbers (https://en.wikipedia.org/wiki/Q_(number_format))
// range: [0, 2**112 - 1]
// resolution: 1 / 2**112
library UQ112x112 {
uint224 constant Q112 = 2**112;
// encode a uint112 as a UQ112x112
function encode(uint112 y) internal pure returns (uint224 z) {
z = uint224(y) * Q112; // never overflows
}
// divide a UQ112x112 by a uint112, returning a UQ112x112
function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {
z = x / uint224(y);
}
}
// File: contracts/uniswapv2/interfaces/IERC20.sol
pragma solidity >=0.5.0;
interface IERC20Uniswap {
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
event Transfer(address indexed from, address indexed to, uint256 value);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint256);
function balanceOf(address owner) external view returns (uint256);
function allowance(address owner, address spender)
external
view
returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transfer(address to, uint256 value) external returns (bool);
function transferFrom(
address from,
address to,
uint256 value
) external returns (bool);
}
// File: contracts/uniswapv2/interfaces/IUniswapV2Callee.sol
pragma solidity >=0.5.0;
interface IUniswapV2Callee {
function uniswapV2Call(
address sender,
uint256 amount0,
uint256 amount1,
bytes calldata data
) external;
}
// File: contracts/uniswapv2/UniswapV2Pair.sol
pragma solidity >=0.6.12;
interface IMigrator {
// Return the desired amount of liquidity token that the migrator wants.
function desiredLiquidity() external view returns (uint256);
}
contract UniswapV2Pair is UniswapV2ERC20 {
using SafeMathUniswap for uint256;
using UQ112x112 for uint224;
uint256 public constant MINIMUM_LIQUIDITY = 10**3;
bytes4 private constant SELECTOR =
bytes4(keccak256(bytes("transfer(address,uint256)")));
address public factory;
address public token0;
address public token1;
uint112 private reserve0; // uses single storage slot, accessible via getReserves
uint112 private reserve1; // uses single storage slot, accessible via getReserves
uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves
uint256 public price0CumulativeLast;
uint256 public price1CumulativeLast;
uint256 public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event
uint256 private unlocked = 1;
modifier lock() {
require(unlocked == 1, "UniswapV2: LOCKED");
unlocked = 0;
_;
unlocked = 1;
}
function getReserves()
public
view
returns (
uint112 _reserve0,
uint112 _reserve1,
uint32 _blockTimestampLast
)
{
_reserve0 = reserve0;
_reserve1 = reserve1;
_blockTimestampLast = blockTimestampLast;
}
function _safeTransfer(
address token,
address to,
uint256 value
) private {
(bool success, bytes memory data) =
token.call(abi.encodeWithSelector(SELECTOR, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
"UniswapV2: TRANSFER_FAILED"
);
}
event Mint(address indexed sender, uint256 amount0, uint256 amount1);
event Burn(
address indexed sender,
uint256 amount0,
uint256 amount1,
address indexed to
);
event Swap(
address indexed sender,
uint256 amount0In,
uint256 amount1In,
uint256 amount0Out,
uint256 amount1Out,
address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);
constructor() public {
factory = msg.sender;
}
// called once by the factory at time of deployment
function initialize(address _token0, address _token1) external {
require(msg.sender == factory, "UniswapV2: FORBIDDEN"); // sufficient check
token0 = _token0;
token1 = _token1;
}
// update reserves and, on the first call per block, price accumulators
function _update(
uint256 balance0,
uint256 balance1,
uint112 _reserve0,
uint112 _reserve1
) private {
require(
balance0 <= uint112(-1) && balance1 <= uint112(-1),
"UniswapV2: OVERFLOW"
);
uint32 blockTimestamp = uint32(block.timestamp % 2**32);
uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired
if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
// * never overflows, and + overflow is desired
price0CumulativeLast +=
uint256(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) *
timeElapsed;
price1CumulativeLast +=
uint256(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) *
timeElapsed;
}
reserve0 = uint112(balance0);
reserve1 = uint112(balance1);
blockTimestampLast = blockTimestamp;
emit Sync(reserve0, reserve1);
}
// if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k)
function _mintFee(uint112 _reserve0, uint112 _reserve1)
private
returns (bool feeOn)
{
address feeTo = IUniswapV2Factory(factory).feeTo();
feeOn = feeTo != address(0);
uint256 _kLast = kLast; // gas savings
if (feeOn) {
if (_kLast != 0) {
uint256 rootK = MathS.sqrt(uint256(_reserve0).mul(_reserve1));
uint256 rootKLast = MathS.sqrt(_kLast);
if (rootK > rootKLast) {
uint256 numerator = totalSupply.mul(rootK.sub(rootKLast));
uint256 denominator = rootK.mul(5).add(rootKLast);
uint256 liquidity = numerator / denominator;
if (liquidity > 0) _mint(feeTo, liquidity);
}
}
} else if (_kLast != 0) {
kLast = 0;
}
}
// this low-level function should be called from a contract which performs important safety checks
function mint(address to) external lock returns (uint256 liquidity) {
(uint112 _reserve0, uint112 _reserve1, ) = getReserves(); // gas savings
uint256 balance0 = IERC20Uniswap(token0).balanceOf(address(this));
uint256 balance1 = IERC20Uniswap(token1).balanceOf(address(this));
uint256 amount0 = balance0.sub(_reserve0);
uint256 amount1 = balance1.sub(_reserve1);
bool feeOn = _mintFee(_reserve0, _reserve1);
uint256 _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
if (_totalSupply == 0) {
address migrator = IUniswapV2Factory(factory).migrator();
if (msg.sender == migrator) {
liquidity = IMigrator(migrator).desiredLiquidity();
require(
liquidity > 0 && liquidity != uint256(-1),
"Bad desired liquidity"
);
} else {
require(migrator == address(0), "Must not have migrator");
liquidity = MathS.sqrt(amount0.mul(amount1)).sub(
MINIMUM_LIQUIDITY
);
_mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
}
} else {
liquidity = MathS.min(
amount0.mul(_totalSupply) / _reserve0,
amount1.mul(_totalSupply) / _reserve1
);
}
require(liquidity > 0, "UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED");
_mint(to, liquidity);
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint256(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
emit Mint(msg.sender, amount0, amount1);
}
// this low-level function should be called from a contract which performs important safety checks
function burn(address to)
external
lock
returns (uint256 amount0, uint256 amount1)
{
(uint112 _reserve0, uint112 _reserve1, ) = getReserves(); // gas savings
address _token0 = token0; // gas savings
address _token1 = token1; // gas savings
uint256 balance0 = IERC20Uniswap(_token0).balanceOf(address(this));
uint256 balance1 = IERC20Uniswap(_token1).balanceOf(address(this));
uint256 liquidity = balanceOf[address(this)];
bool feeOn = _mintFee(_reserve0, _reserve1);
uint256 _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
require(
amount0 > 0 && amount1 > 0,
"UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED"
);
_burn(address(this), liquidity);
_safeTransfer(_token0, to, amount0);
_safeTransfer(_token1, to, amount1);
balance0 = IERC20Uniswap(_token0).balanceOf(address(this));
balance1 = IERC20Uniswap(_token1).balanceOf(address(this));
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint256(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
emit Burn(msg.sender, amount0, amount1, to);
}
// this low-level function should be called from a contract which performs important safety checks
function swap(
uint256 amount0Out,
uint256 amount1Out,
address to,
bytes calldata data
) external lock {
require(
amount0Out > 0 || amount1Out > 0,
"UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT"
);
(uint112 _reserve0, uint112 _reserve1, ) = getReserves(); // gas savings
require(
amount0Out < _reserve0 && amount1Out < _reserve1,
"UniswapV2: INSUFFICIENT_LIQUIDITY"
);
uint256 balance0;
uint256 balance1;
{
// scope for _token{0,1}, avoids stack too deep errors
address _token0 = token0;
address _token1 = token1;
require(to != _token0 && to != _token1, "UniswapV2: INVALID_TO");
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
if (data.length > 0)
IUniswapV2Callee(to).uniswapV2Call(
msg.sender,
amount0Out,
amount1Out,
data
);
balance0 = IERC20Uniswap(_token0).balanceOf(address(this));
balance1 = IERC20Uniswap(_token1).balanceOf(address(this));
}
uint256 amount0In =
balance0 > _reserve0 - amount0Out
? balance0 - (_reserve0 - amount0Out)
: 0;
uint256 amount1In =
balance1 > _reserve1 - amount1Out
? balance1 - (_reserve1 - amount1Out)
: 0;
require(
amount0In > 0 || amount1In > 0,
"UniswapV2: INSUFFICIENT_INPUT_AMOUNT"
);
{
// scope for reserve{0,1}Adjusted, avoids stack too deep errors
uint256 balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint256 balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
require(
balance0Adjusted.mul(balance1Adjusted) >=
uint256(_reserve0).mul(_reserve1).mul(1000**2),
"UniswapV2: K"
);
}
_update(balance0, balance1, _reserve0, _reserve1);
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
// force balances to match reserves
function skim(address to) external lock {
address _token0 = token0; // gas savings
address _token1 = token1; // gas savings
_safeTransfer(
_token0,
to,
IERC20Uniswap(_token0).balanceOf(address(this)).sub(reserve0)
);
_safeTransfer(
_token1,
to,
IERC20Uniswap(_token1).balanceOf(address(this)).sub(reserve1)
);
}
// force reserves to match balances
function sync() external lock {
_update(
IERC20Uniswap(token0).balanceOf(address(this)),
IERC20Uniswap(token1).balanceOf(address(this)),
reserve0,
reserve1
);
}
}
// /**
// *Submitted for verification at Etherscan.io on 2020-07-16
// */
// pragma solidity >=0.4.23;
// /**
// * @title ERC20Basic
// * @dev Simpler version of ERC20 interface
// * @dev see https://github.com/ethereum/EIPs/issues/179
// */
// contract ERC20Basic {
// function totalSupply() public view returns (uint256);
// function balanceOf(address who) public view returns (uint256);
// function transfer(address to, uint256 value) public returns (bool);
// event Transfer(address indexed from, address indexed to, uint256 value);
// }
// /**
// * @title SafeMath
// * @dev Math operations with safety checks that throw on error
// */
// library SafeMath {
// /**
// * @dev Multiplies two numbers, throws on overflow.
// */
// function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {
// // Gas optimization: this is cheaper than asserting 'a' not being zero, but the
// // benefit is lost if 'b' is also tested.
// // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
// if (a == 0) {
// return 0;
// }
// c = a * b;
// assert(c / a == b);
// return c;
// }
// /**
// * @dev Integer division of two numbers, truncating the quotient.
// */
// function div(uint256 a, uint256 b) internal pure returns (uint256) {
// // assert(b > 0); // Solidity automatically throws when dividing by 0
// // uint256 c = a / b;
// // assert(a == b * c + a % b); // There is no case in which this doesn't hold
// return a / b;
// }
// /**
// * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
// */
// function sub(uint256 a, uint256 b) internal pure returns (uint256) {
// assert(b <= a);
// return a - b;
// }
// /**
// * @dev Adds two numbers, throws on overflow.
// */
// function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
// c = a + b;
// assert(c >= a);
// return c;
// }
// }
// /**
// * @title Basic token
// * @dev Basic version of StandardToken, with no allowances.
// */
// contract BasicToken is ERC20Basic {
// using SafeMath for uint256;
// mapping(address => uint256) balances;
// uint256 totalSupply_;
// /**
// * @dev total number of tokens in existence
// */
// function totalSupply() public view returns (uint256) {
// return totalSupply_;
// }
// /**
// * @dev transfer token for a specified address
// * @param _to The address to transfer to.
// * @param _value The amount to be transferred.
// */
// function transfer(address _to, uint256 _value) public returns (bool) {
// require(_to != address(0));
// require(_value <= balances[msg.sender]);
// balances[msg.sender] = balances[msg.sender].sub(_value);
// balances[_to] = balances[_to].add(_value);
// emit Transfer(msg.sender, _to, _value);
// return true;
// }
// /**
// * @dev Gets the balance of the specified address.
// * @param _owner The address to query the the balance of.
// * @return An uint256 representing the amount owned by the passed address.
// */
// function balanceOf(address _owner) public view returns (uint256) {
// return balances[_owner];
// }
// }
// /**
// * @title ERC20 interface
// * @dev see https://github.com/ethereum/EIPs/issues/20
// */
// contract ERC20 is ERC20Basic {
// function allowance(address owner, address spender)
// public view returns (uint256);
// function transferFrom(address from, address to, uint256 value)
// public returns (bool);
// function approve(address spender, uint256 value) public returns (bool);
// event Approval(
// address indexed owner,
// address indexed spender,
// uint256 value
// );
// }
// /**
// * @title Standard ERC20 token
// *
// * @dev Implementation of the basic standard token.
// * @dev https://github.com/ethereum/EIPs/issues/20
// * @dev Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
// */
// contract StandardToken is ERC20, BasicToken {
// mapping (address => mapping (address => uint256)) internal allowed;
// /**
// * @dev Transfer tokens from one address to another
// * @param _from address The address which you want to send tokens from
// * @param _to address The address which you want to transfer to
// * @param _value uint256 the amount of tokens to be transferred
// */
// function transferFrom(
// address _from,
// address _to,
// uint256 _value
// )
// public
// returns (bool)
// {
// require(_to != address(0));
// require(_value <= balances[_from]);
// require(_value <= allowed[_from][msg.sender]);
// balances[_from] = balances[_from].sub(_value);
// balances[_to] = balances[_to].add(_value);
// allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
// emit Transfer(_from, _to, _value);
// return true;
// }
// /**
// * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
// *
// * 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
// * @param _spender The address which will spend the funds.
// * @param _value The amount of tokens to be spent.
// */
// function approve(address _spender, uint256 _value) public returns (bool) {
// allowed[msg.sender][_spender] = _value;
// emit Approval(msg.sender, _spender, _value);
// return true;
// }
// /**
// * @dev Function to check the amount of tokens that an owner allowed to a spender.
// * @param _owner address The address which owns the funds.
// * @param _spender address The address which will spend the funds.
// * @return A uint256 specifying the amount of tokens still available for the spender.
// */
// function allowance(
// address _owner,
// address _spender
// )
// public
// view
// returns (uint256)
// {
// return allowed[_owner][_spender];
// }
// /**
// * @dev Increase the amount of tokens that an owner allowed to a spender.
// *
// * approve should be called when allowed[_spender] == 0. To increment
// * allowed value is better to use this function to avoid 2 calls (and wait until
// * the first transaction is mined)
// * From MonolithDAO Token.sol
// * @param _spender The address which will spend the funds.
// * @param _addedValue The amount of tokens to increase the allowance by.
// */
// function increaseApproval(
// address _spender,
// uint _addedValue
// )
// public
// returns (bool)
// {
// allowed[msg.sender][_spender] = (
// allowed[msg.sender][_spender].add(_addedValue));
// emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
// return true;
// }
// /**
// * @dev Decrease the amount of tokens that an owner allowed to a spender.
// *
// * approve should be called when allowed[_spender] == 0. To decrement
// * allowed value is better to use this function to avoid 2 calls (and wait until
// * the first transaction is mined)
// * From MonolithDAO Token.sol
// * @param _spender The address which will spend the funds.
// * @param _subtractedValue The amount of tokens to decrease the allowance by.
// */
// function decreaseApproval(
// address _spender,
// uint _subtractedValue
// )
// public
// returns (bool)
// {
// uint oldValue = allowed[msg.sender][_spender];
// if (_subtractedValue > oldValue) {
// allowed[msg.sender][_spender] = 0;
// } else {
// allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
// }
// emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
// return true;
// }
// }
// /**
// * @title Ownable
// * @dev The Ownable contract has an owner address, and provides basic authorization control
// * functions, this simplifies the implementation of "user permissions".
// */
// contract Ownable {
// address public owner;
// event OwnershipRenounced(address indexed previousOwner);
// event OwnershipTransferred(
// address indexed previousOwner,
// address indexed newOwner
// );
// /**
// * @dev The Ownable constructor sets the original `owner` of the contract to the sender
// * account.
// */
// constructor() public {
// owner = msg.sender;
// }
// /**
// * @dev Throws if called by any account other than the owner.
// */
// modifier onlyOwner() {
// require(msg.sender == owner);
// _;
// }
// /**
// * @dev Allows the current owner to relinquish control of the contract.
// */
// function renounceOwnership() public onlyOwner {
// emit OwnershipRenounced(owner);
// owner = address(0);
// }
// /**
// * @dev Allows the current owner to transfer control of the contract to a newOwner.
// * @param _newOwner The address to transfer ownership to.
// */
// function transferOwnership(address _newOwner) public onlyOwner {
// _transferOwnership(_newOwner);
// }
// /**
// * @dev Transfers control of the contract to a newOwner.
// * @param _newOwner The address to transfer ownership to.
// */
// function _transferOwnership(address _newOwner) internal {
// require(_newOwner != address(0));
// emit OwnershipTransferred(owner, _newOwner);
// owner = _newOwner;
// }
// }
// /**
// * @title Mintable token
// * @dev Simple ERC20 Token example, with mintable token creation
// * @dev Issue: * https://github.com/OpenZeppelin/openzeppelin-solidity/issues/120
// * Based on code by TokenMarketNet: https://github.com/TokenMarketNet/ico/blob/master/contracts/MintableToken.sol
// */
// contract MintableToken is StandardToken, Ownable {
// event Mint(address indexed to, uint256 amount);
// event MintFinished();
// bool public mintingFinished = false;
// modifier canMint() {
// require(!mintingFinished);
// _;
// }
// modifier hasMintPermission() {
// require(msg.sender == owner);
// _;
// }
// /**
// * @dev Function to mint tokens
// * @param _to The address that will receive the minted tokens.
// * @param _amount The amount of tokens to mint.
// * @return A boolean that indicates if the operation was successful.
// */
// function mint(
// address _to,
// uint256 _amount
// )
// hasMintPermission
// canMint
// public
// returns (bool)
// {
// totalSupply_ = totalSupply_.add(_amount);
// balances[_to] = balances[_to].add(_amount);
// emit Mint(_to, _amount);
// emit Transfer(address(0), _to, _amount);
// return true;
// }
// /**
// * @dev Function to stop minting new tokens.
// * @return True if the operation was successful.
// */
// function finishMinting() onlyOwner canMint public returns (bool) {
// mintingFinished = true;
// emit MintFinished();
// return true;
// }
// }
// contract FreezableToken is StandardToken {
// // freezing chains
// mapping (bytes32 => uint64) internal chains;
// // freezing amounts for each chain
// mapping (bytes32 => uint) internal freezings;
// // total freezing balance per address
// mapping (address => uint) internal freezingBalance;
// event Freezed(address indexed to, uint64 release, uint amount);
// event Released(address indexed owner, uint amount);
// /**
// * @dev Gets the balance of the specified address include freezing tokens.
// * @param _owner The address to query the the balance of.
// * @return An uint256 representing the amount owned by the passed address.
// */
// function balanceOf(address _owner) public view returns (uint256) {
// return super.balanceOf(_owner) + freezingBalance[_owner];
// }
// /**
// * @dev Gets the balance of the specified address without freezing tokens.
// * @param _owner The address to query the the balance of.
// * @return An uint256 representing the amount owned by the passed address.
// */
// function actualBalanceOf(address _owner) public view returns (uint256) {
// return super.balanceOf(_owner);
// }
// function freezingBalanceOf(address _owner) public view returns (uint256) {
// return freezingBalance[_owner];
// }
// /**
// * @dev gets freezing count
// * @param _addr Address of freeze tokens owner.
// */
// function freezingCount(address _addr) public view returns (uint count) {
// uint64 release = chains[toKey(_addr, 0)];
// while (release != 0) {
// count++;
// release = chains[toKey(_addr, release)];
// }
// }
// /**
// * @dev gets freezing end date and freezing balance for the freezing portion specified by index.
// * @param _addr Address of freeze tokens owner.
// * @param _index Freezing portion index. It ordered by release date descending.
// */
// function getFreezing(address _addr, uint _index) public view returns (uint64 _release, uint _balance) {
// for (uint i = 0; i < _index + 1; i++) {
// _release = chains[toKey(_addr, _release)];
// if (_release == 0) {
// return;
// }
// }
// _balance = freezings[toKey(_addr, _release)];
// }
// /**
// * @dev freeze your tokens to the specified address.
// * Be careful, gas usage is not deterministic,
// * and depends on how many freezes _to address already has.
// * @param _to Address to which token will be freeze.
// * @param _amount Amount of token to freeze.
// * @param _until Release date, must be in future.
// */
// function freezeTo(address _to, uint _amount, uint64 _until) public {
// require(_to != address(0));
// require(_amount <= balances[msg.sender]);
// balances[msg.sender] = balances[msg.sender].sub(_amount);
// bytes32 currentKey = toKey(_to, _until);
// freezings[currentKey] = freezings[currentKey].add(_amount);
// freezingBalance[_to] = freezingBalance[_to].add(_amount);
// freeze(_to, _until);
// emit Transfer(msg.sender, _to, _amount);
// emit Freezed(_to, _until, _amount);
// }
// /**
// * @dev release first available freezing tokens.
// */
// function releaseOnce() public {
// bytes32 headKey = toKey(msg.sender, 0);
// uint64 head = chains[headKey];
// require(head != 0);
// require(uint64(block.timestamp) > head);
// bytes32 currentKey = toKey(msg.sender, head);
// uint64 next = chains[currentKey];
// uint amount = freezings[currentKey];
// delete freezings[currentKey];
// balances[msg.sender] = balances[msg.sender].add(amount);
// freezingBalance[msg.sender] = freezingBalance[msg.sender].sub(amount);
// if (next == 0) {
// delete chains[headKey];
// } else {
// chains[headKey] = next;
// delete chains[currentKey];
// }
// emit Released(msg.sender, amount);
// }
// /**
// * @dev release all available for release freezing tokens. Gas usage is not deterministic!
// * @return tokens How many tokens was released
// */
// function releaseAll() public returns (uint tokens) {
// uint release;
// uint balance;
// (release, balance) = getFreezing(msg.sender, 0);
// while (release != 0 && block.timestamp > release) {
// releaseOnce();
// tokens += balance;
// (release, balance) = getFreezing(msg.sender, 0);
// }
// }
// function toKey(address _addr, uint _release) internal pure returns (bytes32 result) {
// // WISH masc to increase entropy
// result = 0x5749534800000000000000000000000000000000000000000000000000000000;
// assembly {
// result := or(result, mul(_addr, 0x10000000000000000))
// result := or(result, _release)
// }
// }
// function freeze(address _to, uint64 _until) internal {
// require(_until > block.timestamp);
// bytes32 key = toKey(_to, _until);
// bytes32 parentKey = toKey(_to, uint64(0));
// uint64 next = chains[parentKey];
// if (next == 0) {
// chains[parentKey] = _until;
// return;
// }
// bytes32 nextKey = toKey(_to, next);
// uint parent;
// while (next != 0 && _until > next) {
// parent = next;
// parentKey = nextKey;
// next = chains[nextKey];
// nextKey = toKey(_to, next);
// }
// if (_until == next) {
// return;
// }
// if (next != 0) {
// chains[key] = next;
// }
// chains[parentKey] = _until;
// }
// }
// /**
// * @title Burnable Token
// * @dev Token that can be irreversibly burned (destroyed).
// */
// contract BurnableToken is BasicToken {
// event Burn(address indexed burner, uint256 value);
// /**
// * @dev Burns a specific amount of tokens.
// * @param _value The amount of token to be burned.
// */
// function burn(uint256 _value) public {
// _burn(msg.sender, _value);
// }
// function _burn(address _who, uint256 _value) internal {
// require(_value <= balances[_who]);
// // no need to require value <= totalSupply, since that would imply the
// // sender's balance is greater than the totalSupply, which *should* be an assertion failure
// balances[_who] = balances[_who].sub(_value);
// totalSupply_ = totalSupply_.sub(_value);
// emit Burn(_who, _value);
// emit Transfer(_who, address(0), _value);
// }
// }
// /**
// * @title Pausable
// * @dev Base contract which allows children to implement an emergency stop mechanism.
// */
// contract Pausable is Ownable {
// event Pause();
// event Unpause();
// bool public paused = false;
// /**
// * @dev Modifier to make a function callable only when the contract is not paused.
// */
// modifier whenNotPaused() {
// require(!paused);
// _;
// }
// /**
// * @dev Modifier to make a function callable only when the contract is paused.
// */
// modifier whenPaused() {
// require(paused);
// _;
// }
// /**
// * @dev called by the owner to pause, triggers stopped state
// */
// function pause() onlyOwner whenNotPaused public {
// paused = true;
// emit Pause();
// }
// /**
// * @dev called by the owner to unpause, returns to normal state
// */
// function unpause() onlyOwner whenPaused public {
// paused = false;
// emit Unpause();
// }
// }
// contract FreezableMintableToken is FreezableToken, MintableToken {
// /**
// * @dev Mint the specified amount of token to the specified address and freeze it until the specified date.
// * Be careful, gas usage is not deterministic,
// * and depends on how many freezes _to address already has.
// * @param _to Address to which token will be freeze.
// * @param _amount Amount of token to mint and freeze.
// * @param _until Release date, must be in future.
// * @return A boolean that indicates if the operation was successful.
// */
// function mintAndFreeze(address _to, uint _amount, uint64 _until) public onlyOwner canMint returns (bool) {
// totalSupply_ = totalSupply_.add(_amount);
// bytes32 currentKey = toKey(_to, _until);
// freezings[currentKey] = freezings[currentKey].add(_amount);
// freezingBalance[_to] = freezingBalance[_to].add(_amount);
// freeze(_to, _until);
// emit Mint(_to, _amount);
// emit Freezed(_to, _until, _amount);
// emit Transfer(msg.sender, _to, _amount);
// return true;
// }
// }
// contract Consts {
// uint public constant TOKEN_DECIMALS = 18;
// uint8 public constant TOKEN_DECIMALS_UINT8 = 18;
// uint public constant TOKEN_DECIMAL_MULTIPLIER = 10 ** TOKEN_DECIMALS;
// string public constant TOKEN_NAME = "Offshift";
// string public constant TOKEN_SYMBOL = "XFT";
// bool public constant PAUSED = false;
// address public constant TARGET_USER = 0x7f3B46E1E7d0E60fE496C41e8f0D21d7e962D37d;
// bool public constant CONTINUE_MINTING = false;
// }
// contract MainToken is Consts, FreezableMintableToken, BurnableToken, Pausable
// {
// event Initialized();
// bool public initialized = false;
// constructor() public {
// init();
// transferOwnership(TARGET_USER);
// }
// function name() public pure returns (string memory _name) {
// return TOKEN_NAME;
// }
// function symbol() public pure returns (string memory _symbol) {
// return TOKEN_SYMBOL;
// }
// function decimals() public pure returns (uint8 _decimals) {
// return TOKEN_DECIMALS_UINT8;
// }
// function transferFrom(address _from, address _to, uint256 _value) public returns (bool _success) {
// require(!paused);
// return super.transferFrom(_from, _to, _value);
// }
// function transfer(address _to, uint256 _value) public returns (bool _success) {
// require(!paused);
// return super.transfer(_to, _value);
// }
// function init() private {
// require(!initialized);
// initialized = true;
// if (PAUSED) {
// pause();
// }
// address[1] memory addresses = [address(0x7f3b46e1e7d0e60fe496c41e8f0d21d7e962d37d)];
// uint[1] memory amounts = [uint(10000000000000000000000000)];
// uint64[1] memory freezes = [uint64(0)];
// for (uint i = 0; i < addresses.length; i++) {
// if (freezes[i] == 0) {
// mint(addresses[i], amounts[i]);
// } else {
// mintAndFreeze(addresses[i], amounts[i], freezes[i]);
// }
// }
// if (!CONTINUE_MINTING) {
// finishMinting();
// }
// emit Initialized();
// }
// }
\ No newline at end of file
/**
*Submitted for verification at Etherscan.io on 2020-08-26
*/
// File: @openzeppelin/contracts/token/ERC20/IERC20M.sol
pragma solidity >=0.6.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20M {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount)
external
returns (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.
*/
function allowance(address owner, address spender)
external
view
returns (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.
*/
function approve(address spender, uint256 amount) external returns (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.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed 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.
*/
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}
// File: @openzeppelin/contracts/math/SafeMathM.sol
pragma solidity >=0.6.0;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMathM` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMathM {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMathM: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMathM: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMathM: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMathM: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMathM: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}
// File: @openzeppelin/contracts/utils/Address.sol
pragma solidity >=0.6.2;
/**
* @dev Collection of functions related to the address type
*/
library AddressM {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
// for accounts without code, i.e. `keccak256('')`
bytes32 codehash;
bytes32 accountHash =
0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
// solhint-disable-next-line no-inline-assembly
assembly {
codehash := extcodehash(account)
}
return (codehash != accountHash && codehash != 0x0);
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(
address(this).balance >= amount,
"AddressM: insufficient balance"
);
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
(bool success, ) = recipient.call{ value: amount }("");
require(
success,
"AddressM: unable to send value, recipient may have reverted"
);
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain`call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data)
internal
returns (bytes memory)
{
return functionCall(target, data, "AddressM: low-level call failed");
}
/**
* @dev Same as {xref-AddressM-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return _functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-AddressM-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return
functionCallWithValue(
target,
data,
value,
"AddressM: low-level call with value failed"
);
}
/**
* @dev Same as {xref-AddressM-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(
address(this).balance >= value,
"AddressM: insufficient balance for call"
);
return _functionCallWithValue(target, data, value, errorMessage);
}
function _functionCallWithValue(
address target,
bytes memory data,
uint256 weiValue,
string memory errorMessage
) private returns (bytes memory) {
require(isContract(target), "AddressM: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) =
target.call{ value: weiValue }(data);
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// File: @openzeppelin/contracts/token/ERC20/SafeERC20M.sol
pragma solidity >=0.6.0;
/**
* @title SafeERC20M
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20M for IERC20M;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20M {
using SafeMathM for uint256;
using AddressM for address;
function safeTransfer(
IERC20M token,
address to,
uint256 value
) internal {
_callOptionalReturn(
token,
abi.encodeWithSelector(token.transfer.selector, to, value)
);
}
function safeTransferFrom(
IERC20M token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(
token,
abi.encodeWithSelector(token.transferFrom.selector, from, to, value)
);
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20M-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20M token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
// solhint-disable-next-line max-line-length
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20M: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(
token,
abi.encodeWithSelector(token.approve.selector, spender, value)
);
}
function safeIncreaseAllowance(
IERC20M token,
address spender,
uint256 value
) internal {
uint256 newAllowance =
token.allowance(address(this), spender).add(value);
_callOptionalReturn(
token,
abi.encodeWithSelector(
token.approve.selector,
spender,
newAllowance
)
);
}
function safeDecreaseAllowance(
IERC20M token,
address spender,
uint256 value
) internal {
uint256 newAllowance =
token.allowance(address(this), spender).sub(
value,
"SafeERC20M: decreased allowance below zero"
);
_callOptionalReturn(
token,
abi.encodeWithSelector(
token.approve.selector,
spender,
newAllowance
)
);
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20M token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {AddressM.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata =
address(token).functionCall(
data,
"SafeERC20M: low-level call failed"
);
if (returndata.length > 0) {
// Return data is optional
// solhint-disable-next-line max-line-length
require(
abi.decode(returndata, (bool)),
"SafeERC20M: ERC20 operation did not succeed"
);
}
}
}
// File: @openzeppelin/contracts/utils/EnumerableSet.sol
pragma solidity >=0.6.0;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.0.0, only sets of type `address` (`AddressSet`) and `uint256`
* (`UintSet`) are supported.
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position of the value in the `values` array, plus 1 because index 0
// means a value is not in the set.
mapping(bytes32 => uint256) _indexes;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._indexes[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We read and store the value's index to prevent multiple reads from the same storage slot
uint256 valueIndex = set._indexes[value];
if (valueIndex != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 toDeleteIndex = valueIndex - 1;
uint256 lastIndex = set._values.length - 1;
// When the value to delete is the last one, the swap operation is unnecessary. However, since this occurs
// so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement.
bytes32 lastvalue = set._values[lastIndex];
// Move the last value to the index where the value to delete is
set._values[toDeleteIndex] = lastvalue;
// Update the index for the moved value
set._indexes[lastvalue] = toDeleteIndex + 1; // All indexes are 1-based
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the index for the deleted slot
delete set._indexes[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value)
private
view
returns (bool)
{
return set._indexes[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index)
private
view
returns (bytes32)
{
require(
set._values.length > index,
"EnumerableSet: index out of bounds"
);
return set._values[index];
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value)
internal
returns (bool)
{
return _add(set._inner, bytes32(uint256(value)));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value)
internal
returns (bool)
{
return _remove(set._inner, bytes32(uint256(value)));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value)
internal
view
returns (bool)
{
return _contains(set._inner, bytes32(uint256(value)));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index)
internal
view
returns (address)
{
return address(uint256(_at(set._inner, index)));
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value)
internal
returns (bool)
{
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value)
internal
view
returns (bool)
{
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index)
internal
view
returns (uint256)
{
return uint256(_at(set._inner, index));
}
}
// File: @openzeppelin/contracts/GSN/ContextM.sol
pragma solidity >=0.6.0;
/*
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with GSN meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextM {
function _msgSender() internal view virtual returns (address payable) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}
// File: @openzeppelin/contracts/access/OwnableM.sol
pragma solidity >=0.6.0;
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
contract OwnableM is ContextM {
address private _owner;
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
address msgSender = _msgSender();
_owner = msgSender;
emit OwnershipTransferred(address(0), msgSender);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(_owner == _msgSender(), "OwnableM: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(
newOwner != address(0),
"OwnableM: new owner is the zero address"
);
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
// File: @openzeppelin/contracts/token/ERC20/ERC20.sol
pragma solidity >=0.6.0;
/**
* @dev Implementation of the {IERC20M} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* We have followed general OpenZeppelin guidelines: functions revert instead
* of returning `false` on failure. This behavior is nonetheless conventional
* and does not conflict with the expectations of ERC20 applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20M-approve}.
*/
contract ERC20M is ContextM, IERC20M {
using SafeMathM for uint256;
using AddressM for address;
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
uint8 private _decimals;
/**
* @dev Sets the values for {name} and {symbol}, initializes {decimals} with
* a default value of 18.
*
* To select a different value for {decimals}, use {_setupDecimals}.
*
* All three of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name, string memory symbol) public {
_name = name;
_symbol = symbol;
_decimals = 18;
}
/**
* @dev Returns the name of the token.
*/
function name() public view returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5,05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
* called.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20M-balanceOf} and {IERC20M-transfer}.
*/
function decimals() public view returns (uint8) {
return _decimals;
}
/**
* @dev See {IERC20M-totalSupply}.
*/
function totalSupply() public view override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20M-balanceOf}.
*/
function balanceOf(address account) public view override returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20M-transfer}.
*
* Requirements:
*
* - `recipient` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address recipient, uint256 amount)
public
virtual
override
returns (bool)
{
_transfer(_msgSender(), recipient, amount);
return true;
}
/**
* @dev See {IERC20M-allowance}.
*/
function allowance(address owner, address spender)
public
view
virtual
override
returns (uint256)
{
return _allowances[owner][spender];
}
/**
* @dev See {IERC20M-approve}.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount)
public
virtual
override
returns (bool)
{
_approve(_msgSender(), spender, amount);
return true;
}
/**
* @dev See {IERC20M-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20};
*
* Requirements:
* - `sender` and `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
* - the caller must have allowance for ``sender``'s tokens of at least
* `amount`.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) public virtual override returns (bool) {
_transfer(sender, recipient, amount);
_approve(
sender,
_msgSender(),
_allowances[sender][_msgSender()].sub(
amount,
"ERC20: transfer amount exceeds allowance"
)
);
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20M-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue)
public
virtual
returns (bool)
{
_approve(
_msgSender(),
spender,
_allowances[_msgSender()][spender].add(addedValue)
);
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20M-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue)
public
virtual
returns (bool)
{
_approve(
_msgSender(),
spender,
_allowances[_msgSender()][spender].sub(
subtractedValue,
"ERC20M: decreased allowance below zero"
)
);
return true;
}
/**
* @dev Moves tokens `amount` from `sender` to `recipient`.
*
* This is internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `sender` cannot be the zero address.
* - `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
*/
function _transfer(
address sender,
address recipient,
uint256 amount
) internal virtual {
require(sender != address(0), "ERC20M: transfer from the zero address");
require(
recipient != address(0),
"ERC20M: transfer to the zero address"
);
_beforeTokenTransfer(sender, recipient, amount);
_balances[sender] = _balances[sender].sub(
amount,
"ERC20M: transfer amount exceeds balance"
);
_balances[recipient] = _balances[recipient].add(amount);
emit Transfer(sender, recipient, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements
*
* - `to` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20M: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply = _totalSupply.add(amount);
_balances[account] = _balances[account].add(amount);
emit Transfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20M: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
_balances[account] = _balances[account].sub(
amount,
"ERC20M: burn amount exceeds balance"
);
_totalSupply = _totalSupply.sub(amount);
emit Transfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
*
* This is internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(
address owner,
address spender,
uint256 amount
) internal virtual {
require(owner != address(0), "ERC20M: approve from the zero address");
require(spender != address(0), "ERC20M: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Sets {decimals} to a value other than the default one of 18.
*
* WARNING: This function should only be called from the constructor. Most
* applications that interact with token contracts will not expect
* {decimals} to ever change, and may work incorrectly if it does.
*/
function _setupDecimals(uint8 decimals_) internal {
_decimals = decimals_;
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be to transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
}
// File: contracts/SushiToken.sol
pragma solidity >=0.6.12;
// SushiToken with Governance.
contract SushiToken is ERC20M("SushiToken", "SUSHI"), OwnableM {
using SafeMathM for uint256;
/// @notice Creates `_amount` token to `_to`. Must only be called by the owner (MasterChef).
function mint(address _to, uint256 _amount) public onlyOwner {
_mint(_to, _amount);
_moveDelegates(address(0), _delegates[_to], _amount);
}
// Copied and modified from YAM code:
// https://github.com/yam-finance/yam-protocol/blob/master/contracts/token/YAMGovernanceStorage.sol
// https://github.com/yam-finance/yam-protocol/blob/master/contracts/token/YAMGovernance.sol
// Which is copied and modified from COMPOUND:
// https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/Comp.sol
// record of each accounts delegate
mapping(address => address) internal _delegates;
/// @notice A checkpoint for marking number of votes from a given block
struct Checkpoint {
uint32 fromBlock;
uint256 votes;
}
/// @notice A record of votes checkpoints for each account, by index
mapping(address => mapping(uint32 => Checkpoint)) public checkpoints;
/// @notice The number of checkpoints for each account
mapping(address => uint32) public numCheckpoints;
/// @notice The EIP-712 typehash for the contract's domain
bytes32 public constant DOMAIN_TYPEHASH =
keccak256(
"EIP712Domain(string name,uint256 chainId,address verifyingContract)"
);
/// @notice The EIP-712 typehash for the delegation struct used by the contract
bytes32 public constant DELEGATION_TYPEHASH =
keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
/// @notice A record of states for signing / validating signatures
mapping(address => uint256) public nonces;
/// @notice An event thats emitted when an account changes its delegate
event DelegateChanged(
address indexed delegator,
address indexed fromDelegate,
address indexed toDelegate
);
/// @notice An event thats emitted when a delegate account's vote balance changes
event DelegateVotesChanged(
address indexed delegate,
uint256 previousBalance,
uint256 newBalance
);
/**
* @notice Delegate votes from `msg.sender` to `delegatee`
* @param delegator The address to get delegatee for
*/
function delegates(address delegator) external view returns (address) {
return _delegates[delegator];
}
/**
* @notice Delegate votes from `msg.sender` to `delegatee`
* @param delegatee The address to delegate votes to
*/
function delegate(address delegatee) external {
return _delegate(msg.sender, delegatee);
}
/**
* @notice Delegates votes from signatory to `delegatee`
* @param delegatee The address to delegate votes to
* @param nonce The contract state required to match the signature
* @param expiry The time at which to expire the signature
* @param v The recovery byte of the signature
* @param r Half of the ECDSA signature pair
* @param s Half of the ECDSA signature pair
*/
function delegateBySig(
address delegatee,
uint256 nonce,
uint256 expiry,
uint8 v,
bytes32 r,
bytes32 s
) external {
bytes32 domainSeparator =
keccak256(
abi.encode(
DOMAIN_TYPEHASH,
keccak256(bytes(name())),
getChainId(),
address(this)
)
);
bytes32 structHash =
keccak256(
abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry)
);
bytes32 digest =
keccak256(
abi.encodePacked("\x19\x01", domainSeparator, structHash)
);
address signatory = ecrecover(digest, v, r, s);
require(
signatory != address(0),
"SUSHI::delegateBySig: invalid signature"
);
require(
nonce == nonces[signatory]++,
"SUSHI::delegateBySig: invalid nonce"
);
require(
block.timestamp <= expiry,
"SUSHI::delegateBySig: signature expired"
);
return _delegate(signatory, delegatee);
}
/**
* @notice Gets the current votes balance for `account`
* @param account The address to get votes balance
* @return The number of current votes for `account`
*/
function getCurrentVotes(address account) external view returns (uint256) {
uint32 nCheckpoints = numCheckpoints[account];
return
nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0;
}
/**
* @notice Determine the prior number of votes for an account as of a block number
* @dev Block number must be a finalized block or else this function will revert to prevent misinformation.
* @param account The address of the account to check
* @param blockNumber The block number to get the vote balance at
* @return The number of votes the account had as of the given block
*/
function getPriorVotes(address account, uint256 blockNumber)
external
view
returns (uint256)
{
require(
blockNumber < block.number,
"SUSHI::getPriorVotes: not yet determined"
);
uint32 nCheckpoints = numCheckpoints[account];
if (nCheckpoints == 0) {
return 0;
}
// First check most recent balance
if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) {
return checkpoints[account][nCheckpoints - 1].votes;
}
// Next check implicit zero balance
if (checkpoints[account][0].fromBlock > blockNumber) {
return 0;
}
uint32 lower = 0;
uint32 upper = nCheckpoints - 1;
while (upper > lower) {
uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow
Checkpoint memory cp = checkpoints[account][center];
if (cp.fromBlock == blockNumber) {
return cp.votes;
} else if (cp.fromBlock < blockNumber) {
lower = center;
} else {
upper = center - 1;
}
}
return checkpoints[account][lower].votes;
}
function _delegate(address delegator, address delegatee) internal {
address currentDelegate = _delegates[delegator];
uint256 delegatorBalance = balanceOf(delegator); // balance of underlying SUSHIs (not scaled);
_delegates[delegator] = delegatee;
emit DelegateChanged(delegator, currentDelegate, delegatee);
_moveDelegates(currentDelegate, delegatee, delegatorBalance);
}
function _moveDelegates(
address srcRep,
address dstRep,
uint256 amount
) internal {
if (srcRep != dstRep && amount > 0) {
if (srcRep != address(0)) {
// decrease old representative
uint32 srcRepNum = numCheckpoints[srcRep];
uint256 srcRepOld =
srcRepNum > 0
? checkpoints[srcRep][srcRepNum - 1].votes
: 0;
uint256 srcRepNew = srcRepOld.sub(amount);
_writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew);
}
if (dstRep != address(0)) {
// increase new representative
uint32 dstRepNum = numCheckpoints[dstRep];
uint256 dstRepOld =
dstRepNum > 0
? checkpoints[dstRep][dstRepNum - 1].votes
: 0;
uint256 dstRepNew = dstRepOld.add(amount);
_writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);
}
}
}
function _writeCheckpoint(
address delegatee,
uint32 nCheckpoints,
uint256 oldVotes,
uint256 newVotes
) internal {
uint32 blockNumber =
safe32(
block.number,
"SUSHI::_writeCheckpoint: block number exceeds 32 bits"
);
if (
nCheckpoints > 0 &&
checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber
) {
checkpoints[delegatee][nCheckpoints - 1].votes = newVotes;
} else {
checkpoints[delegatee][nCheckpoints] = Checkpoint(
blockNumber,
newVotes
);
numCheckpoints[delegatee] = nCheckpoints + 1;
}
emit DelegateVotesChanged(delegatee, oldVotes, newVotes);
}
function safe32(uint256 n, string memory errorMessage)
internal
pure
returns (uint32)
{
require(n < 2**32, errorMessage);
return uint32(n);
}
function getChainId() internal pure returns (uint256) {
uint256 chainId;
assembly {
chainId := chainid()
}
return chainId;
}
}
// File: contracts/MasterChef.sol
pragma solidity >=0.6.12;
interface IMigratorChef {
// Perform LP token migration from legacy UniswapV2 to SushiSwap.
// Take the current LP token address and return the new LP token address.
// Migrator should have full access to the caller's LP token.
// Return the new LP token address.
//
// XXX Migrator must have allowance access to UniswapV2 LP tokens.
// SushiSwap must mint EXACTLY the same amount of SushiSwap LP tokens or
// else something bad will happen. Traditional UniswapV2 does not
// do that so be careful!
function migrate(IERC20M token) external returns (IERC20M);
}
// MasterChef is the master of Sushi. He can make Sushi and he is a fair guy.
//
// Note that it's ownable and the owner wields tremendous power. The ownership
// will be transferred to a governance smart contract once SUSHI is sufficiently
// distributed and the community can show to govern itself.
//
// Have fun reading it. Hopefully it's bug-free. God bless.
contract MasterChef is OwnableM {
using SafeMathM for uint256;
using SafeERC20M for IERC20M;
// Info of each user.
struct UserInfo {
uint256 amount; // How many LP tokens the user has provided.
uint256 rewardDebt; // Reward debt. See explanation below.
//
// We do some fancy math here. Basically, any point in time, the amount of SUSHIs
// entitled to a user but is pending to be distributed is:
//
// pending reward = (user.amount * pool.accSushiPerShare) - user.rewardDebt
//
// Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens:
// 1. The pool's `accSushiPerShare` (and `lastRewardBlock`) gets updated.
// 2. User receives the pending reward sent to his/her address.
// 3. User's `amount` gets updated.
// 4. User's `rewardDebt` gets updated.
}
// Info of each pool.
struct PoolInfo {
IERC20M lpToken; // Address of LP token contract.
uint256 allocPoint; // How many allocation points assigned to this pool. SUSHIs to distribute per block.
uint256 lastRewardBlock; // Last block number that SUSHIs distribution occurs.
uint256 accSushiPerShare; // Accumulated SUSHIs per share, times 1e12. See below.
}
// The SUSHI TOKEN!
SushiToken public sushi;
// Dev address.
address public devaddr;
// Block number when bonus SUSHI period ends.
uint256 public bonusEndBlock;
// SUSHI tokens created per block.
uint256 public sushiPerBlock;
// Bonus muliplier for early sushi makers.
uint256 public constant BONUS_MULTIPLIER = 10;
// The migrator contract. It has a lot of power. Can only be set through governance (owner).
IMigratorChef public migrator;
// Info of each pool.
PoolInfo[] public poolInfo;
// Info of each user that stakes LP tokens.
mapping(uint256 => mapping(address => UserInfo)) public userInfo;
// Total allocation poitns. Must be the sum of all allocation points in all pools.
uint256 public totalAllocPoint = 0;
// The block number when SUSHI mining starts.
uint256 public startBlock;
event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
event Withdraw(address indexed user, uint256 indexed pid, uint256 amount);
event EmergencyWithdraw(
address indexed user,
uint256 indexed pid,
uint256 amount
);
constructor(
SushiToken _sushi,
address _devaddr,
uint256 _sushiPerBlock,
uint256 _startBlock,
uint256 _bonusEndBlock
) public {
sushi = _sushi;
devaddr = _devaddr;
sushiPerBlock = _sushiPerBlock;
bonusEndBlock = _bonusEndBlock;
startBlock = _startBlock;
}
function poolLength() external view returns (uint256) {
return poolInfo.length;
}
// Add a new lp to the pool. Can only be called by the owner.
// XXX DO NOT add the same LP token more than once. Rewards will be messed up if you do.
function add(
uint256 _allocPoint,
IERC20M _lpToken,
bool _withUpdate
) public onlyOwner {
if (_withUpdate) {
massUpdatePools();
}
uint256 lastRewardBlock =
block.number > startBlock ? block.number : startBlock;
totalAllocPoint = totalAllocPoint.add(_allocPoint);
poolInfo.push(
PoolInfo({
lpToken: _lpToken,
allocPoint: _allocPoint,
lastRewardBlock: lastRewardBlock,
accSushiPerShare: 0
})
);
}
// Update the given pool's SUSHI allocation point. Can only be called by the owner.
function set(
uint256 _pid,
uint256 _allocPoint,
bool _withUpdate
) public onlyOwner {
if (_withUpdate) {
massUpdatePools();
}
totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add(
_allocPoint
);
poolInfo[_pid].allocPoint = _allocPoint;
}
// Set the migrator contract. Can only be called by the owner.
function setMigrator(IMigratorChef _migrator) public onlyOwner {
migrator = _migrator;
}
// Migrate lp token to another lp contract. Can be called by anyone. We trust that migrator contract is good.
function migrate(uint256 _pid) public {
require(address(migrator) != address(0), "migrate: no migrator");
PoolInfo storage pool = poolInfo[_pid];
IERC20M lpToken = pool.lpToken;
uint256 bal = lpToken.balanceOf(address(this));
lpToken.safeApprove(address(migrator), bal);
IERC20M newLpToken = migrator.migrate(lpToken);
require(bal == newLpToken.balanceOf(address(this)), "migrate: bad");
pool.lpToken = newLpToken;
}
// Return reward multiplier over the given _from to _to block.
function getMultiplier(uint256 _from, uint256 _to)
public
view
returns (uint256)
{
if (_to <= bonusEndBlock) {
return _to.sub(_from).mul(BONUS_MULTIPLIER);
} else if (_from >= bonusEndBlock) {
return _to.sub(_from);
} else {
return
bonusEndBlock.sub(_from).mul(BONUS_MULTIPLIER).add(
_to.sub(bonusEndBlock)
);
}
}
// View function to see pending SUSHIs on frontend.
function pendingSushi(uint256 _pid, address _user)
external
view
returns (uint256)
{
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][_user];
uint256 accSushiPerShare = pool.accSushiPerShare;
uint256 lpSupply = pool.lpToken.balanceOf(address(this));
if (block.number > pool.lastRewardBlock && lpSupply != 0) {
uint256 multiplier =
getMultiplier(pool.lastRewardBlock, block.number);
uint256 sushiReward =
multiplier.mul(sushiPerBlock).mul(pool.allocPoint).div(
totalAllocPoint
);
accSushiPerShare = accSushiPerShare.add(
sushiReward.mul(1e12).div(lpSupply)
);
}
return user.amount.mul(accSushiPerShare).div(1e12).sub(user.rewardDebt);
}
// Update reward vairables for all pools. Be careful of gas spending!
function massUpdatePools() public {
uint256 length = poolInfo.length;
for (uint256 pid = 0; pid < length; ++pid) {
updatePool(pid);
}
}
// Update reward variables of the given pool to be up-to-date.
function updatePool(uint256 _pid) public {
PoolInfo storage pool = poolInfo[_pid];
if (block.number <= pool.lastRewardBlock) {
return;
}
uint256 lpSupply = pool.lpToken.balanceOf(address(this));
if (lpSupply == 0) {
pool.lastRewardBlock = block.number;
return;
}
uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
uint256 sushiReward =
multiplier.mul(sushiPerBlock).mul(pool.allocPoint).div(
totalAllocPoint
);
sushi.mint(devaddr, sushiReward.div(10));
sushi.mint(address(this), sushiReward);
pool.accSushiPerShare = pool.accSushiPerShare.add(
sushiReward.mul(1e12).div(lpSupply)
);
pool.lastRewardBlock = block.number;
}
// Deposit LP tokens to MasterChef for SUSHI allocation.
function deposit(uint256 _pid, uint256 _amount) public {
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
updatePool(_pid);
if (user.amount > 0) {
uint256 pending =
user.amount.mul(pool.accSushiPerShare).div(1e12).sub(
user.rewardDebt
);
safeSushiTransfer(msg.sender, pending);
}
pool.lpToken.safeTransferFrom(
address(msg.sender),
address(this),
_amount
);
user.amount = user.amount.add(_amount);
user.rewardDebt = user.amount.mul(pool.accSushiPerShare).div(1e12);
emit Deposit(msg.sender, _pid, _amount);
}
// Withdraw LP tokens from MasterChef.
function withdraw(uint256 _pid, uint256 _amount) public {
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
require(user.amount >= _amount, "withdraw: not good");
updatePool(_pid);
uint256 pending =
user.amount.mul(pool.accSushiPerShare).div(1e12).sub(
user.rewardDebt
);
safeSushiTransfer(msg.sender, pending);
user.amount = user.amount.sub(_amount);
user.rewardDebt = user.amount.mul(pool.accSushiPerShare).div(1e12);
pool.lpToken.safeTransfer(address(msg.sender), _amount);
emit Withdraw(msg.sender, _pid, _amount);
}
// Withdraw without caring about rewards. EMERGENCY ONLY.
function emergencyWithdraw(uint256 _pid) public {
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
pool.lpToken.safeTransfer(address(msg.sender), user.amount);
emit EmergencyWithdraw(msg.sender, _pid, user.amount);
user.amount = 0;
user.rewardDebt = 0;
}
// Safe sushi transfer function, just in case if rounding error causes pool to not have enough SUSHIs.
function safeSushiTransfer(address _to, uint256 _amount) internal {
uint256 sushiBal = sushi.balanceOf(address(this));
if (_amount > sushiBal) {
sushi.transfer(_to, sushiBal);
} else {
sushi.transfer(_to, _amount);
}
}
// Update dev address by the previous dev.
function dev(address _devaddr) public {
require(msg.sender == devaddr, "dev: wut?");
devaddr = _devaddr;
}
}
contract StakingRewards {
uint256 public immutable PID; // ip of pair XFT/ETH in Onsen
IERC20 public rewardsTokenXFT;
IERC20 public rewardsTokenSUSHI;
IERC20 public stakingToken;
IMasterChef public masterChef;
PoolInfo public poolInfo;
uint256 public periodFinish;
uint256 public rewardRate;
uint256 public rewardsDuration;
uint256 public lastUpdateTime;
uint256 public rewardPerTokenStored;
mapping(address => UserInfo) public userInfo;
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewardsXFT;
mapping(address => uint256) public rewardsSUSHI;
uint256 private _totalStaked;
function totalStaked() external view override returns (uint256);
// staked balance for specific user
function balanceOf(address _account)
external
view
override
returns (uint256);
// reward notified for duration
function getRewardForDuration() external view override returns (uint256);
// withdraw all staked tokens and get reward
function exit() external override;
function stake(uint256 _amount)
external
override
nonReentrant
whenNotPaused
updateReward(msg.sender);
function withdraw(uint256 _amount)
public
override
nonReentrant
updateReward(msg.sender);
// get reward in both tokens
function getReward() public override nonReentrant updateReward(msg.sender); //
function lastTimeRewardApplicable() public view override returns (uint256);
// reward per token in XFT
function rewardPerToken() public view override returns (uint256);
// reward XFT for specific user
function earnedXFT(address _account)
public
view
override
returns (uint256);
// reward SUSHI for specific user
function earnedSushi(address _account)
public
view
override
returns (uint256);
// onlyOwner
// Ensure the provided reward amount is not more than the balance in the contract.
// This keeps the reward rate in the right range, preventing overflows due to
// very high values of rewardRate in the earned and rewardsPerToken functions;
// Reward + leftover must be less than 2^256 / 10^18 to avoid overflow.
function notifyRewardAmount(uint256 _reward)
external
onlyOwner
updateReward(address(0));
//
function updatePeriodFinish(uint256 _timestamp)
external
onlyOwner
updateReward(address(0));
// Pausable
// status
function paused() public view virtual returns (bool);
function pause() public onlyOwner;
function unpause() public onlyOwner;
// Ownble
function owner() public view virtual returns (address);
// transfere ownership to zero address
function renounceOwnership() public virtual onlyOwner;
function transferOwnership(address newOwner) public virtual onlyOwner;
}
const MasterChef = artifacts.require('MasterChef');
const StakingRewards = artifacts.require('StakingRewards');
const TokenMock = artifacts.require('TokenMock');
const {
expectEvent,
expectRevert,
time,
ether,
BN,
} = require('@openzeppelin/test-helpers');
const {use, expect} = require('chai');
function bn(params) {
return new BN(params.toString());
}
use(require('chai-bn')(BN));
const PID = '0';
const ALLOCATE_POINT_SUSHI = ether('1000');
contract(
'Staking',
([owner, dev, wallet1, wallet2, wallet3]) => {
let slp;
let sushi;
let xft;
let onsen;
let staking;
beforeEach(async () => {
slp = await TokenMock.new('SLP', 'SLP', ether('10000000'), {from: owner});
sushi = await TokenMock.new('SUSHI', 'SUSHI', ether('10000000'), {from: owner});
xft = await TokenMock.new('XFT', 'XFT', ether('10000000'), {from: owner});
onsen = await MasterChef.new(
sushi.address,
dev,
ether('0.01'),
await time.latestBlock(),
(await time.latestBlock()) + 2592000, // apx. 12 month
{from: owner},
);
staking = await StakingRewards.new(
xft.address,
sushi.address,
slp.address,
onsen.address,
PID,
{from: owner},
);
await onsen.add(ALLOCATE_POINT_SUSHI, slp.address, false, {from: owner});
await sushi.mint(onsen.address, ALLOCATE_POINT_SUSHI);
await slp.mint(wallet1, ether('10000000000'), {from: owner});
await slp.mint(wallet2, ether('10000000000'), {from: owner});
await slp.mint(wallet3, ether('10000000000'), {from: owner});
await slp.approve(staking.address, new BN(2).pow(new BN(255)), {from: wallet1});
await slp.approve(staking.address, new BN(2).pow(new BN(255)), {from: wallet2});
await slp.approve(staking.address, new BN(2).pow(new BN(255)), {from: wallet3});
});
describe('check basic init', () => {
it('should init PID', async () => {
expect(await staking.PID()).to.be.a.bignumber.equal(PID);
});
it('should init periodFinish', async () => {
expect(await staking.periodFinish()).to.be.a.bignumber.equal('0');
});
it('should init rewardRate', async () => {
expect(await staking.rewardRate()).to.be.a.bignumber.equal('0');
});
it('should init rewardsDuration', async () => {
const duration = await time.duration.days(30);
expect(await staking.rewardsDuration()).to.be.a.bignumber.equal(duration);
});
describe('constructor', () => {
it('should set SLP token address', async () => {
expect(await staking.stakingToken()).to.be.eq(slp.address);
});
it('should set XFT token address', async () => {
expect(await staking.rewardsTokenXFT()).to.be.eq(xft.address);
});
it('should set SUSHI token address', async () => {
expect(await staking.rewardsTokenSUSHI()).to.be.eq(sushi.address);
});
it('should set Onsen contract address', async () => {
expect(await staking.masterChef()).to.be.eq(onsen.address);
});
});
});
describe('Functions', () => {
describe('lastTimeRewardApplicable', () => {
it('should return zero', async () => {
expect(
await staking.lastTimeRewardApplicable(),
).to.be.a.bignumber.equal('0');
});
it('should return ~current timestamp', async () => {
await xft.transfer(staking.address, ether('100'), {from: owner});
await staking.notifyRewardAmount(ether('100'), {from: owner});
expect(
await staking.lastTimeRewardApplicable(),
).to.be.a.bignumber.that.is.at.closeTo(await time.latest(), '100');
});
it('should return periodFinish', async () => {
expect(await staking.periodFinish()).to.be.a.bignumber.equal('0');
expect(await staking.lastTimeRewardApplicable()).to.be.a.bignumber.eq(
'0',
);
await xft.transfer(staking.address, ether('100'), {from: owner});
await staking.notifyRewardAmount(ether('100'), {from: owner});
const periodFinish = await new BN(await time.latest()).add(
await time.duration.days(30),
);
expect(
await staking.lastTimeRewardApplicable(),
).to.be.a.bignumber.that.is.at.closeTo(await time.latest(), '100');
await xft.transfer(staking.address, ether('100'), {from: owner});
await staking.notifyRewardAmount(ether('100'), {from: owner});
await time.increase(await time.duration.days(40));
expect(
await staking.periodFinish(),
).to.be.a.bignumber.that.is.at.closeTo(periodFinish, '100');
expect(
await staking.lastTimeRewardApplicable(),
).to.be.a.bignumber.that.is.at.closeTo(
await staking.periodFinish(),
'100',
);
});
});
describe('rewardPerToken', () => {
beforeEach(async () => {
await xft.mint(staking.address, ether('100000'), {from: owner});
});
it('should return zero if no one stake', async () => {
expect(await staking.rewardPerToken()).to.be.a.bignumber.equal('0');
});
it('should return reward per token', async () => {
await staking.notifyRewardAmount(ether('1'), {from: owner});
await staking.stake(ether('2'), {from: wallet1});
expect(await staking.rewardPerToken()).to.be.a.bignumber.equal('0');
await time.increase(await time.duration.days(30));
expect(
await staking.rewardPerToken(),
).to.be.a.bignumber.that.is.at.closeTo(ether('0.5'), ether('0.01'));
});
it('should calculate reword per token correctly', async () => {
await staking.notifyRewardAmount(ether('40'), {from: owner});
expect(await staking.rewardPerToken()).to.be.a.bignumber.equal('0');
await staking.stake(ether('2'), {from: wallet1});
await staking.stake(ether('4'), {from: wallet2});
await staking.stake(ether('6'), {from: wallet3});
await staking.stake(ether('8'), {from: wallet1});
await time.increase(await time.duration.days(30));
expect(
await staking.rewardPerToken(),
).to.be.a.bignumber.that.is.at.closeTo(ether('2'), ether('0.001'));
});
it('should calculate reword per token correctly with small amount of tokens', async () => {
await staking.notifyRewardAmount(ether('2'), {from: owner});
expect(await staking.rewardPerToken()).to.be.a.bignumber.equal('0');
await staking.stake(6, {from: wallet1});
await staking.stake(4, {from: wallet2});
await time.increase(await time.duration.days(30));
expect(
await staking.rewardPerToken(),
).to.be.a.bignumber.that.is.at.closeTo(
ether('200000000000000000'),
ether('1000000000000'),
);
});
});
describe('earnedXFT', () => {
beforeEach(async () => {
await xft.mint(staking.address, ether('100000'), {from: owner});
});
describe('should calculate reward correctly', () => {
it('when no stakes', async () => {
expect(await staking.earnedXFT(wallet1)).to.be.a.bignumber.equal(
'0',
);
});
it('when no reward amount', async () => {
await staking.stake(ether('10'), {from: wallet1});
expect(await staking.earnedXFT(wallet1)).to.be.a.bignumber.equal(
'0',
);
await time.increase(await time.duration.days(30));
expect(await staking.earnedXFT(wallet1)).to.be.a.bignumber.equal(
'0',
);
});
it('when stake small amount', async () => {
await xft.mint(staking.address, ether('100000'), {from: owner});
await staking.notifyRewardAmount(ether('10'), {from: owner});
await staking.stake(4, {from: wallet1});
await staking.stake(6, {from: wallet2});
await time.increase(await time.duration.days(30));
expect(
await staking.earnedXFT(wallet1),
).to.be.a.bignumber.that.is.at.closeTo(ether('4'), ether('0.001'));
expect(
await staking.earnedXFT(wallet2),
).to.be.a.bignumber.that.is.at.closeTo(ether('6'), ether('0.001'));
});
it('when stake big amount', async () => {
await staking.notifyRewardAmount(ether('10'), {from: owner});
await staking.stake(ether('2000000000'), {from: wallet1});
await staking.stake(ether('8000000000'), {from: wallet2});
await time.increase(await time.duration.days(30));
expect(
await staking.earnedXFT(wallet1),
).to.be.a.bignumber.that.is.at.closeTo(ether('2'), ether('0.001'));
expect(
await staking.earnedXFT(wallet2),
).to.be.a.bignumber.that.is.at.closeTo(ether('8'), ether('0.001'));
});
it('when stake different amount a few times', async () => {
await staking.notifyRewardAmount(ether('10'), {from: owner});
await staking.stake(ether('2'), {from: wallet1});
await staking.stake(ether('2'), {from: wallet2});
await time.increase(await time.duration.days(30));
expect(
await staking.earnedXFT(wallet1),
).to.be.a.bignumber.that.is.at.closeTo(ether('5'), ether('0.001'));
expect(
await staking.earnedXFT(wallet2),
).to.be.a.bignumber.that.is.at.closeTo(ether('5'), ether('0.001'));
expect(await staking.earnedXFT(wallet3)).to.be.a.bignumber.equal(
'0',
);
await staking.notifyRewardAmount(ether('10'), {from: owner});
await staking.stake(ether('2'), {from: wallet1});
await staking.stake(ether('4'), {from: wallet3});
await time.increase(await time.duration.days(30));
expect(
await staking.earnedXFT(wallet1),
).to.be.a.bignumber.that.is.at.closeTo(
ether('9'), // 5 + 4
ether('0.001'),
);
expect(
await staking.earnedXFT(wallet2),
).to.be.a.bignumber.that.is.at.closeTo(
ether('7'), // 5 + 2
ether('0.001'),
);
expect(
await staking.earnedXFT(wallet3),
).to.be.a.bignumber.that.is.at.closeTo(
ether('4'), // 0 + 4
ether('0.001'),
);
});
it('when stake and withdraw', async () => {
await staking.notifyRewardAmount(ether('10'), {from: owner});
await staking.stake(ether('2'), {from: wallet1});
await staking.stake(ether('2'), {from: wallet2});
await time.increase(await time.duration.days(30));
expect(
await staking.earnedXFT(wallet1),
).to.be.a.bignumber.that.is.at.closeTo(ether('5'), ether('0.001'));
expect(
await staking.earnedXFT(wallet2),
).to.be.a.bignumber.that.is.at.closeTo(ether('5'), ether('0.001'));
await staking.notifyRewardAmount(ether('10'), {from: owner});
await staking.withdraw(ether('2'), {from: wallet1});
await staking.stake(ether('6'), {from: wallet1});
await staking.stake(ether('2'), {from: wallet2});
await time.increase(await time.duration.days(30));
expect(
await staking.earnedXFT(wallet1),
).to.be.a.bignumber.that.is.at.closeTo(
ether('11'), // 5 + 6
ether('0.001'),
);
expect(
await staking.earnedXFT(wallet2),
).to.be.a.bignumber.that.is.at.closeTo(
ether('9'), // 5 + 4
ether('0.001'),
);
});
});
});
describe('earnedSushi', () => {
beforeEach(async () => {
await xft.mint(staking.address, ether('100000'), {from: owner});
});
describe('should calculate reward correctly', () => {
it('when no stakes', async () => {
expect(await staking.earnedSushi(wallet1)).to.be.a.bignumber.equal(
'0',
);
});
it('when stake small amount', async () => {
await xft.mint(staking.address, ether('100000'), {from: owner});
await staking.notifyRewardAmount(ether('10'), {from: owner});
await staking.stake(4, {from: wallet1});
await staking.stake(6, {from: wallet2});
await time.advanceBlock();
expect(
await staking.earnedSushi(wallet1),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.14'),
ether('0.001'),
);
expect(
await staking.earnedSushi(wallet2),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.06'),
ether('0.001'),
);
});
it('when stake big amount', async () => {
await staking.notifyRewardAmount(ether('10'), {from: owner});
await staking.stake(ether('2000000000'), {from: wallet1});
await staking.stake(ether('8000000000'), {from: wallet2});
await time.advanceBlock();
expect(
await staking.earnedSushi(wallet1),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.12'),
ether('0.001'),
);
expect(
await staking.earnedSushi(wallet2),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.08'),
ether('0.001'),
);
});
it('should calculate reward correctly', async () => {
await staking.notifyRewardAmount(ether('10'), {from: owner});
await staking.stake(ether('2'), {from: wallet1});
await time.advanceBlock();
expect(
await staking.earnedSushi(wallet1),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.1'),
ether('0.001'),
);
expect(
await onsen.pendingSushi(PID, staking.address),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.1'),
ether('0.001'),
);
await staking.stake(ether('2'), {from: wallet1});
expect(await staking.rewardsSUSHI(wallet1)).to.be.a.bignumber.equal(
ether('0.2'),
);
expect(
await onsen.pendingSushi(PID, staking.address),
).to.be.a.bignumber.equal('0');
expect(
await staking.earnedSushi(wallet1),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.2'),
ether('0.001'),
);
await time.advanceBlock();
expect(await staking.rewardsSUSHI(wallet1)).to.be.a.bignumber.equal(
ether('0.2'),
);
expect(
await onsen.pendingSushi(PID, staking.address),
).to.be.a.bignumber.equal(ether('0.1'));
expect(
await staking.earnedSushi(wallet1),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.3'),
ether('0.001'),
);
});
it('when stake different amount a few times', async () => {
await staking.notifyRewardAmount(ether('10'), {from: owner});
await staking.stake(ether('2'), {from: wallet1}); // 1 block
await staking.stake(ether('2'), {from: wallet2}); // 2 block
expect(
await staking.earnedSushi(wallet1),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.1'), // only 1 block mined
ether('0.001'),
);
expect(await staking.rewardsSUSHI(wallet1)).to.be.a.bignumber.equal(
'0',
);
await time.advanceBlock(); // 3 block
expect(
await staking.earnedSushi(wallet1),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.15'), // 2 blocks mined
ether('0.001'),
);
expect(await staking.rewardsSUSHI(wallet1)).to.be.a.bignumber.equal(
'0',
);
expect(
await staking.earnedSushi(wallet2),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.05'), // for 1 block
ether('0.001'),
);
expect(await staking.rewardsSUSHI(wallet2)).to.be.a.bignumber.equal(
'0',
);
await staking.stake(ether('2'), {from: wallet1}); // 4 block
expect(await staking.rewardsSUSHI(wallet1)).to.be.a.bignumber.equal(
ether('0.2'),
);
expect(await staking.rewardsSUSHI(wallet2)).to.be.a.bignumber.equal(
'0',
);
await staking.stake(ether('4'), {from: wallet3}); // 5 block
expect(await staking.rewardsSUSHI(wallet2)).to.be.a.bignumber.equal(
'0',
);
expect(
await staking.earnedSushi(wallet1),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.26666'), // 4 blocks mined
ether('0.00001'),
);
expect(
await staking.earnedSushi(wallet2),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.13333'),
ether('0.00001'),
);
expect(await staking.earnedSushi(wallet3)).to.be.a.bignumber.equal(
'0',
);
await time.advanceBlock(); // 6 block
expect(
await staking.earnedSushi(wallet1),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.30666'), // 5 blocks mined
ether('0.00001'),
);
expect(
await staking.earnedSushi(wallet2),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.15333'),
ether('0.00001'),
);
expect(
await staking.earnedSushi(wallet3),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.04'),
ether('0.00001'),
);
});
it('when stake and withdraw', async () => {
await staking.notifyRewardAmount(ether('10'), {from: owner});
await staking.stake(ether('2'), {from: wallet1});
await staking.stake(ether('2'), {from: wallet2});
await time.advanceBlock();
expect(
await staking.earnedSushi(wallet1),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.15'),
ether('0.001'),
);
expect(
await staking.earnedSushi(wallet2),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.05'),
ether('0.001'),
);
await staking.withdraw(ether('2'), {from: wallet1});
await time.advanceBlock();
await time.advanceBlock();
expect(
await staking.earnedSushi(wallet1),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.2'),
ether('0.001'),
);
expect(
await staking.earnedSushi(wallet2),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.3'),
ether('0.001'),
);
});
});
});
describe('stake', () => {
it('should revert if amount == 0', async () => {
await expectRevert(
staking.stake(0, {from: wallet1}),
'Stake: cant stake 0',
);
});
it('should revert if stake on pause', async () => {
expect(await staking.paused()).to.be.equal(false);
await staking.pause({from: owner});
expect(await staking.paused()).to.be.equal(true);
await expectRevert(
staking.stake(ether('10'), {from: wallet1}),
'Pausable: paused',
);
});
it('should increase _totalStaked', async () => {
expect(await staking.totalStaked()).to.be.a.bignumber.equal('0');
await staking.stake(ether('2'), {from: wallet1});
expect(await staking.totalStaked()).to.be.a.bignumber.equal(
ether('2'),
);
await staking.stake(ether('5'), {from: wallet2});
expect(await staking.totalStaked()).to.be.a.bignumber.equal(
ether('7'),
);
});
describe('should transfer tokens', () => {
it('check balances', async () => {
expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal(
ether('10000000000'),
);
expect(await slp.balanceOf(onsen.address)).to.be.a.bignumber.equal(
'0',
);
expect(
await slp.balanceOf(staking.address),
).to.be.a.bignumber.equal('0');
await staking.stake(ether('1000000000'), {from: wallet1}),
expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal(
ether('9000000000'),
);
expect(await slp.balanceOf(onsen.address)).to.be.a.bignumber.equal(
ether('1000000000'),
);
expect(
await slp.balanceOf(staking.address),
).to.be.a.bignumber.equal('0');
});
it('should catch event Transfer', async () => {
await staking.stake(ether('100'), {from: wallet1});
// transfer user -> staking -> onsen
const logs = await slp
.getPastEvents('Transfer', {toBlock: 'latest'})
.then((events) => {
return events;
});
expect(await logs[0].args['from']).to.be.equal(wallet1);
expect(await logs[0].args['to']).to.be.equal(staking.address);
expect(await logs[0].args['value']).to.be.a.bignumber.equal(
ether('100'),
);
expect(await logs[1].args['from']).to.be.equal(staking.address);
expect(await logs[1].args['to']).to.be.equal(onsen.address);
expect(await logs[1].args['value']).to.be.a.bignumber.equal(
ether('100'),
);
});
});
it('should catch event Approval', async () => {
await staking.stake(ether('100'), {from: wallet1});
// transfer user -> staking -> onsen
const logs = await slp
.getPastEvents('Approval', {toBlock: 'latest'})
.then((events) => {
return events[1].args;
});
expect(await logs['owner']).to.be.equal(staking.address);
expect(await logs['spender']).to.be.equal(onsen.address);
expect(await logs['value']).to.be.a.bignumber.equal(ether('100'));
});
describe('should stake to onsen contract', () => {
it('check balances', async () => {
expect(await slp.balanceOf(onsen.address)).to.be.a.bignumber.equal(
'0',
);
await staking.stake(ether('1000000000'), {from: wallet1}),
expect(
await slp.balanceOf(onsen.address),
).to.be.a.bignumber.equal(ether('1000000000'));
});
it('should catch event Transfer', async () => {
await staking.stake(ether('100'), {from: wallet1});
// transfer user -> staking -> onsen
const logs = await slp
.getPastEvents('Transfer', {toBlock: 'latest'})
.then((events) => {
return events[1].args;
});
expect(await logs['from']).to.be.equal(staking.address);
expect(await logs['to']).to.be.equal(onsen.address);
expect(await logs['value']).to.be.a.bignumber.equal(ether('100'));
});
});
// TODO: if multiple stake
describe('should transfer reward in SUSHI to staking contract', () => {
it('check balances', async () => {
await staking.stake(ether('1000000000'), {from: wallet1});
expect(await sushi.balanceOf(wallet1)).to.be.a.bignumber.equal('0');
expect(
await sushi.balanceOf(onsen.address),
).to.be.a.bignumber.equal(ether('1000'));
expect(
await sushi.balanceOf(staking.address),
).to.be.a.bignumber.equal('0');
await staking.withdraw(ether('1000000000'), {from: wallet1});
expect(await sushi.balanceOf(wallet1)).to.be.a.bignumber.equal('0');
expect(
await sushi.balanceOf(onsen.address),
).to.be.a.bignumber.equal(ether('1000'));
expect(
await sushi.balanceOf(staking.address),
).to.be.a.bignumber.equal(ether('0.1'));
});
it('should catch event Transfer', async () => {
await staking.stake(ether('100'), {from: wallet1});
await staking.withdraw(ether('100'), {from: wallet1});
// transfer onsen -> staking and hold
const logs = await sushi
.getPastEvents('Transfer', {toBlock: 'latest'})
.then((events) => {
return events;
});
expect(await logs[2].args['from']).to.be.equal(onsen.address);
expect(await logs[2].args['to']).to.be.equal(staking.address);
expect(await logs[2].args['value']).to.be.a.bignumber.equal(
ether('0.1'),
);
});
});
// TODO:
it('should add stake amount to userInfo', async () => {
expect(
(await staking.userInfo(wallet1)).amount,
).to.be.a.bignumber.equal('0');
expect(
(await staking.userInfo(wallet2)).amount,
).to.be.a.bignumber.equal('0');
await staking.stake(ether('2'), {from: wallet1});
expect(
(await staking.userInfo(wallet1)).amount,
).to.be.a.bignumber.equal(ether('2'));
await staking.stake(ether('5'), {from: wallet2});
expect(
(await staking.userInfo(wallet2)).amount,
).to.be.a.bignumber.equal(ether('5'));
await staking.stake(ether('3'), {from: wallet1});
expect(
(await staking.userInfo(wallet1)).amount,
).to.be.a.bignumber.equal(ether('5'));
});
it('should update rewardDebt', async () => {
let userInfo = await staking.userInfo(wallet1);
expect(userInfo.rewardDebt).to.be.a.bignumber.equal('0');
await staking.stake(ether('100'), {from: wallet1});
await staking.stake(ether('100'), {from: wallet1});
userInfo = await staking.userInfo(wallet1);
const userInfoOnsen = await staking.userInfo(wallet1);
// let rewardDebt = userInfo.amount.mul(poolInfo.accSushiPerShare).div(bn(1e12));
expect(userInfo.rewardDebt).to.be.a.bignumber.equal(
userInfoOnsen.rewardDebt,
);
});
it('should cath event Staked', async () => {
const receipt = await staking.stake(ether('2'), {from: wallet1});
expectEvent.inLogs(receipt.logs, 'Staked', {
user: wallet1,
amount: ether('2'),
});
});
});
describe('withdraw', () => {
it('should revert if amount == 0', async () => {
await expectRevert(
staking.withdraw(0, {from: wallet1}),
'Withdraw: cant withdraw 0',
);
});
it('should fail if withdraw > staked', async () => {
await expectRevert(
staking.withdraw(ether('20'), {from: wallet1}),
'Withdraw: insufficient funds',
);
await staking.stake(ether('20'), {from: wallet1});
await expectRevert(
staking.withdraw(ether('25'), {from: wallet1}),
'Withdraw: insufficient funds',
);
});
it('should decrease _totalStaked', async () => {
expect(await staking.totalStaked()).to.be.a.bignumber.equal('0');
await staking.stake(ether('20'), {from: wallet1});
expect(await staking.totalStaked()).to.be.a.bignumber.equal(
ether('20'),
);
await staking.withdraw(ether('5'), {from: wallet1});
expect(await staking.totalStaked()).to.be.a.bignumber.equal(
ether('15'),
);
});
describe('should withdraw from onsen contract', () => {
it('check balances', async () => {
await staking.stake(ether('1000000000'), {from: wallet1});
expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal(
ether('9000000000'),
);
expect(await slp.balanceOf(onsen.address)).to.be.a.bignumber.equal(
ether('1000000000'),
);
expect(
await slp.balanceOf(staking.address),
).to.be.a.bignumber.equal('0');
await staking.withdraw(ether('1000000000'), {from: wallet1});
expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal(
ether('10000000000'),
);
expect(await slp.balanceOf(onsen.address)).to.be.a.bignumber.equal(
'0',
);
expect(
await slp.balanceOf(staking.address),
).to.be.a.bignumber.equal('0');
});
it('should catch event Transfer', async () => {
await staking.stake(ether('100'), {from: wallet1});
await staking.withdraw(ether('100'), {from: wallet1});
// transfer user -> staking -> onsen
const logs = await slp
.getPastEvents('Transfer', {toBlock: 'latest'})
.then((events) => {
return events;
});
expect(await logs[0].args['from']).to.be.equal(onsen.address);
expect(await logs[0].args['to']).to.be.equal(staking.address);
expect(await logs[0].args['value']).to.be.a.bignumber.equal(
ether('100'),
);
expect(await logs[1].args['from']).to.be.equal(staking.address);
expect(await logs[1].args['to']).to.be.equal(wallet1);
expect(await logs[1].args['value']).to.be.a.bignumber.equal(
ether('100'),
);
});
});
it('should update rewardsSushi', async () => {
expect(await staking.rewardsSUSHI(wallet1)).to.be.a.bignumber.equal(
ether('0'),
);
await staking.stake(ether('100'), {from: wallet1});
await time.advanceBlock();
await time.advanceBlock();
const sushiPerBlock = await onsen.sushiPerBlock.call();
const pendingSushi = await onsen.pendingSushi.call(
PID,
staking.address,
);
await staking.withdraw(ether('100'), {from: wallet1});
expect(await staking.rewardsSUSHI(wallet1)).to.be.a.bignumber.equal(
pendingSushi.add(sushiPerBlock.mul(bn('10'))),
); // bonus 10x
});
describe('should transfer reward in SUSHI to staking contract', () => {
it('check balances', async () => {
await staking.stake(ether('1000000000'), {from: wallet1});
expect(await sushi.balanceOf(wallet1)).to.be.a.bignumber.equal('0');
expect(
await sushi.balanceOf(onsen.address),
).to.be.a.bignumber.equal(ether('1000'));
expect(
await sushi.balanceOf(staking.address),
).to.be.a.bignumber.equal('0');
await staking.withdraw(ether('1000000000'), {from: wallet1});
expect(await sushi.balanceOf(wallet1)).to.be.a.bignumber.equal('0');
expect(
await sushi.balanceOf(onsen.address),
).to.be.a.bignumber.equal(ether('1000'));
expect(
await sushi.balanceOf(staking.address),
).to.be.a.bignumber.equal(ether('0.1'));
});
it('should catch event Transfer', async () => {
await staking.stake(ether('100'), {from: wallet1});
await staking.withdraw(ether('100'), {from: wallet1});
// transfer onsen -> staking and hold
const logs = await sushi
.getPastEvents('Transfer', {toBlock: 'latest'})
.then((events) => {
return events;
});
expect(await logs[2].args['from']).to.be.equal(onsen.address);
expect(await logs[2].args['to']).to.be.equal(staking.address);
expect(await logs[2].args['value']).to.be.a.bignumber.equal(
ether('0.1'),
);
});
});
it('should subtract stake amount from userInfo', async () => {
await staking.stake(ether('5'), {from: wallet1});
await staking.stake(ether('5'), {from: wallet2});
expect(
(await staking.userInfo(wallet1)).amount,
).to.be.a.bignumber.equal(ether('5'));
expect(
(await staking.userInfo(wallet2)).amount,
).to.be.a.bignumber.equal(ether('5'));
await staking.withdraw(ether('2'), {from: wallet1});
expect(
(await staking.userInfo(wallet1)).amount,
).to.be.a.bignumber.equal(ether('3'));
await staking.withdraw(ether('5'), {from: wallet2});
expect(
(await staking.userInfo(wallet2)).amount,
).to.be.a.bignumber.equal('0');
await staking.withdraw(ether('3'), {from: wallet1});
expect(
(await staking.userInfo(wallet1)).amount,
).to.be.a.bignumber.equal('0');
});
it('should update rewardDebt', async () => {
let userInfoOnsen;
let userInfo = await staking.userInfo(wallet1);
await staking.stake(ether('100'), {from: wallet1});
await staking.stake(ether('100'), {from: wallet1});
userInfo = await staking.userInfo(wallet1);
userInfoOnsen = await staking.userInfo(wallet1);
expect(userInfo.rewardDebt).to.be.a.bignumber.equal(
userInfoOnsen.rewardDebt,
);
await staking.withdraw(ether('100'), {from: wallet1});
await staking.withdraw(ether('100'), {from: wallet1});
userInfo = await staking.userInfo(wallet1);
userInfoOnsen = await staking.userInfo(wallet1);
expect(userInfo.rewardDebt).to.be.a.bignumber.equal(
userInfoOnsen.rewardDebt,
);
});
describe('should transfer withdraw amount to member', () => {
it('check balances', async () => {
await staking.stake(ether('1000000000'), {from: wallet1});
expect(await slp.balanceOf(onsen.address)).to.be.a.bignumber.equal(
ether('1000000000'),
);
expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal(
ether('9000000000'),
);
expect(
await slp.balanceOf(staking.address),
).to.be.a.bignumber.equal('0');
await staking.withdraw(ether('1000000000'), {from: wallet1});
expect(await slp.balanceOf(onsen.address)).to.be.a.bignumber.equal(
'0',
);
expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal(
ether('10000000000'),
);
expect(
await slp.balanceOf(staking.address),
).to.be.a.bignumber.equal('0');
});
it('should catch event Transfer', async () => {
await staking.stake(ether('100'), {from: wallet1});
await time.advanceBlock();
await staking.withdraw(ether('100'), {from: wallet1});
// transfer onsen -> staking -> user
const logs = await slp
.getPastEvents('Transfer', {toBlock: 'latest'})
.then((events) => {
return events[1].args;
});
expect(await logs['from']).to.be.equal(staking.address);
expect(await logs['to']).to.be.equal(wallet1);
expect(await logs['value']).to.be.a.bignumber.equal(ether('100'));
});
});
describe('should transfer SUSHI reward to staking contract', () => {
it('check balances', async () => {
await staking.stake(ether('1000000000'), {from: wallet1});
expect(
await sushi.balanceOf(onsen.address),
).to.be.a.bignumber.equal(ether('1000'));
expect(await sushi.balanceOf(wallet1)).to.be.a.bignumber.equal('0');
expect(
await sushi.balanceOf(staking.address),
).to.be.a.bignumber.equal('0');
await staking.withdraw(ether('1000000000'), {from: wallet1});
// expect(await sushi.balanceOf(onsen.address)).to.be.a.bignumber.equal(ether("999.9"));
expect(await sushi.balanceOf(wallet1)).to.be.a.bignumber.equal('0');
expect(
await sushi.balanceOf(staking.address),
).to.be.a.bignumber.equal(ether('0.1'));
});
it('should catch event Transfer', async () => {
await staking.stake(ether('100'), {from: wallet1});
await time.advanceBlock();
await staking.withdraw(ether('100'), {from: wallet1});
// transfer user -> staking -> onsen
const logs = await sushi
.getPastEvents('Transfer', {toBlock: 'latest'})
.then((events) => {
return events[2].args;
});
expect(await logs['from']).to.be.equal(onsen.address);
expect(await logs['to']).to.be.equal(staking.address);
expect(await logs['value']).to.be.a.bignumber.equal(ether('0.2'));
});
});
it('should cath event Withdrawn', async () => {
await staking.stake(ether('2'), {from: wallet1});
const receipt = await staking.withdraw(ether('2'), {from: wallet1});
expectEvent.inLogs(receipt.logs, 'Withdrawn', {
user: wallet1,
amount: ether('2'),
});
});
});
describe('exit', () => {
it('should withdraw all tokens', async () => {
expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal(
ether('10000000000'),
);
expect(
(await staking.userInfo(wallet1)).amount,
).to.be.a.bignumber.equal('0');
await staking.stake(ether('20'), {from: wallet1});
expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal(
ether('9999999980'),
);
expect(
(await staking.userInfo(wallet1)).amount,
).to.be.a.bignumber.equal(ether('20'));
await staking.exit({from: wallet1});
expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal(
ether('10000000000'),
);
expect(
(await staking.userInfo(wallet1)).amount,
).to.be.a.bignumber.equal(ether('0'));
});
it('should get rewards in both tokens', async () => {
expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal(
ether('10000000000'),
);
expect(
(await staking.userInfo(wallet1)).amount,
).to.be.a.bignumber.equal('0');
expect(await slp.balanceOf(wallet2)).to.be.a.bignumber.equal(
ether('10000000000'),
);
expect(
(await staking.userInfo(wallet2)).amount,
).to.be.a.bignumber.equal('0');
await xft.mint(staking.address, ether('100000'), {from: owner});
await staking.notifyRewardAmount(ether('1000'), {from: owner});
await staking.stake(ether('20'), {from: wallet1});
await time.advanceBlock();
await staking.stake(ether('80'), {from: wallet2});
await time.advanceBlock();
await time.increase(await time.duration.days(30));
expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal(
ether('9999999980'),
);
expect(
(await staking.userInfo(wallet1)).amount,
).to.be.a.bignumber.equal(ether('20'));
expect(await slp.balanceOf(wallet2)).to.be.a.bignumber.equal(
ether('9999999920'),
);
expect(
(await staking.userInfo(wallet2)).amount,
).to.be.a.bignumber.equal(ether('80'));
await staking.exit({from: wallet1});
await staking.exit({from: wallet2});
expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal(
ether('10000000000'),
);
expect(await slp.balanceOf(wallet2)).to.be.a.bignumber.equal(
ether('10000000000'),
);
expect(await sushi.balanceOf(wallet1)).to.be.a.bignumber.equal(
ether('0.26'),
);
expect(await sushi.balanceOf(wallet2)).to.be.a.bignumber.equal(
ether('0.34'),
);
expect(
await xft.balanceOf(wallet1),
).to.be.a.bignumber.that.is.at.closeTo(ether('200'), ether('0.0001'));
expect(
await xft.balanceOf(wallet2),
).to.be.a.bignumber.that.is.at.closeTo(ether('800'), ether('0.0001'));
expect(
(await staking.userInfo(wallet1)).amount,
).to.be.a.bignumber.equal(ether('0'));
expect(
(await staking.userInfo(wallet2)).amount,
).to.be.a.bignumber.equal(ether('0'));
});
});
describe('getReward', () => {
beforeEach(async () => {
await xft.mint(staking.address, ether('100000'), {from: owner});
});
it('should pass if reward == 0', async () => {
const receipt = await staking.getReward({from: wallet1});
expect(await receipt.logs).to.have.length(0);
});
it('should zeroed rewardsXFT', async () => {
await staking.notifyRewardAmount(ether('60'), {from: owner});
await staking.stake(ether('20'), {from: wallet1});
await staking.stake(ether('10'), {from: wallet2});
expect(await staking.rewardsXFT(wallet1)).to.be.a.bignumber.equal(
'0',
);
expect(await staking.rewardsXFT(wallet2)).to.be.a.bignumber.equal(
'0',
);
await time.increase(await time.duration.days(30));
await staking.stake(ether('20'), {from: wallet1}); // to call updateReward
await staking.stake(ether('10'), {from: wallet2});
expect(
await staking.rewardsXFT(wallet1),
).to.be.a.bignumber.that.is.at.closeTo(ether('40'), ether('0.001'));
expect(
await staking.rewardsXFT(wallet2),
).to.be.a.bignumber.that.is.at.closeTo(ether('20'), ether('0.001'));
await staking.getReward({from: wallet1});
await staking.getReward({from: wallet2});
expect(await staking.rewardsXFT(wallet1)).to.be.a.bignumber.equal(
'0',
);
expect(await staking.rewardsXFT(wallet2)).to.be.a.bignumber.equal(
'0',
);
});
it('should zeroed rewardsSUSHI', async () => {
await staking.notifyRewardAmount(ether('60'), {from: owner});
await staking.stake(ether('60'), {from: wallet1});
await staking.stake(ether('40'), {from: wallet2});
expect(await staking.rewardsSUSHI(wallet1)).to.be.a.bignumber.equal(
'0',
);
expect(await staking.rewardsSUSHI(wallet2)).to.be.a.bignumber.equal(
'0',
);
await time.advanceBlock();
await staking.stake(ether('40'), {from: wallet1}); // to call updateReward
await staking.stake(ether('60'), {from: wallet2});
await time.advanceBlock();
expect(
await staking.rewardsSUSHI(wallet1),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.22'), // 0.1 + 0.06 + 0.06
ether('0.00001'),
);
expect(
await staking.rewardsSUSHI(wallet2),
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.10857'), // 0.04 + 0.04 + 0.02857
ether('0.00001'),
);
// thats pending reward before 2 stake
await staking.getReward({from: wallet1});
await staking.getReward({from: wallet2});
expect(await staking.rewardsSUSHI(wallet1)).to.be.a.bignumber.equal(
'0',
);
expect(await staking.rewardsSUSHI(wallet2)).to.be.a.bignumber.equal(
'0',
);
});
it('should transfer XFT reward', async () => {
let logs;
await staking.notifyRewardAmount(ether('60'), {from: owner});
await staking.stake(ether('20'), {from: wallet1});
await staking.stake(ether('10'), {from: wallet2});
expect(await staking.rewardsXFT(wallet1)).to.be.a.bignumber.equal(
'0',
);
expect(await staking.rewardsXFT(wallet2)).to.be.a.bignumber.equal(
'0',
);
await time.increase(await time.duration.days(30));
await staking.getReward({from: wallet1});
logs = await xft
.getPastEvents('Transfer', {toBlock: 'latest'})
.then((events) => {
return events;
});
expect(await logs[0].args['from']).to.be.equal(staking.address);
expect(await logs[0].args['to']).to.be.equal(wallet1);
expect(
await logs[0].args['value'],
).to.be.a.bignumber.that.is.at.closeTo(ether('40'), ether('0.001'));
await staking.getReward({from: wallet2});
logs = await xft
.getPastEvents('Transfer', {toBlock: 'latest'})
.then((events) => {
return events;
});
expect(await logs[0].args['from']).to.be.equal(staking.address);
expect(await logs[0].args['to']).to.be.equal(wallet2);
expect(
await logs[0].args['value'],
).to.be.a.bignumber.that.is.at.closeTo(ether('20'), ether('0.001'));
expect(await staking.rewardsXFT(wallet1)).to.be.a.bignumber.equal(
'0',
);
expect(await staking.rewardsXFT(wallet2)).to.be.a.bignumber.equal(
'0',
);
});
it('should transfer SUSHI reward', async () => {
let logs;
await staking.notifyRewardAmount(ether('60'), {from: owner});
expect(
await sushi.balanceOf(staking.address),
).to.be.a.bignumber.equal('0');
await staking.stake(ether('25'), {from: wallet1});
expect(
await sushi.balanceOf(staking.address),
).to.be.a.bignumber.equal(ether('0'));
await staking.stake(ether('75'), {from: wallet2});
expect(
await sushi.balanceOf(staking.address),
).to.be.a.bignumber.equal(ether('0.1'));
await staking.getReward({from: wallet1});
// make third stake 0, send reward from 2 and 1 stake
// 2 blocks mined
// reward wallet1: | 0.1 | 0.025 | = 0.125
// reward wallet2: | - | 0.075 | = 0.075
// total 0.3
logs = await sushi
.getPastEvents('Transfer', {toBlock: 'latest'})
.then((events) => {
return events;
});
expect(await logs[2].args['from']).to.be.equal(onsen.address);
expect(await logs[2].args['to']).to.be.equal(staking.address);
expect(
await logs[2].args['value'],
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.1'), // the last reward transferred to staking contract
ether('0.001'),
);
expect(await logs[3].args['from']).to.be.equal(staking.address);
expect(await logs[3].args['to']).to.be.equal(wallet1);
expect(
await logs[3].args['value'],
).to.be.a.bignumber.that.is.at.closeTo(
ether('0.125'), // reward for wallet1
ether('0.00001'),
);
expect(
await sushi.balanceOf(staking.address),
).to.be.a.bignumber.equal(ether('0.075'));
await staking.getReward({from: wallet2}); // +1 block for wallet2
// reward wallet2: 0.075 + 0.1
// 3 blocks mined
// reward wallet1: | 0.1 | 0.025 | 0.025 | = 0.125 payed, +0.025
// reward wallet2: | - | 0.075 | 0.075 | = 0.15
logs = await sushi
.getPastEvents('Transfer', {toBlock: 'latest'})
.then((events) => {
return events;
});
expect(await logs[2].args['from']).to.be.equal(onsen.address);
expect(await logs[2].args['to']).to.be.equal(staking.address);
expect(
await logs[2].args['value'],
).to.be.a.bignumber.that.is.at.closeTo(ether('0.1'), ether('0.001'));
expect(await logs[3].args['from']).to.be.equal(staking.address);
expect(await logs[3].args['to']).to.be.equal(wallet2);
expect(
await logs[3].args['value'],
).to.be.a.bignumber.that.is.at.closeTo(ether('0.15'), ether('0.001'));
expect(
await sushi.balanceOf(staking.address),
).to.be.a.bignumber.equal(ether('0.025'));
expect(await staking.earnedSushi(wallet1)).to.be.a.bignumber.equal(
ether('0.025'),
);
expect(await staking.earnedSushi(wallet2)).to.be.a.bignumber.equal(
'0',
);
});
it('should cath events RewardPaid', async () => {
await staking.notifyRewardAmount(ether('60'), {from: owner});
await staking.stake(ether('20'), {from: wallet1});
await time.increase(await time.duration.days(30));
const receipt = await staking.getReward({from: wallet1});
await expectEvent.inLogs(receipt.logs, 'XFTRewardPaid', {
user: wallet1,
reward: receipt.logs[0].args.reward,
});
expect(
await receipt.logs[0].args.reward,
).to.be.a.bignumber.that.is.at.closeTo(ether('60'), ether('0.001'));
await expectEvent.inLogs(receipt.logs, 'SUSHIRewardPaid', {
user: wallet1,
reward: receipt.logs[1].args.reward,
});
expect(
await receipt.logs[1].args.reward,
).to.be.a.bignumber.that.is.at.closeTo(ether('0.2'), ether('0.001'));
});
});
describe('notifyRewardAmount', () => {
beforeEach(async () => {
await xft.mint(staking.address, ether('10000000'), {from: owner});
});
it('should revert if param reward > balance', async () => {
await expectRevert(
staking.notifyRewardAmount(ether('60480000000'), {from: owner}),
'Provided reward too high',
);
});
it('only owner can set reward', async () => {
await expectRevert(
staking.notifyRewardAmount(ether('60'), {from: wallet2}),
'Ownable: caller is not the owner',
);
});
it('should update rewardRate', async () => {
expect(await staking.rewardRate()).to.be.a.bignumber.eq('0');
await staking.notifyRewardAmount(ether('2592000'), {from: owner});
expect(await staking.rewardRate()).to.be.a.bignumber.eq(ether('1'));
await time.increase(await time.duration.days(3));
await staking.notifyRewardAmount(ether('2592000'), {from: owner});
expect(
await staking.rewardRate(),
).to.be.a.bignumber.that.is.at.closeTo(
ether('1.9'),
ether('0.00001'),
);
await time.increase(await time.duration.days(30));
await staking.notifyRewardAmount(ether('2592000'), {from: owner});
expect(await staking.rewardRate()).to.be.a.bignumber.eq(ether('1'));
});
it('should update lastUpdateTime', async () => {
expect(await staking.lastUpdateTime()).to.be.a.bignumber.eq('0');
await staking.notifyRewardAmount(ether('604800'), {from: owner});
expect(
await staking.lastUpdateTime(),
).to.be.a.bignumber.that.is.at.closeTo(await time.latest(), '100');
await time.increase(await time.duration.days(3));
await staking.notifyRewardAmount(ether('604800'), {from: owner});
expect(
await staking.lastUpdateTime(),
).to.be.a.bignumber.that.is.at.closeTo(await time.latest(), '100');
});
it('should update periodFinish', async () => {
const month = await time.duration.days(30);
expect(await staking.periodFinish()).to.be.a.bignumber.eq('0');
await staking.notifyRewardAmount(ether('604800'), {from: owner});
expect(
await staking.periodFinish(),
).to.be.a.bignumber.that.is.at.closeTo(
new BN(await time.latest()).add(month),
'100',
);
await time.increase(await time.duration.days(3));
await staking.notifyRewardAmount(ether('604800'), {from: owner});
expect(
await staking.periodFinish(),
).to.be.a.bignumber.that.is.at.closeTo(
new BN(await time.latest()).add(month),
'100',
);
});
it('should catch event RewardAdded', async () => {
const receipt = await staking.notifyRewardAmount(ether('604800'), {
from: owner,
});
expectEvent.inLogs(receipt.logs, 'RewardAdded', {
reward: ether('604800'),
});
});
});
describe('pause', () => {
it('only owner can pause staking', async () => {
await expectRevert(
staking.pause({from: wallet1}),
'Ownable: caller is not the owner',
);
await staking.pause({from: owner});
});
it('should revert if stake on pause', async () => {
expect(await staking.paused()).to.be.equal(false);
await staking.pause({from: owner});
expect(await staking.paused()).to.be.equal(true);
await expectRevert(
staking.stake(ether('10'), {from: wallet1}),
'Pausable: paused',
);
});
it('should revert if paused', async () => {
expect(await staking.paused()).to.be.equal(false);
await staking.pause({from: owner});
expect(await staking.paused()).to.be.equal(true);
await expectRevert(
staking.pause({from: owner}),
'Pausable: paused',
);
});
it('should pause staking', async () => {
expect(await staking.paused()).to.be.equal(false);
await staking.stake(ether('10'), {from: wallet1});
await staking.pause({from: owner});
expect(await staking.paused()).to.be.equal(true);
await expectRevert(
staking.stake(ether('10'), {from: wallet1}),
'Pausable: paused',
);
});
it('should cath event Paused', async () => {
const receipt = await staking.pause({from: owner});
expectEvent.inLogs(receipt.logs, 'Paused', {
account: owner,
});
});
});
describe('unpause', () => {
it('only owner can unpause staking', async () => {
await staking.pause({from: owner});
await expectRevert(
staking.unpause({from: wallet1}),
'Ownable: caller is not the owner',
);
await staking.unpause({from: owner});
});
it('should revert if unpaused', async () => {
expect(await staking.paused()).to.be.equal(false);
await expectRevert(
staking.unpause({from: owner}),
'Pausable: not paused',
);
});
it('should unpause staking', async () => {
expect(await staking.paused()).to.be.equal(false);
await staking.pause({from: owner});
expect(await staking.paused()).to.be.equal(true);
await expectRevert(
staking.stake(ether('10'), {from: wallet1}),
'Pausable: paused',
);
await staking.unpause({from: owner});
await staking.stake(ether('10'), {from: wallet1});
});
it('should cath event Paused', async () => {
await staking.pause({from: owner});
const receipt = await staking.unpause({from: owner});
expectEvent.inLogs(receipt.logs, 'Unpaused', {
account: owner,
});
});
});
describe('updatePeriodFinish', () => {
it('can be invoked only by the owner', async () => {
const now = bn(Math.floor(Date.now() / 1000));
await expectRevert(
staking.updatePeriodFinish(now, {from: wallet1}),
'Ownable: caller is not the owner',
);
await staking.updatePeriodFinish(now);
});
it('should set periodFinish', async () => {
expect(await staking.periodFinish()).to.be.a.bignumber.equal('0');
await xft.mint(staking.address, ether('10000'), {from: owner});
const tx = await staking.notifyRewardAmount(ether('10000'), {from: owner});
const now = (await web3.eth.getBlock(tx.receipt.blockNumber)).timestamp;
const periodFinish = bn(now.toString()).add(await time.duration.days(30));
expect(
await staking.periodFinish(),
).to.be.a.bignumber.equal(periodFinish);
await staking.updatePeriodFinish(now.toString());
expect(await staking.periodFinish()).to.be.a.bignumber.equal(now.toString());
});
});
describe('totalStaked', () => {
it('should return totalStaked', async () => {
expect(await staking.totalStaked()).to.be.a.bignumber.equal('0');
await staking.stake(ether('10'), {from: wallet1});
expect(await staking.totalStaked()).to.be.a.bignumber.equal(
ether('10'),
);
await staking.stake(ether('20'), {from: wallet2});
expect(await staking.totalStaked()).to.be.a.bignumber.equal(
ether('30'),
);
});
});
describe('balanceOf', () => {
it('should return staked amount', async () => {
expect(await staking.balanceOf(wallet1)).to.be.a.bignumber.equal('0');
await staking.stake(ether('15'), {from: wallet1});
await staking.stake(ether('5'), {from: wallet2});
expect(await staking.balanceOf(wallet1)).to.be.a.bignumber.equal(
ether('15'),
);
expect(await staking.balanceOf(wallet2)).to.be.a.bignumber.equal(
ether('5'),
);
await staking.stake(ether('10'), {from: wallet1});
expect(await staking.balanceOf(wallet1)).to.be.a.bignumber.equal(
ether('25'),
);
});
});
describe('getRewardForDuration', () => {
it('should return reward for duration', async () => {
expect(await staking.getRewardForDuration()).to.be.a.bignumber.equal(
'0',
);
await xft.mint(staking.address, ether('10000'), {from: owner});
await staking.notifyRewardAmount(ether('10000'), {from: owner});
expect(
await staking.getRewardForDuration(),
).to.be.a.bignumber.that.is.at.closeTo(
ether('10000'),
ether('0.00001'),
);
});
});
});
},
);
const MasterChef = artifacts.require('MasterChef');
const StakingRewards = artifacts.require('StakingRewards');
const TokenMock = artifacts.require('TokenMock');
const {time, BN, ether} = require('@openzeppelin/test-helpers');
const {use, expect} = require('chai');
function bn(params) {
return new BN(params.toString());
}
use(require('chai-bn')(BN));
const PID = 0;
const ALLOCATE_POINT_SUSHI = ether('1000');
const ALLOCATE_POINT_XFT = ether('1000');
contract('DeltaHubStaking', ([owner, dev, wallet1, wallet2, wallet3, wallet4, wallet5]) => {
let slp;
let sushi;
let xft;
let onsen;
let staking;
beforeEach(async () => {
slp = await TokenMock.new('SLP', 'SLP', ether('10000000'), {from: owner});
sushi = await TokenMock.new('SUSHI', 'SUSHI', ether('10000000'), {from: owner});
xft = await TokenMock.new('XFT', 'XFT', ether('10000000'), {from: owner});
onsen = await MasterChef.new(
sushi.address,
dev,
ether('0.01'),
await time.latestBlock(),
await time.latestBlock() + 2592000, // apx. 12 month
{from: owner},
);
staking = await StakingRewards.new(
xft.address,
sushi.address,
slp.address,
onsen.address,
{from: owner},
);
await onsen.add(ALLOCATE_POINT_SUSHI, slp.address, false, {from: owner});
await sushi.mint(onsen.address, ALLOCATE_POINT_SUSHI);
await xft.transfer(staking.address, ALLOCATE_POINT_XFT, {from: owner});
await staking.notifyRewardAmount(ALLOCATE_POINT_XFT, {from: owner});
await slp.transfer(wallet1, ether('100000'), {from: owner});
await slp.transfer(wallet2, ether('100000'), {from: owner});
await slp.transfer(wallet3, ether('100000'), {from: owner});
await slp.transfer(wallet4, ether('100000'), {from: owner});
await slp.transfer(wallet5, ether('100000'), {from: owner});
await slp.approve(staking.address, new BN(2).pow(new BN(255)), {from: wallet1});
await slp.approve(staking.address, new BN(2).pow(new BN(255)), {from: wallet2});
await slp.approve(staking.address, new BN(2).pow(new BN(255)), {from: wallet3});
await slp.approve(staking.address, new BN(2).pow(new BN(255)), {from: wallet4});
await slp.approve(staking.address, new BN(2).pow(new BN(255)), {from: wallet5});
});
describe('test gas usage', () => {
beforeEach( async () => {
await slp.approve(onsen.address, new BN(2).pow(new BN(255)), {from: wallet1});
await slp.approve(onsen.address, new BN(2).pow(new BN(255)), {from: wallet2});
await slp.approve(onsen.address, new BN(2).pow(new BN(255)), {from: wallet3});
});
it('should stake to onsen contract', async () => {
const amount = ether('2');
let totalStaked = await bn(0);
for (let i = 1; i < 50; i++) {
const stakeAmount = await bn(amount).mul(bn(i));
await onsen.deposit(PID, stakeAmount, {from: wallet1});
await onsen.deposit(PID, stakeAmount, {from: wallet2});
await onsen.deposit(PID, stakeAmount, {from: wallet3});
await staking.stake(stakeAmount, {from: wallet1});
await staking.stake(stakeAmount, {from: wallet2});
await staking.stake(stakeAmount, {from: wallet3});
totalStaked = await totalStaked.add(stakeAmount);
}
for (let i = 1; i < 50; i++) {
const stakeAmount = await bn(amount).mul(bn(i));
await staking.withdraw(stakeAmount, {from: wallet1});
await staking.withdraw(stakeAmount, {from: wallet2});
await staking.withdraw(stakeAmount, {from: wallet3});
await onsen.withdraw(PID, stakeAmount, {from: wallet1});
await onsen.withdraw(PID, stakeAmount, {from: wallet2});
await onsen.withdraw(PID, stakeAmount, {from: wallet3});
}
});
it('should withdraw from onsen contract', async () => {
await staking.stake(ether('100'), {from: wallet1});
expect((await staking.userInfo(wallet1)).amount).to.be.a.bignumber.equal(ether('100'));
expect((await onsen.userInfo(PID, staking.address)).amount).to.be.a.bignumber.equal(ether('100'));
await staking.withdraw(ether('100'), {from: wallet1});
expect((await staking.userInfo(wallet1)).amount).to.be.a.bignumber.equal('0');
expect((await onsen.userInfo(PID, staking.address)).amount).to.be.a.bignumber.equal('0');
});
});
});
// contracts/zkAsset.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "../localhost/@openzeppelin/contracts/access/AccessControl.sol";
import "../localhost/@openzeppelin/contracts/GSN/Context.sol";
import "../localhost/@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../localhost/@openzeppelin/contracts/token/ERC20/ERC20Burnable.sol";
import "../localhost/@openzeppelin/contracts/token/ERC20/ERC20Pausable.sol";
contract zkAsset is ERC20, AccessControl, ERC20Burnable, ERC20Pausable {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
constructor() public ERC20("zkTEST-Asset", "zkA") {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setupRole(MINTER_ROLE, msg.sender);
_setupRole(BURNER_ROLE, msg.sender);
_setupRole(PAUSER_ROLE, msg.sender);
}
/**
* @dev Destroys `amount` tokens for `from`.
*
* See {ERC20-_burn}.
*
* Requirements:
*
* - the caller must have the `BURNER_ROLE`.
*/
function burn(address from, uint256 amount) public {
require(hasRole(BURNER_ROLE, msg.sender), "zkAsset: must have burner role to burn");
_burn(from, amount);
}
/**
* @dev Creates `amount` new tokens for `to`.
*
* See {ERC20-_mint}.
*
* Requirements:
*
* - the caller must have the `MINTER_ROLE`.
*/
function mint(address to, uint256 amount) public {
require(hasRole(MINTER_ROLE, _msgSender()), "zkAsset: must have minter role to mint");
_mint(to, amount);
}
/**
* @dev Pauses all token transfers.
*
* See {ERC20Pausable} and {Pausable-_pause}.
*
* Requirements:
*
* - the caller must have the `PAUSER_ROLE`.
*/
function pause() public {
require(hasRole(PAUSER_ROLE, _msgSender()), "zkAsset: must have pauser role to pause");
_pause();
}
/**
* @dev Unpauses all token transfers.
*
* See {ERC20Pausable} and {Pausable-_unpause}.
*
* Requirements:
*
* - the caller must have the `PAUSER_ROLE`.
*/
function unpause() public {
require(hasRole(PAUSER_ROLE, _msgSender()), "zkAsset: must have pauser role to unpause");
_unpause();
}
function _beforeTokenTransfer(address from, address to, uint256 amount) internal override(ERC20, ERC20Pausable) {
super._beforeTokenTransfer(from, to, amount);
}
}
// contracts/zkAsset.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/AccessControl.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/GSN/Context.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20Pausable.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20Burnable.sol";
contract zkAasset is ERC20, AccessControl, ERC20Burnable, ERC20Pausable {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
constructor() public ERC20("xftTEST-Asset", "xftT") {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setupRole(MINTER_ROLE, msg.sender);
_setupRole(BURNER_ROLE, msg.sender);
_setupRole(PAUSER_ROLE, msg.sender);
}
/**
* @dev Destroys `amount` tokens for `from`.
*
* See {ERC20-_burn}.
*
* Requirements:
*
* - the caller must have the `BURNER_ROLE`.
*/
function burn(address from, uint256 amount) public {
require(hasRole(BURNER_ROLE, msg.sender), "xftTEST-Asset: must have burner role to burn");
_burn(from, amount);
}
/**
* @dev Creates `amount` new tokens for `to`.
*
* See {ERC20-_mint}.
*
* Requirements:
*
* - the caller must have the `MINTER_ROLE`.
*/
function mint(address to, uint256 amount) public {
require(hasRole(MINTER_ROLE, _msgSender()), "xftTEST-Asset: must have minter role to mint");
_mint(to, amount);
}
/**
* @dev Pauses all token transfers.
*
* See {ERC20Pausable} and {Pausable-_pause}.
*
* Requirements:
*
* - the caller must have the `PAUSER_ROLE`.
*/
function pause() public {
require(hasRole(PAUSER_ROLE, _msgSender()), "xftTEST-Asset: must have pauser role to pause");
_pause();
}
/**
* @dev Unpauses all token transfers.
*
* See {ERC20Pausable} and {Pausable-_unpause}.
*
* Requirements:
*
* - the caller must have the `PAUSER_ROLE`.
*/
function unpause() public {
require(hasRole(PAUSER_ROLE, _msgSender()), "xftTEST-Asset: must have pauser role to unpause");
_unpause();
}
function _beforeTokenTransfer(address from, address to, uint256 amount) internal override(ERC20, ERC20Pausable) {
super._beforeTokenTransfer(from, to, amount);
}
}
const Migrations = artifacts.require("Migrations");
module.exports = function (deployer) {
deployer.deploy(Migrations);
};
const StakingRewards = artifacts.require('StakingRewards');
const XFT_TOKEN_ADDRESS = '0xabe580e7ee158da464b51ee1a83ac0289622e6be'.toLowerCase();
const SUSHI_TOKEN_ADDRESS = '0x6b3595068778dd592e39a122f4f5a5cf09c90fe2'.toLowerCase();
const SLP_TOKEN_ADDRESS = '0xF39fF863730268C9bb867b3a69d031d1C1614b31'.toLowerCase();
const MASTER_CHEF_ADDRESS = '0xc2EdaD668740f1aA35E4D8f227fB8E17dcA888Cd'.toLowerCase();
const PID = '149'; // ! BEFORE MIGRATE: make sure that you set up correct PID
module.exports = async (deployer, network, accounts) => {
await deployer.deploy(
StakingRewards,
XFT_TOKEN_ADDRESS,
SUSHI_TOKEN_ADDRESS,
SLP_TOKEN_ADDRESS,
MASTER_CHEF_ADDRESS,
PID,
);
const staking = await StakingRewards.deployed();
// await xft.transfer(staking.address, ALLOCATE_POINT_XFT);
// await staking.notifyRewardAmount(ALLOCATE_POINT_XFT);
console.log(`XFT Token: https://etherscan.io/address/${XFT_TOKEN_ADDRESS}`);
console.log(`SUSHI Token: https://etherscan.io/address/${SUSHI_TOKEN_ADDRESS}`);
console.log(`XFT/ETH SLP Token: https://etherscan.io/address/${SLP_TOKEN_ADDRESS}`);
console.log(`Staking contract: https://etherscan.io/address/${staking.address}`);
console.log(`MasterChef contract: https://etherscan.io/address/${MASTER_CHEF_ADDRESS}`);
console.log('Address owner: ', accounts[0]);
};
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "solidity-contracts",
"version": "1.0.0",
"description": "",
"main": "index.js",
"repository": {
"type": "git",
"url": "git+https://zokyo-secured@github.com/offshift-protocol/solidity-contracts.git"
},
"author": "ZOKYO Team",
"license": "MIT",
"bugs": {
"url": "https://github.com/offshift-protocol/solidity-contracts/issues"
},
"homepage": "https://github.com/offshift-protocol/solidity-contracts#readme",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"solhint": "./node_modules/.bin/solhint -f table contracts/**/*.sol",
"prettier:solidity": "./node_modules/.bin/prettier --write contracts/**/*.sol",
"test:coverage": "truffle run coverage",
"compile": "npm run clean && truffle compile --all",
"lint:js": "eslint *.js test/*.js",
"lint:sol": "solhint contracts/*.sol"
},
"dependencies": {
"@openzeppelin/contracts": "^3.2.1-solc-0.7",
"@truffle/hdwallet-provider": "^1.2.1",
"dotenv": "^8.2.0",
"ganache-cli": "^6.10.1",
"solc": "^0.7.4",
"truffle": "^5.1.64"
},
"devDependencies": {
"@openzeppelin/test-helpers": "^0.5.6",
"chai": "^4.2.0",
"eslint": "^7.7.0",
"eslint-config-google": "^0.14.0",
"eth-gas-reporter": "^0.2.22",
"eth-sig-util": "^3.0.0",
"ethlint": "^1.2.5",
"mocha": "^8.1.1",
"prettier": "^2.2.1",
"prettier-plugin-solidity": "^1.0.0-beta.5",
"solhint": "^3.2.0",
"solhint-plugin-prettier": "0.0.5",
"solidity-coverage": "^0.7.16",
"truffle-plugin-verify": "^0.5.4",
"web3": "^1.2.11"
}
}
/**
* Use this file to configure your truffle project. It's seeded with some
* common settings for different networks and features like migrations,
* compilation and testing. Uncomment the ones you need or modify
* them to suit your project as necessary.
*
* More information about configuration can be found at:
*
* trufflesuite.com/docs/advanced/configuration
*
* To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider)
* to sign your transactions before they're sent to a remote public node. Infura accounts
* are available for free at: infura.io/register.
*
* You'll also need a mnemonic - the twelve word phrase the wallet uses to generate
* public/private key pairs. If you're publishing your code to GitHub make sure you load this
* phrase from a file you've .gitignored so it doesn't accidentally become public.
*
*/
const HDWalletProvider = require('@truffle/hdwallet-provider');
const dotenv = require('dotenv');
dotenv.config();
module.exports = {
/**
* Networks define how you connect to your ethereum client and let you set the
* defaults web3 uses to send transactions. If you don't specify one truffle
* will spin up a development blockchain for you on port 9545 when you
* run `develop` or `test`. You can ask a truffle command to use a specific
* network from the command line, e.g
*
* $ truffle test --network <network-name>
*/
networks: {
development: {
host: '127.0.0.1', // Localhost (default: none)
port: 8545, // Standard Ethereum port (default: none)
network_id: '*', // Any network (default: none)
},
ropsten: {
provider: () => new HDWalletProvider([process.env.PRIVATE_KEY], `wss://ropsten.infura.io/ws/v3/${process.env.PROJECT_ID}`),
network_id: 3, // Ropsten's id
gas: 6721975, // Ropsten has a lower block limit than mainnet
gasPrice: 160000000000,
skipDryRun: true, // Skip dry run before migrations? (default: false for public nets )
},
rinkeby: {
provider: () => new HDWalletProvider([process.env.PRIVATE_KEY], `wss://rinkeby.infura.io/ws/v3/${process.env.PROJECT_ID}`),
network_id: 4,
gas: 10000000,
skipDryRun: true,
},
mainnet: {
provider: () => new HDWalletProvider([process.env.PRIVATE_KEY], `wss://mainnet.infura.io/ws/v3/${process.env.PROJECT_ID}`),
network_id: 1,
gas: 3000000,
gasPrice: 160000000000,
skipDryRun: true,
},
},
mocha: {
timeout: 300000,
reporter: 'eth-gas-reporter',
reporterOptions: {
showTimeSpent: true,
currency: 'USD',
coinmarketcap: process.env.COINMARKETCAP_API_KEY,
},
},
plugins: ['truffle-plugin-verify', 'solidity-coverage'],
api_keys: {
etherscan: process.env.ETHERSCAN_KEY,
},
// Configure your compilers
compilers: {
solc: {
version: '>=0.4.20 <0.8.0', // Fetch exact version from solc-bin (default: truffle's version)
docker: false, // Use "0.5.1" you've installed locally with docker (default: false)
settings: { // See the solidity docs for advice about optimization and evmVersion
optimizer: {
enabled: true,
runs: 200,
},
evmVersion: 'istanbul',
},
},
},
};
MIT License
Copyright (c) 2020 offshift
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment