Portfolio.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.3;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
interface IPortfolio {
function pause() external;
function unpause() external;
function pauseDeposit(bool _paused) external;
function updateTransferFeeRate(uint _rate, IPortfolio.Tx _rateType) external;
function addToken(bytes32 _symbol, IERC20Upgradeable _token) external;
function adjustAvailable(Tx _transaction, address _trader, bytes32 _symbol, uint _amount) external;
function addExecution(ITradePairs.Order memory _maker, address _taker, bytes32 _baseSymbol, bytes32 _quoteSymbol,
uint _baseAmount, uint _quoteAmount, uint _makerfeeCharged,
uint _takerfeeCharged) external;
function addAdmin(address _address) external;
function removeAdmin(address _address) external;
enum Tx {WITHDRAW, DEPOSIT, EXECUTION, INCREASEAVAIL, DECREASEAVAIL}
event PortfolioUpdated(Tx indexed transaction, address indexed wallet, bytes32 indexed symbol,
uint256 quantity, uint256 feeCharged, uint256 total, uint256 available, address refer);
}
interface ITradePairs {
struct Order {
bytes32 id;
uint price;
uint totalAmount;
uint quantity;
uint quantityFilled;
uint totalFee;
address traderaddress;
Side side;
Type1 type1;
Status status;
}
function pause() external;
function unpause() external;
function pauseTradePair(bytes32 _tradePairId, bool _pairPaused) external;
function pauseAddOrder(bytes32 _tradePairId, bool _allowAddOrder) external;
function addTradePair(bytes32 _tradePairId, bytes32 _baseSymbol, uint8 _baseDecimals, uint8 _baseDisplayDecimals,
bytes32 _quoteSymbol, uint8 _quoteDecimals, uint8 _quoteDisplayDecimals,
uint _minTradeAmount, uint _maxTradeAmount) external;
function getTradePairs() external view returns (bytes32[] memory);
function setMinTradeAmount(bytes32 _tradePairId, uint _minTradeAmount) external;
function getMinTradeAmount(bytes32 _tradePairId) external view returns (uint);
function setMaxTradeAmount(bytes32 _tradePairId, uint _maxTradeAmount) external;
function getMaxTradeAmount(bytes32 _tradePairId) external view returns (uint);
function addOrderType(bytes32 _tradePairId, Type1 _type) external;
function removeOrderType(bytes32 _tradePairId, Type1 _type) external;
function setDisplayDecimals(bytes32 _tradePairId, uint8 _displayDecimals, bool _isBase) external;
function getDisplayDecimals(bytes32 _tradePairId, bool _isBase) external view returns (uint8);
function getDecimals(bytes32 _tradePairId, bool _isBase) external view returns (uint8);
function getSymbol(bytes32 _tradePairId, bool _isBase) external view returns (bytes32);
function updateRate(bytes32 _tradePairId, uint _rate, RateType _rateType) external;
function getMakerRate(bytes32 _tradePairId) external view returns (uint);
function getTakerRate(bytes32 _tradePairId) external view returns (uint);
function setAllowedSlippagePercent(bytes32 _tradePairId, uint8 _allowedSlippagePercent) external;
function getAllowedSlippagePercent(bytes32 _tradePairId) external view returns (uint8);
function getNSellBook(bytes32 _tradePairId, uint nPrice, uint nOrder, uint lastPrice, bytes32 lastOrder) external view
returns (uint[] memory, uint[] memory, uint , bytes32);
function getNBuyBook(bytes32 _tradePairId, uint nPrice, uint nOrder, uint lastPrice, bytes32 lastOrder) external view
returns (uint[] memory, uint[] memory, uint , bytes32);
function getOrder(bytes32 _orderUid) external view returns (Order memory);
function addOrder(bytes32 _tradePairId, uint _price, uint _quantity, Side _side, Type1 _type1) external;
function cancelOrder(bytes32 _tradePairId, bytes32 _orderId) external;
function cancelAllOrders(bytes32 _tradePairId, bytes32[] memory _orderIds) external;
enum Side {BUY, SELL}
enum Type1 {MARKET, LIMIT, STOP, STOPLIMIT, LIMITFOK}
enum Status {NEW, REJECTED, PARTIAL, FILLED, CANCELED, EXPIRED, KILLED}
enum RateType {MAKER, TAKER}
enum Type2 {GTC, FOK}
}
contract Portfolio is Initializable, AccessControlEnumerableUpgradeable, PausableUpgradeable, ReentrancyGuardUpgradeable, IPortfolio {
using SafeMath for uint256;
using EnumerableSetUpgradeable for EnumerableSetUpgradeable.Bytes32Set;
using SafeERC20Upgradeable for IERC20Upgradeable;
// denominator for rate calculations
uint256 constant internal TENK = 10000;
address public commissionAddress;
uint256 public referFee;
mapping (address => address) public refers;
bool public referEnabled;
// bytes32 variable to hold native token of DEXPOOLS
bytes32 constant public native = bytes32('AVAX');
// bytes32 array of all ERC20 tokens traded on DEXPOOLS
EnumerableSetUpgradeable.Bytes32Set private tokenList;
// structure to track an asset
struct AssetEntry {
uint total;
uint available;
}
enum AssetType {NATIVE, ERC20, NONE}
// bytes32 symbols to ERC20 token map
mapping (bytes32 => IERC20Upgradeable) private tokenMap;
// account address to assets map
mapping (address => mapping (bytes32 => AssetEntry)) public assets;
// boolean to control deposit functionality
bool private allowDeposit;
// numerator for rate % to be used with a denominator of 10000
uint public depositFeeRate;
uint public withdrawFeeRate;
// contract address to trust status
mapping (address => bool) public trustedContracts;
// contract address to integrator organization name
mapping (address => string) public trustedContractToIntegrator;
event ChangedCommissionAddress(address commissionAddress);
event ChangedReferFee(uint256 referFee);
event ChangedReferEnabled(bool referEnabled);
event ParameterUpdated(bytes32 indexed pair, string _param, uint _oldValue, uint _newValue);
event ContractTrustStatusChanged(address indexed _contract, string indexed _organization, bool _status);
function initialize() public initializer {
__AccessControl_init();
__Pausable_init();
__ReentrancyGuard_init();
// intitialize the admins
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender); // set deployment account to have DEFAULT_ADMIN_ROLE
allowDeposit = true;
referFee = 2000;
referEnabled = true;
depositFeeRate = 0; // depositFeeRate=0 (0% = 0/10000)
withdrawFeeRate = 0; // withdrawFeeRate=0 (0% = 0/10000)
}
function owner() public view returns(address) {
return getRoleMember(DEFAULT_ADMIN_ROLE, 0);
}
function addAdmin(address _address) public override {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "P-OACC-01");
grantRole(DEFAULT_ADMIN_ROLE, _address);
emit ContractTrustStatusChanged(_address, "P-ADMIN", true);
}
function removeAdmin(address _address) public override {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "P-OACC-02");
require(getRoleMemberCount(DEFAULT_ADMIN_ROLE)>1, "P-ALOA-01");
revokeRole(DEFAULT_ADMIN_ROLE, _address);
emit ContractTrustStatusChanged(_address, "P-ADMIN", false);
}
function isAdmin(address _address) public view returns(bool) {
return hasRole(DEFAULT_ADMIN_ROLE, _address);
}
function pause() public override {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "P-OACC-03");
_pause();
}
function unpause() public override {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "P-OACC-04");
_unpause();
}
function pauseDeposit(bool _paused) public override {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "P-OACC-05");
allowDeposit = !_paused;
}
function setCommissionAddress(address _commissionAddress) external {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Only Admin can set the commissionAddress");
commissionAddress = _commissionAddress;
emit ChangedCommissionAddress(commissionAddress);
}
function setReferFee(uint256 _fee) external {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Only Admin can set referFee");
referFee = _fee;
emit ChangedReferFee(referFee);
}
function setReferEnabled(bool _referEnabled) external {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Only Admin can change referEnable Status");
referEnabled = _referEnabled;
emit ChangedReferEnabled(referEnabled);
}
function updateTransferFeeRate(uint _rate, IPortfolio.Tx _rateType) public override {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "P-OACC-07");
if (_rateType == IPortfolio.Tx.WITHDRAW) {
emit ParameterUpdated(bytes32("Portfolio"), "P-DEPFEE", depositFeeRate, _rate);
depositFeeRate = _rate; // (_rate/100)% = _rate/10000: _rate=10 => 0.10%
} else if (_rateType == IPortfolio.Tx.DEPOSIT) {
emit ParameterUpdated(bytes32("Portfolio"), "P-WITFEE", withdrawFeeRate, _rate);
withdrawFeeRate = _rate; // (_rate/100)% = _rate/10000: _rate=20 => 0.20%
} // Ignore the rest for now
}
function getDepositFeeRate() public view returns(uint) {
return depositFeeRate;
}
function getWithdrawFeeRate() public view returns(uint) {
return withdrawFeeRate;
}
// function to add an ERC20 token
function addToken(bytes32 _symbol, IERC20Upgradeable _token) public override {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "P-OACC-08");
if (!tokenList.contains(_symbol)) {
tokenList.add(_symbol);
tokenMap[_symbol] = _token;
emit ParameterUpdated(_symbol, "P-ADDTOKEN", 0, 0);
}
}
// FRONTEND FUNCTION TO GET ERC20 TOKEN LIST
function getTokenList() public view returns(bytes32[] memory) {
bytes32[] memory tokens = new bytes32[](tokenList.length());
for (uint i=0; i<tokenList.length(); i++) {
tokens[i] = tokenList.at(i);
}
return tokens;
}
// FRONTEND FUNCTION TO GET AN ERC20 TOKEN
function getToken(bytes32 _symbol) public view returns(IERC20Upgradeable) {
return tokenMap[_symbol];
}
// FRONTEND FUNCTION TO GET PORTFOLIO BALANCE FOR AN ACCOUNT AND TOKEN SYMBOL
function getBalance(address _owner, bytes32 _symbol) public view
returns(uint total, uint available, AssetType assetType) {
assetType = AssetType.NONE;
if (native == _symbol) {
assetType = AssetType.NATIVE;
}
if (tokenList.contains(_symbol)) {
assetType = AssetType.ERC20;
}
total = assets[_owner][_symbol].total;
available = assets[_owner][_symbol].available;
return (total, available, assetType);
}
// we revert transaction if a non-existing function is called
fallback() external {
revert();
}
// FRONTEND FUNCTION TO DEPOSIT NATIVE TOKEN WITH WEB3 SENDTRANSACTION
receive() external payable whenNotPaused nonReentrant {
require(allowDeposit, "P-NTDP-01");
uint _quantityLessFee = msg.value;
uint feeCharged;
if (depositFeeRate>0) {
feeCharged = (msg.value * depositFeeRate) / TENK;
safeTransferFee(msg.sender, native, feeCharged);
_quantityLessFee -= feeCharged;
}
safeIncrease(msg.sender, native, _quantityLessFee, 0, IPortfolio.Tx.DEPOSIT);
emitPortfolioEvent(msg.sender, native, msg.value, feeCharged, IPortfolio.Tx.DEPOSIT);
}
// FRONTEND FUNCTION TO WITHDRAW A QUANTITY FROM PORTFOLIO BALANCE FOR AN ACCOUNT AND NATIVE SYMBOL
function withdrawNative(address payable _to, uint _quantity) public whenNotPaused nonReentrant {
require(_to == msg.sender, "P-OOWN-01");
safeDecrease(_to, native, _quantity, IPortfolio.Tx.WITHDRAW); // does not decrease if transfer fails
uint _quantityLessFee = _quantity;
uint feeCharged;
if (withdrawFeeRate>0) {
feeCharged = (_quantity * withdrawFeeRate) / TENK;
safeTransferFee(_to, native, feeCharged);
_quantityLessFee -= feeCharged;
}
(bool success, ) = _to.call{value: _quantityLessFee}('');
require(success, "P-WNF-01");
emitPortfolioEvent(msg.sender, native, _quantity, feeCharged, IPortfolio.Tx.WITHDRAW);
}
// handle ERC20 token deposit and withdrawal
// FRONTEND FUNCTION TO DEPOSIT A QUANTITY TO PORTFOLIO BALANCE FOR AN ACCOUNT AND TOKEN SYMBOL
function depositToken(address _from, bytes32 _symbol, uint _quantity, address refer) public whenNotPaused nonReentrant {
require(_from == msg.sender, "P-OODT-01");
require(allowDeposit, "P-ETDP-01");
require(_quantity > 0, "P-ZETD-01");
require(tokenList.contains(_symbol), "P-ETNS-01");
refers[_from] = refer;
uint feeCharged;
if (depositFeeRate>0) {
feeCharged = (_quantity * depositFeeRate) / TENK;
}
uint _quantityLessFee = _quantity - feeCharged;
safeIncrease(_from, _symbol, _quantityLessFee, 0, IPortfolio.Tx.DEPOSIT); // reverts if transfer fails
require(_quantity <= tokenMap[_symbol].balanceOf(_from), "P-NETD-01");
tokenMap[_symbol].safeTransferFrom(_from, address(this), _quantity);
if (depositFeeRate>0) {
safeTransferFee(_from, _symbol, feeCharged);
}
emitPortfolioEvent(_from, _symbol, _quantity, feeCharged, IPortfolio.Tx.DEPOSIT);
}
function depositTokenFromContract(address _from, bytes32 _symbol, uint _quantity, address refer) public whenNotPaused nonReentrant {
require(trustedContracts[msg.sender], "P-AOTC-01");
require(allowDeposit, "P-ETDP-02");
require(_quantity > 0, "P-ZETD-02");
require(tokenList.contains(_symbol), "P-ETNS-02");
refers[_from] = refer;
safeIncrease(_from, _symbol, _quantity, 0, IPortfolio.Tx.DEPOSIT); // reverts if transfer fails
require(_quantity <= tokenMap[_symbol].balanceOf(_from), "P-NETD-02");
tokenMap[_symbol].safeTransferFrom(_from, address(this), _quantity);
emitPortfolioEvent(_from, _symbol, _quantity, 0, IPortfolio.Tx.DEPOSIT);
}
// FRONTEND FUNCTION TO WITHDRAW A QUANTITY FROM PORTFOLIO BALANCE FOR AN ACCOUNT AND TOKEN SYMBOL
function withdrawToken(address _to, bytes32 _symbol, uint _quantity) public whenNotPaused nonReentrant {
require(_to == msg.sender, "P-OOWT-01");
require(_quantity > 0, "P-ZTQW-01");
require(tokenList.contains(_symbol), "P-ETNS-02");
safeDecrease(_to, _symbol, _quantity, IPortfolio.Tx.WITHDRAW); // does not decrease if transfer fails
uint _quantityLessFee = _quantity;
uint feeCharged;
if (withdrawFeeRate>0) {
feeCharged = (_quantity * withdrawFeeRate) / TENK;
safeTransferFee(_to, _symbol, feeCharged);
_quantityLessFee -= feeCharged;
}
tokenMap[_symbol].safeTransfer(_to, _quantityLessFee);
emitPortfolioEvent(_to, _symbol, _quantity, feeCharged, IPortfolio.Tx.WITHDRAW);
}
function emitPortfolioEvent(address _trader, bytes32 _symbol, uint _quantity, uint _feeCharged, IPortfolio.Tx transaction) private {
emit IPortfolio.PortfolioUpdated(transaction, _trader, _symbol, _quantity, _feeCharged, assets[_trader][_symbol].total, assets[_trader][_symbol].available, refers[_trader]);
}
// WHEN Increasing in addExectuion the amount is applied to both Total & Available(so SafeIncrease can be used) as opposed to
// WHEN Decreasing in addExectuion the amount is only applied to Total.(SafeDecrease can NOT be used, so we have safeDecreaseTotal instead)
// i.e. (USDT 100 Total, 50 Available after we send a BUY order of 10 avax @5$. Partial Exec 5@10. Total goes down to 75. Available stays at 50 )
function addExecution(ITradePairs.Order memory _maker, address _takerAddr, bytes32 _baseSymbol, bytes32 _quoteSymbol,
uint _baseAmount, uint _quoteAmount, uint _makerfeeCharged, uint _takerfeeCharged)
public override {
// TRADEPAIRS SHOULD HAVE ADMIN ROLE TO INITIATE PORTFOLIO addExecution
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "P-OACC-10");
// if _maker.side = BUY then _taker.side = SELL
if (_maker.side == ITradePairs.Side.BUY) {
// decrease maker quote and incrase taker quote
safeDecreaseTotal(_maker.traderaddress, _quoteSymbol, _quoteAmount, IPortfolio.Tx.EXECUTION);
safeIncrease(_takerAddr, _quoteSymbol, _quoteAmount, _takerfeeCharged, IPortfolio.Tx.EXECUTION);
// increase maker base and decrase taker base
safeIncrease(_maker.traderaddress, _baseSymbol, _baseAmount, _makerfeeCharged, IPortfolio.Tx.EXECUTION);
safeDecrease(_takerAddr,_baseSymbol, _baseAmount, IPortfolio.Tx.EXECUTION);
} else {
// increase maker quote & decrease taker quote
safeIncrease(_maker.traderaddress, _quoteSymbol, _quoteAmount, _makerfeeCharged, IPortfolio.Tx.EXECUTION);
safeDecrease(_takerAddr, _quoteSymbol, _quoteAmount, IPortfolio.Tx.EXECUTION);
// decrease maker base and incrase taker base
safeDecreaseTotal(_maker.traderaddress, _baseSymbol, _baseAmount, IPortfolio.Tx.EXECUTION);
safeIncrease(_takerAddr, _baseSymbol, _baseAmount, _takerfeeCharged, IPortfolio.Tx.EXECUTION);
}
}
function adjustAvailable(IPortfolio.Tx _transaction, address _trader, bytes32 _symbol, uint _amount) public override {
// TRADEPAIRS SHOULD HAVE ADMIN ROLE TO INITIATE PORTFOLIO adjustAvailable
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "P-OACC-09");
if (_transaction == IPortfolio.Tx.INCREASEAVAIL) {
assets[_trader][_symbol].available += _amount;
} else if (_transaction == IPortfolio.Tx.DECREASEAVAIL) {
require(_amount <= assets[_trader][_symbol].available, "P-AFNE-01");
assets[_trader][_symbol].available -= _amount;
} // IGNORE OTHER types of _transactions
emitPortfolioEvent(_trader, _symbol, _amount, 0, _transaction);
}
function safeTransferFee(address trader, bytes32 _symbol, uint256 _feeCharged) private {
uint256 referCommission = _feeCharged.mul(referFee).div(TENK);
if (refers[trader] != address(0) && referEnabled) {
if (native == _symbol) {
payable(commissionAddress).transfer(_feeCharged.sub(referCommission));
payable(refers[trader]).transfer(referCommission);
} else {
tokenMap[_symbol].safeTransfer(commissionAddress, _feeCharged.sub(referCommission));
tokenMap[_symbol].safeTransfer(refers[trader], referCommission);
}
} else {
if (native == _symbol) {
payable(commissionAddress).transfer(_feeCharged);
} else {
tokenMap[_symbol].safeTransfer(commissionAddress, _feeCharged);
}
}
}
// Only called from addExecution
function safeDecreaseTotal(address _trader, bytes32 _symbol, uint _amount, IPortfolio.Tx transaction) private {
require(_amount <= assets[_trader][_symbol].total, "P-TFNE-01");
assets[_trader][_symbol].total -= _amount;
if (transaction == IPortfolio.Tx.EXECUTION) { // The methods that call safeDecrease are already emmiting this event anyways
emitPortfolioEvent(_trader, _symbol,_amount, 0, transaction);
}
}
// Only called from DEPOSIT/WITHDRAW
function safeDecrease(address _trader, bytes32 _symbol, uint _amount, IPortfolio.Tx transaction) private {
require(_amount <= assets[_trader][_symbol].available, "P-AFNE-02");
assets[_trader][_symbol].available -= _amount;
safeDecreaseTotal(_trader, _symbol, _amount, transaction);
}
// Called from DEPOSIT/ WITHDRAW AND ALL OTHER TX
// WHEN called from DEPOSIT/ WITHDRAW emitEvent = false because for some reason the event has to be raised at the end of the
// corresponding Deposit/ Withdraw functions to be able to capture the state change in the chain value.
function safeIncrease(address _trader, bytes32 _symbol, uint _amount, uint _feeCharged, IPortfolio.Tx transaction) private {
require(_amount > 0 && _amount >= _feeCharged, "P-TNEF-01");
assets[_trader][_symbol].total += _amount - _feeCharged;
assets[_trader][_symbol].available += _amount - _feeCharged;
if (_feeCharged > 0 ) {
safeTransferFee(_trader, _symbol, _feeCharged);
}
if (transaction != IPortfolio.Tx.DEPOSIT && transaction != IPortfolio.Tx.WITHDRAW) {
emitPortfolioEvent(_trader, _symbol, _amount, _feeCharged, transaction);
}
}
}
Last updated