Contract Name:
IPMBStaking
Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.5;
/*
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with GSN meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}
// SPDX-License-Identifier: MIT
/**
*
* @title IERC20
*/
pragma solidity ^0.8.5;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @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 amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` 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 amount) 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 `amount` 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 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` 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 amount) external returns (bool);
}
// SPDX-License-Identifier: MIT
/**
*
* @title: IPMB Staking Pools
* @date: 20-November-2024
* @version: 2.5
* @author: IPMB Dev Team
*/
import "./IERC20.sol";
import "./Ownable.sol";
import "./IPriceFeed.sol";
pragma solidity ^0.8.19;
contract IPMBStaking is Ownable {
// pool structure
struct poolStr {
uint256 poolID;
string poolName;
uint256 duration;
uint256 amount;
uint256 discount;
uint256 lockDuration;
uint256 poolMax;
bool status;
}
// address pool data structure
struct addressStr {
uint256 amount;
uint256 dateDeposit;
uint256 epoch;
uint256 ipmbPrice;
uint256 goldPrice;
}
// mappings declaration
mapping (address => bool) public admin;
mapping (address => bool) public authority;
mapping (address => bool) public blacklist;
mapping (uint256 => poolStr) public poolsRegistry;
mapping (address => bool) public kycAddress;
mapping (address => mapping (uint256 => uint256)) public addressCounter;
mapping (address => mapping (uint256 => uint256[])) public addressArray;
mapping (address => mapping (uint256 => mapping (uint256 => addressStr))) public addressDataNew;
// variables declaration
uint256 public nextpoolCounter;
address public ipmbAddress;
IPriceFeed public priceFeedAddress;
address public gemMintingContract;
uint256 public blackPeriod;
uint256 public minDiscount;
uint256 public maxDiscount;
// modifiers
modifier onlyAdmin() {
require(admin[msg.sender] == true, "Not allowed");
_;
}
modifier onlyAuthority() {
require(admin[msg.sender] == true || authority[msg.sender] == true, "Not allowed");
_;
}
// events
event poolRegistration(uint256 indexed poolId);
event poolUpdate(uint256 indexed poolId);
event poolDeposit(uint256 indexed poolId, address indexed addr, uint256 indexed index, uint256 amount);
event poolWithdrawal(uint256 indexed poolId, address indexed addr, uint256 indexed index, uint256 amount);
event blacklistWithdrawal(uint256 indexed poolId, address indexed addr, uint256 indexed index, uint256 amount);
event poolResetAfterMinting(uint256 indexed poolId, address indexed addr, uint256 indexed index);
event adminStatus(address indexed addr, bool indexed status);
event authorityStatus(address indexed addr, bool indexed status);
event blacklistStatus(address indexed addr, bool indexed status);
event updateBlackPeriod(uint256 indexed bperiod);
event kycStatus(address indexed addr, bool indexed status);
// constructor
constructor (address _ipmbAddress, address _priceFeedAddress, uint256 _blackPeriod) {
admin[msg.sender] = true;
ipmbAddress = _ipmbAddress;
nextpoolCounter = 1;
priceFeedAddress = IPriceFeed(_priceFeedAddress);
blackPeriod = _blackPeriod;
minDiscount = 2;
maxDiscount = 11;
}
// function to register a Pool
function registerPool(string memory _poolName, uint256 _duration, uint256 _discount, uint256 _amount, uint256 _lockDuration, uint256 _poolMax) public onlyAdmin {
require(_duration > 0 && _amount > 0 , "err");
require(_discount >= minDiscount && _discount <= maxDiscount, "Check min & max");
uint256 poolID = nextpoolCounter;
poolsRegistry[poolID].poolID = poolID;
poolsRegistry[poolID].poolName = _poolName;
poolsRegistry[poolID].duration = _duration;
poolsRegistry[poolID].amount = _amount;
poolsRegistry[poolID].discount = _discount;
poolsRegistry[poolID].lockDuration = _lockDuration;
poolsRegistry[poolID].poolMax = _poolMax;
poolsRegistry[poolID].status = true;
emit poolRegistration(poolID);
nextpoolCounter = nextpoolCounter + 1;
}
// function to deposit funds
function depositPool(uint256 _poolID) public {
require(kycAddress[msg.sender] == true, "No KYC");
require(blacklist[msg.sender] == false, "Address is blacklisted");
require(poolsRegistry[_poolID].status == true, "Pool is inactive");
require(poolsRegistry[_poolID].poolMax > addressArray[msg.sender][_poolID].length, "Already deposited max times");
require(IERC20(ipmbAddress).balanceOf(msg.sender) >= poolsRegistry[_poolID].amount, "Your ERC20 balance is not enough");
(uint256 epoch, uint256 ipmbPrice, uint256 goldPrice, , ,) = priceFeedAddress.getLatestPrices();
uint256 count = addressCounter[msg.sender][_poolID];
addressDataNew[msg.sender][_poolID][count].amount = poolsRegistry[_poolID].amount;
addressDataNew[msg.sender][_poolID][count].dateDeposit = block.timestamp;
addressDataNew[msg.sender][_poolID][count].epoch = epoch;
addressDataNew[msg.sender][_poolID][count].ipmbPrice = ipmbPrice;
addressDataNew[msg.sender][_poolID][count].goldPrice = goldPrice;
addressArray[msg.sender][_poolID].push(count);
addressCounter[msg.sender][_poolID]++;
IERC20(ipmbAddress).transferFrom(msg.sender, address(this), poolsRegistry[_poolID].amount);
emit poolDeposit(_poolID, msg.sender, count, poolsRegistry[_poolID].amount);
}
// function to deposit multitimes
function multiDepositPool(uint256[] memory _poolIDs, uint256[] memory _quantity) public {
require(_poolIDs.length == _quantity.length , "Check lengths");
for (uint256 i = 0; i < _poolIDs.length; i++) {
for (uint256 y = 0; y < _quantity[i]; y++) {
depositPool(_poolIDs[i]);
}
}
}
// function to withdrawl deposit amounts
function withdrawalPool(uint256 _poolID, uint256 _index) public {
require(blacklist[msg.sender] == false, "Address is blacklisted");
require(addressDataNew[msg.sender][_poolID][_index].amount == poolsRegistry[_poolID].amount, "No deposit");
require(block.timestamp >= addressDataNew[msg.sender][_poolID][_index].dateDeposit + poolsRegistry[_poolID].lockDuration, "Time has not passed");
uint256 amount = addressDataNew[msg.sender][_poolID][_index].amount;
addressDataNew[msg.sender][_poolID][_index].amount = 0;
addressDataNew[msg.sender][_poolID][_index].dateDeposit = 0;
addressDataNew[msg.sender][_poolID][_index].epoch = 0;
addressDataNew[msg.sender][_poolID][_index].ipmbPrice = 0;
addressDataNew[msg.sender][_poolID][_index].goldPrice = 0;
for (uint256 i = 0; i < addressArray[msg.sender][_poolID].length; i++) {
if (_index == addressArray[msg.sender][_poolID][i]) {
addressArray[msg.sender][_poolID][i] = addressArray[msg.sender][_poolID][addressArray[msg.sender][_poolID].length-1];
addressArray[msg.sender][_poolID].pop();
}
}
IERC20(ipmbAddress).transfer(msg.sender, amount);
emit poolWithdrawal(_poolID, msg.sender, _index, amount);
}
// function to update pool data
function updatePoolData(uint256 _poolID, uint256 _poolMax, bool status) public onlyAdmin {
poolsRegistry[_poolID].poolMax = _poolMax;
poolsRegistry[_poolID].status = status;
emit poolUpdate(_poolID);
}
// function to update address pool details after nft minting
function updateAddressPool(address _address, uint256 _poolID, uint256 _index) public {
require(msg.sender == gemMintingContract, "Not allowed");
addressDataNew[_address][_poolID][_index].amount = 0;
addressDataNew[_address][_poolID][_index].dateDeposit = 0;
addressDataNew[_address][_poolID][_index].epoch = 0;
addressDataNew[_address][_poolID][_index].ipmbPrice = 0;
addressDataNew[_address][_poolID][_index].goldPrice = 0;
emit poolResetAfterMinting(_poolID, _address, _index);
}
// function to add/remove an admin
function addAdmin(address _address, bool _status) public onlyOwner {
admin[_address] = _status;
emit adminStatus(_address, _status);
}
// function to add/remove an authority
function addAuthority(address _address, bool _status) public onlyOwner {
authority[_address] = _status;
emit authorityStatus(_address, _status);
}
// function to add/remove a wallet from blacklist
function addBlacklist(address _address, bool _status) public onlyAuthority {
blacklist[_address] = _status;
emit blacklistStatus(_address, _status);
}
// function to set GEM minting contract
function setGEMMintingContract(address _address) public onlyOwner {
gemMintingContract = _address;
}
// function to update prices contract admin
function updatePricesContract(address _address) public onlyOwner {
priceFeedAddress = IPriceFeed(_address);
}
// function to approve GEM minting contract
function approveGEMMintingContract(uint256 _amount) public onlyAdmin {
IERC20(ipmbAddress).approve(gemMintingContract, _amount);
}
// function to modify the time that the blacklist funds can be withdrawl
function changeBlackPeriod(uint256 _blackPeriod) public onlyAdmin {
blackPeriod = _blackPeriod;
emit updateBlackPeriod(_blackPeriod);
}
// function to update address kyc status
function updateKYCAddress(address _address, bool _status) public onlyAdmin {
kycAddress[_address] = _status;
emit kycStatus(_address, _status);
}
// function to update the min and max % of discounts for pool registration
function updateMinMaxDiscounts(uint256 _min, uint256 _max) public onlyAdmin {
minDiscount = _min;
maxDiscount = _max;
}
// function to update kyc status for multiple addresses
function updateKYCAddressBatch(address[] memory _address, bool[] memory _status) public onlyAdmin {
for (uint256 i = 0; i < _address.length; i++) {
kycAddress[_address[i]] = _status[i];
emit kycStatus(_address[i], _status[i]);
}
}
// function to withdrawal blacklist amount for a blaclist address
function blacklistAddressWithdrawalPool(address _receiver, address _address, uint256 _poolID, uint256 _index) public onlyOwner {
require(blacklist[_address] == true, "Address is not blacklisted");
require(addressDataNew[_address][_poolID][_index].amount == poolsRegistry[_poolID].amount, "No deposit");
require(block.timestamp >= addressDataNew[_address][_poolID][_index].dateDeposit + blackPeriod, "Time has not passed");
uint256 amount = addressDataNew[_address][_poolID][_index].amount;
addressDataNew[_address][_poolID][_index].amount = 0;
addressDataNew[_address][_poolID][_index].dateDeposit = 0;
addressDataNew[_address][_poolID][_index].epoch = 0;
addressDataNew[_address][_poolID][_index].ipmbPrice = 0;
addressDataNew[_address][_poolID][_index].goldPrice = 0;
IERC20(ipmbAddress).transfer(_receiver, amount);
emit blacklistWithdrawal(_poolID, _address, _index, amount);
}
// retrieve discount
function getDiscount(uint256 _poolID, address _address, uint256 _index) public view returns (uint256) {
if ((addressDataNew[_address][_poolID][_index].amount == poolsRegistry[_poolID].amount) && (block.timestamp >= addressDataNew[_address][_poolID][_index].dateDeposit + poolsRegistry[_poolID].duration)) {
return poolsRegistry[_poolID].discount;
} else {
return 0;
}
}
// retrieve pool info
function poolInfo(uint256 _poolID) public view returns (string memory, uint256, uint256, uint256, uint256, uint256, bool) {
return (poolsRegistry[_poolID].poolName, poolsRegistry[_poolID].duration, poolsRegistry[_poolID].amount, poolsRegistry[_poolID].discount, poolsRegistry[_poolID].lockDuration, poolsRegistry[_poolID].poolMax, poolsRegistry[_poolID].status);
}
// retrieve pool price
function poolPrice(uint256 _poolID) public view returns (uint256) {
return (poolsRegistry[_poolID].amount);
}
// retrieve pool status
function poolStatus(uint256 _poolID) public view returns (bool) {
return (poolsRegistry[_poolID].status);
}
// retrieve pool discount
function poolDiscount(uint256 _poolID) public view returns (uint256) {
return (poolsRegistry[_poolID].discount);
}
// retrieve deposit amount
function poolAmountPerAddress(uint256 _poolID, address _address, uint256 _index) public view returns (uint256) {
return (addressDataNew[_address][_poolID][_index].amount);
}
// retrieve deposit amount
function poolDataPerAddress(uint256 _poolID, address _address, uint256 _index) public view returns (uint256, uint256, uint256, uint256, uint256) {
return (addressDataNew[_address][_poolID][_index].amount, addressDataNew[_address][_poolID][_index].dateDeposit, addressDataNew[_address][_poolID][_index].epoch, addressDataNew[_address][_poolID][_index].ipmbPrice, addressDataNew[_address][_poolID][_index].goldPrice);
}
// retrieve deposit date
function poolDepositDatePerAddress(uint256 _poolID, address _address, uint256 _index) public view returns (uint256) {
return (addressDataNew[_address][_poolID][_index].dateDeposit);
}
// retrieve ipmb price at pool deposit
function poolIPMBPricePerAddress(uint256 _poolID, address _address, uint256 _index) public view returns (uint256) {
return (addressDataNew[_address][_poolID][_index].ipmbPrice);
}
// retrieve gold price at pool deposit
function poolGoldPricePerAddress(uint256 _poolID, address _address, uint256 _index) public view returns (uint256) {
return (addressDataNew[_address][_poolID][_index].goldPrice);
}
// retrieve epoch at pool deposit
function poolEpochPerAddress(uint256 _poolID, address _address, uint256 _index) public view returns (uint256) {
return (addressDataNew[_address][_poolID][_index].epoch);
}
// retrieve KYC address status
function retrieveKYCStatus(address _address) public view returns (bool) {
return (kycAddress[_address]);
}
// retrieve the deposit indeces per address per pool
function retrieveAddressArrayPool(address _address, uint256 _pool) public view returns (uint256[] memory) {
return (addressArray[_address][_pool]);
}
// retrieve counter per address per pool
function retrieveAddressCounterPool(address _address, uint256 _pool) public view returns (uint256) {
return (addressCounter[_address][_pool]);
}
// retrieve blacklist status
function retrieveBlackListStatus(address _address) public view returns (bool) {
return (blacklist[_address]);
}
}
// SPDX-License-Identifier: MIT
/**
*
* @title IPMB and Gold Price Feed Interface
*/
pragma solidity ^0.8.5;
interface IPriceFeed {
function getLatestPrices() external view returns (uint256, uint256, uint256, uint256, bytes32, uint256);
function getEpochPrices(uint256 _epoch) external view returns (uint256, uint256, uint256, bytes32, uint256);
function getEpochDataSetHash(uint256 _epoch) external view returns (bytes32, bytes32);
}
// SPDX-License-Identifier: MIT
// File: @openzeppelin/contracts/access/Ownable.sol
pragma solidity ^0.8.5;
/**
* @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.
*
* By default, the owner account will be the one that deploys the contract. 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.
*/
import "./Context.sol";
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor () internal {
address msgSender = _msgSender();
_owner = msgSender;
emit OwnershipTransferred(address(0), msgSender);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = 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 {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}