Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./ownable.sol";
import "./ierc20.sol";
interface IOmnicoin {
function remintBurntCoins(address _recipient) external;
function changeOmnifyAddress(address newAddress) external;
}
contract Omnify is Ownable {
uint256 internal MAXUINT = 2 ** 256 - 1;
constructor(uint8 _paramNativeDecimals, uint256 _paramProposalFee) Ownable(msg.sender) {
nativeCoinDecimals = _paramNativeDecimals;
proposalFee = _paramProposalFee;
distributionRoundInterval = 3 days;
coinHoldingPeriod = 4 days;
proposalVotingPeriod = 3 days;
}
event NewMilestoneEvent(uint256 _count, string _title, string _description, uint256 _date);
event NewProposalEvent(uint256 _count, string _title, string _description, address _proposer, uint256 _date);
struct Proposal {
address proposer;
string title;
string description;
uint256 date;
uint24 yesVotes;
uint24 noVotes;
mapping(address => bool) votes;
mapping(address => bool) yesVoteAddresses;
mapping(address => bool) noVoteAddresses;
}
struct Milestone {
string title;
string description;
uint256 date;
}
struct DistributionRound {
uint256 feesCollected;
uint256 roundNumber;
uint256 profitPerShare;
uint256 amountWithdrawn;
uint256 amountRemaining;
mapping(address => bool) hasWithdrawn;
uint256 date;
bool roundOpen;
}
mapping(address => uint256) public dateCoinsReceived;
mapping(address => uint256) public addressProfits;
uint256 public proposalCount = 1;
mapping(uint256 => Proposal) public proposals;
mapping(uint256 => Milestone) public milestones;
mapping(uint256 => DistributionRound) public distributionRounds;
mapping(address => bool) public whitelistedExternalContracts;
uint256 public proposalFee;
uint256 public totalProfitsDistributed;
uint256 public currentProfitsCollected;
uint256 public totalProfitsCollected;
address public omniCoinAddress;
uint256 public milestoneCount = 1;
uint256 public currentRoundNumber = 0;
uint256 public coinHoldingPeriod;
uint256 public proposalVotingPeriod;
uint256 public distributionRoundInterval;
uint8 public nativeCoinDecimals;
address public keeperAddress;
address public feeKeeperAddress;
address public coinsaleAddress;
modifier onlyOmniCoin(address _sender) {
require(_sender == omniCoinAddress);
_;
}
modifier onlyKeeper(address _sender) {
require(_sender == keeperAddress);
_;
}
modifier onlyFeeKeeper(address _sender) {
require(_sender == feeKeeperAddress);
_;
}
function setOmniCoinAddress(address _omniCoinAddress) external onlyOwner {
omniCoinAddress = _omniCoinAddress;
}
function setCoinsaleAddress(address _coinsale) external onlyOwner {
coinsaleAddress = _coinsale;
}
function setKeeperAddress(address _keeper) external onlyOwner {
keeperAddress = _keeper;
}
function setFeeKeeperAddress(address _feeKeeper) external onlyOwner {
feeKeeperAddress = _feeKeeper;
}
function whitelistAddress(address _contract) external onlyOwner {
whitelistedExternalContracts[_contract] = true;
}
function removeAddressWhitelist(address _contract) external onlyOwner {
whitelistedExternalContracts[_contract] = false;
}
function addProfitsFromExternalContract() external payable {
require(whitelistedExternalContracts[msg.sender] == true);
currentProfitsCollected = safeAdd(currentProfitsCollected, msg.value);
}
function checkIfContractWhiteListed(address _contract) external view returns (bool) {
return whitelistedExternalContracts[_contract];
}
function setProposalFee(uint256 _fee) public onlyOwner {
proposalFee = _fee;
}
function setProposalFeeByFeeKeeper(uint256 _fee) public onlyFeeKeeper(msg.sender) {
proposalFee = _fee;
}
function _setCoinHoldingPeriod(uint256 _period) internal onlyOwner {
require(_period > 0);
require(_period >= distributionRoundInterval + 1 days);
require(_period != coinHoldingPeriod);
coinHoldingPeriod = _period;
}
function _setDistributionRoundInterval(uint256 _interval) internal onlyOwner {
require(_interval >= 1 days);
require(distributionRoundInterval != _interval);
distributionRoundInterval = _interval;
}
function setHoldingAndRoundInterval(uint256 _holdingPeriod, uint256 _interval) external onlyOwner {
_setDistributionRoundInterval(_interval);
_setCoinHoldingPeriod(_holdingPeriod);
}
function setCoinsReceivedDate(address _recipient) external onlyOmniCoin(msg.sender) {
dateCoinsReceived[_recipient] = block.timestamp;
}
function lookupAddressProfits(address _address) public view returns (uint256) {
return addressProfits[_address];
}
function lookupHasWithdrawnFromRound(uint256 _roundNumber, address _address) public view returns (bool) {
return distributionRounds[_roundNumber].hasWithdrawn[_address];
}
function lookupMilestone(uint256 _count) public view returns (Milestone memory) {
return milestones[_count];
}
function lookupMilestones() public view returns (Milestone[] memory) {
Milestone[] memory _milestones = new Milestone[](milestoneCount);
if (milestoneCount > 1) {
for (uint256 i = 1; i < milestoneCount; i++) {
_milestones[i] = milestones[i];
}
}
return _milestones;
}
function lookupProposalProposer(uint256 _id) public view returns (address) {
return proposals[_id].proposer;
}
function lookupProposalTitle(uint256 _id) public view returns (string memory) {
return proposals[_id].title;
}
function lookupProposalDescription(uint256 _id) public view returns (string memory) {
return proposals[_id].description;
}
function lookupProposalDate(uint256 _id) public view returns (uint256) {
return proposals[_id].date;
}
function lookupProposalYesses(uint256 _id) public view returns (uint256) {
return proposals[_id].yesVotes;
}
function lookupProposalNos(uint256 _id) public view returns (uint256) {
return proposals[_id].noVotes;
}
function checkHasVoted(uint256 _id, address _voter) public view returns (bool) {
return proposals[_id].votes[_voter];
}
function checkIfVotedYes(uint256 _id, address _voter) public view returns (bool) {
return proposals[_id].yesVoteAddresses[_voter];
}
function checkIfVotedNo(uint256 _id, address _voter) public view returns (bool) {
return proposals[_id].noVoteAddresses[_voter];
}
function lookupRoundFeesCollected(uint256 _roundNum) public view returns (uint256) {
return distributionRounds[_roundNum].feesCollected;
}
function lookupRoundProfitPerShare(uint256 _roundNum) public view returns (uint256) {
return distributionRounds[_roundNum].profitPerShare;
}
function lookupAmountWithdrawn(uint256 _roundNum) public view returns (uint256) {
return distributionRounds[_roundNum].amountWithdrawn;
}
function lookupAmountRemaining(uint256 _roundNum) public view returns (uint256) {
return distributionRounds[_roundNum].amountRemaining;
}
function lookUpRoundOpen(uint256 _roundNum) public view returns (bool) {
return distributionRounds[_roundNum].roundOpen;
}
function lookupRoundDate(uint256 _roundNum) public view returns (uint256) {
return distributionRounds[_roundNum].date;
}
function submitProposal(string calldata _title, string calldata _description) external payable {
require(proposals[proposalCount].date == 0);
require(msg.value == proposalFee);
MYIERC20 omniCoin = MYIERC20(omniCoinAddress);
uint256 balanceOfProposer = omniCoin.balanceOf(msg.sender);
require(balanceOfProposer >= 1);
proposals[proposalCount].proposer = msg.sender;
proposals[proposalCount].title = _title;
proposals[proposalCount].description = _description;
proposals[proposalCount].date = block.timestamp;
emit NewProposalEvent(proposalCount, _title, _description, msg.sender, block.timestamp);
proposalCount++;
addProfits(msg.value);
}
function voteYes(uint256 _proposalId) external {
require(proposals[_proposalId].date != 0);
require(proposals[_proposalId].date + proposalVotingPeriod > block.timestamp);
uint256 dateVoterCoinsReceived = dateCoinsReceived[msg.sender];
uint256 dateAllowedToVote = dateVoterCoinsReceived + proposalVotingPeriod + 1 days;
require(dateAllowedToVote <= block.timestamp);
bool hasVoted = proposals[_proposalId].votes[msg.sender];
require(hasVoted == false);
MYIERC20 omniCoin = MYIERC20(omniCoinAddress);
uint24 balanceOfVoter = uint24(omniCoin.balanceOf(msg.sender));
require(balanceOfVoter >= 1);
proposals[_proposalId].yesVotes += balanceOfVoter;
proposals[_proposalId].votes[msg.sender] = true;
proposals[_proposalId].yesVoteAddresses[msg.sender] = true;
}
function voteNo(uint256 _proposalId) external {
require(proposals[_proposalId].date != 0);
require(proposals[_proposalId].date + proposalVotingPeriod > block.timestamp);
uint256 dateVoterCoinsReceived = dateCoinsReceived[msg.sender];
uint256 dateAllowedToVote = dateVoterCoinsReceived + proposalVotingPeriod + 1 days;
require(dateAllowedToVote <= block.timestamp);
bool hasVoted = proposals[_proposalId].votes[msg.sender];
require(hasVoted == false);
MYIERC20 omniCoin = MYIERC20(omniCoinAddress);
uint24 balanceOfVoter = uint24(omniCoin.balanceOf(msg.sender));
require(balanceOfVoter >= 1);
proposals[_proposalId].noVotes += balanceOfVoter;
proposals[_proposalId].votes[msg.sender] = true;
proposals[_proposalId].noVoteAddresses[msg.sender] = true;
}
function newMilestone(string calldata _title, string calldata _description) external onlyOwner {
require(milestones[milestoneCount].date == 0);
milestones[milestoneCount].title = _title;
milestones[milestoneCount].description = _description;
milestones[milestoneCount].date = block.timestamp;
emit NewMilestoneEvent(milestoneCount, _title, _description, block.timestamp);
milestoneCount++;
}
function triggerRound() private {
require(distributionRounds[currentRoundNumber].date + distributionRoundInterval <= block.timestamp);
uint256 oneCoin = 1 * (10 ** nativeCoinDecimals);
require(currentProfitsCollected >= oneCoin);
distributionRounds[currentRoundNumber].roundOpen = false;
uint256 bufferRemainder;
if (currentRoundNumber > 0) {
bufferRemainder = address(this).balance - currentProfitsCollected;
}
currentRoundNumber++;
totalProfitsCollected = safeAdd(totalProfitsCollected, currentProfitsCollected);
distributionRounds[currentRoundNumber].feesCollected = currentProfitsCollected;
distributionRounds[currentRoundNumber].feesCollected += bufferRemainder;
distributionRounds[currentRoundNumber].amountRemaining = distributionRounds[currentRoundNumber].feesCollected;
distributionRounds[currentRoundNumber].date = block.timestamp;
uint256 profitPerShare = distributionRounds[currentRoundNumber].feesCollected / 250_000;
distributionRounds[currentRoundNumber].profitPerShare = profitPerShare;
distributionRounds[currentRoundNumber].roundOpen = true;
currentProfitsCollected = 0;
}
function triggerNewDistributionRoundByKeeper() external onlyKeeper(msg.sender) {
triggerRound();
}
function triggerNewDistributionRoundByOwner() external onlyOwner {
triggerRound();
}
function endDistributionRoundByKeeper() external onlyKeeper(msg.sender) {
require(distributionRounds[currentRoundNumber].date + distributionRoundInterval <= block.timestamp);
distributionRounds[currentRoundNumber].roundOpen = false;
}
function endDistributionRoundByOwner() external onlyOwner {
require(distributionRounds[currentRoundNumber].date + distributionRoundInterval <= block.timestamp);
distributionRounds[currentRoundNumber].roundOpen = false;
}
function withdrawProfits() external {
require(distributionRounds[currentRoundNumber].roundOpen);
require(distributionRounds[currentRoundNumber].hasWithdrawn[msg.sender] == false);
require(distributionRounds[currentRoundNumber].date + distributionRoundInterval > block.timestamp);
MYIERC20 omniCoin = MYIERC20(omniCoinAddress);
uint256 balanceOfPerson = omniCoin.balanceOf(msg.sender);
require(balanceOfPerson >= 1);
uint256 amountPerShare = distributionRounds[currentRoundNumber].profitPerShare;
uint256 amountProfit = amountPerShare * balanceOfPerson;
require(distributionRounds[currentRoundNumber].amountRemaining >= amountProfit);
uint256 dateRequesterCoinsReceived = dateCoinsReceived[msg.sender];
uint256 dateAllowedToWithdraw = dateRequesterCoinsReceived + coinHoldingPeriod;
require(dateAllowedToWithdraw <= block.timestamp);
distributionRounds[currentRoundNumber].amountWithdrawn += amountProfit;
distributionRounds[currentRoundNumber].amountRemaining -= amountProfit;
distributionRounds[currentRoundNumber].hasWithdrawn[msg.sender] = true;
totalProfitsDistributed = safeAdd(totalProfitsDistributed, amountProfit);
addressProfits[msg.sender] = safeAdd(addressProfits[msg.sender], amountProfit);
address payable sendTo = payable(msg.sender);
(bool success,) = sendTo.call{value: amountProfit}("");
require(success);
}
function addProfits(uint256 _amount) internal {
currentProfitsCollected = safeAdd(currentProfitsCollected, _amount);
}
function safeAdd(uint256 _currentAmount, uint256 _amountToBeAdded) internal view returns (uint256) {
uint256 _allowedAmount = MAXUINT - _currentAmount;
if (_amountToBeAdded <= _allowedAmount) {
return _currentAmount + _amountToBeAdded;
}
return _currentAmount;
}
function remintBurnt() external onlyOwner {
IOmnicoin omniCoin = IOmnicoin(omniCoinAddress);
omniCoin.remintBurntCoins(owner());
}
function changeOmnifyAddressOnOmnicoin(address newAddress) external onlyOwner {
IOmnicoin omniCoin = IOmnicoin(omniCoinAddress);
omniCoin.changeOmnifyAddress(newAddress);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
abstract contract MyContext {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is MyContext {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface MYIERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}