// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/solc-0.7/contracts/access/Ownable.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/solc-0.7/contracts/token/ERC20/ERC20.sol";
import "https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/interfaces/IUniswapV2Factory.sol";
/// @title DIV token rewards buyers from specific addresses (AMMs such as uniswap) by minting purchase rewards immediately, and burns a percentage of all on chain transactions.
/// @author datenshishi | Hanedan; in collaboration with CommunityToken.io; original ver. by KrippTorofu @ RiotOfTheBlock
/// NOTES: This version includes uniswap router registration, to prevent owner from setting mint addresses that are not UniswapV2Pairs.
/// No tax/burn limits have been added to this token.
/// Until the Ownership is rescinded, owner can modify the parameters of the contract (tax, interest, whitelisted addresses, uniswap pairs).
/// Minting is disabled, except for the interest generating address, which is now behind a uniswap router check.
contract DIVToken2 is ERC20, Ownable {
using SafeMath for uint256;
using SafeMath for uint32;
uint32 internal _burnRatePerTransferThousandth = 10; // default of 1%, can go as low as 0.1%, or set to 0 to disable
uint32 internal _interestRatePerBuyThousandth = 20; // default of 2%, can go as low as 0.1%, or set to 0 to disable
address internal constant uniswapV2FactoryAddress = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
mapping(address => bool) internal _burnWhitelistTo;
mapping(address => bool) internal _burnWhitelistFrom;
mapping(address => bool) internal _UniswapAddresses;
/// @notice Transfers from IUniswapV2Pair at address `addr` now will mint an extra `_interestRatePerBuyThousandth`/1000 DIV tokens per 1 Token for the recipient.
/// @param addr Address of an IUniswapV2Pair Contract
event UniswapAddressAdded(address indexed addr);
/// @notice IUniswapV2Pair at address `addr` now will stop minting
event UniswapAddressRemoved(address indexed addr);
/// @notice The address `addr` is now whitelisted, any funds sent to it will not incur a burn.
/// @param addr Address of Contract / EOA to whitelist
event AddedToWhitelistTo(address indexed addr);
/// @notice The address `addr` is removed from whitelist, any funds sent to it will now incur a burn of `_burnRatePerTransferThousandth`/1000 DIV tokens as normal.
/// @param addr Address of Contract / EOA to whitelist
event RemovedFromWhitelistTo(address indexed addr);
/// @notice The address `addr` is now whitelisted, any funds sent FROM this address will not incur a burn.
/// @param addr Address of Contract / EOA to whitelist
event AddedToWhitelistFrom(address indexed addr);
/// @notice The address `addr` is removed from whitelist, any funds sent FROM this address will now incur a burn of `_burnRatePerTransferThousandth`/1000 DIV tokens as normal.
/// @param addr Address of Contract / EOA to whitelist
event RemovedFromWhitelistFrom(address indexed addr);
/// @notice The Burn rate has been changed to `newRate`/1000 per 1 DIV token on every transaction
event BurnRateChanged(uint32 newRate);
/// @notice The Buy Interest rate has been changed to `newRate`/1000 per 1 DIV token on every transaction
event InterestRateChanged(uint32 newRate);
constructor(address tokenOwnerWallet) ERC20("DIV Token 2", "DIV2") {
_mint(tokenOwnerWallet, 500000000000000000000000);
}
/// @notice Changes the burn rate on transfers in thousandths
/// @param value Set this value in thousandths. Max of 50. i.e. 10 = 1%, 1 = 0.1%, 0 = burns are disabled.
function setBurnRatePerThousandth(uint32 value) external onlyOwner {
// enforce a Max of 50 = 5%.
_burnRatePerTransferThousandth = value;
validateContractParameters();
emit BurnRateChanged(value);
}
/// @notice Changes the interest rate for purchases in thousandths
/// @param value Set this value in thousandths. Max of 50. i.e. 10 = 1%, 1 = 0.1%, 0 = interest is disabled.
function setInterestRatePerThousandth(uint32 value) external onlyOwner {
_interestRatePerBuyThousandth = value;
validateContractParameters();
emit InterestRateChanged(value);
}
/// @notice Address `addr` will no longer incur the `_burnRatePerTransferThousandth`/1000 burn on Transfers
/// @param addr Address to whitelist / dewhitelist
/// @param whitelisted True to add to whitelist, false to remove.
function setBurnWhitelistToAddress (address addr, bool whitelisted) external onlyOwner {
if(whitelisted) {
_burnWhitelistTo[addr] = whitelisted;
emit AddedToWhitelistTo(addr);
} else {
delete _burnWhitelistTo[addr];
emit RemovedFromWhitelistTo(addr);
}
}
/// @notice Address `addr` will no longer incur the `_burnRatePerTransferThousandth`/1000 burn on Transfers from it.
/// @param addr Address to whitelist / dewhitelist
/// @param whitelisted True to add to whitelist, false to remove.
function setBurnWhitelistFromAddress (address addr, bool whitelisted) external onlyOwner {
if(whitelisted) {
_burnWhitelistFrom[addr] = whitelisted;
emit AddedToWhitelistFrom(addr);
} else {
delete _burnWhitelistFrom[addr];
emit RemovedFromWhitelistFrom(addr);
}
}
/// @notice Will query uniswapV2Factory to find a pair. Pair now will mint an extra `_interestRatePerBuyThousandth`/1000 DIV tokens per 1 Token for the recipient.
/// @dev This will only work with the existing uniswapV2Factory and will require a new token overall if UniswapV3 comes out...etc.
/// @dev Hardcoding the factory pair in contract ensures someone can't create a fake uniswapV2Factory that would return a hardcoded EOA.
/// @param erc20token address of the ACTUAL ERC20 liquidity token, e.g. to mint on buys against WETH, pass in the WETH ERC20 address, not the uniswap LP Address.
/// @param generateInterest True to begin generating interest, false to remove.
function enableInterestForToken (address erc20token, bool generateInterest) external onlyOwner {
// returns 0x0 if pair doesn't exist.
address uniswapV2Pair = IUniswapV2Factory(uniswapV2FactoryAddress).getPair(address(this), erc20token);
require(uniswapV2Pair != 0x0000000000000000000000000000000000000000, "EnableInterest: No valid pair exists for erc20token");
if(generateInterest) {
_UniswapAddresses[uniswapV2Pair] = generateInterest;
emit UniswapAddressAdded(uniswapV2Pair);
} else {
delete _UniswapAddresses[uniswapV2Pair];
emit UniswapAddressRemoved(uniswapV2Pair);
}
}
/// @notice This function can be used by Contract Owner to disperse tokens bypassing incurring penalties or interest. The tokens will be sent from the Owner Address Balance.
/// @param dests Array of recipients
/// @param values Array of values. Ensure the values are in wei. i.e. you must multiply the amount of DIV tokens to be sent by 10**18.
function airdrop(address[] calldata dests, uint256[] calldata values) external onlyOwner returns (uint256) {
uint256 i = 0;
while (i < dests.length) {
ERC20._transfer(_msgSender(), dests[i], values[i]);
i += 1;
}
return(i);
}
/// @notice Returns the burn rate on transfers in thousandths
function getBurnRatePerThousandth() external view returns (uint32) {
return _burnRatePerTransferThousandth;
}
/// @notice Returns the interest rate for purchases in thousandths
function getInterestRate() external view returns (uint32) {
return _interestRatePerBuyThousandth;
}
/// @notice If true, Address `addr` will not incur `_burnRatePerTransferThousandth`/1000 burn for any Transfers to it.
/// @param addr Address to check
/// @dev it is not trivial to return a mapping without incurring further storage costs
function isAddressWhitelistedTo(address addr) external view returns (bool) {
return _burnWhitelistTo[addr];
}
/// @notice If true, Address `addr` will not incur `_burnRatePerTransferThousandth`/1000 burn for any Transfers from it.
/// @param addr Address to check
/// @dev it is not trivial to return a mapping without incurring further storage costs
function isAddressWhitelistedFrom(address addr) external view returns (bool) {
return _burnWhitelistFrom[addr];
}
/// @notice If true, transfers from IUniswapV2Pair at address `addr` will mint an extra `_interestRatePerBuyThousandth`/1000 DIV tokens per 1 Token for the recipient.
/// @param addr Address to check
/// @dev it is not trivial to return a mapping without incurring further storage costs
function checkInterestGenerationForAddress(address addr) external view returns (bool) {
return _UniswapAddresses[addr];
}
/**
@notice ERC20 transfer function overridden to add `_burnRatePerTransferThousandth`/1000 burn on transfers as well as `_interestRatePerBuyThousandth`/1000 interest for AMM purchases.
@param amount amount in wei
Burn rate is applied independently of the interest.
No reentrancy check required, since these functions are not transferring ether and only modifying internal balances.
*/
function _transfer(address sender, address recipient, uint256 amount) internal virtual override {
// FROM uniswap address, mint interest tokens
// Constraint: Anyone in burn whitelist cannot receive interest, to reduce owner abuse possibility.
// This means whitelisting uniswap for any reason will also turn off interest.
if(_UniswapAddresses[sender] &&
_interestRatePerBuyThousandth > 0 &&
!_burnWhitelistTo[recipient] &&
!_burnWhitelistFrom[sender]) {
super._mint(recipient, amount.mul(_interestRatePerBuyThousandth).div(1000));
// no need to adjust amount
}
// Apply burn
if(!_burnWhitelistTo[recipient] && !_burnWhitelistFrom[sender] && _burnRatePerTransferThousandth>0) {
uint256 burnAmount = amount.mul(_burnRatePerTransferThousandth).div(1000);
super._burn(sender, burnAmount);
// reduce the amount to be sent
amount = amount.sub(burnAmount);
}
// Send the modified amount to recipient
super._transfer(sender, recipient, amount);
}
/// @notice After modifying contract parameters, call this function to run internal consistency checks.
function validateContractParameters() internal view {
// These upper bounds have been added per community request
require(_burnRatePerTransferThousandth <= 50, "Error: Burn cannot be larger than 5%");
require(_interestRatePerBuyThousandth <= 50, "Error: Interest cannot be larger than 5%");
// This is to avoid an owner accident/misuse, if uniswap can reward a larger amount than a single buy+sell,
// that would allow anyone to drain the Uniswap pool with a flash loan.
// Since Uniswap fees are not considered, all Uniswap transactions are ultimately deflationary.
require(_interestRatePerBuyThousandth <= _burnRatePerTransferThousandth.mul(2), "Error: Interest cannot exceed 2*Burn");
}
}
{
"compilationTarget": {
"browser/contracts/DivToken.sol": "DIVToken2"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"tokenOwnerWallet","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addr","type":"address"}],"name":"AddedToWhitelistFrom","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addr","type":"address"}],"name":"AddedToWhitelistTo","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"newRate","type":"uint32"}],"name":"BurnRateChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"newRate","type":"uint32"}],"name":"InterestRateChanged","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":"addr","type":"address"}],"name":"RemovedFromWhitelistFrom","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addr","type":"address"}],"name":"RemovedFromWhitelistTo","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addr","type":"address"}],"name":"UniswapAddressAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addr","type":"address"}],"name":"UniswapAddressRemoved","type":"event"},{"inputs":[{"internalType":"address[]","name":"dests","type":"address[]"},{"internalType":"uint256[]","name":"values","type":"uint256[]"}],"name":"airdrop","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"checkInterestGenerationForAddress","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"erc20token","type":"address"},{"internalType":"bool","name":"generateInterest","type":"bool"}],"name":"enableInterestForToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getBurnRatePerThousandth","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getInterestRate","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"isAddressWhitelistedFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"isAddressWhitelistedTo","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"value","type":"uint32"}],"name":"setBurnRatePerThousandth","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"bool","name":"whitelisted","type":"bool"}],"name":"setBurnWhitelistFromAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"bool","name":"whitelisted","type":"bool"}],"name":"setBurnWhitelistToAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"value","type":"uint32"}],"name":"setInterestRatePerThousandth","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]