// File: openzeppelin-solidity/contracts/ownership/Ownable.sol
pragma solidity ^0.5.0;
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
constructor () internal {
_owner = msg.sender;
emit OwnershipTransferred(address(0), _owner);
}
/**
* @return the address of the owner.
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(isOwner());
_;
}
/**
* @return true if `msg.sender` is the owner of the contract.
*/
function isOwner() public view returns (bool) {
return msg.sender == _owner;
}
/**
* @dev Allows the current owner to relinquish control of the contract.
* @notice Renouncing to ownership will leave the contract without an owner.
* It will not be possible to call the functions with the `onlyOwner`
* modifier anymore.
*/
function renounceOwnership() public onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
_transferOwnership(newOwner);
}
/**
* @dev Transfers control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function _transferOwnership(address newOwner) internal {
require(newOwner != address(0));
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
// File: openzeppelin-solidity/contracts/token/ERC20/IERC20.sol
pragma solidity ^0.5.0;
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
interface IERC20 {
function transfer(address to, uint256 value) external returns (bool);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
function totalSupply() external view returns (uint256);
function balanceOf(address who) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// File: contracts/PaymentHandler.sol
pragma solidity ^0.5.0;
/**
* The payment handler is responsible for receiving payments.
* If the payment is in ETH, it auto forwards to its parent master's owner.
* If the payment is in ERC20, it holds the tokens until it is asked to sweep.
* It can only sweep ERC20s to the parent master's owner.
*/
contract PaymentHandler {
// Keep track of the parent master contract - cannot be changed once set
PaymentMaster public master;
/**
* General constructor called by the master
*/
constructor(PaymentMaster _master) public {
master = _master;
}
/**
* Helper function to return the parent master's address
*/
function getMasterAddress() public view returns (address) {
return address(master);
}
/**
* Default payable function - forwards to the owner and triggers event
*/
function() external payable {
// Get the parent master's owner address - explicity convert to payable
address payable ownerAddress = address(uint160(master.owner()));
// Forward the funds to the owner
ownerAddress.transfer(msg.value);
// Trigger the event notification in the parent master
master.firePaymentReceivedEvent(address(this), msg.sender, msg.value);
}
/**
* Sweep any tokens to the owner of the master
*/
function sweepTokens(IERC20 token) public {
// Get the owner address
address ownerAddress = master.owner();
// Get the current balance
uint balance = token.balanceOf(address(this));
// Transfer to the owner
token.transfer(ownerAddress, balance);
}
}
// File: contracts/PaymentMaster.sol
pragma solidity ^0.5.0;
/**
* The PaymentMaster sits above the payment handler contracts.
* It deploys and keeps track of all the handlers.
* It can trigger events by child handlers when they receive ETH.
* It allows ERC20 tokens to be swept in bulk to the owner account.
*/
contract PaymentMaster is Ownable {
// A list of handler addresses for retrieval
address[] public handlerList;
// A mapping of handler addresses for lookups
mapping(address => bool) public handlerMap;
// Events triggered for listeners
event HandlerCreated(address indexed _addr);
event EthPaymentReceived(address indexed _to, address indexed _from, uint256 _amount);
/**
* Anyone can call the function to deploy a new payment handler.
* The new contract will be created, added to the list, and an event fired.
*/
function deployNewHandler() public {
// Deploy the new contract
PaymentHandler createdHandler = new PaymentHandler(this);
// Add it to the list and the mapping
handlerList.push(address(createdHandler));
handlerMap[address(createdHandler)] = true;
// Emit event to let watchers know that a new handler was created
emit HandlerCreated(address(createdHandler));
}
/**
* This is a convenience method to allow watchers to get the entire list
*/
function getHandlerList() public view returns (address[] memory) {
// Return the entire list
return handlerList;
}
/**
* Allows caller to determine how long the handler list is for convenience
*/
function getHandlerListLength() public view returns (uint) {
return handlerList.length;
}
/**
* This function is called by handlers when they receive ETH payments.
*/
function firePaymentReceivedEvent(address to, address from, uint256 amount) public {
// Verify the call is coming from a handler
require(handlerMap[msg.sender], "Only payment handlers are allowed to trigger payment events.");
// Emit the event
emit EthPaymentReceived(to, from, amount);
}
/**
* Allows a caller to sweep multiple handlers in one transaction
*/
function multiHandlerSweep(address[] memory handlers, IERC20 tokenContract) public {
for (uint i = 0; i < handlers.length; i++) {
// Whitelist calls to only handlers
require(handlerMap[handlers[i]], "Only payment handlers are valid sweep targets.");
// Trigger sweep
PaymentHandler(address(uint160(handlers[i]))).sweepTokens(tokenContract);
}
}
/**
* Safety function to allow sweep of ERC20s if accidentally sent to this contract
*/
function sweepTokens(IERC20 token) public {
// Get the current balance
uint balance = token.balanceOf(address(this));
// Transfer to the owner
token.transfer(this.owner(), balance);
}
}
{
"compilationTarget": {
"PaymentMaster.sol": "PaymentMaster"
},
"evmVersion": "petersburg",
"libraries": {},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"constant":false,"inputs":[{"name":"handlers","type":"address[]"},{"name":"tokenContract","type":"address"}],"name":"multiHandlerSweep","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getHandlerListLength","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"handlerMap","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"renounceOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"handlerList","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"from","type":"address"},{"name":"amount","type":"uint256"}],"name":"firePaymentReceivedEvent","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getHandlerList","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"deployNewHandler","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"token","type":"address"}],"name":"sweepTokens","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_addr","type":"address"}],"name":"HandlerCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_to","type":"address"},{"indexed":true,"name":"_from","type":"address"},{"indexed":false,"name":"_amount","type":"uint256"}],"name":"EthPaymentReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"}]