文件 1 的 11:BloodFarm.sol
pragma solidity ^0.8.6;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "../IVampireGameERC721.sol";
import "./IBloodFarm.sol";
import "../random/IRandom.sol";
struct HumanStake {
address owner;
uint16 tokenId;
uint80 stakedAt;
}
struct HumanUnstakeRequest {
uint16 tokenId;
uint240 blocknumber;
}
contract BloodFarm is IBloodFarm, IERC721Receiver, Ownable {
uint256 public constant DAILY_BLOODBAG_RATE = 5 ether;
uint256 public constant MINIMUM_TO_EXIT = 2 days;
uint256 public constant MAXIMUM_GLOBAL_BLOOD = 4500000 ether;
uint256 public REVEAL_BLOCK_SPACE;
uint256 public totalBloodDrained;
uint256 public totalHumansStaked;
uint256 public lastBloodUpdate;
mapping(uint16 => HumanStake) public stakingMap;
mapping(uint16 => HumanUnstakeRequest) public unstakingRequestMap;
mapping(address => bool) public controllers;
constructor(uint256 _REVEAL_BLOCK_SPACE) {
REVEAL_BLOCK_SPACE = _REVEAL_BLOCK_SPACE;
}
modifier onlyControllers() {
require(controllers[_msgSender()], "ONLY_CONTROLLERS_ALLOWED");
_;
}
modifier updateEarnings() {
if (totalBloodDrained < MAXIMUM_GLOBAL_BLOOD) {
totalBloodDrained +=
((block.timestamp - lastBloodUpdate) *
totalHumansStaked *
DAILY_BLOODBAG_RATE) /
1 days;
lastBloodUpdate = block.timestamp;
}
_;
}
event StakedHuman(address indexed owner, uint16 indexed tokenId);
event BloodBagClaimed(
address indexed owner,
uint16 indexed tokenId,
uint256 amount
);
event RequestedUnstake(address indexed owner, uint16 indexed tokenId);
event UnstakedHuman(
address indexed owner,
uint16 indexed tokenId,
uint256 amount
);
function stakeHuman(address owner, uint16 tokenId)
external
override
onlyControllers
{
stakingMap[tokenId] = HumanStake({
owner: owner,
tokenId: tokenId,
stakedAt: uint80(block.timestamp)
});
totalHumansStaked += 1;
emit StakedHuman(owner, tokenId);
}
function claimBloodBags(address sender, uint16 tokenId)
external
override
onlyControllers
returns (uint256 owed)
{
HumanStake memory stake = stakingMap[tokenId];
require(stake.owner == sender, "NOT_OWNER");
require(
unstakingRequestMap[tokenId].blocknumber == 0,
"CANT_CLAIM_WITH_PENDING_UNSTAKE_REQUEST"
);
owed = _calculateOwedBloodBags(stake);
stakingMap[tokenId] = HumanStake({
owner: sender,
tokenId: tokenId,
stakedAt: uint80(block.timestamp)
});
emit BloodBagClaimed(sender, tokenId, owed);
}
function requestToUnstakeHuman(address sender, uint16 tokenId)
external
override
onlyControllers
{
require(stakingMap[tokenId].owner == sender, "NOT_YOURS");
require(stakingMap[tokenId].stakedAt != 0, "NOT_STAKED");
require(
unstakingRequestMap[tokenId].blocknumber == 0,
"ALREADY_REQUESTED"
);
require(
block.timestamp - stakingMap[tokenId].stakedAt > MINIMUM_TO_EXIT,
"NOT_ENOUGH_BLOOD"
);
_requestToUnstakeHuman(tokenId);
emit RequestedUnstake(sender, tokenId);
}
function unstakeHuman(address sender, uint16 tokenId)
external
override
onlyControllers
returns (uint256 owed)
{
require(stakingMap[tokenId].owner == sender, "NOT_YOURS");
require(stakingMap[tokenId].stakedAt != 0, "NOT_STAKED");
require(unstakingRequestMap[tokenId].blocknumber != 0, "NOT_REQUESTED");
owed = _unstakeHuman(tokenId);
emit UnstakedHuman(sender, tokenId, owed);
}
function _calculateOwedBloodBags(HumanStake memory stake)
private
view
returns (uint256 owed)
{
if (totalBloodDrained < MAXIMUM_GLOBAL_BLOOD) {
owed =
((block.timestamp - stake.stakedAt) * DAILY_BLOODBAG_RATE) /
1 days;
} else if (stake.stakedAt > lastBloodUpdate) {
owed = 0;
} else {
owed =
((lastBloodUpdate - stake.stakedAt) * DAILY_BLOODBAG_RATE) /
1 days;
}
}
function _requestToUnstakeHuman(uint16 tokenId) private {
uint16 tid = uint16(tokenId);
unstakingRequestMap[tokenId] = HumanUnstakeRequest({
tokenId: tid,
blocknumber: uint240(block.number)
});
}
function _unstakeHuman(uint16 tokenId) private returns (uint256 owed) {
HumanStake memory stake = stakingMap[tokenId];
require(
block.number - unstakingRequestMap[tokenId].blocknumber >=
REVEAL_BLOCK_SPACE,
"HUMAN_NOT_READY_FOR_CLAIM"
);
owed = _calculateOwedBloodBags(stake);
delete unstakingRequestMap[tokenId];
delete stakingMap[tokenId];
totalHumansStaked -= 1;
}
function setRevealBlockspace(uint256 space) external onlyOwner {
require(REVEAL_BLOCK_SPACE != space, "NO_CHANGES");
REVEAL_BLOCK_SPACE = space;
}
function addController(address controller) external onlyOwner {
controllers[controller] = true;
}
function removeController(address controller) external onlyOwner {
controllers[controller] = false;
}
function onERC721Received(
address,
address from,
uint256,
bytes calldata
) external pure override returns (bytes4) {
require(from == address(0x0), "PLEASE_DONT");
return IERC721Receiver.onERC721Received.selector;
}
function isStaked(uint16 tokenId) external view override returns (bool) {
return stakingMap[tokenId].stakedAt != 0;
}
function hasRequestedToUnstake(uint16 tokenId)
external
view
override
returns (bool)
{
return unstakingRequestMap[tokenId].blocknumber != 0;
}
function ownerOf(uint16 tokenId) public view override returns (address) {
return stakingMap[tokenId].owner;
}
}
文件 2 的 11: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) {
return msg.data;
}
}
文件 3 的 11:IBloodFarm.sol
pragma solidity ^0.8.6;
interface IBloodFarm {
function stakeHuman(address owner, uint16 tokenId) external;
function claimBloodBags(address sender, uint16 tokenId)
external
returns (uint256 owed);
function requestToUnstakeHuman(address sender, uint16 tokenId) external;
function unstakeHuman(
address sender,
uint16 tokenId
) external returns (uint256 owed);
function isStaked(uint16 tokenId) external view returns (bool);
function hasRequestedToUnstake(uint16 tokenId) external view returns (bool);
function ownerOf(uint16 tokenId) external view returns (address);
}
文件 4 的 11:IERC165.sol
pragma solidity ^0.8.0;
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
文件 5 的 11: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;
}
文件 6 的 11:IERC721Receiver.sol
pragma solidity ^0.8.0;
interface IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
文件 7 的 11:IRandom.sol
pragma solidity ^0.8.6;
interface IRandom {
function submitHash(address sender, uint256 tokenId) external;
function getRandomNumber(uint256 tokenId) external returns (uint256);
}
文件 8 的 11:IVampireGame.sol
pragma solidity ^0.8.6;
import "./traits/TokenTraits.sol";
interface IVampireGame {
function getTotalSupply() external view returns (uint16);
function getOGSupply() external view returns (uint16);
function getGenZeroSupply() external view returns (uint16);
function getMaxSupply() external view returns (uint16);
function getTokenTraits(uint16 tokenId) external view returns (TokenTraits memory);
function isTokenVampire(uint16 tokenId) external view returns (bool);
function getPredatorIndex(uint16 tokenId) external view returns (uint8);
function isTokenRevealed(uint16 tokenId) external view returns (bool);
}
interface IVampireGameControls {
function mintFromController(address receiver, uint16 amount) external;
function controllerRevealTokens(uint16[] calldata tokenIds, uint256[] calldata seeds) external;
}
文件 9 的 11:IVampireGameERC721.sol
pragma solidity ^0.8.6;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "./IVampireGame.sol";
interface IVampireGameERC721 is IVampireGame, IERC721 {}
文件 10 的 11:Ownable.sol
pragma solidity ^0.8.0;
import "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
_setOwner(_msgSender());
}
function owner() public view virtual returns (address) {
return _owner;
}
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
function renounceOwnership() public virtual onlyOwner {
_setOwner(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_setOwner(newOwner);
}
function _setOwner(address newOwner) private {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 11 的 11:TokenTraits.sol
pragma solidity ^0.8.6;
struct TokenTraits {
bool isVampire;
uint8 skin;
uint8 face;
uint8 clothes;
uint8 pants;
uint8 boots;
uint8 accessory;
uint8 hair;
uint8 cape;
uint8 predatorIndex;
}
{
"compilationTarget": {
"contracts/staking/BloodFarm.sol": "BloodFarm"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"uint256","name":"_REVEAL_BLOCK_SPACE","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"uint16","name":"tokenId","type":"uint16"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"BloodBagClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"uint16","name":"tokenId","type":"uint16"}],"name":"RequestedUnstake","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"uint16","name":"tokenId","type":"uint16"}],"name":"StakedHuman","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"uint16","name":"tokenId","type":"uint16"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"UnstakedHuman","type":"event"},{"inputs":[],"name":"DAILY_BLOODBAG_RATE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAXIMUM_GLOBAL_BLOOD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINIMUM_TO_EXIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REVEAL_BLOCK_SPACE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"}],"name":"addController","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint16","name":"tokenId","type":"uint16"}],"name":"claimBloodBags","outputs":[{"internalType":"uint256","name":"owed","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"controllers","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"tokenId","type":"uint16"}],"name":"hasRequestedToUnstake","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"tokenId","type":"uint16"}],"name":"isStaked","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastBloodUpdate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"tokenId","type":"uint16"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"}],"name":"removeController","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint16","name":"tokenId","type":"uint16"}],"name":"requestToUnstakeHuman","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"space","type":"uint256"}],"name":"setRevealBlockspace","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint16","name":"tokenId","type":"uint16"}],"name":"stakeHuman","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"","type":"uint16"}],"name":"stakingMap","outputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint16","name":"tokenId","type":"uint16"},{"internalType":"uint80","name":"stakedAt","type":"uint80"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalBloodDrained","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalHumansStaked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint16","name":"tokenId","type":"uint16"}],"name":"unstakeHuman","outputs":[{"internalType":"uint256","name":"owed","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"","type":"uint16"}],"name":"unstakingRequestMap","outputs":[{"internalType":"uint16","name":"tokenId","type":"uint16"},{"internalType":"uint240","name":"blocknumber","type":"uint240"}],"stateMutability":"view","type":"function"}]