712 lines
24 KiB
Solidity
712 lines
24 KiB
Solidity
// SPDX-License-Identifier: MIT OR Apache-2.0
|
|
|
|
pragma solidity ^0.8.14;
|
|
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";
|
|
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
|
|
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
|
|
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
|
import "@openzeppelin/contracts-upgradeable/utils/Base64Upgradeable.sol";
|
|
import "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol";
|
|
import "./interfaces/ITreasury.sol";
|
|
import "./interfaces/IPancake.sol";
|
|
import "./mixins/signature-control.sol";
|
|
|
|
contract NFT is ERC721EnumerableUpgradeable, SignatureControl {
|
|
using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet;
|
|
using SafeERC20Upgradeable for IERC20Upgradeable;
|
|
|
|
//variables
|
|
bool pause = true;
|
|
uint256 nonce;
|
|
ITreasury treasury;
|
|
IERC20Upgradeable BoM;
|
|
address redTrustFund;
|
|
IPancakeSwapPair public pairContract;
|
|
IPancakeSwapRouter public swapRouter;
|
|
|
|
uint256 treasuryFee;
|
|
uint256 redTrustFee;
|
|
uint256 rewardPoolFee;
|
|
|
|
uint256 supply;
|
|
|
|
uint256 wethInRewardPool;
|
|
uint256 busdInLotteryPool;
|
|
|
|
mapping(uint256 => uint256) mintCapOfToken;
|
|
mapping(uint256 => address[]) tokenIdToType;
|
|
mapping(address => mapping(uint256 => uint256)) tokensOwnedOfType;
|
|
mapping(uint256 => bool) usedNonces;
|
|
|
|
//mint prices, caps, addresses of reward tokens(shiba,floki,doggy,doge)
|
|
uint256[4] prices = [
|
|
300 * 10**18,
|
|
250 * 10**18,
|
|
200 * 10**18,
|
|
250 * 10**18
|
|
];
|
|
IERC20Upgradeable BUSD;
|
|
IERC20Upgradeable WETH;
|
|
mapping(address => uint256) tokenToPrices;
|
|
uint256[4] nftCaps = [1000, 1500, 2000, 1500];
|
|
address[4] public rewardTokens = [
|
|
0x0000000000000000000000000000000000000000,
|
|
0x0000000000000000000000000000000000000000,
|
|
0x0000000000000000000000000000000000000000,
|
|
0x0000000000000000000000000000000000000000
|
|
];
|
|
mapping(address => uint256) addressToType;
|
|
mapping(address => uint256) totalRatesForType;
|
|
mapping(address => mapping(address => uint256)) addrerssToRatesForType;
|
|
|
|
uint256[5] rewardPoolForToken;
|
|
uint256[5] rewardsPerShareStored;
|
|
uint256[5] lastUpdatePools;
|
|
mapping(address => uint256[5]) accountShares;
|
|
uint256[5] totalShares;
|
|
mapping(address => mapping(uint256 => uint256)) accountRewards;
|
|
mapping(address => mapping(uint256 => uint256)) accountRewardsPerTokenPaid;
|
|
|
|
//whitelist shit
|
|
bool public isPresale = true;
|
|
uint256 whitelistPrice = 200 ether;
|
|
uint256 maxMintPerAddressDuringWhitelist = 5;
|
|
uint256 whitelistMintCapPerType = 250;
|
|
mapping(address => bool) isWhitelisted;
|
|
mapping(address => uint256) mintedOnWhitelist;
|
|
mapping(uint256 => uint256) whitelistMintCapOfToken;
|
|
|
|
EnumerableSetUpgradeable.AddressSet _holders; // TODO: migrate?
|
|
|
|
uint256 statMultiplier = 100000;
|
|
mapping(uint256 => TokenInfo) tokenIdToInfo;
|
|
struct TokenInfo {
|
|
string image;
|
|
address[] tokens;
|
|
uint256 rate;
|
|
uint256 attack;
|
|
uint256 defense;
|
|
uint256 health;
|
|
uint256 critChance;
|
|
uint256 critDmg;
|
|
uint256 recover;
|
|
}
|
|
|
|
mapping(address => Tokenomic) addressToToken;
|
|
struct Tokenomic {
|
|
uint256 stakingRate;
|
|
uint256 attack;
|
|
uint256 defense;
|
|
uint256 health;
|
|
uint256 critChance;
|
|
uint256 critDmg;
|
|
uint256 recovery;
|
|
}
|
|
|
|
event TokenMinted(
|
|
address indexed minter,
|
|
uint256 tokenId,
|
|
address token,
|
|
uint256 rate
|
|
);
|
|
|
|
event TokenCombined(
|
|
address indexed user,
|
|
uint256[] burntTokens,
|
|
address[] tokens,
|
|
uint256 tokenId,
|
|
uint256 rate
|
|
);
|
|
|
|
event RewardPoolRaised(uint256 amount);
|
|
event LotteryPoolRaised(uint256 amount);
|
|
|
|
//modifiers
|
|
modifier onlyAdmin() {
|
|
require(treasury.isAdmin(msg.sender));
|
|
_;
|
|
}
|
|
|
|
modifier isUnpaused() {
|
|
require(!pause);
|
|
_;
|
|
}
|
|
|
|
modifier isValidWhitelistMint(uint256 amount, uint256 tokenType) {
|
|
require(isPresale);
|
|
require(isWhitelisted[msg.sender]);
|
|
require(
|
|
mintedOnWhitelist[msg.sender] + amount <=
|
|
maxMintPerAddressDuringWhitelist
|
|
);
|
|
require(tokenType <= 3);
|
|
require(
|
|
whitelistMintCapOfToken[tokenType] + amount <=
|
|
whitelistMintCapPerType
|
|
);
|
|
_;
|
|
}
|
|
|
|
modifier isValidMint(uint256 amount, uint256 tokenType) {
|
|
require(!isPresale);
|
|
require(tokenType <= 3);
|
|
require(mintCapOfToken[tokenType] + amount <= nftCaps[tokenType]);
|
|
_;
|
|
}
|
|
|
|
modifier isValidCombine(uint256[] memory tokenIds) {
|
|
require(tokenIds.length > 1 && tokenIds.length <= 4);
|
|
bool hasDupes;
|
|
for (uint256 i = 0; i < tokenIds.length; i++) {
|
|
require(ownerOf(tokenIds[i]) == msg.sender);
|
|
require(tokenIdToType[tokenIds[i]].length == 1);
|
|
for (uint256 index = i; index < tokenIds.length; index++) {
|
|
if (
|
|
index != i &&
|
|
tokenIdToType[tokenIds[i]][0] ==
|
|
tokenIdToType[tokenIds[index]][0]
|
|
) {
|
|
hasDupes = true;
|
|
}
|
|
}
|
|
require(!hasDupes);
|
|
}
|
|
_;
|
|
}
|
|
|
|
function initialize(
|
|
string memory name,
|
|
string memory symbol,
|
|
ITreasury _treasury,
|
|
address _redTrustFund,
|
|
IERC20Upgradeable token,
|
|
uint256 _rewardFee,
|
|
uint256 _treasuryFee,
|
|
uint256 _redTrustFee,
|
|
IPancakeSwapPair _pair,
|
|
IPancakeSwapRouter _router,
|
|
IERC20Upgradeable _BUSD,
|
|
IERC20Upgradeable _WETH
|
|
) public initializer {
|
|
treasury = _treasury;
|
|
BoM = token;
|
|
__ERC721_init(name, symbol);
|
|
redTrustFund = _redTrustFund;
|
|
rewardPoolFee = _rewardFee;
|
|
redTrustFee = _redTrustFee;
|
|
treasuryFee = _treasuryFee;
|
|
uint16[4] memory attack = [1000, 1000, 700, 850];
|
|
uint16[4] memory defense = [800, 1000, 850, 1000];
|
|
uint16[4] memory health = [800, 800, 1000, 850];
|
|
uint16[4] memory critChance = [400, 200, 400, 300];
|
|
uint16[4] memory critDmg = [600, 500, 800, 500];
|
|
uint16[4] memory recovery = [600, 700, 1000, 900];
|
|
for (uint256 i = 0; i < 4; i++) {
|
|
addressToToken[rewardTokens[i]].attack = attack[i];
|
|
addressToToken[rewardTokens[i]].defense = defense[i];
|
|
addressToToken[rewardTokens[i]].health = health[i];
|
|
addressToToken[rewardTokens[i]].critChance = critChance[i];
|
|
addressToToken[rewardTokens[i]].critDmg = critDmg[i];
|
|
addressToToken[rewardTokens[i]].recovery = recovery[i];
|
|
tokenToPrices[rewardTokens[i]] = prices[i];
|
|
addressToType[rewardTokens[i]] = i;
|
|
}
|
|
pairContract = _pair;
|
|
swapRouter = _router;
|
|
BUSD = _BUSD;
|
|
WETH = _WETH;
|
|
}
|
|
|
|
function addWhitelist(address[] memory whitelist) public onlyAdmin {
|
|
for (uint256 i = 0; i < whitelist.length; i++) {
|
|
isWhitelisted[whitelist[i]] = true;
|
|
}
|
|
}
|
|
|
|
function mintPresale(uint256 amount, uint256 tokenType)
|
|
public
|
|
isUnpaused
|
|
isValidWhitelistMint(amount, tokenType)
|
|
{
|
|
for (uint256 i = 0; i < amount; i++) {
|
|
supply++;
|
|
_mint(msg.sender, supply);
|
|
tokenIdToType[supply].push(rewardTokens[tokenType]);
|
|
uint256 rate = createTokenStats(supply);
|
|
emit TokenMinted(msg.sender, supply, rewardTokens[tokenType], rate);
|
|
}
|
|
mintedOnWhitelist[msg.sender] += amount;
|
|
whitelistMintCapOfToken[tokenType] += amount;
|
|
mintCapOfToken[tokenType] += amount;
|
|
//TBD: swap to bnb
|
|
_distributeFunds(amount * whitelistPrice);
|
|
}
|
|
|
|
function mint(uint256 amount, uint256 tokenType)
|
|
public
|
|
isUnpaused
|
|
isValidMint(amount, tokenType)
|
|
{
|
|
for (uint256 i = 0; i < amount; i++) {
|
|
supply++;
|
|
_mint(msg.sender, supply);
|
|
tokenIdToInfo[supply].tokens.push(rewardTokens[tokenType]);
|
|
uint256 rate = createTokenStats(supply);
|
|
emit TokenMinted(msg.sender, supply, rewardTokens[tokenType], rate);
|
|
}
|
|
mintCapOfToken[tokenType] += amount;
|
|
//TBD: swap to bnb
|
|
_distributeFunds(amount * prices[tokenType]);
|
|
}
|
|
|
|
function combineTokens(uint256[] memory tokenIds)
|
|
public
|
|
payable
|
|
isUnpaused
|
|
{
|
|
require(msg.value == 0.05 ether);
|
|
supply++;
|
|
uint256 price;
|
|
_mint(msg.sender, supply);
|
|
address[] memory tokens = new address[](tokenIds.length);
|
|
for (uint256 i = 0; i < tokenIds.length; i++) {
|
|
TokenInfo storage token = tokenIdToInfo[tokenIds[i]];
|
|
address intermediateStorageForToken = tokenIdToType[tokenIds[i]][0];
|
|
tokenIdToInfo[supply].tokens.push(intermediateStorageForToken);
|
|
tokens[i] = intermediateStorageForToken;
|
|
_burn(tokenIds[i]);
|
|
price += tokenToPrices[tokenIdToType[tokenIds[i]][0]] / 4;
|
|
for (uint256 index = 0; index < token.tokens.length; index++) {
|
|
totalRatesForType[token.tokens[index]] -= token.rate;
|
|
}
|
|
}
|
|
uint256 rate = createTokenStats(supply);
|
|
emit TokenCombined(msg.sender, tokenIds, tokens, supply, rate);
|
|
//TBD: calculate price with usd in mind
|
|
_distributeFunds(price);
|
|
}
|
|
|
|
function createTokenStats(uint256 tokenId) internal returns (uint256 rate) {
|
|
TokenInfo storage token = tokenIdToInfo[tokenId];
|
|
uint256[7] memory seeds;
|
|
for (uint8 i = 0; i < 7; i++) {
|
|
nonce++;
|
|
seeds[i] = (
|
|
uint256(
|
|
keccak256(
|
|
abi.encodePacked(msg.sender, block.timestamp, nonce)
|
|
)
|
|
)
|
|
);
|
|
}
|
|
if (token.tokens.length == 1) {
|
|
uint256 rateMod = (seeds[0] % 100) + 1;
|
|
if (rateMod == 1) {
|
|
token.rate = 2000;
|
|
} else if (rateMod <= 3) {
|
|
token.rate = 1750;
|
|
} else if (rateMod <= 10) {
|
|
token.rate = 1500;
|
|
} else if (rateMod <= 20) {
|
|
token.rate = 1250;
|
|
} else {
|
|
token.rate = 1000;
|
|
}
|
|
} else if (token.tokens.length == 2) {
|
|
token.rate = 2250;
|
|
} else if (token.tokens.length == 3) {
|
|
token.rate = 2500;
|
|
} else {
|
|
token.rate = 3000;
|
|
}
|
|
rate = token.rate;
|
|
for (uint256 i = 0; i < token.tokens.length; i++) {
|
|
totalRatesForType[token.tokens[i]] += token.rate;
|
|
}
|
|
createBattleStats(seeds, token, getStatMultipliers(token));
|
|
}
|
|
|
|
function createBattleStats(
|
|
uint256[7] memory seeds,
|
|
TokenInfo storage token,
|
|
uint256[6] memory statMultipliers
|
|
) internal {
|
|
token.attack =
|
|
(((seeds[1] % 71) + 30) * (token.rate * statMultipliers[0])) /
|
|
statMultiplier;
|
|
token.defense =
|
|
(((seeds[2] % 71) + 30) * (token.rate * statMultipliers[1])) /
|
|
statMultiplier;
|
|
token.health =
|
|
(((seeds[3] % 51) + 50) * (token.rate * statMultipliers[2])) /
|
|
statMultiplier;
|
|
token.critChance =
|
|
(((seeds[4] % 41) + 30) * (token.rate * statMultipliers[3])) /
|
|
statMultiplier;
|
|
token.critDmg =
|
|
(((seeds[5] % 61) + 10) * (token.rate * statMultipliers[4])) /
|
|
statMultiplier;
|
|
token.recover =
|
|
(((seeds[6] % 51) + 50) * (token.rate * statMultipliers[5])) /
|
|
statMultiplier;
|
|
}
|
|
|
|
function getStatMultipliers(TokenInfo storage token)
|
|
internal
|
|
view
|
|
returns (uint256[6] memory statMultipliers)
|
|
{
|
|
uint16[4] memory megaStatMultipliers = [1000, 1250, 1500, 2000];
|
|
for (uint8 i = 0; i < token.tokens.length; i++) {
|
|
Tokenomic memory tokenomic = addressToToken[token.tokens[i]];
|
|
statMultipliers[0] += tokenomic.attack;
|
|
statMultipliers[1] += tokenomic.defense;
|
|
statMultipliers[2] += tokenomic.health;
|
|
statMultipliers[3] += tokenomic.critChance;
|
|
statMultipliers[4] += tokenomic.critDmg;
|
|
statMultipliers[5] += tokenomic.recovery;
|
|
}
|
|
for (uint8 i = 0; i < 6; i++) {
|
|
statMultipliers[i] =
|
|
(statMultipliers[i] / token.tokens.length) *
|
|
megaStatMultipliers[token.tokens.length - 1];
|
|
}
|
|
}
|
|
|
|
function _distributeFunds(uint256 amount) internal {
|
|
uint256[] memory requiredAmountOfTokens = _getAmountsIn(
|
|
amount,
|
|
address(BoM),
|
|
address(WETH)
|
|
);
|
|
require(
|
|
BoM.allowance(msg.sender, address(this)) >=
|
|
requiredAmountOfTokens[0],
|
|
"allowance too low"
|
|
);
|
|
BoM.transferFrom(msg.sender, address(this), requiredAmountOfTokens[0]);
|
|
uint256 bnbAmount = _swap(
|
|
amount,
|
|
requiredAmountOfTokens[0],
|
|
address(BoM),
|
|
address(WETH)
|
|
)[0];
|
|
uint256 _treasuryFee = (bnbAmount * treasuryFee) / 1000;
|
|
uint256 _redTrustFee = (bnbAmount * redTrustFee) / 1000;
|
|
uint256 _rewardPoolFee = bnbAmount - _treasuryFee - _redTrustFee;
|
|
BoM.transferFrom(msg.sender, address(treasury), _treasuryFee);
|
|
BoM.transferFrom(msg.sender, redTrustFund, _redTrustFee);
|
|
BoM.transferFrom(msg.sender, address(this), _rewardPoolFee);
|
|
_addToPool(_rewardPoolFee);
|
|
}
|
|
|
|
function lottery() public onlyAdmin {
|
|
require(busdInLotteryPool > 0, "no funds in lottery pool");
|
|
|
|
uint256[10] memory seeds;
|
|
|
|
uint256 holdersCount = _holders.length();
|
|
uint256 winnersCount = holdersCount;
|
|
if (winnersCount > 10) {
|
|
winnersCount = 10;
|
|
|
|
for (uint8 i = 0; i < winnersCount; i++) {
|
|
nonce++;
|
|
uint256 idx = (
|
|
uint256(
|
|
keccak256(
|
|
abi.encodePacked(msg.sender, block.timestamp, blockhash(block.number - 1), nonce)
|
|
)
|
|
)
|
|
) % holdersCount;
|
|
|
|
while (true) {
|
|
bool retry = false;
|
|
for (uint8 j=0; j < i; j++) {
|
|
if (idx == seeds[j]) {
|
|
retry = true;
|
|
idx = (idx + 1) % holdersCount;
|
|
break;
|
|
}
|
|
}
|
|
if (!retry)
|
|
break;
|
|
}
|
|
|
|
seeds[i] = idx;
|
|
}
|
|
} else {
|
|
for (uint8 i=0; i < winnersCount; i++) {
|
|
seeds[i] = i;
|
|
}
|
|
}
|
|
|
|
uint256 winEach = 100 ether;
|
|
if (busdInLotteryPool < (100 ether * winnersCount))
|
|
winEach = busdInLotteryPool / winnersCount;
|
|
|
|
for (uint256 i = 0; i < winnersCount; i++) {
|
|
BUSD.safeTransfer(_holders.at(seeds[i]), winEach);
|
|
}
|
|
}
|
|
|
|
function _getAmountsIn(
|
|
uint256 price,
|
|
address path0,
|
|
address path1
|
|
) internal view returns (uint256[] memory) {
|
|
address[] memory path;
|
|
path[0] = path0;
|
|
path[1] = path1;
|
|
return swapRouter.getAmountsIn(price, path);
|
|
}
|
|
|
|
function _swap(
|
|
uint256 amount,
|
|
uint256 price,
|
|
address path0,
|
|
address path1
|
|
) internal returns (uint256[] memory) {
|
|
address[] memory path;
|
|
path[0] = path0;
|
|
path[1] = path1;
|
|
return
|
|
swapRouter.swapTokensForExactTokens(
|
|
amount,
|
|
price,
|
|
path,
|
|
address(this),
|
|
block.timestamp
|
|
);
|
|
// return
|
|
// swapRouter.swapTokensForExactETH(
|
|
// price,
|
|
// amount,
|
|
// path,
|
|
// address(this),
|
|
// block.timestamp
|
|
// );
|
|
}
|
|
|
|
// function isValidClaim(
|
|
// uint256[] memory tokenAmounts,
|
|
// bytes memory signature,
|
|
// uint256 userNonce,
|
|
// uint256 timestamp
|
|
// ) internal returns (bool) {
|
|
// bytes memory data = abi.encodePacked(
|
|
// _toAsciiString(msg.sender),
|
|
// " is authorized to claim ",
|
|
// StringsUpgradeable.toString(tokenAmounts[0]),
|
|
// " shiba, ",
|
|
// StringsUpgradeable.toString(tokenAmounts[1]),
|
|
// " floki, ",
|
|
// StringsUpgradeable.toString(tokenAmounts[2]),
|
|
// " doggy, ",
|
|
// StringsUpgradeable.toString(tokenAmounts[3]),
|
|
// " doge before ",
|
|
// StringsUpgradeable.toString(timestamp),
|
|
// ", ",
|
|
// StringsUpgradeable.toString(userNonce)
|
|
// );
|
|
// bytes32 hash = _toEthSignedMessage(data);
|
|
// address signer = ECDSAUpgradeable.recover(hash, signature);
|
|
// require(treasury.isOperator(signer), "Mint not verified by operator");
|
|
// require(block.timestamp <= timestamp, "Outdated signed message");
|
|
// require(!usedNonces[userNonce], "Used nonce");
|
|
// return true;
|
|
// }
|
|
//
|
|
// function claimRewards(
|
|
// uint256[] memory amounts,
|
|
// bytes memory signature,
|
|
// uint256 userNonce,
|
|
// uint256 timestamp
|
|
// ) public {
|
|
// require(isValidClaim(amounts, signature, userNonce, timestamp), "");
|
|
// for (uint256 i = 0; i < 4; i++) {
|
|
// if (amounts[i] > 0) {
|
|
// address[] memory path;
|
|
// path[0] = address(WETH);
|
|
// path[1] = rewardTokens[i];
|
|
// swapRouter.swapExactTokensForTokens(
|
|
// amounts[i],
|
|
// 1,
|
|
// path,
|
|
// msg.sender,
|
|
// block.timestamp
|
|
// );
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
function walletOfOwner(address _owner)
|
|
public
|
|
view
|
|
returns (uint256[] memory)
|
|
{
|
|
uint256 ownerTokenCount = balanceOf(_owner);
|
|
uint256[] memory tokenIds = new uint256[](ownerTokenCount);
|
|
for (uint256 i; i < ownerTokenCount; i++) {
|
|
tokenIds[i] = tokenOfOwnerByIndex(_owner, i);
|
|
}
|
|
return tokenIds;
|
|
}
|
|
|
|
function tokenURI(uint256 tokenId)
|
|
public
|
|
view
|
|
override
|
|
returns (string memory)
|
|
{
|
|
require(_exists(tokenId), "Non-existant token");
|
|
TokenInfo storage token = tokenIdToInfo[tokenId];
|
|
return
|
|
string(
|
|
abi.encodePacked(
|
|
"data:application/json;base64,",
|
|
Base64Upgradeable.encode(
|
|
bytes(
|
|
abi.encodePacked(
|
|
'{"name": Babies of Mars #',
|
|
tokenId,
|
|
', "description": "adasdasdasd", "image": ',
|
|
token.image,
|
|
',{ "attributes": [ {"trait_type": "tokens", "value": ',
|
|
token.tokens,
|
|
'}, { "trait_type": "attack", "value": ',
|
|
token.attack,
|
|
'}, { "trait_type": "defense", "value": ',
|
|
token.defense,
|
|
'}, { "trait_type": "health", "value": ',
|
|
token.health,
|
|
'}, { "trait_type": "critical rate", "value": ',
|
|
token.critChance,
|
|
'}, { "trait_type": "critical damage", "value": ',
|
|
token.critDmg,
|
|
'}, { "trait_type": "recovery", "value": ',
|
|
token.recover,
|
|
"} ] }"
|
|
)
|
|
)
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
function raiseRewardPool(uint256 amount) public {
|
|
require(
|
|
BoM.allowance(msg.sender, address(this)) >= amount,
|
|
"Not enough allowance"
|
|
);
|
|
BoM.transferFrom(msg.sender, address(this), amount);
|
|
_addToPool(amount);
|
|
}
|
|
|
|
function _addToPool(uint256 amount) internal {
|
|
uint256 lotteryPoolAmount = amount / 10;
|
|
|
|
address[] memory path;
|
|
path[0] = address(BoM);
|
|
|
|
path[1] = address(BUSD);
|
|
uint256 swappedFor = swapRouter.swapExactTokensForTokens(
|
|
lotteryPoolAmount, // 10% to lottery
|
|
0,
|
|
path,
|
|
address(this),
|
|
block.timestamp
|
|
)[0];
|
|
busdInLotteryPool += swappedFor;
|
|
emit LotteryPoolRaised(swappedFor);
|
|
|
|
|
|
path[1] = address(WETH);
|
|
swappedFor = swapRouter.swapExactTokensForTokens(
|
|
amount - lotteryPoolAmount,
|
|
0,
|
|
path,
|
|
address(this),
|
|
block.timestamp
|
|
)[0];
|
|
wethInRewardPool += swappedFor;
|
|
for (uint8 i=0; i < 4; i++) {
|
|
rewardPoolForToken[i] += swappedFor * 2 / 9; // 20% of total each
|
|
}
|
|
rewardPoolForToken[4] += swappedFor - swappedFor * 8 / 9; // remaining 10%
|
|
emit RewardPoolRaised(swappedFor);
|
|
}
|
|
|
|
function _afterTokenTransfer(
|
|
address from,
|
|
address to,
|
|
uint256 tokenId
|
|
) internal virtual override {
|
|
super._afterTokenTransfer(from, to, tokenId);
|
|
TokenInfo memory info = tokenIdToInfo[tokenId];
|
|
if (from != address(0)) {
|
|
if (ERC721Upgradeable.balanceOf(from) == 0) {
|
|
_holders.remove(from);
|
|
}
|
|
if (info.tokens.length > 1) {
|
|
totalShares[4] -= info.rate;
|
|
accountShares[from][4] -= info.rate;
|
|
} else {
|
|
uint256 idx = addressToType[info.tokens[0]];
|
|
totalShares[idx] -= info.rate;
|
|
accountShares[from][idx] -= info.rate;
|
|
}
|
|
}
|
|
if (to != address(0)) {
|
|
_holders.add(to);
|
|
if (info.tokens.length > 1) {
|
|
totalShares[4] += info.rate;
|
|
accountShares[to][4] += info.rate;
|
|
} else {
|
|
uint256 idx = addressToType[info.tokens[0]];
|
|
totalShares[idx] += info.rate;
|
|
accountShares[from][idx] += info.rate;
|
|
}
|
|
}
|
|
}
|
|
|
|
function pendingReward(uint256 idx, address account) public view returns (uint256) {
|
|
return accountShares[account][idx] + (_rewardPerShare(idx) - accountRewardsPerTokenPaid[account][idx]) / 1e18 + accountRewards[account][idx];
|
|
}
|
|
|
|
function _rewardPerShare(uint256 idx) internal view returns (uint256) {
|
|
if (totalShares[idx] == 0)
|
|
return rewardsPerShareStored[idx];
|
|
return rewardsPerShareStored[idx] + (rewardPoolForToken[idx] - lastUpdatePools[idx]) / totalShares[idx];
|
|
}
|
|
|
|
function _updateRewards(address account) internal {
|
|
for (uint256 i=0; i < 5; i++) {
|
|
rewardsPerShareStored[i] = _rewardPerShare(i);
|
|
lastUpdatePools[i] = rewardPoolForToken[i];
|
|
if (account != address(0)) {
|
|
accountRewards[account][i] = pendingReward(i, account);
|
|
accountRewardsPerTokenPaid[account][i] = rewardsPerShareStored[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
function claimReward(uint256 idx) external {
|
|
_updateRewards(msg.sender);
|
|
uint256 reward = accountRewards[msg.sender][idx];
|
|
require(reward > 0, "nothing to claim");
|
|
accountRewards[msg.sender][idx] = 0;
|
|
|
|
address[] memory path;
|
|
path[0] = address(WETH);
|
|
path[1] = rewardTokens[idx];
|
|
uint256 swappedFor = swapRouter.swapExactTokensForTokens(
|
|
reward,
|
|
0,
|
|
path,
|
|
msg.sender,
|
|
block.timestamp
|
|
)[0];
|
|
}
|
|
}
|