文件 1 的 14:AccessControl.sol
pragma solidity ^0.8.0;
import "../utils/Context.sol";
import "../utils/Strings.sol";
import "../utils/introspection/ERC165.sol";
interface IAccessControl {
function hasRole(bytes32 role, address account) external view returns (bool);
function getRoleAdmin(bytes32 role) external view returns (bytes32);
function grantRole(bytes32 role, address account) external;
function revokeRole(bytes32 role, address account) external;
function renounceRole(bytes32 role, address account) external;
}
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping (address => bool) members;
bytes32 adminRole;
}
mapping (bytes32 => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
modifier onlyRole(bytes32 role) {
_checkRole(role, _msgSender());
_;
}
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId
|| super.supportsInterface(interfaceId);
}
function hasRole(bytes32 role, address account) public view override returns (bool) {
return _roles[role].members[account];
}
function _checkRole(bytes32 role, address account) internal view {
if(!hasRole(role, account)) {
revert(string(abi.encodePacked(
"AccessControl: account ",
Strings.toHexString(uint160(account), 20),
" is missing role ",
Strings.toHexString(uint256(role), 32)
)));
}
}
function getRoleAdmin(bytes32 role) public view override returns (bytes32) {
return _roles[role].adminRole;
}
function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
function renounceRole(bytes32 role, address account) public virtual override {
require(account == _msgSender(), "AccessControl: can only renounce roles for self");
_revokeRole(role, account);
}
function _setupRole(bytes32 role, address account) internal virtual {
_grantRole(role, account);
}
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
emit RoleAdminChanged(role, getRoleAdmin(role), adminRole);
_roles[role].adminRole = adminRole;
}
function _grantRole(bytes32 role, address account) private {
if (!hasRole(role, account)) {
_roles[role].members[account] = true;
emit RoleGranted(role, account, _msgSender());
}
}
function _revokeRole(bytes32 role, address account) private {
if (hasRole(role, account)) {
_roles[role].members[account] = false;
emit RoleRevoked(role, account, _msgSender());
}
}
}
文件 2 的 14:Address.sol
pragma solidity ^0.8.0;
library Address {
function isContract(address account) internal view returns (bool) {
uint256 size;
assembly { size := extcodesize(account) }
return size > 0;
}
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{ value: amount }("");
require(success, "Address: unable to send value, recipient may have reverted");
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{ value: value }(data);
return _verifyCallResult(success, returndata, errorMessage);
}
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
if (success) {
return returndata;
} else {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
文件 3 的 14:Context.sol
pragma solidity ^0.8.0;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
this;
return msg.data;
}
}
文件 4 的 14:ERC165.sol
pragma solidity ^0.8.0;
import "./IERC165.sol";
abstract contract ERC165 is IERC165 {
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
文件 5 的 14:ERC721Holder.sol
pragma solidity ^0.8.0;
import "../IERC721Receiver.sol";
contract ERC721Holder is IERC721Receiver {
function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) {
return this.onERC721Received.selector;
}
}
文件 6 的 14:IERC165.sol
pragma solidity ^0.8.0;
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
文件 7 的 14:IERC20.sol
pragma solidity ^0.8.0;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
文件 8 的 14:IERC721.sol
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
interface IERC721 is IERC165 {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function setApprovalForAll(address operator, bool _approved) external;
function isApprovedForAll(address owner, address operator) external view returns (bool);
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
}
文件 9 的 14:IERC721Receiver.sol
pragma solidity ^0.8.0;
interface IERC721Receiver {
function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external returns (bytes4);
}
文件 10 的 14:Math.sol
pragma solidity ^0.8.0;
library Math {
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a >= b ? a : b;
}
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
function average(uint256 a, uint256 b) internal pure returns (uint256) {
return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);
}
}
文件 11 的 14:RainiLPv3StakingPool.sol
pragma solidity ^0.8.3;
import "@openzeppelin/contracts/utils/math/Math.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
interface INonfungiblePositionManager is IERC721 {
function positions(uint256 tokenId)
external
view
returns (
uint96 nonce,
address operator,
address token0,
address token1,
uint24 fee,
int24 tickLower,
int24 tickUpper,
uint128 liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128,
uint128 tokensOwed0,
uint128 tokensOwed1
);
}
interface RainiLpv2StakingPool {
function burn(address _owner, uint256 _amount) external;
function balanceOf(address _owner) external view returns(uint256);
}
contract RainiLpv3StakingPool is AccessControl, ReentrancyGuard, ERC721Holder {
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
using SafeERC20 for IERC20;
uint256 public rewardRate;
uint256 public minRewardStake;
uint256 constant public REWARD_DECIMALS = 1000000;
uint256 public maxBonus;
uint256 public bonusDuration;
uint256 public bonusRate;
uint256 constant public BONUS_DECIMALS = 1000000000;
uint256 constant public RAINI_REWARD_DECIMALS = 1000000000000;
int24 public minTickUpper;
int24 public maxTickLower;
uint24 public feeRequired;
INonfungiblePositionManager public rainiLpNFT;
RainiLpv2StakingPool public rainiLpv2StakingPool;
IERC20 public rainiToken;
address public exchangeTokenAddress;
uint256 public unicornToEth;
uint256 public totalSupply;
struct GeneralRewardVars {
uint32 lastUpdateTime;
uint64 rainiRewardPerTokenStored;
uint32 periodFinish;
uint128 rainiRewardRate;
}
GeneralRewardVars public generalRewardVars;
struct AccountRewardVars {
uint40 lastBonus;
uint32 lastUpdated;
uint104 rainiRewards;
uint64 rainiRewardPerTokenPaid;
}
struct AccountVars {
uint128 staked;
uint128 unicornBalance;
}
mapping(address => AccountRewardVars) internal accountRewardVars;
mapping(address => AccountVars) internal accountVars;
mapping(address => uint32[]) internal stakedNFTs;
event EthWithdrawn(uint256 amount);
event RewardSet(uint256 rewardRate, uint256 minRewardStake);
event BonusesSet(uint256 maxBonus, uint256 bonusDuration);
event RainiLpTokenSet(address token);
event UnicornToEthSet(uint256 unicornToEth);
event TickRangeSet(int24 minTickUpper, int24 maxTickLower);
event FeeRequiredSet(uint24 feeRequired);
event TokensStaked(address payer, uint256 amount, uint256 timestamp);
event TokensWithdrawn(address owner, uint256 amount, uint256 timestamp);
event UnicornPointsBurned(address owner, uint256 amount);
event UnicornPointsMinted(address owner, uint256 amount);
event UnicornPointsBought(address owner, uint256 amount);
event RewardWithdrawn(address owner, uint256 amount, uint256 timestamp);
event RewardPoolAdded(uint256 _amount, uint256 _duration, uint256 timestamp);
constructor(address _rainiLpNFT, address _rainiToken, address _exchangeToken, address _v2Pool) {
require(_rainiLpNFT != address(0), "RainiLpv3StakingPool: _rainiLpToken is zero address");
_setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
rainiLpNFT = INonfungiblePositionManager(_rainiLpNFT);
exchangeTokenAddress = _exchangeToken;
rainiToken = IERC20(_rainiToken);
rainiLpv2StakingPool = RainiLpv2StakingPool(_v2Pool);
}
modifier onlyOwner() {
require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "RainiLpv3StakingPool: caller is not an owner");
_;
}
modifier onlyBurner() {
require(hasRole(BURNER_ROLE, _msgSender()), "RainiLpv3StakingPool: caller is not a burner");
_;
}
modifier onlyMinter() {
require(hasRole(MINTER_ROLE, _msgSender()), "RainiLpv3StakingPool: caller is not a minter");
_;
}
modifier balanceUpdate(address _owner) {
AccountRewardVars memory _accountRewardVars = accountRewardVars[_owner];
AccountVars memory _accountVars = accountVars[_owner];
GeneralRewardVars memory _generalRewardVars = generalRewardVars;
_generalRewardVars.rainiRewardPerTokenStored = uint64(rainiRewardPerToken());
_generalRewardVars.lastUpdateTime = uint32(lastTimeRewardApplicable());
if (_owner != address(0)) {
uint32 duration = uint32(block.timestamp) - _accountRewardVars.lastUpdated;
uint128 reward = calculateReward(_owner, _accountVars.staked, duration);
_accountVars.unicornBalance = _accountVars.unicornBalance + reward;
_accountRewardVars.lastUpdated = uint32(block.timestamp);
_accountRewardVars.lastBonus = uint40(Math.min(maxBonus, _accountRewardVars.lastBonus + bonusRate * duration));
_accountRewardVars.rainiRewards = uint104(rainiEarned(_owner));
_accountRewardVars.rainiRewardPerTokenPaid = _generalRewardVars.rainiRewardPerTokenStored;
}
accountRewardVars[_owner] = _accountRewardVars;
accountVars[_owner] = _accountVars;
generalRewardVars = _generalRewardVars;
_;
}
function getRewardByDuration(address _owner, uint256 _amount, uint256 _duration)
public view returns(uint256) {
return calculateReward(_owner, _amount, _duration);
}
function getStaked(address _owner)
public view returns(uint256) {
return accountVars[_owner].staked;
}
function getStakedPositions(address _owner)
public view returns(uint32[] memory) {
return stakedNFTs[_owner];
}
function balanceOf(address _owner)
public view returns(uint256) {
uint256 reward = calculateReward(_owner, accountVars[_owner].staked, block.timestamp - accountRewardVars[_owner].lastUpdated);
return accountVars[_owner].unicornBalance + reward;
}
function getCurrentBonus(address _owner)
public view returns(uint256) {
AccountRewardVars memory _accountRewardVars = accountRewardVars[_owner];
if(accountVars[_owner].staked == 0) {
return 0;
}
uint256 duration = block.timestamp - _accountRewardVars.lastUpdated;
return Math.min(maxBonus, _accountRewardVars.lastBonus + bonusRate * duration);
}
function getCurrentAvgBonus(address _owner, uint256 _duration)
public view returns(uint256) {
AccountRewardVars memory _accountRewardVars = accountRewardVars[_owner];
if(accountVars[_owner].staked == 0) {
return 0;
}
uint256 avgBonus;
if(_accountRewardVars.lastBonus < maxBonus) {
uint256 durationTillMax = (maxBonus - _accountRewardVars.lastBonus) / bonusRate;
if(_duration > durationTillMax) {
uint256 avgWeightedBonusTillMax = (_accountRewardVars.lastBonus + maxBonus) * durationTillMax / 2;
uint256 weightedMaxBonus = maxBonus * (_duration - durationTillMax);
avgBonus = (avgWeightedBonusTillMax + weightedMaxBonus) / _duration;
} else {
avgBonus = (_accountRewardVars.lastBonus + bonusRate * _duration + _accountRewardVars.lastBonus) / 2;
}
} else {
avgBonus = maxBonus;
}
return avgBonus;
}
function setReward(uint256 _rewardRate, uint256 _minRewardStake)
external onlyOwner {
rewardRate = _rewardRate;
minRewardStake = _minRewardStake;
emit RewardSet(rewardRate, minRewardStake);
}
function setUnicornToEth(uint256 _unicornToEth)
external onlyOwner {
unicornToEth = _unicornToEth;
emit UnicornToEthSet(_unicornToEth);
}
function setBonus(uint256 _maxBonus, uint256 _bonusDuration)
external onlyOwner {
maxBonus = _maxBonus * BONUS_DECIMALS;
bonusDuration = _bonusDuration;
bonusRate = maxBonus / _bonusDuration;
emit BonusesSet(_maxBonus, _bonusDuration);
}
function setTickRange(int24 _maxTickLower, int24 _minTickUpper)
external onlyOwner {
minTickUpper = _minTickUpper;
maxTickLower = _maxTickLower;
emit TickRangeSet(_minTickUpper, _maxTickLower);
}
function setFeeRequired(uint24 _feeRequired)
external onlyOwner {
feeRequired = _feeRequired;
emit FeeRequiredSet(_feeRequired);
}
function stake(uint32 _tokenId)
external nonReentrant balanceUpdate(_msgSender()) {
(
,
,
address token0,
address token1,
uint24 fee,
int24 tickLower,
int24 tickUpper,
uint128 liquidity,
,
,
,
) = rainiLpNFT.positions(_tokenId);
require(tickUpper > minTickUpper, "RainiLpv3StakingPool: tickUpper too low");
require(tickLower < maxTickLower, "RainiLpv3StakingPool: tickLower too high");
require((token0 == exchangeTokenAddress && token1 == address(rainiToken)) ||
(token1 == exchangeTokenAddress && token0 == address(rainiToken)), "RainiLpv3StakingPool: NFT tokens not correct");
require(fee == feeRequired, "RainiLpv3StakingPool: fee != feeRequired");
rainiLpNFT.safeTransferFrom(_msgSender(), address(this), _tokenId);
uint32[] memory nfts = stakedNFTs[_msgSender()];
bool wasAdded = false;
for (uint i = 0; i < nfts.length; i++) {
if (nfts[i] == 0) {
stakedNFTs[_msgSender()][i] = _tokenId;
wasAdded = true;
break;
}
}
if (!wasAdded) {
stakedNFTs[_msgSender()].push(_tokenId);
}
totalSupply = totalSupply + liquidity;
uint128 currentStake = accountVars[_msgSender()].staked;
accountVars[_msgSender()].staked = currentStake + liquidity;
accountRewardVars[_msgSender()].lastBonus = uint40(accountRewardVars[_msgSender()].lastBonus * currentStake / (currentStake + liquidity));
emit TokensStaked(_msgSender(), liquidity, block.timestamp);
}
function withdraw(uint256 _tokenId)
external nonReentrant balanceUpdate(_msgSender()) {
bool ownsNFT = false;
uint32[] memory nfts = stakedNFTs[_msgSender()];
for (uint i = 0; i < nfts.length; i++) {
if (nfts[i] == _tokenId) {
ownsNFT = true;
delete stakedNFTs[_msgSender()][i];
break;
}
}
require(ownsNFT == true, "RainiLpv3StakingPool: Not the owner");
rainiLpNFT.safeTransferFrom(address(this), _msgSender(), _tokenId);
(
,
,
,
,
,
,
,
uint128 liquidity,
,
,
,
) = rainiLpNFT.positions(_tokenId);
accountVars[_msgSender()].staked = accountVars[_msgSender()].staked - liquidity;
totalSupply = totalSupply - liquidity;
emit TokensWithdrawn(_msgSender(), liquidity, block.timestamp);
}
function withdrawEth(uint256 _amount)
external onlyOwner {
require(_amount <= address(this).balance, "RainiLpv3StakingPool: not enough balance");
(bool success, ) = _msgSender().call{ value: _amount }("");
require(success, "RainiLpv3StakingPool: transfer failed");
emit EthWithdrawn(_amount);
}
function mint(address[] calldata _addresses, uint256[] calldata _points)
external onlyMinter {
require(_addresses.length == _points.length, "RainiLpv3StakingPool: Arrays not equal");
for (uint256 i = 0; i < _addresses.length; i++) {
accountVars[_addresses[i]].unicornBalance = uint128(accountVars[_addresses[i]].unicornBalance + _points[i]);
emit UnicornPointsMinted(_addresses[i], _points[i]);
}
}
function buyUnicorn(uint256 _amount)
external payable {
require(_amount > 0, "RainiLpv3StakingPool: _amount is zero");
require(msg.value * unicornToEth >= _amount, "RainiLpv3StakingPool: not enougth eth");
accountVars[_msgSender()].unicornBalance = uint128(accountVars[_msgSender()].unicornBalance + _amount);
uint256 refund = msg.value - (_amount / unicornToEth);
if(refund > 0) {
(bool success, ) = _msgSender().call{ value: refund }("");
require(success, "RainiLpv3StakingPool: transfer failed");
}
emit UnicornPointsBought(_msgSender(), _amount);
}
function burn(address _owner, uint256 _amount)
external nonReentrant onlyBurner balanceUpdate(_owner) {
accountVars[_owner].unicornBalance = uint128(accountVars[_owner].unicornBalance - _amount);
emit UnicornPointsBurned(_owner, _amount);
}
function calculateReward(address _owner, uint256 _amount, uint256 _duration)
private view returns(uint128) {
uint256 reward = _duration * rewardRate * _amount / (REWARD_DECIMALS * minRewardStake);
return calculateBonus(_owner, reward, _duration);
}
function calculateBonus(address _owner, uint256 _amount, uint256 _duration)
private view returns(uint128) {
uint256 avgBonus = getCurrentAvgBonus(_owner, _duration);
return uint128(_amount + _amount * avgBonus / BONUS_DECIMALS / 100);
}
function lastTimeRewardApplicable() public view returns (uint256) {
return Math.min(block.timestamp, generalRewardVars.periodFinish);
}
function rainiRewardPerToken() public view returns (uint256) {
GeneralRewardVars memory _generalRewardVars = generalRewardVars;
if (totalSupply == 0) {
return _generalRewardVars.rainiRewardPerTokenStored;
}
return _generalRewardVars.rainiRewardPerTokenStored + (uint256(lastTimeRewardApplicable() - _generalRewardVars.lastUpdateTime) * _generalRewardVars.rainiRewardRate * RAINI_REWARD_DECIMALS) / totalSupply;
}
function rainiEarned(address account) public view returns (uint256) {
AccountRewardVars memory _accountRewardVars = accountRewardVars[account];
AccountVars memory _accountVars = accountVars[account];
uint256 calculatedEarned = (uint256(_accountVars.staked) * (rainiRewardPerToken() - _accountRewardVars.rainiRewardPerTokenPaid)) / RAINI_REWARD_DECIMALS + _accountRewardVars.rainiRewards;
uint256 poolBalance = rainiToken.balanceOf(address(this));
if (calculatedEarned > poolBalance) return poolBalance;
return calculatedEarned;
}
function addRainiRewardPool(uint256 _amount, uint256 _duration)
external onlyOwner nonReentrant balanceUpdate(address(0)) {
GeneralRewardVars memory _generalRewardVars = generalRewardVars;
if (_generalRewardVars.periodFinish > block.timestamp) {
uint256 timeRemaining = _generalRewardVars.periodFinish - block.timestamp;
_amount += timeRemaining * _generalRewardVars.rainiRewardRate;
}
rainiToken.safeTransferFrom(_msgSender(), address(this), _amount);
_generalRewardVars.rainiRewardRate = uint128(_amount / _duration);
_generalRewardVars.periodFinish = uint32(block.timestamp + _duration);
_generalRewardVars.lastUpdateTime = uint32(block.timestamp);
generalRewardVars = _generalRewardVars;
emit RewardPoolAdded(_amount, _duration, block.timestamp);
}
function abortRainiRewardPool() external onlyOwner nonReentrant balanceUpdate(address(0)) {
GeneralRewardVars memory _generalRewardVars = generalRewardVars;
require (_generalRewardVars.periodFinish > block.timestamp, "Reward pool is not active");
uint256 timeRemaining = _generalRewardVars.periodFinish - block.timestamp;
uint256 remainingAmount = timeRemaining * _generalRewardVars.rainiRewardRate;
rainiToken.transfer(_msgSender(), remainingAmount);
_generalRewardVars.rainiRewardRate = 0;
_generalRewardVars.periodFinish = uint32(block.timestamp);
_generalRewardVars.lastUpdateTime = uint32(block.timestamp);
generalRewardVars = _generalRewardVars;
}
function recoverRaini(uint256 _amount) external onlyOwner nonReentrant {
require(generalRewardVars.periodFinish < block.timestamp, "Raini cannot be recovered while reward pool active.");
rainiToken.transfer(_msgSender(), _amount);
}
function withdrawReward() external nonReentrant balanceUpdate(_msgSender()) {
uint256 reward = rainiEarned(_msgSender());
require(reward > 1, "no reward to withdraw");
if (reward > 1) {
accountRewardVars[_msgSender()].rainiRewards = 0;
rainiToken.safeTransfer(_msgSender(), reward);
}
emit RewardWithdrawn(_msgSender(), reward, block.timestamp);
}
function migrateV2Unicorns() external {
uint256 balance = rainiLpv2StakingPool.balanceOf(_msgSender());
rainiLpv2StakingPool.burn(_msgSender(), balance);
accountVars[_msgSender()].unicornBalance = uint128(accountVars[_msgSender()].unicornBalance + balance);
emit UnicornPointsMinted(_msgSender(), balance);
}
}
文件 12 的 14:ReentrancyGuard.sol
pragma solidity ^0.8.0;
abstract contract ReentrancyGuard {
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor () {
_status = _NOT_ENTERED;
}
modifier nonReentrant() {
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
_status = _ENTERED;
_;
_status = _NOT_ENTERED;
}
}
文件 13 的 14:SafeERC20.sol
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../../../utils/Address.sol";
library SafeERC20 {
using Address for address;
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
function safeApprove(IERC20 token, address spender, uint256 value) internal {
require((value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
function _callOptionalReturn(IERC20 token, bytes memory data) private {
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
文件 14 的 14:Strings.sol
pragma solidity ^0.8.0;
library Strings {
bytes16 private constant alphabet = "0123456789abcdef";
function toString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
function toHexString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0x00";
}
uint256 temp = value;
uint256 length = 0;
while (temp != 0) {
length++;
temp >>= 8;
}
return toHexString(value, length);
}
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = alphabet[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
}
{
"compilationTarget": {
"contracts/RainiLPv3StakingPool.sol": "RainiLpv3StakingPool"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_rainiLpNFT","type":"address"},{"internalType":"address","name":"_rainiToken","type":"address"},{"internalType":"address","name":"_exchangeToken","type":"address"},{"internalType":"address","name":"_v2Pool","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxBonus","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"bonusDuration","type":"uint256"}],"name":"BonusesSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"EthWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint24","name":"feeRequired","type":"uint24"}],"name":"FeeRequiredSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"}],"name":"RainiLpTokenSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_duration","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"RewardPoolAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"rewardRate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minRewardStake","type":"uint256"}],"name":"RewardSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"RewardWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"int24","name":"minTickUpper","type":"int24"},{"indexed":false,"internalType":"int24","name":"maxTickLower","type":"int24"}],"name":"TickRangeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"payer","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"TokensStaked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"TokensWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"UnicornPointsBought","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"UnicornPointsBurned","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"UnicornPointsMinted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"unicornToEth","type":"uint256"}],"name":"UnicornToEthSet","type":"event"},{"inputs":[],"name":"BONUS_DECIMALS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURNER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RAINI_REWARD_DECIMALS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REWARD_DECIMALS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"abortRainiRewardPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_duration","type":"uint256"}],"name":"addRainiRewardPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bonusDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bonusRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"buyUnicorn","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"exchangeTokenAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeRequired","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"generalRewardVars","outputs":[{"internalType":"uint32","name":"lastUpdateTime","type":"uint32"},{"internalType":"uint64","name":"rainiRewardPerTokenStored","type":"uint64"},{"internalType":"uint32","name":"periodFinish","type":"uint32"},{"internalType":"uint128","name":"rainiRewardRate","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"uint256","name":"_duration","type":"uint256"}],"name":"getCurrentAvgBonus","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"getCurrentBonus","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_duration","type":"uint256"}],"name":"getRewardByDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"getStaked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"getStakedPositions","outputs":[{"internalType":"uint32[]","name":"","type":"uint32[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastTimeRewardApplicable","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxBonus","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxTickLower","outputs":[{"internalType":"int24","name":"","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"migrateV2Unicorns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"minRewardStake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minTickUpper","outputs":[{"internalType":"int24","name":"","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_addresses","type":"address[]"},{"internalType":"uint256[]","name":"_points","type":"uint256[]"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"rainiEarned","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rainiLpNFT","outputs":[{"internalType":"contract INonfungiblePositionManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rainiLpv2StakingPool","outputs":[{"internalType":"contract RainiLpv2StakingPool","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rainiRewardPerToken","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rainiToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"recoverRaini","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rewardRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxBonus","type":"uint256"},{"internalType":"uint256","name":"_bonusDuration","type":"uint256"}],"name":"setBonus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint24","name":"_feeRequired","type":"uint24"}],"name":"setFeeRequired","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_rewardRate","type":"uint256"},{"internalType":"uint256","name":"_minRewardStake","type":"uint256"}],"name":"setReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"int24","name":"_maxTickLower","type":"int24"},{"internalType":"int24","name":"_minTickUpper","type":"int24"}],"name":"setTickRange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_unicornToEth","type":"uint256"}],"name":"setUnicornToEth","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"_tokenId","type":"uint32"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unicornToEth","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdrawEth","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawReward","outputs":[],"stateMutability":"nonpayable","type":"function"}]