Contract Source Code:
File 1 of 1 : Processor
pragma solidity ^0.5.0;
interface IReferrals {
function getSplit(address user) external view returns (uint8 discount, uint8 referrer);
}
// from OZ
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
/**
* @dev Multiplies two numbers, throws on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {
// Gas optimization: this is cheaper than asserting 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
c = a * b;
assert(c / a == b);
return c;
}
/**
* @dev Integer division of two numbers, truncating the quotient.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
// uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return a / b;
}
/**
* @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
c = a + b;
assert(c >= a);
return c;
}
}
contract Ownable {
address payable public owner;
constructor() public {
owner = msg.sender;
}
function setOwner(address payable _owner) public onlyOwner {
owner = _owner;
}
function getOwner() public view returns (address payable) {
return owner;
}
modifier onlyOwner {
require(msg.sender == owner, "must be owner to call this function");
_;
}
}
contract Referrals is Ownable {
using SafeMath for uint;
uint public discountLimit;
uint public defaultDiscount;
uint public defaultRefer;
mapping(address => Split) public splits;
struct Split {
bool set;
uint8 discountPercentage;
uint8 referrerPercentage;
}
event SplitChanged(address user, uint8 discount, uint8 referrer);
constructor(uint _discountLimit, uint _defaultDiscount, uint _defaultRefer) public {
setDiscountLimit(_discountLimit);
setDefaults(_defaultDiscount, _defaultRefer);
}
// sets the discount and referral percentages for the current user
// this is deliberately user-customisable
function setSplit(uint8 discount, uint8 referrer) public {
require(discountLimit >= discount + referrer, "can't give more than the limit");
require(discount + referrer >= discount, "can't overflow");
splits[msg.sender] = Split({
discountPercentage: discount,
referrerPercentage: referrer,
set: true
});
emit SplitChanged(msg.sender, discount, referrer);
}
// override a user's split
function overrideSplit(address user, uint8 discount, uint8 referrer) public onlyOwner {
require(discountLimit >= discount + referrer, "can't give more than the limit");
require(discount + referrer >= discount, "can't overflow");
splits[user] = Split({
discountPercentage: discount,
referrerPercentage: referrer,
set: true
});
emit SplitChanged(user, discount, referrer);
}
// sets the max purchase total discount
function setDiscountLimit(uint _limit) public onlyOwner {
require(_limit <= 100, "discount limit must be <= 100");
discountLimit = _limit;
}
// sets the default discount and referral percentages
function setDefaults(uint _discount, uint _refer) public onlyOwner {
require(discountLimit >= _discount + _refer, "can't be more than the limit");
require(_discount + _refer >= _discount, "can't overflow");
defaultDiscount = _discount;
defaultRefer = _refer;
}
// gets the discount and referral rates for a particular user
function getSplit(address user) public view returns (uint8 discount, uint8 referrer) {
if (user == address(0)) {
return (0, 0);
}
Split memory s = splits[user];
if (!s.set) {
return (uint8(defaultDiscount), uint8(defaultRefer));
}
return (s.discountPercentage, s.referrerPercentage);
}
}
contract Processor is Ownable {
using SafeMath for uint256;
IReferrals public referrals;
address payable public vault;
uint public count;
mapping(address => bool) public approvedSellers;
event PaymentProcessed(uint id, address user, uint cost, uint items, address referrer, uint toVault, uint toReferrer);
event SellerApprovalChanged(address seller, bool approved);
constructor(address payable _vault, IReferrals _referrals) public {
referrals = _referrals;
vault = _vault;
}
function setCanSell(address seller, bool approved) public onlyOwner {
approvedSellers[seller] = approved;
emit SellerApprovalChanged(seller, approved);
}
function processPayment(address payable user, uint cost, uint items, address payable referrer) public payable returns (uint) {
require(approvedSellers[msg.sender]);
require(user != referrer, "can't refer yourself");
require(items != 0, "have to purchase at least one item");
require(cost > 0, "items must cost something");
// TODO: are these necessary for the simple percentage logic?
require(cost >= 100, "items must cost at least 100 wei");
require(cost % 100 == 0, "costs must be multiples of 100");
uint toVault;
uint toReferrer;
(toVault, toReferrer) = getAllocations(cost, items, referrer);
uint total = toVault.add(toReferrer);
// check that the tx has enough value to complete the payment
require(msg.value >= total, "not enough value sent to contract");
if (msg.value > total) {
uint change = msg.value.sub(total);
user.transfer(change);
}
vault.transfer(toVault);
// pay the referral fee
if (toReferrer > 0 && referrer != address(0)) {
referrer.transfer(toReferrer);
}
// give this payment a unique ID
uint id = count++;
emit PaymentProcessed(id, user, cost, items, referrer, toVault, toReferrer);
return id;
}
// get the amount of the purchase which will be allocated to
// the vault and to the referrer
function getAllocations(uint cost, uint items, address referrer) public view returns (uint toVault, uint toReferrer) {
uint8 discount;
uint8 refer;
(discount, refer) = referrals.getSplit(referrer);
require(discount + refer <= 100 && discount + refer >= discount, "invalid referral split");
// avoid overflow
uint total = cost.mul(items);
uint8 vaultPercentage = 100 - discount - refer;
toVault = getPercentage(total, vaultPercentage);
toReferrer = getPercentage(total, refer);
uint discountedTotal = getPercentage(total, 100 - discount);
require(discountedTotal == toVault.add(toReferrer), "not all funds allocated");
return (toVault, toReferrer);
}
// returns the price (including discount) which must be paid by the user
function getPrice(uint cost, uint items, address referrer) public view returns (uint) {
uint8 discount;
(discount, ) = referrals.getSplit(referrer);
return getPercentage(cost.mul(items), 100 - discount);
}
function getPercentage(uint amount, uint8 percentage) public pure returns (uint) {
// TODO: are these necessary for the percentage logic?
require(amount >= 100, "items must cost at least 100 wei");
require(amount % 100 == 0, "costs must be multiples of 100 wei");
return amount.mul(percentage).div(100);
}
}