// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
/// @title Access Control Contract
/// @notice This contract provides basic access control with an admin role, pause functionality, and pending admin transfer.
/// @dev The contract uses role-based permissioning for administrative actions and pausing functionality.
abstract contract AccessControl {
/// @notice Address of the current admin
address public admin;
/// @notice Address of the pending admin, to be accepted as new admin
address public pendingAdmin;
/// @notice Indicates whether the contract is paused
bool public paused;
/// Custom errors for contract-specific revert reasons
error HasPaused(bool _paused);
error NullAddress();
error NotAdmin();
error NotPendingAdmin();
/// Events for logging state changes
event AdminChange(address newAdmin, address oldAdmin);
event Pause(address account);
event Unpause(address account);
/// @notice Ensures only the admin can perform certain actions
modifier onlyAuth() {
if (msg.sender != admin) revert NotAdmin();
_;
}
/// @notice Ensures certain actions can only be performed when the contract is not paused
modifier whenNotPaused() {
if (paused) revert HasPaused({_paused: true});
_;
}
/// @notice Ensures certain actions can only be performed when the contract is paused
modifier whenPaused() {
if (!paused) revert HasPaused({_paused: false});
_;
}
/// @notice Constructor to set the initial admin of the contract
/// @param _admin The initial admin address
constructor(address _admin) {
admin = _admin;
}
/// @notice Sets a new pending admin
/// @dev Can only be called by the current admin
/// @param newAdmin The address of the proposed new admin
function setPendingAdmin(address newAdmin) public onlyAuth {
if (newAdmin == address(0)) revert NullAddress();
pendingAdmin = newAdmin;
}
/// @notice Accepts the role of admin by the pending admin
/// @dev Transfers the admin role from the current admin to the pending admin
function acceptAdmin() public {
if (pendingAdmin != msg.sender) revert NotPendingAdmin();
address oldAdmin = admin;
admin = pendingAdmin;
pendingAdmin = address(0);
emit AdminChange(admin, oldAdmin);
}
/// @notice Pauses the contract
/// @dev Can only be called by the current admin when the contract is not paused
function pause() external onlyAuth whenNotPaused {
paused = true;
emit Pause(msg.sender);
}
/// @notice Unpauses the contract
/// @dev Can only be called by the current admin when the contract is paused
function unpause() external onlyAuth whenPaused {
paused = false;
emit Unpause(msg.sender);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import "./AccessControl.sol";
/// @title User Manager Interface
/// @notice Interface for user management operations.
interface IUserManager {
function registerMember(address) external;
function newMemberFee() external view returns (uint256);
}
/// @title ERC20 Token Interface
/// @notice Interface for basic ERC20 token operations.
interface IErc20 {
function balanceOf(address) external view returns (uint256);
function approve(address, uint256) external returns (bool);
function transfer(address, uint256) external returns (bool);
}
/// @title Referral Interface
/// @notice Interface for managing referral relationships.
interface IReferral {
function setReferrer(address user, address referrer) external;
}
/// @title UNION Member Registration Helper Contract
/// @notice This contract facilitates user registration and referral in the UNION protocol.
/// @dev Extends from the AccessControl contract for role-based permissions.
contract RegisterHelper is AccessControl {
address public immutable union;
address public immutable userManager;
address public immutable referral;
address payable public regFeeRecipient;
uint public rebate;
uint public regFee;
/// Custom errors for specific revert cases.
error NotEnoughBalance();
error NotEnoughFee(uint256 fee);
/// Events for logging state changes and operations.
event RegFeeRecipientChange(address newRecipient, address oldRecipient);
event RebateChange(uint newRebate, uint oldRebate);
event RegFeeChange(uint newRegFee, uint oldRegFee);
event Register(address newUser, address regFeeRecipient, address referrer, uint fee, uint regFee, uint rebate);
/// @notice Constructor to initialize the contract with necessary addresses.
/// @param _admin Address of the initial admin.
/// @param _union Address of the UNION token.
/// @param _userManager Address of the user manager contract.
/// @param _referral Address of the referral contract.
constructor(
address _admin,
address _union,
address _userManager,
address _referral,
address payable _regFeeRecipient,
uint _rebate,
uint _regFee
) AccessControl(_admin) {
if (_regFeeRecipient == payable(address(0))) revert NullAddress();
union = _union;
userManager = _userManager;
referral = _referral;
IErc20(union).approve(userManager, type(uint256).max);
regFeeRecipient = _regFeeRecipient;
rebate = _rebate;
regFee = _regFee;
emit RegFeeRecipientChange(regFeeRecipient, address(0));
emit RebateChange(rebate, 0);
emit RegFeeChange(regFee, 0);
}
/// @notice Fallback receive function to accept ETH.
receive() external payable {}
/// @notice Sets the recipient for registration fees.
/// @param newRecipient The address of the new fee recipient.
function setRegFeeRecipient(address payable newRecipient) external onlyAuth {
if (newRecipient == address(0)) revert NullAddress();
address payable oldRecipient = regFeeRecipient;
regFeeRecipient = newRecipient;
emit RegFeeRecipientChange(newRecipient, oldRecipient);
}
/// @notice Sets the rebate amount.
/// @param newRebate The new rebate amount.
function setRebate(uint newRebate) external onlyAuth {
uint oldRebate = rebate;
rebate = newRebate;
emit RebateChange(newRebate, oldRebate);
}
/// @notice Sets the registration fee amount.
/// @param newRegFee The new registration fee amount.
function setRegFee(uint newRegFee) external onlyAuth {
uint oldRegFee = regFee;
regFee = newRegFee;
emit RegFeeChange(newRegFee, oldRegFee);
}
/// @notice Registers a new user, sets their referrer, and handles fees.
/// @param newUser The address of the user to be registered.
/// @param referrer The address of the referrer.
function register(address newUser, address payable referrer) external payable whenNotPaused {
IReferral(referral).setReferrer(newUser, referrer);
if (IErc20(union).balanceOf(address(this)) < IUserManager(userManager).newMemberFee())
revert NotEnoughBalance();
if (msg.value < regFee + rebate) revert NotEnoughFee(msg.value);
uint diff = msg.value - regFee - rebate;
if(referrer != address(0)) {
if(regFee > 0)sendCall(regFeeRecipient, regFee);
if(rebate > 0)sendCall(referrer, rebate);
} else {
if(regFee > 0 || rebate > 0)sendCall(regFeeRecipient,regFee + rebate);
}
if(diff > 0)sendCall(payable(msg.sender), diff);
IUserManager(userManager).registerMember(newUser);
emit Register(newUser, regFeeRecipient, referrer, msg.value, regFee, rebate);
}
function claimToken(address recipient) external onlyAuth {
if (recipient== address(0)) revert NullAddress();
IErc20 token = IErc20(union);
uint256 balance = token.balanceOf(address(this));
token.transfer(recipient, balance);
}
function sendCall(address payable to, uint amount) public payable {
(bool sent, ) = to.call{value: amount}("");
require(sent, "Failed to send Ether");
}
}
{
"compilationTarget": {
"src/RegisterHelper.sol": "RegisterHelper"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":ds-test/=lib/forge-std/lib/ds-test/src/",
":forge-std/=lib/forge-std/src/"
]
}
[{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_union","type":"address"},{"internalType":"address","name":"_userManager","type":"address"},{"internalType":"address","name":"_referral","type":"address"},{"internalType":"address payable","name":"_regFeeRecipient","type":"address"},{"internalType":"uint256","name":"_rebate","type":"uint256"},{"internalType":"uint256","name":"_regFee","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"bool","name":"_paused","type":"bool"}],"name":"HasPaused","type":"error"},{"inputs":[],"name":"NotAdmin","type":"error"},{"inputs":[],"name":"NotEnoughBalance","type":"error"},{"inputs":[{"internalType":"uint256","name":"fee","type":"uint256"}],"name":"NotEnoughFee","type":"error"},{"inputs":[],"name":"NotPendingAdmin","type":"error"},{"inputs":[],"name":"NullAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"oldAdmin","type":"address"}],"name":"AdminChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newRebate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"oldRebate","type":"uint256"}],"name":"RebateChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newRegFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"oldRegFee","type":"uint256"}],"name":"RegFeeChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newRecipient","type":"address"},{"indexed":false,"internalType":"address","name":"oldRecipient","type":"address"}],"name":"RegFeeRecipientChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newUser","type":"address"},{"indexed":false,"internalType":"address","name":"regFeeRecipient","type":"address"},{"indexed":false,"internalType":"address","name":"referrer","type":"address"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"regFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"rebate","type":"uint256"}],"name":"Register","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpause","type":"event"},{"inputs":[],"name":"acceptAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"}],"name":"claimToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingAdmin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rebate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"referral","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"regFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"regFeeRecipient","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newUser","type":"address"},{"internalType":"address payable","name":"referrer","type":"address"}],"name":"register","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address payable","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"sendCall","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"newAdmin","type":"address"}],"name":"setPendingAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newRebate","type":"uint256"}],"name":"setRebate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newRegFee","type":"uint256"}],"name":"setRegFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"newRecipient","type":"address"}],"name":"setRegFeeRecipient","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"union","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"userManager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]