pragma solidity 0.5.16;
// File: ../interfaces/IGovernedContract.sol
// Copyright 2021 Energi Core
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Energi Governance system is the fundamental part of Energi Core.
// NOTE: It's not allowed to change the compiler due to byte-to-byte
// match requirement.
/**
* Genesis version of GovernedContract interface.
*
* Base Consensus interface for upgradable contracts.
* Unlike common approach, the implementation is NOT expected to be
* called through delegatecall() to minimize risks of shared storage.
*
* NOTE: it MUST NOT change after blockchain launch!
*/
interface IGovernedContract {
// Return actual proxy address for secure validation
function proxy() external view returns (address);
// It must check that the caller is the proxy
// and copy all required data from the old address.
function migrate(IGovernedContract _oldImpl) external;
// It must check that the caller is the proxy
// and self destruct to the new address.
function destroy(IGovernedContract _newImpl) external;
// function () external payable; // This line (from original Energi IGovernedContract) is commented because it
// makes truffle migrations fail
}
// File: ../StorageBase.sol
// Copyright 2021 Energi Core
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Energi Governance system is the fundamental part of Energi Core.
// NOTE: It's not allowed to change the compiler due to byte-to-byte
// match requirement.
/**
* Base for contract storage (SC-14).
*
* NOTE: it MUST NOT change after blockchain launch!
*/
contract StorageBase {
address payable internal owner;
modifier requireOwner {
require(msg.sender == address(owner), "StorageBase: Not owner!");
_;
}
constructor() public {
owner = msg.sender;
}
function setOwner(IGovernedContract _newOwner) external requireOwner {
owner = address(uint160(address(_newOwner)));
}
function kill() external requireOwner {
selfdestruct(msg.sender);
}
}
// File: ../GovernedContract.sol
// Copyright 2021 Energi Core
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Energi Governance system is the fundamental part of Energi Core.
// NOTE: It's not allowed to change the compiler due to byte-to-byte
// match requirement.
/**
* Genesis version of GovernedContract common base.
*
* Base Consensus interface for upgradable contracts.
* Unlike common approach, the implementation is NOT expected to be
* called through delegatecall() to minimize risks of shared storage.
*
* NOTE: it MUST NOT change after blockchain launch!
*/
contract GovernedContract is IGovernedContract {
address public proxy;
constructor(address _proxy) public {
proxy = _proxy;
}
modifier requireProxy {
require(msg.sender == proxy, "Governed Contract: Not proxy");
_;
}
function getProxy() internal view returns (address _proxy) {
_proxy = proxy;
}
// Function overridden in child contract
function migrate(IGovernedContract _oldImpl) external requireProxy {
_migrate(_oldImpl);
}
// Function overridden in child contract
function destroy(IGovernedContract _newImpl) external requireProxy {
_destroy(_newImpl);
}
// solium-disable-next-line no-empty-blocks
function _migrate(IGovernedContract) internal {}
function _destroy(IGovernedContract _newImpl) internal {
selfdestruct(address(uint160(address(_newImpl))));
}
function _callerAddress() internal view returns (address payable) {
if (msg.sender == proxy) {
// This is guarantee of the GovernedProxy
// solium-disable-next-line security/no-tx-origin
return tx.origin;
} else {
return msg.sender;
}
}
}
// File: ../NonReentrant.sol
// Copyright 2021 Energi Core
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Energi Governance system is the fundamental part of Energi Core.
// NOTE: It's not allowed to change the compiler due to byte-to-byte
// match requirement.
/**
* A little helper to protect contract from being re-entrant in state
* modifying functions.
*/
contract NonReentrant {
uint256 private entry_guard;
modifier noReentry {
require(entry_guard == 0, "NonReentrant: Reentry");
entry_guard = 1;
_;
entry_guard = 0;
}
}
// File: ../interfaces/IProposal.sol
// Copyright 2021 Energi Core
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Energi Governance system is the fundamental part of Energi Core.
// NOTE: It's not allowed to change the compiler due to byte-to-byte
// match requirement.
interface IProposal {
function parent() external view returns (address);
function created_block() external view returns (uint256);
function deadline() external view returns (uint256);
function fee_payer() external view returns (address payable);
function fee_amount() external view returns (uint256);
function accepted_weight() external view returns (uint256);
function rejected_weight() external view returns (uint256);
function total_weight() external view returns (uint256);
function quorum_weight() external view returns (uint256);
function isFinished() external view returns (bool);
function isAccepted() external view returns (bool);
function withdraw() external;
function destroy() external;
function collect() external;
function voteAccept() external;
function voteReject() external;
function setFee() external payable;
function canVote(address owner) external view returns (bool);
}
// File: ../interfaces/IUpgradeProposal.sol
// Copyright 2021 Energi Core
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Energi Governance system is the fundamental part of Energi Core.
// NOTE: It's not allowed to change the compiler due to byte-to-byte
// match requirement.
/**
* Interface of UpgradeProposal
*/
contract IUpgradeProposal is IProposal {
function impl() external view returns (IGovernedContract);
}
// File: ../interfaces/IGovernedProxy.sol
// Copyright 2021 Energi Core
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Energi Governance system is the fundamental part of Energi Core.
// NOTE: It's not allowed to change the compiler due to byte-to-byte
// match requirement.
//pragma experimental SMTChecker;
/**
* Genesis version of IGovernedProxy interface.
*
* Base Consensus interface for upgradable contracts proxy.
* Unlike common approach, the implementation is NOT expected to be
* called through delegatecall() to minimize risks of shared storage.
*
* NOTE: it MUST NOT change after blockchain launch!
*/
interface IGovernedProxy {
event UpgradeProposal(
IGovernedContract indexed impl,
IUpgradeProposal proposal
);
event Upgraded(IGovernedContract indexed impl, IUpgradeProposal proposal);
function impl() external view returns (IGovernedContract);
function spork_proxy() external view returns (IGovernedProxy);
function proposeUpgrade(IGovernedContract _newImpl, uint256 _period)
external
payable
returns (IUpgradeProposal);
function upgrade(IUpgradeProposal _proposal) external;
function upgradeProposalImpl(IUpgradeProposal _proposal)
external
view
returns (IGovernedContract new_impl);
function listUpgradeProposals()
external
view
returns (IUpgradeProposal[] memory proposals);
function collectUpgradeProposal(IUpgradeProposal _proposal) external;
function() external payable;
}
// File: ../interfaces/ISporkRegistry.sol
// Copyright 2021 Energi Core
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Energi Governance system is the fundamental part of Energi Core.
// NOTE: It's not allowed to change the compiler due to byte-to-byte
// match requirement.
interface ISporkRegistry {
function createUpgradeProposal(
IGovernedContract _impl,
uint256 _period,
address payable _fee_payer
) external payable returns (IUpgradeProposal);
function consensusGasLimits()
external
view
returns (uint256 callGas, uint256 xferGas);
}
// File: ReferralGovernedProxy.sol
// Copyright 2021 The Energi Core Authors
// This file is part of Energi Core.
//
// Energi Core is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Energi Core is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Energi Core. If not, see <http://www.gnu.org/licenses/>.
// Energi Governance system is the fundamental part of Energi Core.
// NOTE: It's not allowed to change the compiler due to byte-to-byte
// match requirement.
/**
* SC-9: This contract has no chance of being updated. It must be stupid simple.
*
* If another upgrade logic is required in the future - it can be done as proxy stage II.
*/
contract ReferralGovernedProxy is
NonReentrant,
IGovernedContract,
IGovernedProxy
{
modifier senderOrigin {
// Internal calls are expected to use impl directly.
// That's due to use of call() instead of delegatecall() on purpose.
// solium-disable-next-line security/no-tx-origin
require(
tx.origin == msg.sender,
"ReferralGovernedProxy: Only direct calls are allowed!"
);
_;
}
modifier onlyImpl {
require(
msg.sender == address(impl),
"ReferralGovernedProxy: Only calls from impl are allowed!"
);
_;
}
IGovernedContract public impl;
IGovernedProxy public spork_proxy;
mapping(address => IGovernedContract) public upgrade_proposals;
IUpgradeProposal[] public upgrade_proposal_list;
constructor(address payable _sporkProxy, address _impl) public {
spork_proxy = IGovernedProxy(_sporkProxy);
impl = IGovernedContract(_impl);
}
event Referral(address indexed referrer, address indexed referee);
function emitReferral(address referrer, address referee) external onlyImpl {
emit Referral(referrer, referee);
}
/**
* Pre-create a new contract first.
* Then propose upgrade based on that.
*/
function proposeUpgrade(IGovernedContract _newImpl, uint256 _period)
external
payable
senderOrigin
noReentry
returns (IUpgradeProposal)
{
require(
_newImpl != impl,
"ReferralGovernedProxy: Already active!"
);
require(
_newImpl.proxy() == address(this),
"ReferralGovernedProxy: Wrong proxy!"
);
ISporkRegistry spork_reg = ISporkRegistry(address(spork_proxy.impl()));
IUpgradeProposal proposal =
spork_reg.createUpgradeProposal.value(msg.value)(
_newImpl,
_period,
msg.sender
);
upgrade_proposals[address(proposal)] = _newImpl;
upgrade_proposal_list.push(proposal);
emit UpgradeProposal(_newImpl, proposal);
return proposal;
}
/**
* Once proposal is accepted, anyone can activate that.
*/
function upgrade(IUpgradeProposal _proposal) external noReentry {
IGovernedContract new_impl = upgrade_proposals[address(_proposal)];
require(
new_impl != impl,
"ReferralGovernedProxy: Already active!"
);
// in case it changes in the flight
require(
address(new_impl) != address(0),
"ReferralGovernedProxy: Not registered!"
);
require(
_proposal.isAccepted(),
"ReferralGovernedProxy: Not accepted!"
);
IGovernedContract old_impl = impl;
new_impl.migrate(old_impl);
impl = new_impl;
old_impl.destroy(new_impl);
// SECURITY: prevent downgrade attack
_cleanupProposal(_proposal);
// Return fee ASAP
_proposal.destroy();
emit Upgraded(new_impl, _proposal);
}
/**
* Map proposal to implementation
*/
function upgradeProposalImpl(IUpgradeProposal _proposal)
external
view
returns (IGovernedContract new_impl)
{
new_impl = upgrade_proposals[address(_proposal)];
}
/**
* Lists all available upgrades
*/
function listUpgradeProposals()
external
view
returns (IUpgradeProposal[] memory proposals)
{
uint256 len = upgrade_proposal_list.length;
proposals = new IUpgradeProposal[](len);
for (uint256 i = 0; i < len; ++i) {
proposals[i] = upgrade_proposal_list[i];
}
return proposals;
}
/**
* Once proposal is reject, anyone can start collect procedure.
*/
function collectUpgradeProposal(IUpgradeProposal _proposal)
external
noReentry
{
IGovernedContract new_impl = upgrade_proposals[address(_proposal)];
require(
address(new_impl) != address(0),
"ReferralGovernedProxy: Not registered!"
);
_proposal.collect();
delete upgrade_proposals[address(_proposal)];
_cleanupProposal(_proposal);
}
function _cleanupProposal(IUpgradeProposal _proposal) internal {
delete upgrade_proposals[address(_proposal)];
uint256 len = upgrade_proposal_list.length;
for (uint256 i = 0; i < len; ++i) {
if (upgrade_proposal_list[i] == _proposal) {
upgrade_proposal_list[i] = upgrade_proposal_list[len - 1];
upgrade_proposal_list.pop();
break;
}
}
}
/**
* Related to above
*/
function proxy() external view returns (address) {
return address(this);
}
/**
* SECURITY: prevent on-behalf-of calls
*/
function migrate(IGovernedContract) external {
revert("ReferralGovernedProxy: Good try");
}
/**
* SECURITY: prevent on-behalf-of calls
*/
function destroy(IGovernedContract) external {
revert("ReferralGovernedProxy: Good try");
}
/**
* Proxy all other calls to implementation.
*/
function() external payable senderOrigin {
// SECURITY: senderOrigin() modifier is mandatory
IGovernedContract impl_m = impl;
// solium-disable-next-line security/no-inline-assembly
assembly {
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize)
let res := call(sub(gas, 10000), impl_m, callvalue, ptr, calldatasize, 0, 0)
// NOTE: returndatasize should allow repeatable calls
// what should save one opcode.
returndatacopy(ptr, 0, returndatasize)
switch res
case 0 {
revert(ptr, returndatasize)
}
default {
return(ptr, returndatasize)
}
}
}
}
// File: ReferralAutoProxy.sol
// Copyright 2021 Energi Core
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Energi Governance system is the fundamental part of Energi Core.
// NOTE: It's not allowed to change the compiler due to byte-to-byte
// match requirement.
/**
* ReferralAutoProxy is a version of GovernedContract which deploys its own proxy.
* This is useful to avoid a circular dependency between GovernedContract and GovernedProxy
* wherein they need each other's address in the constructor.
* If you want a new governed contract to create a proxy, pass address(0) when deploying
* otherwise, you can pass a proxy address like in normal GovernedContract
*/
contract ReferralAutoProxy is GovernedContract {
constructor(address _proxy, address payable _sporkProxy, address _impl) public GovernedContract(_proxy) {
if (_proxy == address(0)) {
_proxy = address(new ReferralGovernedProxy(_sporkProxy, _impl));
}
proxy = _proxy;
}
}
// File: IReferralGovernedProxy.sol
// Copyright 2021 Energi Core
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Energi Governance system is the fundamental part of Energi Core.
// NOTE: It's not allowed to change the compiler due to byte-to-byte
// match requirement.
interface IReferralGovernedProxy {
event Referral(address indexed referrer, address indexed referee);
function emitReferral(address referrer, address referee) external;
}
// File: ../interfaces/IStorageBase.sol
// Copyright 2021 Energi Core
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Energi Governance system is the fundamental part of Energi Core.
// NOTE: It's not allowed to change the compiler due to byte-to-byte
// match requirement.
interface IStorageBase {
function setOwner(address _newOwner) external;
}
// File: ../interfaces/IWhitelist.sol
// Copyright 2021 Energi Core
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Energi Governance system is the fundamental part of Energi Core.
// NOTE: It's not allowed to change the compiler due to byte-to-byte
// match requirement.
interface IWhitelist {
function isWhitelisted(address _item) external view returns (bool);
}
// File: Referral.sol
// Copyright 2021 Energi Core
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Energi Governance system is the fundamental part of Energi Core.
// NOTE: It's not allowed to change the compiler due to byte-to-byte
// match requirement.
contract ReferralStorage is StorageBase {
uint256 private count = 0;
// A referrer can have many referees
mapping(address => address[]) private referees; // referrer => referees[]
mapping(address => uint256) private refereesCount; // referrer => refereesCount
mapping(address => address) private referrer; // referee => referrer
function setReferral(address _referrer, address _referee) external requireOwner {
referees[_referrer].push(_referee);
referrer[_referee] = _referrer;
refereesCount[_referrer] += 1;
count += 1;
}
function getCount() external view returns (uint256 _count) {
_count = count;
}
function getReferrer(address _referee) external view returns (address _referrer) {
_referrer = referrer[_referee];
}
function getReferees(address _referrer, uint256 _offset, uint256 _count) external view returns (address[] memory _referees) {
// Offset should be smaller than referees array length
require(_offset < referees[_referrer].length, "Referral: offset too large");
// Adjust count if needed
if (_offset + _count >= referees[_referrer].length) {
_count = referees[_referrer].length - _offset;
}
// Return specified subset of referees array
_referees = new address[](_count);
for (uint256 i = 0; i < _count; i++) {
uint256 index = _offset + i;
_referees[i] = referees[_referrer][index];
}
}
function getRefereesCount(address _referrer) external view returns (uint256 _refereesCount) {
_refereesCount = refereesCount[_referrer];
}
}
contract Referral is ReferralAutoProxy {
ReferralStorage public _storage;
address public whitelistProxyAddress;
modifier requireWhitelisted {
require(msg.sender == proxy || IWhitelist(getWhitelistImpl()).isWhitelisted(msg.sender), "Referral: FORBIDDEN, not whitelisted");
_;
}
function getWhitelistImpl() private view returns(address _whitelist) {
_whitelist = address(IGovernedProxy(address(uint160(whitelistProxyAddress))).impl());
}
function getSender() private view returns(address _sender){
// Determine sender to allow whitelisted contracts to interact
// If msg.sender is a whitelisted contract, this contract is the sender
// Else, tx.origin is the sender
_sender = IWhitelist(getWhitelistImpl()).isWhitelisted(msg.sender) ? msg.sender : tx.origin;
}
constructor(
address _proxy,
address payable _sporkProxy,
address _whitelistProxyAddress
) public ReferralAutoProxy(_proxy, _sporkProxy, address(this)) {
whitelistProxyAddress = _whitelistProxyAddress;
_storage = new ReferralStorage();
}
// This function is called in order to upgrade to a new Referral implementation
function destroy(IGovernedContract _newImpl) external requireProxy {
IStorageBase(address(_storage)).setOwner(address(_newImpl));
// Self destruct
_destroy(_newImpl);
}
// This function (placeholder) would be called on the new implementation if necessary for the upgrade
function migrate(IGovernedContract _oldImpl) external requireProxy {
_migrate(_oldImpl);
}
function refereesCount(address _referrer) external view returns (uint256) {
return _storage.getRefereesCount(_referrer);
}
function allRefereesCount() external view returns (uint256) {
return _storage.getCount();
}
function referrer(address _referee) external view returns (address) {
return _storage.getReferrer(_referee);
}
function referees(address _referrer, uint256 _offset, uint256 _count) external view returns (address[] memory) {
return _storage.getReferees(_referrer, _offset, _count);
}
function setReferral(address _referrer) external requireWhitelisted {
// Get referee address
address referee = getSender();
// User cannot refer self
require(referee != _referrer, "Referral: cannot refer self");
// Referee can only be referred once
require(_storage.getReferrer(referee) == address(0), "Referral: already referred");
// Register referral in storage
_storage.setReferral(_referrer, referee);
// Emit referral event
IReferralGovernedProxy(proxy).emitReferral(_referrer, referee);
}
}