Skip to content

Instantly share code, notes, and snippets.

@Nicky010
Created June 4, 2023 20:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Nicky010/25166ac534dc4070de239ef263bbb035 to your computer and use it in GitHub Desktop.
Save Nicky010/25166ac534dc4070de239ef263bbb035 to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.9;
/* hhhhhhhhhhhhhhhhhp
aaaaaaaaaaaaaaaaaaap
f haaaaaaaaaaap
iaaaaaaaaaah
aahhp fhaaaaaaah
aaaaa haaahfiau
aaaaap aaaap
aaap pppp aaaahf ahaaaaaah
hhp ihh p anfnp hhh ihhpaaaaaaah
hhh hh aaffhap ahfff h hhh ihhp phh hhaaaaaaaaaaaap
hhhp ihh phh hhh ihhh hhh hhh hhh apahaaaaaaaaaaaaaaah
hhhffhhhhhh ihh ihhp ihh hhh hhh hhhhp iaaaaaaaaaaaaaaaaaaaah
ihhh ihh hhh hhp ihh hhh hhh fhhhp iaaaaaaaaaaaaaaaaaaaah
ihhp ihh ihhp ihh hhh ihh ihhh p fhhp haaaaaaaaaaaaaaaaaaaaap
phhp ihhp ffhhpcsahf fhhhpp fhhhenfihhhphp hh haaaaaaaaaaaaaaaaaaaahh
ffff f f ffffffhnfff hhp fhaaaaaaaaaaaaaaaaaahhh
a haap fhaaaaaaaaaaaaaaahhhf
hhhhfffhhhp s sanup iaahp fh fhhfffhff a
ihhh hhh phf fihp aphfffhh hhhh ihhhf shp haahu apphhaahh
ihhh hhh ihh ihh hhh ihh hhp ihh ahhpaapaaaaaaaah
ihhp ahhf hh hhpihh ihh ihp hhhhp haaaaaa apaaaaaaaaaaaaaaah
ihhppaphff hhp hhhihhp ihh ihp ffhhhp fhaaaaaaaaaaaaaaaaaaaah
ihhh ihhp ph hhhp ihh hhp i fhhh aaaaaaaaaaaaaaaaaah
ihh ffhhhhhf fhhhp fhhhpaefhhhpip ph aaaaaaaaaaaaaaaah
hhh f fff ffffhhhnfff iaaaaaaaaaaaaaaah
ahhh ahhaaaaaaaaaaaaaah
aphaaaaaaaaaaaaaaaaa
ahaaaaaaaaaaaaaaaaaaah
paaaaaaaaaaaaaaaaaaaaah
aaaaaaaaaaaaaaaaaaaaaah
aaaaaaaaaaaaaaaaaaaah
aaaaaaaaaaaaaaaaaaah
aaaaaaaaaaaaaaaaa*/
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "hardhat/console.sol";
interface IUniswapV2Factory {
function createPair(address tokenA, address tokenB)
external
returns (address pair);
}
interface IUniswapV2Router02 {
function factory() external pure returns (address);
function WPLS() external pure returns (address);
function swapExactETHForTokens(
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external payable returns (uint256[] memory amounts);
function addLiquidityETH(
address token,
uint256 amountTokenDesired,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
)
external
payable
returns (
uint256 amountToken,
uint256 amountETH,
uint256 liquidity
);
function getAmountsOut(uint256 amountIn, address[] calldata path)
external
view
returns (uint256[] memory amounts);
}
abstract contract Constants {
uint256 internal constant SECONDS_PER_DAY = 86400;
uint256 internal constant INVALID_TOKEN_ID = 2**256 - 1;
uint256 internal constant INITIAL_SUPPLY = 555_500_000_000;
address internal constant TREASURY_WALLET =
0x1e18ed1bfCa02e59a43de32c60Ac0FD4923b64b5;
uint256 BOOSTER_INITIAL_PRICE = 1 ether;
address BOOSTER_REFERENCE_TOKEN_ADDRESS =
0x826e4e896CC2f5B371Cd7Bb0bd929DB3e3DB67c0;
uint256 BOOSTER_REFERENCE_PRICE = 20 * 10**18;
uint256 internal constant CLAIM_PERIOD = 183 * SECONDS_PER_DAY; // approximately 6 months
uint256 internal constant BUY_AND_BURN_MINIMUM_BALANCE = 1 ether;
uint256 internal constant BUY_AND_BURN_REWARD_PERCENTAGE = 369;
uint256 internal constant BUY_AND_BURN_REWARD_PERCENTAGE_DIVIDER = 10000;
address internal constant ROUTER_ADDRESS =
0xDaE9dd3d1A52CfCe9d5F2fAC7fDe164D500E50f7;
}
abstract contract BaseContract {
address public aetherAddress;
modifier magicOnlyComesFromAether() {
require(
aetherAddress == msg.sender,
"Method is callable only from Aether"
);
_;
}
constructor(address addr) {
aetherAddress = addr;
}
}
abstract contract NFTBaseContract is
ERC721,
ERC721Burnable,
BaseContract,
Constants
{
uint256 internal nextTokenId = 0;
uint256 public totalSupply = 0;
mapping(address => uint256[]) private ownedTokens;
constructor(string memory name, string memory symbol)
ERC721(name, symbol)
{}
function _mintNew(address to) internal returns (uint256 tokenId) {
tokenId = nextTokenId++;
_safeMint(to, tokenId);
++totalSupply;
}
function _burn(uint256 tokenId) internal virtual override {
super._burn(tokenId);
--totalSupply;
}
function getTokenIdsForOwner(address owner)
public
view
returns (uint256[] memory tokenIds)
{
return ownedTokens[owner];
}
function _beforeTokenTransfer(
address from,
address to,
uint256 firstTokenId,
uint256 batchSize
) internal virtual override {
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
require(batchSize == 1, "Only transfer of one NFT is supported");
require(firstTokenId != INVALID_TOKEN_ID, "Invalid token ID");
if (from != address(0)) {
// remove firstTokenId from ownedTokens[from]
uint256 length = ownedTokens[from].length;
for (uint256 i = 0; i < length; ) {
if (ownedTokens[from][i] == firstTokenId) {
if (i < length - 1)
ownedTokens[from][i] = ownedTokens[from][length - 1];
ownedTokens[from].pop();
break;
}
unchecked {
++i;
}
}
if (ownedTokens[from].length == 0) {
delete ownedTokens[from];
}
}
if (to != address(0)) {
ownedTokens[to].push(firstTokenId);
}
}
}
/**
Hocus Pocus Booster NFT contract whose token can be used (locked) when starting
brewing to boost its rate. Each Booster NFT has specific brewing rate bonus.
Booster NFT can be acquired in 2 ways:
- Holders of Hocus Pocus Sacrifice Spell NFT will get copy of their Spell NFT token
preserving it's bonus.
- Anybody can mint 10% Booster NFT forever for fixed fee.
*/
contract Booster is NFTBaseContract {
uint256 public price = BOOSTER_INITIAL_PRICE;
struct Attributes {
uint8 boosterBonus;
uint8 gender;
bytes30 name;
uint64 summonTimestamp;
/** Total number of brewings performed since birth. This values is preserved during forge process. */
uint64 brewingsPerformed;
/** Number of brewings performed on the current level. This value resets during forge process. */
uint64 brewingsPerformedAtCurrentLevel;
/** Total amount of HOC conjured. */
uint256 conjuredTokens;
/** If other than INVALID_TOKEN_ID it means that this Booster is locked in active brewing. */
uint256 currentCauldronTokenId;
}
mapping(uint256 => Attributes) public attributes;
/**
Determines if copies of WIZ Spell NFTs were already minted. This should happen
right after contract deployment.
*/
bool public spellsMinted = false;
constructor()
NFTBaseContract("Hocus Pocus Booster", unicode"HOC🧙")
BaseContract(msg.sender)
{}
function _baseURI() internal pure override returns (string memory) {
return "https://nft.hocuspocus.finance/booster/";
}
function getSummonCost(uint256 amount)
public
view
returns (uint256 cost, uint256 firstTokenId)
{
firstTokenId = nextTokenId;
cost = amount * price;
}
function _mintNewWithBonus(address to, Attributes memory newAttributes)
private
returns (uint256 tokenId)
{
tokenId = _mintNew(to);
attributes[tokenId] = newAttributes;
}
/**
Summon (mint) specified amount of Booster NFTs with 10% Bonus.
*/
function summon(
address to,
uint256 amount,
uint8 gender
) public payable {
if (amount > 0) {
require(spellsMinted, "Spell NFT copies are not minted yet");
uint256 cost;
uint256 firstTokenId;
// Check if enough funds are provided considering current price
(cost, firstTokenId) = getSummonCost(amount);
require(msg.value >= cost, "Not enough funds for minting");
// Do minting
while (amount > 0) {
_mintNewWithBonus(
to,
Attributes({
boosterBonus: 10,
gender: gender,
name: bytes30(0),
summonTimestamp: uint64(block.timestamp),
brewingsPerformed: 0,
brewingsPerformedAtCurrentLevel: 0,
conjuredTokens: 0,
currentCauldronTokenId: INVALID_TOKEN_ID
})
);
unchecked {
--amount;
}
}
// Return remaining funds
uint256 remainingBalance = msg.value - cost;
if (remainingBalance > 0) {
payable(msg.sender).transfer(remainingBalance);
}
}
// Update NFT price based on current market value of reference token
IUniswapV2Router02 dexRouter = IUniswapV2Router02(ROUTER_ADDRESS);
address[] memory path = new address[](2);
path[0] = BOOSTER_REFERENCE_TOKEN_ADDRESS;
path[1] = dexRouter.WPLS();
uint256[] memory amounts = dexRouter.getAmountsOut(
BOOSTER_REFERENCE_PRICE,
path
);
price = amounts[1];
}
function _burn(uint256 tokenId) internal virtual override {
super._burn(tokenId);
delete attributes[tokenId];
}
/** Forge 2 NFTS into new one with higher Bonus. */
function forge(
address to,
uint256 tokenId1,
uint256 tokenId2
) public returns (uint256 newTokenId1, uint256 newTokenId2) {
require(
ownerOf(tokenId1) == msg.sender,
"You are not owner of 1st Booster NFT"
);
require(
ownerOf(tokenId2) == msg.sender,
"You are not owner of 2nd Booster NFT"
);
Attributes memory attributes1 = attributes[tokenId1];
require(
attributes1.currentCauldronTokenId == INVALID_TOKEN_ID,
"Cannot forge 1st Booster NFT while it is used in brewing"
);
require(
attributes1.brewingsPerformedAtCurrentLevel > 0,
"1st Booster NFT has no brewing experience at its current level"
);
Attributes memory attributes2 = attributes[tokenId2];
require(
attributes2.currentCauldronTokenId == INVALID_TOKEN_ID,
"Cannot forge 2nd Booster NFT while it is used in brewing"
);
require(
attributes2.brewingsPerformedAtCurrentLevel > 0,
"2nd Booster NFT has no brewing experience at its current level"
);
require(
attributes1.gender == attributes2.gender,
"Only Booster NFTs of same gender can be forged together"
);
// merge bonuses
uint8 bonus1 = attributes1.boosterBonus;
require(bonus1 <= 90, "1st NFTs has already highest Bonus");
uint8 bonus2 = attributes2.boosterBonus;
require(bonus2 <= 90, "2nd NFTs has already highest Bonus");
bonus1 += bonus2;
bonus2 = 0;
if (bonus1 > 100) {
bonus2 = bonus1 - 100;
bonus1 = 100;
}
attributes1.boosterBonus = bonus1;
// merge statistics for 1st
attributes1.brewingsPerformed += attributes2.brewingsPerformed;
attributes1.brewingsPerformedAtCurrentLevel = 0;
attributes1.conjuredTokens += attributes2.conjuredTokens;
// burn old and mint new NFTs
_burn(tokenId1);
_burn(tokenId2);
newTokenId1 = _mintNewWithBonus(to, attributes1);
newTokenId2 = 0;
if (bonus2 > 0) {
// reset statistics for 2nd
attributes2.boosterBonus = bonus2;
attributes2.brewingsPerformed = 0;
attributes2.brewingsPerformedAtCurrentLevel = 0;
attributes2.conjuredTokens = 0;
newTokenId2 = _mintNewWithBonus(to, attributes2);
}
}
function setName(uint256 tokenId, bytes30 name) public {
require(
ownerOf(tokenId) == msg.sender,
"You are not owner of this NFT"
);
attributes[tokenId].name = name;
}
function getBoosterBonus(uint256 tokenId) public view returns (uint8) {
return attributes[tokenId].boosterBonus;
}
function _lockForBrewing(uint256 tokenId, uint256 cauldronTokenId)
public
magicOnlyComesFromAether
{
require(
attributes[tokenId].currentCauldronTokenId == INVALID_TOKEN_ID,
"Cannot reused Booster while it is used in other brewing"
);
attributes[tokenId].currentCauldronTokenId = cauldronTokenId;
}
function _unlockForBrewing(
uint256 tokenId,
uint256 conjuredTokens,
bool finished
) public magicOnlyComesFromAether {
if (finished) {
attributes[tokenId].brewingsPerformed++;
attributes[tokenId].brewingsPerformedAtCurrentLevel++;
}
attributes[tokenId].conjuredTokens += conjuredTokens;
attributes[tokenId].currentCauldronTokenId = INVALID_TOKEN_ID;
}
function _beforeTokenTransfer(
address from,
address to,
uint256 firstTokenId,
uint256 batchSize
) internal virtual override {
if (from != address(0)) {
require(
attributes[firstTokenId].currentCauldronTokenId ==
INVALID_TOKEN_ID,
"Cannot transfer Booster while it is used in brewing"
);
}
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
}
/** Helper structure for passing data to mintSpells(). */
struct SpellNFTToken {
address owner;
uint8 bonus;
}
/**
Creates copies for Spell NFT tokens
This function is publicly callable, but can be called only once.
After this is called, then mint() is unlocked.
*/
function mintSpells(SpellNFTToken[] calldata tokens, bool finishMinting)
public
{
require(!spellsMinted, "Spell NFT copies were already minted");
for (uint256 tokenId = 0; tokenId < tokens.length; ) {
SpellNFTToken memory token = tokens[tokenId];
_mintNewWithBonus(
token.owner,
Attributes({
boosterBonus: token.bonus,
gender: 0,
name: bytes30(0),
summonTimestamp: uint64(block.timestamp),
brewingsPerformed: 0,
brewingsPerformedAtCurrentLevel: 0,
conjuredTokens: 0,
currentCauldronTokenId: INVALID_TOKEN_ID
})
);
unchecked {
tokenId++;
}
}
spellsMinted = finishMinting;
}
function _withdraw() public magicOnlyComesFromAether {
uint256 balance = address(this).balance;
payable(msg.sender).transfer(balance);
}
}
contract Dime is ERC20, ERC20Burnable, BaseContract, Constants {
constructor(address poolAddress, uint256 poolAllocation)
ERC20("Hocus Pocus Finance", "HOC")
BaseContract(msg.sender)
{
_mint(msg.sender, INITIAL_SUPPLY * 10**decimals());
_transfer(msg.sender, poolAddress, poolAllocation);
}
function _ignite(address from, uint256 amount)
public
magicOnlyComesFromAether
{
_burn(from, amount);
}
function _conjure(address to, uint256 amount)
public
magicOnlyComesFromAether
{
_mint(to, amount);
}
}
contract Cauldron is NFTBaseContract {
struct Attributes {
uint256 amount; // 32
uint256 boosterTokenId; // 32
uint64 startTimestamp; // 8
uint32 periodDays; // 4
}
struct Progress {
/** Original HOC base amount when brewing started. */
uint256 baseAmountFull;
/** Restored part of original HOC base amount. */
uint256 baseAmountCurrent;
/** Collectaeble HOC from brewing bonus rate at the end of brewing. */
uint256 boosterBonusAmountFull;
/** So far collected HOC from brewing bonus rate. */
uint256 boosterBonusAmountCurrent;
/** Collectable HOC from long bonus rate at the end of brewing. */
uint256 longBonusAmountFull;
/** So far collected HOC from long bonus rate. */
uint256 longBonusAmountCurrent;
/** Number of seconds this brewing is running. */
uint64 servedSeconds;
/** Number of days this brewing is running. */
uint32 servedDays;
/** Brewing rate bonus = base rate + booster rate (stored as nominator par of fraction with constant denominator). */
uint32 boosterBonusRate;
/** Brewing rate bonus based on length of brewing (stored as nominitor par of fraction with constant denominator). */
uint32 longBonusRate;
/** True if brewing is finished. */
bool finished;
}
/** Mapping of tokenId to Attributes record. */
mapping(uint256 => Attributes) public attributes;
Booster private boosterContract;
constructor(Booster _boosterContract)
NFTBaseContract("Hocus Pocus Cauldron", unicode"HOC🍯")
BaseContract(msg.sender)
{
boosterContract = _boosterContract;
}
function _baseURI() internal pure override returns (string memory) {
return "https://nft.hocuspocus.finance/cauldron/";
}
function _conjure(
Attributes memory record,
address sender,
uint8 boosterBonus
) public magicOnlyComesFromAether returns (uint256 tokenId) {
// check minimum period
require(
record.periodDays >= getMinimumPeriod(boosterBonus),
"Too short period"
);
tokenId = NFTBaseContract._mintNew(sender);
attributes[tokenId] = record;
}
function _ignite(uint256 tokenId) public magicOnlyComesFromAether {
_burn(tokenId);
delete attributes[tokenId];
}
/**
Computes statistics of brewing progress.
*/
function _getProgress(Attributes storage record, bool assumeFullFinish)
private
view
returns (Progress memory progress)
{
progress.servedSeconds = uint64(
block.timestamp - record.startTimestamp
);
progress.servedDays = uint32(progress.servedSeconds / SECONDS_PER_DAY);
uint32 periodDays = record.periodDays;
uint256 periodSeconds = uint256(periodDays) * SECONDS_PER_DAY;
uint256 denominator = 10000000 * 365; // 365 means that % values below are per year
// 3.69% as fraction with denominator
progress.boosterBonusRate = 369000;
if (record.boosterTokenId != INVALID_TOKEN_ID) {
uint8 boosterBonus = boosterContract.getBoosterBonus(
record.boosterTokenId
);
progress.boosterBonusRate +=
(progress.boosterBonusRate * uint32(boosterBonus)) /
100;
}
uint256 servedSecondsCapped = progress.servedSeconds;
if (servedSecondsCapped > periodSeconds) {
servedSecondsCapped = periodSeconds;
progress.finished = true;
} else if (assumeFullFinish) {
progress.finished = true;
}
// 0.01845% nominator part of fraction with denominator
progress.longBonusRate = progress.finished ? periodDays * 1845 : 0;
uint256 amount = record.amount;
progress.baseAmountFull = amount;
progress.baseAmountCurrent =
(progress.baseAmountFull * servedSecondsCapped) /
periodSeconds;
progress.boosterBonusAmountFull =
(amount * progress.boosterBonusRate * periodDays) /
denominator;
progress.boosterBonusAmountCurrent =
(progress.boosterBonusAmountFull * servedSecondsCapped) /
periodSeconds;
progress.longBonusAmountFull =
(amount * progress.longBonusRate * periodDays) /
denominator;
progress.longBonusAmountCurrent =
(progress.longBonusAmountFull * servedSecondsCapped) /
periodSeconds;
}
/** Public version of _getProgress(). */
function getProgress(uint256 tokenId, bool assumeFullFinish)
public
view
returns (Progress memory progress)
{
Attributes storage record = attributes[tokenId];
return _getProgress(record, assumeFullFinish);
}
/**
Computes minimum period based on Booster Bonus.
- Bonus 100%: 7 days
- Bonus 90%: 8 days
- ...
- Bonus 20%: 15 days
- Bonus 10%: 16 days
- Bonus 0%: 30 days
*/
function getMinimumPeriod(uint8 boosterBonus)
public
pure
returns (uint32 periodDays)
{
require(
boosterBonus >= 0 && boosterBonus <= 100,
"Bonus must be in range from 0 to 100"
);
if (boosterBonus == 0) return 30;
return 17 - boosterBonus / 10;
}
function _finish(uint256 tokenId, address sender)
public
view
magicOnlyComesFromAether
returns (
uint256 conjuredTokens,
uint256 boosterTokenId,
Progress memory progress
)
{
require(_exists(tokenId), "Cauldron does not exist");
address owner = ownerOf(tokenId);
require(owner == sender, "Only Cauldron ownercan finish it");
Attributes storage record = attributes[tokenId];
progress = _getProgress(record, false);
conjuredTokens =
progress.baseAmountCurrent +
progress.boosterBonusAmountCurrent +
progress.longBonusAmountCurrent;
boosterTokenId = record.boosterTokenId;
}
}
contract Aether is IERC721Receiver, Constants {
Dime public dimeContract;
Booster public boosterContract;
Cauldron public cauldronContract;
bytes32 private merkleRoot;
receive() external payable {}
constructor(
address poolAddress,
uint256 poolAllocation,
bytes32 _merkleRoot
) {
dimeContract = new Dime(poolAddress, poolAllocation);
boosterContract = new Booster();
cauldronContract = new Cauldron(boosterContract);
merkleRoot = _merkleRoot;
IUniswapV2Router02 dexRouter = IUniswapV2Router02(ROUTER_ADDRESS);
IUniswapV2Factory(dexRouter.factory()).createPair(
dexRouter.WPLS(),
address(dimeContract)
);
}
/** Timestamp when claim period is over - approximately 6 months after deployment. */
uint256 public claimEndTimestamp = block.timestamp + CLAIM_PERIOD;
/**
Public function to claim tokens for sender.
*/
function wizClaimDimes(bytes32[] memory proof, uint256 amount) public {
require(block.timestamp <= claimEndTimestamp, "Claim period is over");
_wizClaimDimes(proof, msg.sender, amount);
}
mapping(address => uint256) public dimesClaimed;
/**
Unrestricted claim function which does not check right to claim for provided address.
*/
function _wizClaimDimes(
bytes32[] memory proof,
address addr,
uint256 amount
) private {
bytes32 leaf = keccak256(
bytes.concat(keccak256(abi.encode(addr, amount)))
);
require(MerkleProof.verify(proof, merkleRoot, leaf), "Invalid proof");
require(dimesClaimed[addr] == 0, "Already claimed");
dimeContract.transfer(addr, amount);
dimesClaimed[addr] = amount;
}
/**
After claimEndTimestamp allows anybody to burn unclaimed HOC.
*/
function burnUnclaimedDimes() public {
require(
block.timestamp > claimEndTimestamp,
"Claim period is not over yet"
);
uint256 amount = dimeContract.balanceOf(address(this));
if (amount > 0) {
dimeContract._ignite(address(this), amount);
}
}
event BrewingStarted(
address indexed cauldronOwner,
uint256 cauldronTokenId,
uint256 boosterTokenId,
uint256 amount,
uint32 periodDays,
uint8 boosterBonus
);
/**
Starts Brewing process:
- Provided amount of HOC tokens get ignited (burnt) to start brewing.
- If valid Booster NFT is provided, use it's bonus to speead up brewing rate.
- Mints Cauldron NFT which holds all necessary information to track Cauldron's progress.
*/
function startBrewing(
uint256 amount,
uint32 periodDays,
uint256 boosterTokenId
) public returns (uint256 cauldronTokenId) {
require(amount > 0, "Amount must be greater than zero");
require(periodDays <= 3690, "Maximum period if 3690 days");
require(
dimeContract.balanceOf(msg.sender) >= amount,
"Insufficient balance"
);
// Burn provided HOC amount, as it was thrown into Cauldron
dimeContract._ignite(msg.sender, amount);
// Resolve booster bonus, if provided
uint8 boosterBonus = 0;
if (boosterTokenId != INVALID_TOKEN_ID) {
// check if Booster NFT is associated with sender
require(
boosterContract.ownerOf(boosterTokenId) == msg.sender,
"Cannot start brewing with somebody else's booster"
);
boosterBonus = boosterContract.getBoosterBonus(boosterTokenId);
}
// Mint Cauldron NFT
cauldronTokenId = cauldronContract._conjure(
Cauldron.Attributes({
amount: amount,
startTimestamp: uint64(block.timestamp),
periodDays: periodDays,
boosterTokenId: boosterTokenId
}),
msg.sender,
boosterBonus
);
if (boosterTokenId != INVALID_TOKEN_ID) {
boosterContract._lockForBrewing(boosterTokenId, cauldronTokenId);
}
emit BrewingStarted(
msg.sender,
cauldronTokenId,
boosterTokenId,
amount,
periodDays,
boosterBonus
);
}
event BrewingFinished(
address indexed cauldronOwner,
uint256 cauldronTokenId,
uint256 boosterTokenId,
uint256 baseAmount,
uint256 boosterBonusAmount,
uint256 longBonusAmount,
uint64 servedSeconds,
uint32 servedDays,
bool finished
);
/**
Finished brewing process:
- Conjures (mints) amount of HOC tokens based on Progress
(this includes calculation in case of finishing brewing too early).
- Ignites (burns) Cauldron NFT.
*/
function finishBrewing(uint256 tokenId) public {
(
uint256 conjuredTokens,
uint256 boosterTokenId,
Cauldron.Progress memory progress
) = cauldronContract._finish(tokenId, msg.sender);
// Conjure tokens for owner
dimeContract._conjure(msg.sender, conjuredTokens);
// Return Booster NFT to its original owner, not to current NFT owner
if (boosterTokenId != INVALID_TOKEN_ID) {
boosterContract._unlockForBrewing(
boosterTokenId,
conjuredTokens,
progress.finished
);
}
emit BrewingFinished(
msg.sender,
tokenId,
boosterTokenId,
progress.baseAmountCurrent,
progress.boosterBonusAmountCurrent,
progress.longBonusAmountCurrent,
progress.servedSeconds,
progress.servedDays,
progress.finished
);
// finally burn this Cauldron NFT token
cauldronContract._ignite(tokenId);
}
event BuyAndBurn(
uint256 funds,
uint256 fundsForReward,
uint256 fundsForTreasury,
uint256 fundsForBuy,
uint256 amountBurnt
);
/**
Public function to perform Buy & Burn feature:
- Withdraws funds collected for minting Booster NFT contract.
- Sender of transaction receives reward to incentivize doing it.
- 75% of remaining funds goes to Treasury Wallet.
- Rest of funds is used to buy HOC tokens from main HOC/PLS pool and burn it.
*/
function buyAndBurn() public {
// Ather can withdraw() funds from Booster NFT contract
boosterContract._withdraw();
uint256 funds = address(this).balance;
require(funds >= BUY_AND_BURN_MINIMUM_BALANCE, "Not enough PLS");
IUniswapV2Router02 dexRouter = IUniswapV2Router02(ROUTER_ADDRESS);
uint256 fundsForReward = (funds * BUY_AND_BURN_REWARD_PERCENTAGE) /
BUY_AND_BURN_REWARD_PERCENTAGE_DIVIDER;
payable(msg.sender).transfer(fundsForReward);
funds -= fundsForReward;
// 25% of remaining funds used to buy and burn
uint256 fundsForBuy = funds / 4;
address[] memory path = new address[](2);
path[0] = dexRouter.WPLS();
path[1] = address(dimeContract);
uint256[] memory amounts = dexRouter.swapExactETHForTokens{
value: fundsForBuy
}(
0, // accept any amount of tokens
path,
address(this), // send tokens to this contract
block.timestamp
);
uint256 amountBurnt = amounts[1]; // HOC
// Burn tokens which were bought
dimeContract._ignite(address(this), amountBurnt);
// 75% of funds go to Treasury Wallet
uint256 fundsForTreasury = funds - fundsForBuy;
payable(TREASURY_WALLET).transfer(fundsForTreasury);
emit BuyAndBurn(
funds,
fundsForReward,
fundsForTreasury,
fundsForBuy,
amountBurnt
);
}
function onERC721Received(
address,
address,
uint256,
bytes memory
) external pure override returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment