/** * Source Code first verified at https://etherscan.io on Tuesday, May 7, 2019 (UTC) */ pragma solidity ^0.4.24; /** * @title AdvertisingInterface */ interface AdvertisingInterface { function incrementBetCounter() external returns (bool); } // https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/math/SafeMath.sol /** * @title SafeMath * @dev Math operations with safety checks that throw on error */ library SafeMath { function mul(uint256 a, uint256 b) internal pure returns (uint256) { if (a == 0) { return 0; } uint256 c = a * b; assert(c / a == b); return c; } 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 c; } function sub(uint256 a, uint256 b) internal pure returns (uint256) { assert(b <= a); return a - b; } function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; assert(c >= a); return c; } } interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) external; } contract TokenERC20 { // Public variables of the token string public name; string public symbol; uint8 public decimals = 18; // 18 decimals is the strongly suggested default, avoid changing it uint256 public totalSupply; // This creates an array with all balances mapping (address => uint256) public balanceOf; mapping (address => mapping (address => uint256)) public allowance; // This generates a public event on the blockchain that will notify clients event Transfer(address indexed from, address indexed to, uint256 value); // This generates a public event on the blockchain that will notify clients event Approval(address indexed _owner, address indexed _spender, uint256 _value); // This notifies clients about the amount burnt event Burn(address indexed from, uint256 value); /** * Constructor function * * Initializes contract with initial supply tokens to the creator of the contract */ constructor( uint256 initialSupply, string tokenName, string tokenSymbol ) public { totalSupply = initialSupply * 10 ** uint256(decimals); // Update total supply with the decimal amount balanceOf[msg.sender] = totalSupply; // Give the creator all initial tokens name = tokenName; // Set the name for display purposes symbol = tokenSymbol; // Set the symbol for display purposes } /** * Internal transfer, only can be called by this contract */ function _transfer(address _from, address _to, uint _value) internal { // Prevent transfer to 0x0 address. Use burn() instead require(_to != 0x0); // Check if the sender has enough require(balanceOf[_from] >= _value); // Check for overflows require(balanceOf[_to] + _value > balanceOf[_to]); // Save this for an assertion in the future uint previousBalances = balanceOf[_from] + balanceOf[_to]; // Subtract from the sender balanceOf[_from] -= _value; // Add the same to the recipient balanceOf[_to] += _value; emit Transfer(_from, _to, _value); // Asserts are used to use static analysis to find bugs in your code. They should never fail assert(balanceOf[_from] + balanceOf[_to] == previousBalances); } /** * Transfer tokens * * Send `_value` tokens to `_to` from your account * * @param _to The address of the recipient * @param _value the amount to send */ function transfer(address _to, uint256 _value) public returns (bool success) { _transfer(msg.sender, _to, _value); return true; } /** * Transfer tokens from other address * * Send `_value` tokens to `_to` in behalf of `_from` * * @param _from The address of the sender * @param _to The address of the recipient * @param _value the amount to send */ function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { require(_value <= allowance[_from][msg.sender]); // Check allowance allowance[_from][msg.sender] -= _value; _transfer(_from, _to, _value); return true; } /** * Set allowance for other address * * Allows `_spender` to spend no more than `_value` tokens in your behalf * * @param _spender The address authorized to spend * @param _value the max amount they can spend */ function approve(address _spender, uint256 _value) public returns (bool success) { allowance[msg.sender][_spender] = _value; emit Approval(msg.sender, _spender, _value); return true; } /** * Set allowance for other address and notify * * Allows `_spender` to spend no more than `_value` tokens in your behalf, and then ping the contract about it * * @param _spender The address authorized to spend * @param _value the max amount they can spend * @param _extraData some extra information to send to the approved contract */ function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) { tokenRecipient spender = tokenRecipient(_spender); if (approve(_spender, _value)) { spender.receiveApproval(msg.sender, _value, this, _extraData); return true; } } /** * Destroy tokens * * Remove `_value` tokens from the system irreversibly * * @param _value the amount of money to burn */ function burn(uint256 _value) public returns (bool success) { require(balanceOf[msg.sender] >= _value); // Check if the sender has enough balanceOf[msg.sender] -= _value; // Subtract from the sender totalSupply -= _value; // Updates totalSupply emit Burn(msg.sender, _value); return true; } /** * Destroy tokens from other account * * Remove `_value` tokens from the system irreversibly on behalf of `_from`. * * @param _from the address of the sender * @param _value the amount of money to burn */ function burnFrom(address _from, uint256 _value) public returns (bool success) { require(balanceOf[_from] >= _value); // Check if the targeted balance is enough require(_value <= allowance[_from][msg.sender]); // Check allowance balanceOf[_from] -= _value; // Subtract from the targeted balance allowance[_from][msg.sender] -= _value; // Subtract from the sender's allowance totalSupply -= _value; // Update totalSupply emit Burn(_from, _value); return true; } } contract developed { address public developer; /** * Constructor */ constructor() public { developer = msg.sender; } /** * @dev Checks only developer address is calling */ modifier onlyDeveloper { require(msg.sender == developer); _; } /** * @dev Allows developer to switch developer address * @param _developer The new developer address to be set */ function changeDeveloper(address _developer) public onlyDeveloper { developer = _developer; } /** * @dev Allows developer to withdraw ERC20 Token */ function withdrawToken(address tokenContractAddress) public onlyDeveloper { TokenERC20 _token = TokenERC20(tokenContractAddress); if (_token.balanceOf(this) > 0) { _token.transfer(developer, _token.balanceOf(this)); } } } /** * @title Advertising */ contract Advertising is developed, AdvertisingInterface { using SafeMath for uint256; address private incrementer; bool public paused; bool public contractKilled; uint256 public numCreatives; uint256 public numCreativeTypes; uint256 public maxCountPerCreativeType; uint256 public earnedBalance; struct Creative { bytes32 creativeId; address advertiser; uint256 creativeTypeId; // This determines the creative size and where we display it string name; uint256 weiBudget; uint256 weiPerBet; uint256 betCounter; int256 position; string url; string imageUrl; bool approved; uint256 createdOn; } struct CreativeType { string name; uint256 width; uint256 height; /** * @dev Where to display the creative * 1 = top * 2 = right * 3 = bottom * 4 = left */ uint256 position; bool active; } mapping (bytes32 => Creative) public creatives; mapping (bytes32 => uint256) private creativeIdLookup; mapping (uint256 => CreativeType) public creativeTypes; mapping (address => uint256) public advertiserPendingWithdrawals; mapping (uint256 => bytes32[]) public pendingCreativePosition; mapping (uint256 => bytes32[]) public approvedCreativePosition; /** * @dev Log when dev add new creative type */ event LogAddCreativeType(uint256 indexed creativeTypeId, string name, uint256 width, uint256 height, uint256 position); /** * @dev Log when dev activate/deactivate creative type */ event LogSetActiveCreativeType(uint256 creativeTypeId, bool active); /** * @dev Log when dev approves creative */ event LogApproveCreative(bytes32 indexed creativeId, address indexed advertiser, uint256 indexed creativeTypeId, int256 position); /** * @dev Log when dev set contract to emergency mode */ event LogEscapeHatch(); /** * @dev Log when advertiser creates creative */ event LogCreateCreative(bytes32 indexed creativeId, address indexed advertiser, uint256 indexed creativeTypeId, string name, uint256 weiBudget, uint256 weiPerBet, int256 position); /** * @dev Log when we refund creative * creativeStatus: * 0 = pending * 1 = approved * refundStatus: * 0 = failed * 1 = success */ event LogRefundCreative(bytes32 indexed creativeId, address indexed advertiser, uint256 refundAmount, uint256 creativeStatus, uint256 refundStatus); /** * @dev Log when advertiser withdraws balance from failed transfer * * Status: * 0 = failed * 1 = success */ event LogWithdrawBalance(address indexed advertiser, uint256 withdrawAmount, uint256 status); /** * @dev Log when we increment bet counter for a creative */ event LogIncrementBetCounter(bytes32 indexed creativeId, address indexed advertiser, uint256 numBets); /** * Constructor */ constructor(address _incrementer) public { devSetMaxCountPerCreativeType(10); devSetIncrementer(_incrementer); } /** * @dev Checks if the contract is currently alive */ modifier contractIsAlive { require(contractKilled == false); _; } /** * @dev Checks if contract is active */ modifier isActive { require(paused == false); _; } /** * @dev Check if creative is valid * @param creativeTypeId The creative type ID * @param name The name of this creative * @param weiBudget The budget for this creative * @param weiPerBet Cost per bet for an ad * @param url The url of the ad that we want to redirect to * @param imageUrl The image url for this ad */ modifier creativeIsValid(uint256 creativeTypeId, string name, uint256 weiBudget, uint256 weiPerBet, string url, string imageUrl) { require (creativeTypes[creativeTypeId].active == true && bytes(name).length > 0 && weiBudget > 0 && weiPerBet > 0 && weiBudget >= weiPerBet && bytes(url).length > 0 && bytes(imageUrl).length > 0 && (pendingCreativePosition[creativeTypeId].length < maxCountPerCreativeType || (pendingCreativePosition[creativeTypeId].length == maxCountPerCreativeType && weiPerBet > creatives[pendingCreativePosition[creativeTypeId][maxCountPerCreativeType-1]].weiPerBet) ) ); _; } /** * @dev Checks is caller is from incrementer */ modifier onlyIncrementer { require (msg.sender == incrementer); _; } /******************************************/ /* DEVELOPER ONLY METHODS */ /******************************************/ /** * @dev Dev sets address that is allowed to increment ad metrics * @param _incrementer The address to be set */ function devSetIncrementer(address _incrementer) public onlyDeveloper { incrementer = _incrementer; } /** * @dev Dev get incrementer address */ function devGetIncrementer() public onlyDeveloper constant returns (address) { return incrementer; } /** * @dev Dev sets max count per creative type * @param _maxCountPerCreativeType The max number of ad for a creative type */ function devSetMaxCountPerCreativeType(uint256 _maxCountPerCreativeType) public onlyDeveloper { require (_maxCountPerCreativeType > 0); maxCountPerCreativeType = _maxCountPerCreativeType; } /** * @dev Dev add creative type * @param name The name of this creative type * @param width The width of the creative * @param height The height of the creative * @param position The position of the creative */ function devAddCreativeType(string name, uint256 width, uint256 height, uint256 position) public onlyDeveloper { require (width > 0 && height > 0 && position > 0); // Increment num creative types numCreativeTypes++; CreativeType storage _creativeType = creativeTypes[numCreativeTypes]; // Store the info about this creative type _creativeType.name = name; _creativeType.width = width; _creativeType.height = height; _creativeType.position = position; _creativeType.active = true; emit LogAddCreativeType(numCreativeTypes, _creativeType.name, _creativeType.width, _creativeType.height, _creativeType.position); } /** * @dev Dev activate/deactivate creative type * @param creativeTypeId The creative type ID to be set * @param active The bool value to be set */ function devSetActiveCreativeType(uint256 creativeTypeId, bool active) public onlyDeveloper { creativeTypes[creativeTypeId].active = active; emit LogSetActiveCreativeType(creativeTypeId, active); } /** * @dev Allows dev to approve/disapprove a creative * @param creativeId The creative ID to be approved */ function devApproveCreative(bytes32 creativeId) public onlyDeveloper { Creative storage _creative = creatives[creativeId]; require (_creative.approved == false && _creative.position > -1 && _creative.createdOn > 0); _creative.approved = true; _removePending(creativeId); _insertSortApprovedCreative(_creative.creativeTypeId, _creative.creativeId); } /** * @dev Allows dev to withdraw earned balance */ function devWithdrawEarnedBalance() public onlyDeveloper returns (bool) { require (earnedBalance > 0); require (address(this).balance >= earnedBalance); uint256 withdrawAmount = earnedBalance; earnedBalance = 0; if (!developer.send(withdrawAmount)) { earnedBalance = withdrawAmount; return false; } else { return true; } } /** * @dev Dev ends the ad * @param creativeId The creative ID to be ended */ function devEndCreative(bytes32 creativeId) public onlyDeveloper { _endCreative(creativeId); } /** * @dev Allows developer to pause the contract * @param _paused The new paused value to be set */ function devSetPaused(bool _paused) public onlyDeveloper { paused = _paused; } /** * @dev Allows developer to trigger emergency mode. */ function escapeHatch() public onlyDeveloper contractIsAlive returns (bool) { contractKilled = true; if (earnedBalance > 0) { uint256 withdrawAmount = earnedBalance; earnedBalance = 0; if (!developer.send(withdrawAmount)) { earnedBalance = withdrawAmount; } } if (numCreativeTypes > 0) { for (uint256 i=1; i <= numCreativeTypes; i++) { /* * First, we refund all the pending creatives. * Instead of sending the refund amount, we ask advertisers to withdraw the refunded amount themselves */ uint256 creativeCount = pendingCreativePosition[i].length; if (creativeCount > 0) { for (uint256 j=0; j < creativeCount; j++) { Creative memory _creative = creatives[pendingCreativePosition[i][j]]; // let advertiser withdraw via advertiserPendingWithdrawals advertiserPendingWithdrawals[_creative.advertiser] = advertiserPendingWithdrawals[_creative.advertiser].add(_creative.weiBudget); } } /* * Then, we refund all the approved creatives */ creativeCount = approvedCreativePosition[i].length; if (creativeCount > 0) { for (j=0; j < creativeCount; j++) { _creative = creatives[approvedCreativePosition[i][j]]; uint256 refundAmount = _creative.weiBudget.sub(_creative.betCounter.mul(_creative.weiPerBet)); // let advertiser withdraw via advertiserPendingWithdrawals advertiserPendingWithdrawals[_creative.advertiser] = advertiserPendingWithdrawals[_creative.advertiser].add(refundAmount); } } } } emit LogEscapeHatch(); return true; } /******************************************/ /* INCREMENT ADDRESS METHODS */ /******************************************/ function incrementBetCounter() public onlyIncrementer contractIsAlive isActive returns (bool) { if (numCreativeTypes > 0) { for (uint256 i=1; i <= numCreativeTypes; i++) { CreativeType memory _creativeType = creativeTypes[i]; uint256 creativeCount = approvedCreativePosition[i].length; if (_creativeType.active == false || creativeCount == 0) { continue; } Creative storage _creative = creatives[approvedCreativePosition[i][0]]; _creative.betCounter++; emit LogIncrementBetCounter(_creative.creativeId, _creative.advertiser, _creative.betCounter); uint256 totalSpent = _creative.weiPerBet.mul(_creative.betCounter); if (totalSpent > _creative.weiBudget) { earnedBalance = earnedBalance.add(_creative.weiBudget.sub(_creative.weiPerBet.mul(_creative.betCounter.sub(1)))); _removeApproved(_creative.creativeId); } else { earnedBalance = earnedBalance.add(_creative.weiPerBet); } } } return true; } /******************************************/ /* PUBLIC METHODS */ /******************************************/ /** * @dev Advertiser submits an ad * @param creativeTypeId The creative type ID (determines where we display it) * @param name The name of this creative * @param weiPerBet Cost per bet for an ad * @param url The url of the ad that we want to redirect to * @param imageUrl The image url for this ad */ function createCreative(uint256 creativeTypeId, string name, uint256 weiPerBet, string url, string imageUrl) public payable contractIsAlive isActive creativeIsValid(creativeTypeId, name, msg.value, weiPerBet, url, imageUrl) { // Increment num creatives numCreatives++; // Generate ID for this creative bytes32 creativeId = keccak256(abi.encodePacked(this, msg.sender, numCreatives)); Creative storage _creative = creatives[creativeId]; // Store the info about this creative _creative.creativeId = creativeId; _creative.advertiser = msg.sender; _creative.creativeTypeId = creativeTypeId; _creative.name = name; _creative.weiBudget = msg.value; _creative.weiPerBet = weiPerBet; _creative.url = url; _creative.imageUrl = imageUrl; _creative.createdOn = now; // Decide which position this creative is _insertSortPendingCreative(creativeTypeId, creativeId); } /** * @dev Advertiser ends the ad * @param creativeId The creative ID to be ended */ function endCreative(bytes32 creativeId) public contractIsAlive isActive { Creative storage _creative = creatives[creativeId]; require (_creative.advertiser == msg.sender); _endCreative(creativeId); } /** * @dev withdraws balance in case of a failed refund or failed win send */ function withdrawPendingTransactions() public { uint256 withdrawAmount = advertiserPendingWithdrawals[msg.sender]; require (withdrawAmount > 0); require (address(this).balance >= withdrawAmount); advertiserPendingWithdrawals[msg.sender] = 0; // External call to untrusted contract if (msg.sender.send(withdrawAmount)) { emit LogWithdrawBalance(msg.sender, withdrawAmount, 1); } else { /* * If send failed, revert advertiserPendingWithdrawals[msg.sender] = 0 * so that player can try to withdraw again later */ advertiserPendingWithdrawals[msg.sender] = withdrawAmount; emit LogWithdrawBalance(msg.sender, withdrawAmount, 0); } } /******************************************/ /* INTERNAL METHODS */ /******************************************/ /** * @dev Insert the newly submitted creative and sort the array to determine its position. * If the array container length is greater than max count, then we want to refund the last element * @param creativeTypeId The creative type ID of this ad * @param creativeId The creative ID of this ad */ function _insertSortPendingCreative(uint256 creativeTypeId, bytes32 creativeId) internal { pendingCreativePosition[creativeTypeId].push(creativeId); uint256 pendingCount = pendingCreativePosition[creativeTypeId].length; bytes32[] memory copyArray = new bytes32[](pendingCount); for (uint256 i=0; i 0 && creatives[copyArray[j-1]].weiPerBet < value; j--) { copyArray[j] = copyArray[j-1]; } copyArray[j] = key; } for (i=0; i maxCountPerCreativeType) { bytes32 removeCreativeId = pendingCreativePosition[creativeTypeId][pendingCount-1]; creatives[removeCreativeId].position = -1; delete pendingCreativePosition[creativeTypeId][pendingCount-1]; pendingCreativePosition[creativeTypeId].length--; _refundPending(removeCreativeId); } } /** * @dev Refund the pending creative * @param creativeId The creative ID of this ad */ function _refundPending(bytes32 creativeId) internal { Creative memory _creative = creatives[creativeId]; require (address(this).balance >= _creative.weiBudget); require (_creative.position == -1); if (!_creative.advertiser.send(_creative.weiBudget)) { emit LogRefundCreative(_creative.creativeId, _creative.advertiser, _creative.weiBudget, 0, 0); // If send failed, let advertiser withdraw via advertiserPendingWithdrawals advertiserPendingWithdrawals[_creative.advertiser] = advertiserPendingWithdrawals[_creative.advertiser].add(_creative.weiBudget); } else { emit LogRefundCreative(_creative.creativeId, _creative.advertiser, _creative.weiBudget, 0, 1); } } /** * @dev Insert the newly approved creative and sort the array to determine its position. * If the array container length is greater than max count, then we want to refund the last element. * @param creativeTypeId The creative type ID of this ad * @param creativeId The creative ID of this ad */ function _insertSortApprovedCreative(uint256 creativeTypeId, bytes32 creativeId) internal { approvedCreativePosition[creativeTypeId].push(creativeId); uint256 approvedCount = approvedCreativePosition[creativeTypeId].length; bytes32[] memory copyArray = new bytes32[](approvedCount); for (uint256 i=0; i 0 && creatives[copyArray[j-1]].weiPerBet < value; j--) { copyArray[j] = copyArray[j-1]; } copyArray[j] = key; } for (i=0; i maxCountPerCreativeType) { bytes32 removeCreativeId = approvedCreativePosition[creativeTypeId][approvedCount-1]; creatives[removeCreativeId].position = -1; delete approvedCreativePosition[creativeTypeId][approvedCount-1]; approvedCreativePosition[creativeTypeId].length--; _refundApproved(removeCreativeId); } } /** * @dev Refund the approved creative * @param creativeId The creative ID of this ad */ function _refundApproved(bytes32 creativeId) internal { Creative memory _creative = creatives[creativeId]; uint256 refundAmount = _creative.weiBudget.sub(_creative.betCounter.mul(_creative.weiPerBet)); require (address(this).balance >= refundAmount); require (_creative.position == -1); if (!_creative.advertiser.send(refundAmount)) { emit LogRefundCreative(_creative.creativeId, _creative.advertiser, refundAmount, 1, 0); // If send failed, let advertiser withdraw via advertiserPendingWithdrawals advertiserPendingWithdrawals[_creative.advertiser] = advertiserPendingWithdrawals[_creative.advertiser].add(refundAmount); } else { emit LogRefundCreative(_creative.creativeId, _creative.advertiser, refundAmount, 1, 1); } } /** * @dev End creative * @param creativeId The creative ID to be removed */ function _endCreative(bytes32 creativeId) internal { Creative storage _creative = creatives[creativeId]; require (_creative.position > -1 && _creative.createdOn > 0); if (_creative.approved == false) { _removePending(creativeId); _refundPending(creativeId); } else { _removeApproved(creativeId); _refundApproved(creativeId); } } /** * @dev Remove element in pending creatives array * @param creativeId The creative ID to be removed */ function _removePending(bytes32 creativeId) internal { Creative storage _creative = creatives[creativeId]; uint256 pendingCount = pendingCreativePosition[_creative.creativeTypeId].length; if (_creative.position >= int256(pendingCount)) return; for (uint256 i = uint256(_creative.position); i < pendingCount-1; i++){ pendingCreativePosition[_creative.creativeTypeId][i] = pendingCreativePosition[_creative.creativeTypeId][i+1]; creatives[pendingCreativePosition[_creative.creativeTypeId][i]].position = int256(i); } _creative.position = -1; delete pendingCreativePosition[_creative.creativeTypeId][pendingCount-1]; pendingCreativePosition[_creative.creativeTypeId].length--; } /** * @dev Remove element in approved creatives array * @param creativeId The creative ID to be removed */ function _removeApproved(bytes32 creativeId) internal { Creative storage _creative = creatives[creativeId]; uint256 approvedCount = approvedCreativePosition[_creative.creativeTypeId].length; if (_creative.position >= int256(approvedCount)) return; for (uint256 i = uint256(_creative.position); i < approvedCount-1; i++){ approvedCreativePosition[_creative.creativeTypeId][i] = approvedCreativePosition[_creative.creativeTypeId][i+1]; creatives[approvedCreativePosition[_creative.creativeTypeId][i]].position = int256(i); } _creative.position = -1; delete approvedCreativePosition[_creative.creativeTypeId][approvedCount-1]; approvedCreativePosition[_creative.creativeTypeId].length--; } }