You can certainly do that.
What you will need to do is to add whitelist
logic to your contract.
As an admin of your contract, you will keep a whitelist
mapping of addresses than can have more than 1% of tokens for any reason. You would need to be able to add/remove addresses from that whitelist
at any time.
I will provide you with an idea.
MyERC20
contract extends from ERC20
, so it is an ERC20 token contract.
I override
the transfer
and transferFrom
functions to add the ensureNoMoreThan1PercentIfNotWhitelisted
modifier on them, so, before sending tokens from one account to another, it checks if the final amount for the receiving account would exceed 1% of the total supply. If it's a whitelisted address, it will allow it to have more than 1% of the total supply. If it's not whitelisted, it will not allow it.
Feel free to do any modifications.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol";
contract MyERC20 is ERC20("Abc", "ABC") {
mapping(address => bool) public whitelist;
address public admin;
uint256 public constant initialSupply = 100;
constructor() {
admin = msg.sender;
_mint(admin, initialSupply);
}
modifier onlyAdmin() {
require(msg.sender == admin);
_;
}
// modifier to make sure that only whitelisted addresses can have more than 1% of the total supply.
modifier ensureNoMoreThan1PercentIfNotWhitelisted(address to, uint256 amount) {
uint256 toBalance = balanceOf(to);
uint256 onePercentOfTotalSupply = totalSupply() / 100;
// If it's not whitelisted to have more than 1% of the total supply and is trying to have more than allowed, revert.
if(!whitelist[msg.sender]) {
require((toBalance + amount) <= onePercentOfTotalSupply, "Illegal operation. Account owould have more than 1% of the total supply.");
}
_;
}
// Overriding `transfer` and `transferFrom` so they cannot be called externally directly
// and all `transfer` and `transferFrom` calls should pass through the `ensureNoMoreThan1PercentIfNotWhitelisted` modifier.
function transfer(address to, uint256 amount) public override ensureNoMoreThan1PercentIfNotWhitelisted(to, amount) returns (bool) {
return super.transfer(to, amount);
}
function transferFrom(address from, address to, uint256 amount) public override ensureNoMoreThan1PercentIfNotWhitelisted(to, amount) returns (bool) {
return super.transferFrom(from, to, amount);
}
function addToWhitelist(address addr) public onlyAdmin {
whitelist[addr] = true;
}
function removeFromWhitelist(address addr) public onlyAdmin {
delete whitelist[addr];
}
// more logic...
}
One problem with this approach would be if you implement burning logic. Because the total supply would reduce and then users could end up with more tokens than the 1% allowed because the calculation of the 1% would be affected by the reduced tokens in circulation due to burning.
For minting I don't think there would be an issue, because when minting users would now have more room to get more tokens and still be below 1%.
Feel free to change anything and add your own logic.
With separate logic for whitelisted users:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol";
contract MyERC20 is ERC20("Abc", "ABC") {
mapping(address => bool) public whitelist;
address public admin;
uint256 public constant initialSupply = 100;
constructor() {
admin = msg.sender;
_mint(admin, initialSupply);
}
modifier onlyAdmin() {
require(msg.sender == admin);
_;
}
modifier whitelisted() {
require(whitelist[msg.sender], "User is not whitelisted to access this funcionality");
_;
}
// modifier to make sure that only whitelisted addresses can have more than 1% of the total supply.
modifier ensureNoMoreThan1PercentIfNotWhitelisted(address to, uint256 amount) {
uint256 toBalance = balanceOf(to);
uint256 onePercentOfTotalSupply = totalSupply() / 100;
require((toBalance + amount) <= onePercentOfTotalSupply, "Illegal operation. Account owould have more than 1% of the total supply.");
_;
}
// Overriding `transfer` and `transferFrom` so they cannot be called externally directly
// and all `transfer` and `transferFrom` calls should pass through the `ensureNoMoreThan1PercentIfNotWhitelisted` modifier.
function transfer(address to, uint256 amount) public override ensureNoMoreThan1PercentIfNotWhitelisted(to, amount) returns (bool) {
return super.transfer(to, amount);
}
function transferFrom(address from, address to, uint256 amount) public override ensureNoMoreThan1PercentIfNotWhitelisted(to, amount) returns (bool) {
return super.transferFrom(from, to, amount);
}
function addToWhitelist(address addr) public onlyAdmin {
whitelist[addr] = true;
}
function removeFromWhitelist(address addr) public onlyAdmin {
delete whitelist[addr];
}
function whitelistedTransfer(address to, uint256 amount) public whitelisted returns (bool) {
return super.transfer(to, amount);
}
function whitelistedTransferFrom(address from, address to, uint256 amount) public whitelisted returns (bool) {
return super.transferFrom(from, to, amount);
}
// more logic...
}
Remember this is just an example. More logic should be added, like making sure only whitelisted could get more than 1% of the supply when using any of the transfer functions, etc.