You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

567 lines
17 KiB

// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.15;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "./interfaces/IPancakeSwapPair.sol";
import "./mixins/SafeMathInt.sol";
import "./interfaces/ITreasury.sol";
import "./interfaces/INFT.sol";
contract BabiesOfMars is ERC20Upgradeable {
event LogRebase(uint256 indexed epoch, uint256 totalSupply);
string public _name = "BabiesOfMars";
string public _symbol = "BoM";
uint8 public _decimals = 5;
IPancakeSwapPair public pairContract;
mapping(address => bool) _isFeeExempt;
modifier validRecipient(address to) {
require(to != address(0x0), "!recipient");
_;
}
modifier onlyAdmin() {
require(treasury.isAdmin(msg.sender), "!admin");
_;
}
uint256 public constant DECIMALS = 5;
uint256 public constant MAX_UINT256 = ~uint256(0);
uint8 public constant RATE_DECIMALS = 7;
uint256 public feeDenominator = 10000;
address DEAD = 0x000000000000000000000000000000000000dEaD;
address ZERO = 0x0000000000000000000000000000000000000000;
address public autoLiquidityReceiver;
ITreasury public treasury;
address public redTrustWallet;
address public redFurnace;
address public pairAddress;
INFT public nftRewardPool;
bool public swapEnabled = true;
IPancakeSwapRouter public router;
IPancakeSwapPair public pair;
bool inSwap = false;
uint256 lastPrice;
uint256 defenderTimer;
uint256 accumulatedImpact;
bool rdStatus;
modifier swapping() {
inSwap = true;
_;
inSwap = false;
}
uint256 private constant INITIAL_FRAGMENTS_SUPPLY = 150_000 * 10**DECIMALS; // should be 50
uint256 private constant MAX_SUPPLY = 500_000 * 10**DECIMALS;
uint256 private constant TOTAL_GONS = MAX_UINT256 - (MAX_UINT256 % INITIAL_FRAGMENTS_SUPPLY);
bool public _autoRebase;
bool public _autoAddLiquidity;
uint256 public _initRebaseStartTime;
uint256 public _lastRebasedTime;
uint256 public _lastAddLiquidityTime;
uint256 public _totalSupply;
uint256 private _gonsPerFragment;
uint256 public rebaseInterval = 15 minutes;
mapping(address => uint256) private _gonBalances;
mapping(address => mapping(address => uint256)) private _allowedFragments;
mapping(address => bool) public blacklist;
function initialize(
address _router,
address _owner,
ITreasury _treasury,
address _redTrustWallet,
INFT _nftRewardPool,
address _redFurnace,
address _BUSD
) public initializer {
__ERC20_init(_name, _symbol);
router = IPancakeSwapRouter(_router);
address factoryAddress = router.factory();
IPancakeSwapFactory factory = IPancakeSwapFactory(factoryAddress);
pair = IPancakeSwapPair(
factory.createPair(
router.WETH(),
address(this)
)
);
IPancakeSwapPair(
factory.createPair(
_BUSD,
address(this)
)
);
autoLiquidityReceiver = DEAD;
treasury = _treasury;
redTrustWallet = _redTrustWallet;
redFurnace = _redFurnace;
nftRewardPool = _nftRewardPool;
_allowedFragments[address(this)][address(router)] = type(uint256).max;
pairContract = IPancakeSwapPair(pair);
_totalSupply = INITIAL_FRAGMENTS_SUPPLY;
_gonBalances[_owner] = TOTAL_GONS / 3;
_gonBalances[address(_nftRewardPool)] = TOTAL_GONS / 3; // TODO: remove after test
_gonBalances[0x62F650c0eE84E3a1998A2EEe042a12D9E9728843] = TOTAL_GONS / 3; // TODO: remove after test
_gonsPerFragment = TOTAL_GONS / _totalSupply;
_initRebaseStartTime = block.timestamp;
_lastRebasedTime = block.timestamp;
_autoRebase = false;
_autoAddLiquidity = true;
_isFeeExempt[_owner] = true;
_isFeeExempt[address(this)] = true;
_isFeeExempt[redTrustWallet] = true;
_isFeeExempt[address(nftRewardPool)] = true;
_isFeeExempt[address(treasury)] = true;
defenderTimer = block.timestamp;
emit Transfer(address(0x0), _owner, _totalSupply);
}
function rebase() internal {
if (inSwap) return;
uint256 rebaseRate;
uint256 deltaTimeFromInit = block.timestamp - _initRebaseStartTime;
uint256 deltaTime = block.timestamp - _lastRebasedTime;
uint256 times = deltaTime / rebaseInterval;
uint256 epoch = times * 15;
if (deltaTimeFromInit < (365 days)) {
rebaseRate = 2731;
} else if (deltaTimeFromInit >= (365 days)) {
rebaseRate = 211;
} else if (deltaTimeFromInit >= ((15 * 365 days) / 10)) {
rebaseRate = 14;
} else if (deltaTimeFromInit >= (7 * 365 days)) {
rebaseRate = 2;
}
for (uint256 i = 0; i < times; i++) {
_totalSupply = _totalSupply * (10**RATE_DECIMALS + rebaseRate) / 10**RATE_DECIMALS;
}
_gonsPerFragment = TOTAL_GONS / _totalSupply;
_lastRebasedTime += times * rebaseInterval;
pairContract.sync();
emit LogRebase(epoch, _totalSupply);
}
function transfer(address to, uint256 value)
public
override
validRecipient(to)
returns (bool)
{
_transferFrom(msg.sender, to, value);
return true;
}
function transferFrom(
address from,
address to,
uint256 value
) public override validRecipient(to) returns (bool) {
if (from != msg.sender && _allowedFragments[from][msg.sender] != type(uint256).max) {
require(value <= _allowedFragments[from][msg.sender], "Insufficient Allowance");
_allowedFragments[from][msg.sender] -= (value);
}
_transferFrom(from, to, value);
return true;
}
function _basicTransfer(
address from,
address to,
uint256 amount
) internal returns (bool) {
uint256 gonAmount = amount * _gonsPerFragment;
_gonBalances[from] -= gonAmount;
_gonBalances[to] += gonAmount;
return true;
}
function _transferFrom(
address sender,
address recipient,
uint256 amount
) internal returns (bool) {
require(!blacklist[sender] && !blacklist[recipient], "in_blacklist");
if (inSwap) {
return _basicTransfer(sender, recipient, amount);
}
if (shouldRebase()) {
rebase();
}
if (shouldAddLiquidity()) {
addLiquidity();
}
uint256 gonAmount = amount * _gonsPerFragment;
_gonBalances[sender] -= gonAmount;
uint256 gonAmountReceived = shouldTakeFee(sender, recipient)
? takeFee(sender, recipient, gonAmount)
: gonAmount;
_gonBalances[recipient] += gonAmountReceived;
emit Transfer(
sender,
recipient,
gonAmountReceived / _gonsPerFragment
);
return true;
}
function takeFee(
address sender,
address recipient,
uint256 gonAmount
) internal returns (uint256) {
(uint256 impact, uint256 oldPrice, uint256 newPrice) = getImpact(
gonAmount
);
// deactivate defender if 1 hour window has passed
if (rdStatus == true) {
if (block.timestamp - defenderTimer > 1 hours) {
rdStatus = false;
defenderTimer = block.timestamp - (1);
accumulatedImpact = 1;
}
}
uint256 impactTax;
uint256 feeAmount;
// sell
if (recipient == address(pair)) {
if (block.timestamp - defenderTimer < 1 hours) {
// add impact to accumulator
accumulatedImpact += impact;
} else {
// window has passed, reset accumulator
accumulatedImpact = impact;
defenderTimer = block.timestamp;
}
require(accumulatedImpact <= 500, "price impact is too large");
// activate defender if accumulated impact is > 1%
if (accumulatedImpact > 100) {
rdStatus = true;
defenderTimer = block.timestamp;
} else {
impactTax = ((gonAmount * impact) / 1000) * 4;
}
if (rdStatus) {
feeAmount = distributeFees(500, 1000, 100 + 4 * impact, 0, 0, gonAmount);
} else {
feeAmount = distributeFees(400, 500, 500 + 4 * impact, 200, 300, gonAmount);
}
} else { // buy
if (rdStatus) {
feeAmount = distributeFees(200, 500, 300, 0, 0, gonAmount);
} else {
feeAmount = distributeFees(400, 500, 300, 200, 300, gonAmount);
}
}
return gonAmount - (feeAmount);
}
function distributeFees(uint256 liquidityFee, uint256 rtfFee, uint256 rtFee, uint256 rfFee, uint256 rewardFee, uint256 gonAmount) private returns (uint256) {
uint256 _totalFee = liquidityFee + rtfFee;
_totalFee += rtFee;
_totalFee += rfFee;
_totalFee += rewardFee;
uint256 feeAmount = gonAmount * _totalFee / feeDenominator;
_gonBalances[redFurnace] += gonAmount * rfFee / feeDenominator;
_gonBalances[address(treasury)] += gonAmount * rtFee / feeDenominator;
_gonBalances[redTrustWallet] += gonAmount * rtfFee / feeDenominator;
_gonBalances[autoLiquidityReceiver] += gonAmount * liquidityFee / feeDenominator;
approve(address(nftRewardPool), rewardFee);
nftRewardPool.raiseRewardPool(rewardFee);
emit Transfer(msg.sender, address(treasury), rtFee / _gonsPerFragment);
emit Transfer(msg.sender, redTrustWallet, rtfFee / _gonsPerFragment);
emit Transfer(msg.sender, redFurnace, rfFee / _gonsPerFragment);
emit Transfer(msg.sender, autoLiquidityReceiver, liquidityFee / _gonsPerFragment);
return feeAmount;
}
function getImpact(uint256 amount)
internal
view
returns (
uint256,
uint256,
uint256
)
{
uint256 price0 = pair.price0CumulativeLast();
(uint256 reserve0, uint256 reserve1, ) = pair.getReserves();
uint256 constProduct = reserve0 * reserve1;
uint256 new1Amount = reserve1 + amount;
uint256 new0Amount = constProduct / new1Amount;
uint256 amountTradedFor = reserve1 - new0Amount;
uint256 new0Price = amount / amountTradedFor;
return (((new0Price - price0) / price0) * 10000, price0, new0Price);
// return (amount*priceImpact/1000)*4;
}
function addLiquidity() internal swapping {
uint256 autoLiquidityAmount = _gonBalances[autoLiquidityReceiver] / (
_gonsPerFragment
);
_gonBalances[address(this)] += _gonBalances[autoLiquidityReceiver];
_gonBalances[autoLiquidityReceiver] = 0;
uint256 amountToLiquify = autoLiquidityAmount / 2;
uint256 amountToSwap = autoLiquidityAmount - amountToLiquify;
if (amountToSwap == 0) {
return;
}
address[] memory path = new address[](2);
path[0] = address(this);
path[1] = router.WETH();
uint256 balanceBefore = address(this).balance;
router.swapExactTokensForETHSupportingFeeOnTransferTokens(
amountToSwap,
0,
path,
address(this),
block.timestamp
);
uint256 amountETHLiquidity = address(this).balance - (balanceBefore);
if (amountToLiquify > 0 && amountETHLiquidity > 0) {
router.addLiquidityETH{value: amountETHLiquidity}(
address(this),
amountToLiquify,
0,
0,
DEAD,
block.timestamp
);
}
_lastAddLiquidityTime = block.timestamp;
}
function withdrawAllToTreasury() public swapping onlyAdmin {
uint256 amountToSwap = _gonBalances[address(this)] / (
_gonsPerFragment
);
require(
amountToSwap > 0,
"There is no token deposited in token contract"
);
address[] memory path = new address[](2);
path[0] = address(this);
path[1] = router.WETH();
router.swapExactTokensForETHSupportingFeeOnTransferTokens(
amountToSwap,
0,
path,
address(treasury),
block.timestamp
);
}
function shouldTakeFee(address from, address to)
internal
view
returns (bool)
{
return
(address(pair) == from || address(pair) == to) &&
!_isFeeExempt[from] &&
!_isFeeExempt[to];
}
function shouldRebase() internal view returns (bool) {
return
_autoRebase &&
(_totalSupply < MAX_SUPPLY) &&
msg.sender != address(pair) &&
!inSwap &&
block.timestamp >= (_lastRebasedTime + rebaseInterval);
}
function shouldAddLiquidity() internal view returns (bool) {
return
_autoAddLiquidity &&
!inSwap &&
msg.sender != address(pair) &&
block.timestamp >= (_lastAddLiquidityTime + 48 hours);
}
function shouldSwapBack() internal view returns (bool) {
return !inSwap && msg.sender != address(pair);
}
function setAutoRebase(bool _flag) public onlyAdmin {
if (_flag) {
_autoRebase = _flag;
_lastRebasedTime = block.timestamp;
} else {
_autoRebase = _flag;
}
}
function setAutoAddLiquidity(bool _flag) public onlyAdmin {
if (_flag) {
_autoAddLiquidity = _flag;
_lastAddLiquidityTime = block.timestamp;
} else {
_autoAddLiquidity = _flag;
}
}
function allowance(address owner_, address spender)
public
view
override
returns (uint256)
{
return _allowedFragments[owner_][spender];
}
function decreaseAllowance(address spender, uint256 subtractedValue)
public
override
returns (bool)
{
uint256 oldValue = _allowedFragments[msg.sender][spender];
if (subtractedValue >= oldValue) {
_allowedFragments[msg.sender][spender] = 0;
} else {
_allowedFragments[msg.sender][spender] = oldValue - (
subtractedValue
);
}
emit Approval(
msg.sender,
spender,
_allowedFragments[msg.sender][spender]
);
return true;
}
function increaseAllowance(address spender, uint256 addedValue)
public
override
returns (bool)
{
_allowedFragments[msg.sender][spender] += addedValue;
emit Approval(
msg.sender,
spender,
_allowedFragments[msg.sender][spender]
);
return true;
}
function approve(address spender, uint256 value)
public
override
returns (bool)
{
_allowedFragments[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
return true;
}
function checkFeeExempt(address _addr) public view returns (bool) {
return _isFeeExempt[_addr];
}
function getCirculatingSupply() public view returns (uint256) {
return (TOTAL_GONS - _gonBalances[DEAD] - _gonBalances[ZERO]) / _gonsPerFragment;
}
function isNotInSwap() public view returns (bool) {
return !inSwap;
}
function manualSync() public {
IPancakeSwapPair(pair).sync();
}
function setFeeReceivers(
ITreasury _treasury,
address _redTrustWallet,
address _redFurnace
) public onlyAdmin {
treasury = _treasury;
redTrustWallet = _redTrustWallet;
redFurnace = _redFurnace;
}
function getLiquidityBacking(uint256 accuracy)
public
view
returns (uint256)
{
uint256 liquidityBalance = _gonBalances[address(pair)] / _gonsPerFragment;
return
accuracy * liquidityBalance * 2 / getCirculatingSupply();
}
function setWhitelist(address _addr) public onlyAdmin {
_isFeeExempt[_addr] = true;
}
function setBotBlacklist(address _botAddress, bool _flag) public onlyAdmin {
require(
isContract(_botAddress),
"only contract address, not allowed exteranlly owned account"
);
blacklist[_botAddress] = _flag;
}
function totalSupply() public view override returns (uint256) {
return _totalSupply;
}
function balanceOf(address who) public view override returns (uint256) {
return _gonBalances[who] / _gonsPerFragment;
}
function isContract(address addr) internal view returns (bool) {
uint256 size;
assembly {
size := extcodesize(addr)
}
return size > 0;
}
function decimals() public view override returns (uint8) {
return _decimals;
}
receive() external payable {}
}