Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
/**
* @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 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 Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
/**
* @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.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: 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 {
_transferOwnership(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), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// 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, "SafeMath: multiplication overflow");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
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;
}
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
function percentageOf(uint a, uint b) internal pure returns (uint256) {
require(b > 0);
return a * b / 100;
}
function percentageOf10000(uint a, uint b) internal pure returns (uint256) {
require(b > 0);
return a * b / 10000;
}
}
interface IToken {
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);
function name() external pure returns (string memory);
function symbol() external pure returns (string memory);
function decimals() external pure returns (uint);
function mint(address to, uint256 amount) external;
function burn(uint256 amount) external;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
interface INft {
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 transferFrom(
address from,
address to,
uint256 tokenId
) external;
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool _approved) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
library TransferHelper {
function safeApprove(
address token,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('approve(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::safeApprove: approve failed'
);
}
function safeTransfer(
address token,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('transfer(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::safeTransfer: transfer failed'
);
}
function safeTransferFrom(
address token,
address from,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::transferFrom: transferFrom failed'
);
}
function safeTransferETH(address to, uint256 value) internal {
(bool success, ) = to.call{value: value}(new bytes(0));
require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
}
function deposit(address _weth, uint256 _value) internal {
(bool success, ) = _weth.call{value: _value}(new bytes(0));
require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
}
function withdraw(address _weth, uint256 _value) internal {
(bool success, bytes memory data) = _weth.call(abi.encodeWithSelector(0x2e1a7d4d, _value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::withdraw: withdraw failed'
);
}
}
abstract contract BaseStake is Ownable {
struct Pool {
address erc721;
uint duration;
uint reward;
}
bytes32[] keys;
mapping(bytes32 => Pool) values;
mapping(bytes32 => uint256) indexOf;
mapping(bytes32 => bool) inserted;
uint private poolCounter;
function get(bytes32 key) internal view returns (bytes32, address, uint, uint) {
Pool memory val = values[key];
return (key, val.erc721, val.duration, val.reward);
}
function getKeyAtIndex(uint256 index) internal view returns (bytes32) {
return keys[index];
}
function size() internal view returns (uint256) {
return keys.length;
}
function push(
uint counter,
address erc721,
uint duration,
uint reward) private {
bytes32 key = keccak256(abi.encode(counter, erc721, duration, reward));
Pool memory val = Pool(erc721, duration, reward);
if (inserted[key]) {
values[key] = val;
} else {
inserted[key] = true;
values[key] = val;
indexOf[key] = keys.length;
keys.push(key);
}
}
function remove(bytes32 key) private {
if (!inserted[key]) {
return;
}
delete inserted[key];
delete values[key];
uint256 index = indexOf[key];
bytes32 lastKey = keys[keys.length - 1];
indexOf[lastKey] = index;
delete indexOf[key];
keys[index] = lastKey;
keys.pop();
}
function _addPool(address erc721, uint duration, uint reward) internal {
push(poolCounter, erc721, duration, reward);
poolCounter += 1;
}
function _removePool(bytes32 key) internal {
remove(key);
}
function getPools(bytes32 key) public view virtual returns (bytes32, address, uint, uint) {
(bytes32 id, address nft, uint duration, uint reward) = get(key);
return (id, nft, duration, reward);
}
function getPools() public view virtual returns (bytes32[] memory, address[] memory, uint[] memory, uint[] memory) {
uint poolSize = size();
bytes32[] memory ids = new bytes32[](poolSize);
address[] memory erc721 = new address[](poolSize);
uint[] memory durations = new uint[](poolSize);
uint[] memory rewards = new uint[](poolSize);
for (uint256 i = 0; i < size(); i++) {
bytes32 key = getKeyAtIndex(i);
(bytes32 id, address nft, uint duration, uint reward) = get(key);
ids[i] = id;
erc721[i] = nft;
durations[i] = duration;
rewards[i] = reward;
}
return (ids, erc721, durations, rewards);
}
}
contract Staking is BaseStake {
using SafeMath for uint;
struct Vesting {
bytes32 uid;
address erc721;
uint tokenId;
bytes32 poolId;
uint duration;
uint startDate;
uint endDate;
uint reward;
}
event LogStake(address sender, uint createdAt, bytes32 uid, address erc721, uint tokenId, bytes32 poolId, uint duration, uint startDate, uint endDate, uint reward);
event LogUnstake(address sender, uint createdAt, bytes32 uid, address erc721, uint tokenId, bytes32 poolId, uint duration, uint startDate, uint endDate, uint reward);
uint constant EXTRA_PERCENT = 10;
bool public initiated;
bool public paused;
address public stakingToken;
address public stakingNft;
uint256 private decimal;
mapping(bytes32 => Vesting) private staker;
uint private stakerCount;
constructor() BaseStake() {
}
modifier whenNotPaused() {
require(!paused, "paused");
_;
}
function init(address erc20, address erc721, uint256 duration) onlyOwner external {
require(!initiated, "already initiated");
stakingToken = erc20;
stakingNft = erc721;
decimal = 10 ** IToken(erc20).decimals();
_addPool(stakingNft, 10 * duration, 750 * decimal);
_addPool(stakingNft, 30 * duration, 3500 * decimal);
_addPool(stakingNft, 90 * duration, 16500 * decimal);
initiated = true;
}
function pause(bool enable) onlyOwner external {
paused = enable;
}
function addPool(address[] memory erc721, uint256[] memory duration, uint256[] memory reward) onlyOwner external {
for (uint256 i = 0; i < erc721.length; i++) {
_addPool(erc721[i], duration[i], reward[i]);
}
}
function removePool(bytes32[] memory ids) onlyOwner external {
for (uint256 i = 0; i < ids.length; i++) {
_removePool(ids[i]);
}
}
function stake(bytes32 poolId, address[] memory erc721, uint256[] memory tokenId, bool[] memory hasExtra) whenNotPaused external {
require(erc721.length > 0 && erc721.length == tokenId.length && erc721.length == hasExtra.length, "input error");
require(balance() > 0, "pool empty");
(bytes32 _poolId, address _nft, uint _duration, uint _reward) = get(poolId);
for (uint256 i = 0; i < erc721.length; i++) {
bytes32 uid = nftHash(erc721[i], tokenId[i]);
require(poolId == _poolId && erc721[i] == _nft, "pool not found");
require(staker[uid].uid == bytes32(0), "already staked");
require(INft(erc721[i]).ownerOf(tokenId[i]) == msg.sender, "not owner");
uint reward = _reward;
if(stakingNft == erc721[i] && hasExtra[i]) {
uint extra = reward.percentageOf(EXTRA_PERCENT);
reward += extra;
}
Vesting memory item = Vesting(uid, erc721[i], tokenId[i], poolId, _duration, block.timestamp, block.timestamp + _duration, reward);
staker[uid] = item;
logStake(item);
stakerCount += 1;
}
}
function claimAndStake(address[] memory erc721, uint256[] memory tokenId) whenNotPaused external {
require(erc721.length > 0 && erc721.length == tokenId.length, "input error");
require(balance() > 0, "pool empty");
uint totalReward = 0;
for (uint256 i = 0; i < erc721.length; i++) {
require(INft(erc721[i]).ownerOf(tokenId[i]) == msg.sender, "not owner");
bytes32 uid = nftHash(erc721[i], tokenId[i]);
Vesting memory current = staker[uid];
require(current.uid != bytes32(0), "not staked");
require(block.timestamp > current.endDate, "not reached yet");
(bytes32 _poolId, , ,) = get(current.poolId);
require(current.poolId == _poolId && current.erc721 == erc721[i], "pool not found");
logUnstake(current);
totalReward += current.reward;
Vesting memory item = Vesting(uid, erc721[i], tokenId[i], current.poolId, current.duration, block.timestamp, block.timestamp + current.duration, current.reward);
staker[uid] = item;
logStake(item);
}
sendReward(stakingToken, msg.sender, totalReward);
}
function unstake(address[] memory erc721, uint256[] memory tokenId) external {
require(erc721.length > 0 && erc721.length == tokenId.length, "input error");
uint totalReward = 0;
for (uint256 i = 0; i < erc721.length; i++) {
require(INft(erc721[i]).ownerOf(tokenId[i]) == msg.sender, "not owner");
Vesting memory current = staker[nftHash(erc721[i], tokenId[i])];
require(current.uid != bytes32(0), "not staked");
require(block.timestamp > current.endDate, "not reached yet");
logUnstake(current);
totalReward += current.reward;
stakerCount -= 1;
delete staker[current.uid];
}
sendReward(stakingToken, msg.sender, totalReward);
}
function balance() public view returns (uint256) {
return IToken(stakingToken).balanceOf(address(this));
}
function logStake(Vesting memory data) private {
emit LogStake(msg.sender, block.timestamp, data.uid, data.erc721, data.tokenId, data.poolId, data.duration, data.startDate, data.endDate, data.reward);
}
function logUnstake(Vesting memory data) private {
emit LogUnstake(msg.sender, block.timestamp, data.uid, data.erc721, data.tokenId, data.poolId, data.duration, data.startDate, data.endDate, data.reward);
}
function nftHash(address nft, uint256 tokenId) private pure returns (bytes32) {
return keccak256(abi.encode(nft, tokenId));
}
function sendReward(address erc20, address recipient, uint amount) private returns (uint256) {
uint _balance = balance();
if(_balance == 0 || amount == 0) {
return 0;
}
if(_balance >= amount) {
TransferHelper.safeTransfer(erc20, recipient, amount);
return amount;
}
TransferHelper.safeTransfer(erc20, recipient, _balance);
return _balance;
}
function participants() public view returns (uint256) {
return stakerCount;
}
function vesting(address erc721, uint256 tokenId) public view returns (Vesting memory data) {
return staker[nftHash(erc721, tokenId)];
}
function vestings(address[] calldata erc721, uint256[] calldata tokenId) public view returns (Vesting[] memory data) {
data = new Vesting[](erc721.length);
for (uint256 i = 0; i < erc721.length; i++) {
data[i] = staker[nftHash(erc721[i], tokenId[i])];
}
}
}