Contract Source Code:
File 1 of 1 : Syndicate
pragma solidity 0.5.11;
//Safe math libarry.
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
}
//EURS stablecoin interface
contract EURSToken {
function transferFrom(address, address, uint256) public returns(bool);
function transfer(address, uint256) public returns(bool);
}
//BGBP stablecoin interface
contract BGBPToken {
function transferFrom(address, address, uint256) public returns(bool);
function transfer(address, uint256) public returns(bool);
}
//USDT stablecoin interface
contract USDTToken {
function transferFrom(address, address, uint256) public returns(bool);
function transfer(address, uint256) public returns(bool);
}
//Main contract.
contract Syndicate {
//Math data.
using SafeMath for uint; //decrlare safe math library usage
//Stablecoin referrence data.
address public eursAddress = 0xdB25f211AB05b1c97D595516F45794528a807ad8; //EURS stablecoin for EUR currency
address public bgbpAddress = 0xC9a2C4868F0f96fAaa739b59934Dc9cB304112ec; //BGBP stablecoin for GBP currency
address public usdtAddress = 0xdAC17F958D2ee523a2206206994597C13D831ec7; //USDT stablecoin for USD currency
uint256 public decimalCorrection; //additional decimals (beyond cents/pennies) used in stablecoin, options = 1 (EURS), 1000000 (BGBP), 10000 (USDT)
//Admin data.
address public admin; //admin's address
uint256 public adminShare = 40; //admin's share percent from profit generated by syndicate
uint256 public adminProfit = 0; //total net profit which belongs to admin
//Angel data.
address public angel; //angel's (syndicate owner's) address
uint256 public angelInvestment = 0; //total amount of money angel has invested
uint256 public angelProfit = 0; //total net profit which belongs to angel
//Syndicate data.
uint256 public pendingSyndicateBalance = 0; //syndicate's new balance after settling latest bets (not enforced by contract yet)
int256 public pendingSyndicateProfit = 0; //syndicate's new profit after settling latest bets (not enforced by contract yet)
uint256 public syndicateBalance = 0; //syndicate's current, contract enforced balance
int256 public syndicateProfit = 0; //syndicate's current, contract enforced profit
uint256 public distributionWaitTime; //time (30 days) which needs to pass before monthly earnings are distributed among angel and admin
uint256 public closureWaitTime; //time (47h) which angel needs to wait before he/she can close syndicate after closure announcement
//User data.
struct Agreement {
uint256 lsBalance; //user's current, contract enforced LoopSyndicate balance
uint256 lsInplay; //user's current, contract enforced LoopSyndicate inplay balance
uint256 bookieBalance; //user's bookmakers current, contract enforced total balance
uint256 bookieInplay; //user's bookmakers current, contract enforced total inplay balance
uint256 userBankroll; //user's current, contract enforced total capital
uint256 userProfit; //total guaranteed profit user generated from placing bets and enforced by contract
}
mapping(address => Agreement) public userAgreements;
//Betting data.
struct Betting {
uint256 lsBalance; //user's LoopSyndicate new balance after settling latest bets placed by user (not enforced by contract yet)
uint256 lsInplay; //user's LoopSyndicate new inplay balance after settling latest bets placed by user (not enforced by contract yet)
uint256 bookieBalance; //user's bookmakers new total balance after settling latest bets placed by user (not enforced by contract yet)
uint256 bookieInplay; //user's bookmakers new total inplay balance after settling latest bets placed by user (not enforced by contract yet)
uint256 userBankroll; //user's new total capital after settling latest bets placed by user (not enforced by contract yet)
uint256 userProfit; //user's new guaranteed profit after settling latest bets placed by user (not enforced by contract yet)
uint256 pubTime; //publishing time of latest betting data
}
mapping(address => Betting) public bettingResults;
/*!new betting result's won't take actual effect, i.e. being set in "Agreement" data, if either user or angel
rejects this new betting result's data proposed by admin by breaking contract!*/
//Contract agreements status data.
bool public syndicateActive = true; //angel's consent to "buy" users' value bets and in exchange provide guaranteed profit to them
mapping(address => bool) public brokenAgreements; //list of users who broke contract agreement from their side
//Set admin and angel addresses as well as currency choice on smart contract deployment.
constructor(address _angel, uint256 _currency) public {
//Admin and angel addresses.
admin = msg.sender;
angel = _angel;
//EUR
if (_currency == 1) {
decimalCorrection = 1;
}
//GBP
else if (_currency == 2) {
decimalCorrection = 1000000;
}
//USD
else if (_currency == 3) {
decimalCorrection = 10000;
}
}
//Create admin modifier to ensure that only admin can call certain functions.
modifier onlyAdmin() {
require(msg.sender == admin, "only admin allowed to call this function");
_;
}
//Event which is being emitted when user or angel breaks contract agreement.
event AgreementBreak (
address _from
);
//Event which is being emitted when angel announces his/her syndicate closure.
event ClosureAnnouncement (
address _from
);
//Call EURS smart contract transferFrom function.
function eursTransferFrom(address _from, address _to, uint256 _coins) internal {
EURSToken stablecoin = EURSToken(eursAddress);
require(stablecoin.transferFrom(_from, _to, _coins), "error at EURS stablecoin transferFrom function");
}
//Call BGBP smart contract transferFrom function.
function bgbpTransferFrom(address _from, address _to, uint256 _coins) internal {
BGBPToken stablecoin = BGBPToken(bgbpAddress);
require(stablecoin.transferFrom(_from, _to, _coins), "error at BGBP stablecoin transferFrom function");
}
//Call USDT smart contract transferFrom function.
function usdtTransferFrom(address _from, address _to, uint256 _coins) internal {
USDTToken stablecoin = USDTToken(usdtAddress);
require(stablecoin.transferFrom(_from, _to, _coins), "error at USDT stablecoin transferFrom function");
}
//Call EURS smart contract transfer function.
function eursTransfer(address _to, uint256 _coins) internal {
EURSToken stablecoin = EURSToken(eursAddress);
require(stablecoin.transfer(_to, _coins), "error at EURS stablecoin transfer function");
}
//Call BGBP smart contract transfer function.
function bgbpTransfer(address _to, uint256 _coins) internal {
BGBPToken stablecoin = BGBPToken(bgbpAddress);
require(stablecoin.transfer(_to, _coins), "error at BGBP stablecoin transfer function");
}
//Call USDT smart contract transfer function.
function usdtTransfer(address _to, uint256 _coins) internal {
USDTToken stablecoin = USDTToken(usdtAddress);
require(stablecoin.transfer(_to, _coins), "error at USDT stablecoin transfer function");
}
//Allow angel to deposit into syndicate.
function angelDeposit(address _from, uint256 _value) public {
//Make sure angel's deposit value is at least 100 units (cents/pennies).
require(_value >= 100, "deposit value less than minimum (100 cents/pennies)");
//Make sure angel address matches.
require(_from == angel, "from address does not match angel address");
//Convert cents/pennies into coins.
uint256 _coins = decimalCorrection.mul(_value);
//Make transfer from angel's private wallet into Syndicate contract and make sure transfer was successful.
address _to = address(this);
if (decimalCorrection == 1) {
eursTransferFrom(_from, _to, _coins);
}
else if (decimalCorrection == 1000000) {
bgbpTransferFrom(_from, _to, _coins);
}
else if (decimalCorrection == 10000) {
usdtTransferFrom(_from, _to, _coins);
}
//Update syndicate's balance value.
syndicateBalance += _value;
pendingSyndicateBalance += _value;
//Update angel's investment value.
angelInvestment += _value;
}
//Allow user to make deposit.
function userDeposit(address _from, uint256 _value) public {
//Make sure syndicate is still active.
require(syndicateActive, "angel has closed this syndicate");
//Make sure user's deposit value is at least 100 units (cents/pennies).
require(_value >= 100, "deposit value must be minimum 100 cents/pennies");
//Convert cents/pennies into coins.
uint256 _coins = decimalCorrection.mul(_value);
//Make deposit and make sure it was successful.
address _to = address(this);
if (decimalCorrection == 1) {
eursTransferFrom(_from, _to, _coins);
}
else if (decimalCorrection == 1000000) {
bgbpTransferFrom(_from, _to, _coins);
}
else if (decimalCorrection == 10000) {
usdtTransferFrom(_from, _to, _coins);
}
//Update user's balance.
Agreement memory _agreement = userAgreements[_from];
_agreement.lsBalance = _agreement.lsBalance + _value;
userAgreements[_from] = _agreement;
//Update user's betting results because of new deposit.
Betting memory _result = bettingResults[_from];
_result.lsBalance = _result.lsBalance + _value;
bettingResults[_from] = _result;
}
//Display user's latest betting results.
function displayResults(address _user, uint256 _lsBalance, uint256 _lsInplay, uint256 _bookieBalance, uint256 _bookieInplay, uint256 _userBankroll, uint256 _userProfit) public onlyAdmin {
//Make sure syndicate is still active.
require(syndicateActive, "angel has closed this syndicate");
//Make sure user has active contract agreement.
require(!(brokenAgreements[_user]), "user has broken agreement");
//Make sure user has balance (if user does not have balance then betting was not possible).
Agreement memory _agreement = userAgreements[_user];
require(_agreement.lsBalance > 0 || _agreement.lsInplay > 0, "user does not have balance");
//Make sure previous betting results were applied.
Betting memory _result = bettingResults[_user];
require(_result.pubTime == 0, "previous betting results were not applied");
//Calculate syndicate's pending profit or loss based on new betting results.
uint256 _prevTotalLoopBalance = _result.lsBalance + _result.lsInplay;
uint256 _newTotalLoopBalance = _lsBalance + _lsInplay;
int256 _profit = int(_prevTotalLoopBalance) - int(_newTotalLoopBalance);
//Update syndicate's pending balance and profit.
pendingSyndicateBalance += uint(_profit);
pendingSyndicateProfit += _profit;
//Set new betting results data.
_result.lsBalance = _lsBalance;
_result.lsInplay = _lsInplay;
_result.bookieBalance = _bookieBalance;
_result.bookieInplay = _bookieInplay;
_result.userBankroll = _userBankroll;
_result.userProfit = _userProfit;
_result.pubTime = block.timestamp;
bettingResults[_user] = _result;
}
//Allow angel to break his/her syndicate's contract in case of disagreement.
function breakSyndicate() public {
//Make sure angel is calling this function.
require(msg.sender == angel, "msg sender address does not match angel address");
/*By breaking contract agreement angel rejects any changes to be made to syndicate's balance value but in exchange all
profit angel generated will be wiped out as a punishment and 10000 cents/pennies will be payed by angel as
a fine. Admin's profit share is also wiped out.*/
syndicateActive = false;
//Apply 10000 cents/pennies fine to angel.
if (syndicateBalance >= 10000) {
syndicateBalance -= 10000;
syndicateProfit -= 10000;
}
else {
syndicateBalance = 0;
syndicateProfit = 0;
}
//Wipe out all profit if such exists.
if (syndicateProfit > 0) {
syndicateBalance -= uint(syndicateProfit);
syndicateProfit = 0;
}
//Emit signal when angel breaks agreement.
emit AgreementBreak(msg.sender);
}
//Allow user to break contract agreement.
function breakAgreement() public {
/*By setting broken agreements to true user rejects any changes to be made to his/her current Agreement
data and agrees to stop receiving guaranteed profit service.*/
brokenAgreements[msg.sender] = true;
//Emit signal when user breaks agreement.
emit AgreementBreak(msg.sender);
}
//Confirm latest user betting results as new contract agreement.
function setNewAgreement(address _user) public onlyAdmin returns(bool) {
//Make sure syndicate is still active.
require(syndicateActive, "angel has closed this syndicate");
//Make sure user has active contract agreement.
require(!(brokenAgreements[_user]), "user has broken agreement");
//Make sure user has balance (if user does not have balance then betting was not possible).
Agreement memory _agreement = userAgreements[_user];
require(_agreement.lsBalance > 0 || _agreement.lsInplay > 0, "user does not have balance");
/*Make sure 23h passed since betting results were displayed where user had enough time to reject betting
results and break agreement.*/
Betting memory _result = bettingResults[_user];
uint256 _waitLimit = _result.pubTime + 82800; //there are 82,800 seconds in 23h
if (_result.pubTime == 0 || block.timestamp < _waitLimit) {
return false;
}
//Calculate syndicate's profit or loss based on new betting results.
uint256 _prevTotalLoopBalance = _agreement.lsBalance + _agreement.lsInplay;
uint256 _newTotalLoopBalance = _result.lsBalance + _result.lsInplay;
int256 _profit = int(_prevTotalLoopBalance) - int(_newTotalLoopBalance);
//Update syndicate's balance and profit.
syndicateBalance += uint(_profit);
syndicateProfit += _profit;
//Set user's new accepted agreement data.
_agreement.lsBalance = _result.lsBalance;
_agreement.lsInplay = _result.lsInplay;
_agreement.bookieBalance = _result.bookieBalance;
_agreement.bookieInplay = _result.bookieInplay;
_agreement.userBankroll = _result.userBankroll;
_agreement.userProfit = _result.userProfit;
userAgreements[_user] = _agreement;
//Reset user's latest betting results.
bettingResults[_user] = Betting(0, 0, 0, 0, 0, 0, 0);
return true;
}
//Allow user to withdraw his/her balance.
function userWithdraw(uint256 _value) public {
Agreement memory _agreement = userAgreements[msg.sender];
Betting memory _result = bettingResults[msg.sender];
/*If user broke contract agreement then void all active bets and allow him/her to withdraw balance as
recorded in last valid agreement.*/
if (brokenAgreements[msg.sender]) {
_value = _agreement.lsBalance + _agreement.lsInplay;
_agreement.lsBalance = _agreement.lsBalance + _agreement.lsInplay;
_agreement.lsInplay = 0;
}
//Otherwise validate withdrawal request.
else {
//Make sure user's accepted agreement balance matches user's pending agreement balance.
require(_agreement.lsBalance == _result.lsBalance);
require(_agreement.lsInplay == _result.lsInplay);
//Make sure user has enough balance.
require(_agreement.lsBalance >= _value, "user balance is less than withdrawal value");
}
//Convert cents/pennies into coins.
uint256 _coins = decimalCorrection.mul(_value);
//Make transfer from contract into user's private wallet and make sure it was successful.
if (decimalCorrection == 1) {
eursTransfer(msg.sender, _coins);
}
else if (decimalCorrection == 1000000) {
bgbpTransfer(msg.sender, _coins);
}
else if (decimalCorrection == 10000) {
usdtTransfer(msg.sender, _coins);
}
//Update user's balance.
_agreement.lsBalance = _agreement.lsBalance - _value;
userAgreements[msg.sender] = _agreement;
//Set user's latest betting result's data same as agreement data because of new withdrawal.
_result.lsBalance = _agreement.lsBalance;
bettingResults[msg.sender] = _result;
}
//Distribute monthly earnings.
function distributeEarnings() public returns(bool) {
//Make sure 30 days passed since last earnings distribution time.
require(block.timestamp > distributionWaitTime, "month did not pass since last earnings distribution");
//Make sure profit is at least 100 cents/pennies, otherwise don't bother to distribute earnings.
if (syndicateProfit < 100) {
return false;
}
//Let admin receive his share.
uint256 _share = uint(syndicateProfit) * adminShare / 100;
adminProfit += _share;
syndicateBalance -= _share;
pendingSyndicateBalance -= _share;
syndicateProfit -= int(_share);
//Let angel to receive the rest.
angelProfit += uint(syndicateProfit);
syndicateBalance -= uint(syndicateProfit);
pendingSyndicateBalance -= uint(syndicateProfit);
syndicateProfit = 0;
//Reset distribution wait time.
distributionWaitTime = block.timestamp + 2592000; //there are 2,592,000 seconds in 30 days
return true;
}
//Allow angel to withdraw profit.
function angelWithdraw() public {
//Make sure angel is making withdrawal.
require(msg.sender == angel, "msg sender address does not match angel address");
//Make sure angel has profit.
require(angelProfit > 0, "angel does not have profit to withdraw");
//Convert cents/pennies into coins.
uint256 _coins = decimalCorrection * angelProfit;
//Make transfer.
if (decimalCorrection == 1) {
eursTransfer(msg.sender, _coins);
}
else if (decimalCorrection == 1000000) {
bgbpTransfer(msg.sender, _coins);
}
else if (decimalCorrection == 10000) {
usdtTransfer(msg.sender, _coins);
}
//Update angel's profit value.
angelProfit = 0;
}
//Allow angel to reinvest profit back into syndicate.
function angelReinvest() public {
//Make sure angel is reinvesting profit.
require(msg.sender == angel, "msg sender address does not match angel address");
//Make sure angel has profit.
require(angelProfit > 0, "angel does not have profit to reinvest");
//Update syndicate balance value.
syndicateBalance += angelProfit;
pendingSyndicateBalance += angelProfit;
//Set angel profit to zero.
angelProfit = 0;
}
//Allow admin to withdraw profit.
function adminWithdraw(address _to) public onlyAdmin {
//Make sure admin has profit.
require(adminProfit > 0, "admin does not have profit share");
//Convert cents/pennies into coins.
uint256 _coins = decimalCorrection * adminProfit;
//Make transfe.
if (decimalCorrection == 1) {
eursTransfer(_to, _coins);
}
else if (decimalCorrection == 1000000) {
bgbpTransfer(_to, _coins);
}
else if (decimalCorrection == 10000) {
usdtTransfer(_to, _coins);
}
//Update admin's profit value.
adminProfit = 0;
}
//Announce syndicate's closure.
function announceClosure() public {
//Make sure angel is making announcement.
require(msg.sender == angel, "msg sender address does not match angel address");
//Allow closure after 47 hours from current time.
closureWaitTime = block.timestamp + 169200; //there are 169,200 seconds in 47 hours
//Emit signal when angel announces sydicate's closure.
emit ClosureAnnouncement(msg.sender);
}
//Close angel's syndicate.
function closeSyndicate() public {
//Make sure 2 days passed since closure announcement.
require(block.timestamp > closureWaitTime && closureWaitTime != 0, "2 days did not pass since closure announcement");
//Subtract admin share from profit, if such exists.
if (syndicateProfit >= 100) {
uint256 _share = uint(syndicateProfit) * adminShare / 100;
adminProfit += _share;
syndicateBalance -= _share;
pendingSyndicateBalance -= _share;
syndicateProfit = 0;
}
//Convert cents/pennies into coins.
uint256 _coins = decimalCorrection * syndicateBalance;
//Make transfer.
if (decimalCorrection == 1) {
eursTransfer(angel, _coins);
}
else if (decimalCorrection == 1000000) {
bgbpTransfer(angel, _coins);
}
else if (decimalCorrection == 10000) {
usdtTransfer(angel, _coins);
}
//Update syndicate's balance value.
syndicateBalance = 0;
pendingSyndicateBalance = 0;
//Reset angel investment value and disable syndicate.
angelInvestment = 0;
syndicateActive = false;
//Reset closure wait time.
closureWaitTime = 0;
}
}