Transaction Hash:
Block:
7231268 at Feb-17-2019 09:01:22 AM +UTC
Transaction Fee:
0.000237944 ETH
$0.47
Gas Used:
59,486 Gas / 4 Gwei
Emitted Events:
| 43 |
0x3029b0d5e022d19f7c869e9d55449b43b8f8754d.0x014952e2ce3b8fa83537611793c762bcbc39f3ab3d68b71721aa4ffdada2fb3e( 0x014952e2ce3b8fa83537611793c762bcbc39f3ab3d68b71721aa4ffdada2fb3e, 0x0000000000000000000000001e7c3581d7dab8025659cf0c46bbe88ff4f94c19, 0x000000000000000000000000829bd824b016326a401d083b33d092293333a830, 00000000000000000000000000000000000000000000000000038d7ea4c68000 )
|
| 44 |
ClashHash.RoundBetAdded( blockNumber=7233000, user=[Sender] 0x1e7c3581d7dab8025659cf0c46bbe88ff4f94c19, option=0x829BD824...93333A830, value=1000000000000000 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x1e7C3581...Ff4F94c19 |
0.011640800421171934 Eth
Nonce: 45
|
0.010402856421171934 Eth
Nonce: 46
| 0.001237944 | ||
| 0x3029b0D5...3b8F8754d | 0.04 Eth | 0.041 Eth | 0.001 | ||
|
0x52bc44d5...b7d7bE3b5
Miner
| (Nanopool) | 9,060.820077104532567254 Eth | 9,060.820315048532567254 Eth | 0.000237944 | |
| 0xaE331855...02caEE424 |
Execution Trace
ETH 0.001
ClashHash.addBet( blockNumber=7233000, option=0x829BD824B016326A401d083B33D092293333A830 )
- ETH 0.001
0x3029b0d5e022d19f7c869e9d55449b43b8f8754d.12dd9b94( )
addBet[ClashHash (ln:618)]
sub[ClashHash (ln:620)]RoundCreated[ClashHash (ln:625)]add[ClashHash (ln:629)]value[ClashHash (ln:630)]RoundBetAdded[ClashHash (ln:632)]
pragma solidity ^0.4.24;
// File: openzeppelin-solidity/contracts/math/SafeMath.sol
/**
* @title SafeMath
* @dev Math operations with safety checks that revert on error
*/
library SafeMath {
/**
* @dev Multiplies two numbers, reverts on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring '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;
}
uint256 c = a * b;
require(c / a == b);
return c;
}
/**
* @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0); // Solidity only automatically asserts 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;
}
/**
* @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a);
uint256 c = a - b;
return c;
}
/**
* @dev Adds two numbers, reverts on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a);
return c;
}
/**
* @dev Divides two numbers and returns the remainder (unsigned integer modulo),
* reverts when dividing by zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0);
return a % b;
}
}
// File: openzeppelin-solidity/contracts/ownership/Ownable.sol
/**
* @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: solidity-rlp/contracts/RLPReader.sol
/*
* @author Hamdi Allam hamdi.allam97@gmail.com
* Please reach out with any questions or concerns
*/
pragma solidity ^0.4.24;
library RLPReader {
uint8 constant STRING_SHORT_START = 0x80;
uint8 constant STRING_LONG_START = 0xb8;
uint8 constant LIST_SHORT_START = 0xc0;
uint8 constant LIST_LONG_START = 0xf8;
uint8 constant WORD_SIZE = 32;
struct RLPItem {
uint len;
uint memPtr;
}
/*
* @param item RLP encoded bytes
*/
function toRlpItem(bytes memory item) internal pure returns (RLPItem memory) {
if (item.length == 0)
return RLPItem(0, 0);
uint memPtr;
assembly {
memPtr := add(item, 0x20)
}
return RLPItem(item.length, memPtr);
}
/*
* @param item RLP encoded list in bytes
*/
function toList(RLPItem memory item) internal pure returns (RLPItem[] memory result) {
require(isList(item));
uint items = numItems(item);
result = new RLPItem[](items);
uint memPtr = item.memPtr + _payloadOffset(item.memPtr);
uint dataLen;
for (uint i = 0; i < items; i++) {
dataLen = _itemLength(memPtr);
result[i] = RLPItem(dataLen, memPtr);
memPtr = memPtr + dataLen;
}
}
/*
* Helpers
*/
// @return indicator whether encoded payload is a list. negate this function call for isData.
function isList(RLPItem memory item) internal pure returns (bool) {
uint8 byte0;
uint memPtr = item.memPtr;
assembly {
byte0 := byte(0, mload(memPtr))
}
if (byte0 < LIST_SHORT_START)
return false;
return true;
}
// @return number of payload items inside an encoded list.
function numItems(RLPItem memory item) internal pure returns (uint) {
uint count = 0;
uint currPtr = item.memPtr + _payloadOffset(item.memPtr);
uint endPtr = item.memPtr + item.len;
while (currPtr < endPtr) {
currPtr = currPtr + _itemLength(currPtr); // skip over an item
count++;
}
return count;
}
// @return entire rlp item byte length
function _itemLength(uint memPtr) internal pure returns (uint len) {
uint byte0;
assembly {
byte0 := byte(0, mload(memPtr))
}
if (byte0 < STRING_SHORT_START)
return 1;
else if (byte0 < STRING_LONG_START)
return byte0 - STRING_SHORT_START + 1;
else if (byte0 < LIST_SHORT_START) {
assembly {
let byteLen := sub(byte0, 0xb7) // # of bytes the actual length is
memPtr := add(memPtr, 1) // skip over the first byte
/* 32 byte word size */
let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to get the len
len := add(dataLen, add(byteLen, 1))
}
}
else if (byte0 < LIST_LONG_START) {
return byte0 - LIST_SHORT_START + 1;
}
else {
assembly {
let byteLen := sub(byte0, 0xf7)
memPtr := add(memPtr, 1)
let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to the correct length
len := add(dataLen, add(byteLen, 1))
}
}
}
// @return number of bytes until the data
function _payloadOffset(uint memPtr) internal pure returns (uint) {
uint byte0;
assembly {
byte0 := byte(0, mload(memPtr))
}
if (byte0 < STRING_SHORT_START)
return 0;
else if (byte0 < STRING_LONG_START || (byte0 >= LIST_SHORT_START && byte0 < LIST_LONG_START))
return 1;
else if (byte0 < LIST_SHORT_START) // being explicit
return byte0 - (STRING_LONG_START - 1) + 1;
else
return byte0 - (LIST_LONG_START - 1) + 1;
}
/** RLPItem conversions into data types **/
// @returns raw rlp encoding in bytes
function toRlpBytes(RLPItem memory item) internal pure returns (bytes) {
bytes memory result = new bytes(item.len);
uint ptr;
assembly {
ptr := add(0x20, result)
}
copy(item.memPtr, ptr, item.len);
return result;
}
function toBoolean(RLPItem memory item) internal pure returns (bool) {
require(item.len == 1, "Invalid RLPItem. Booleans are encoded in 1 byte");
uint result;
uint memPtr = item.memPtr;
assembly {
result := byte(0, mload(memPtr))
}
return result == 0 ? false : true;
}
function toAddress(RLPItem memory item) internal pure returns (address) {
// 1 byte for the length prefix according to RLP spec
require(item.len <= 21, "Invalid RLPItem. Addresses are encoded in 20 bytes or less");
return address(toUint(item));
}
function toUint(RLPItem memory item) internal pure returns (uint) {
uint offset = _payloadOffset(item.memPtr);
uint len = item.len - offset;
uint memPtr = item.memPtr + offset;
uint result;
assembly {
result := div(mload(memPtr), exp(256, sub(32, len))) // shift to the correct location
}
return result;
}
function toBytes(RLPItem memory item) internal pure returns (bytes) {
uint offset = _payloadOffset(item.memPtr);
uint len = item.len - offset; // data length
bytes memory result = new bytes(len);
uint destPtr;
assembly {
destPtr := add(0x20, result)
}
copy(item.memPtr + offset, destPtr, len);
return result;
}
/*
* @param src Pointer to source
* @param dest Pointer to destination
* @param len Amount of memory to copy from the source
*/
function copy(uint src, uint dest, uint len) internal pure {
// copy as many word sizes as possible
for (; len >= WORD_SIZE; len -= WORD_SIZE) {
assembly {
mstore(dest, mload(src))
}
src += WORD_SIZE;
dest += WORD_SIZE;
}
// left over bytes. Mask is used to remove unwanted bytes from the word
uint mask = 256 ** (WORD_SIZE - len) - 1;
assembly {
let srcpart := and(mload(src), not(mask)) // zero out src
let destpart := and(mload(dest), mask) // retrieve the bytes
mstore(dest, or(destpart, srcpart))
}
}
}
// File: contracts/BetStorage.sol
/**
* @title ClashHash
* This product is protected under license. Any unauthorized copy, modification, or use without
* express written consent from the creators is prohibited.
*/
contract BetStorage is Ownable {
using SafeMath for uint256;
mapping(address => mapping(address => uint256)) public bets;
mapping(address => uint256) public betsSumByOption;
address public wonOption;
event BetAdded(address indexed user, address indexed option, uint256 value);
event Finalized(address indexed option);
event RewardClaimed(address indexed user, uint256 reward);
function addBet(address user, address option) public payable onlyOwner {
require(msg.value > 0, "Empty bet is not allowed");
require(option != address(0), "Option should not be zero");
bets[user][option] = bets[user][option].add(msg.value);
betsSumByOption[option] = betsSumByOption[option].add(msg.value);
emit BetAdded(user, option, msg.value);
}
function finalize(address option, address admin) public onlyOwner {
require(wonOption == address(0), "Finalization could be called only once");
require(option != address(0), "Won option should not be zero");
wonOption = option;
emit Finalized(option);
if (betsSumByOption[option] == 0) {
selfdestruct(admin);
}
}
function rewardFor(address user) public view returns(uint256 reward) {
if (bets[user][wonOption] > 0) {
reward = address(this).balance
.mul(bets[user][wonOption])
.div(betsSumByOption[wonOption]);
}
}
function rewards(
address user,
address referrer,
uint256 referrerFee,
uint256 adminFee
)
public
view
returns(uint256 userReward, uint256 referrerReward, uint256 adminReward)
{
userReward = rewardFor(user);
adminReward = userReward.sub(bets[user][wonOption]).mul(adminFee).div(100);
if (referrer != address(0)) {
referrerReward = adminReward.mul(referrerFee).div(100);
adminReward = adminReward.sub(referrerReward);
}
userReward = userReward.sub(adminReward).sub(referrerReward);
}
function claimReward(
address user,
address admin,
uint256 adminFee,
address referrer,
uint256 referrerFee
)
public
onlyOwner
{
require(wonOption != address(0), "Round not yet finalized");
(uint256 userReward, uint256 referrerReward, uint256 adminReward) = rewards(
user,
referrer,
referrerFee,
adminFee
);
require(userReward > 0, "Reward was claimed previously or never existed");
betsSumByOption[wonOption] = betsSumByOption[wonOption].sub(bets[user][wonOption]);
bets[user][wonOption] = 0;
if (referrerReward > 0) {
referrer.send(referrerReward);
}
if (adminReward > 0) {
admin.send(adminReward);
}
user.transfer(userReward);
emit RewardClaimed(user, userReward);
if (betsSumByOption[wonOption] == 0) {
selfdestruct(admin);
}
}
}
// File: contracts/BlockHash.sol
contract BlockHash {
using SafeMath for uint256;
using RLPReader for RLPReader.RLPItem;
mapping (uint256 => bytes32) private _hashes;
function blockhashes(
uint256 blockNumber
)
public
view
returns(bytes32)
{
if (blockNumber >= block.number.sub(256)) {
return blockhash(blockNumber);
}
return _hashes[blockNumber];
}
function addBlocks(
uint256 blockNumber,
bytes blocksData,
uint256[] starts
)
public
{
require(starts.length > 0 && starts[starts.length - 1] == blocksData.length, "Wrong starts argument");
bytes32 expectedHash = blockhashes(blockNumber);
for (uint i = 0; i < starts.length - 1; i++) {
uint256 offset = starts[i];
uint256 length = starts[i + 1].sub(starts[i]);
bytes32 result;
uint256 ptr;
assembly {
ptr := add(add(blocksData, 0x20), offset)
result := keccak256(ptr, length)
}
require(result == expectedHash, "Blockhash didn't match");
expectedHash = bytes32(RLPReader.RLPItem({len: length, memPtr: ptr}).toList()[0].toUint());
}
uint256 index = blockNumber.add(1).sub(starts.length);
if (_hashes[index] == 0) {
_hashes[index] = expectedHash;
}
}
}
// File: contracts/ClashHash.sol
/**
* @title ClashHash
* This product is protected under license. Any unauthorized copy, modification, or use without
* express written consent from the creators is prohibited.
*/
contract ClashHash is Ownable {
using SafeMath for uint256;
using RLPReader for bytes;
using RLPReader for RLPReader.RLPItem;
struct Round {
BetStorage records;
uint256 betsCount;
uint256 totalReward;
address winner;
}
uint256 public minBet = 0.001 ether;
uint256 constant public MIN_BLOCKS_BEFORE_ROUND = 10;
uint256 constant public MIN_BLOCKS_AFTER_ROUND = 10;
uint256 constant public MAX_BLOCKS_AFTER_ROUND = 256;
uint256 public adminFee = 5;
uint256 public referrerFee = 50;
mapping(address => address) public referrers;
mapping(uint256 => Round) public rounds;
BlockHash public _blockStorage;
//
event RoundCreated(uint256 indexed blockNumber, address contractAddress);
event RoundBetAdded(uint256 indexed blockNumber, address indexed user, address indexed option, uint256 value);
event RoundFinalized(uint256 indexed blockNumber, address indexed option);
event RewardClaimed(uint256 indexed blockNumber, address indexed user, address indexed winner, uint256 reward);
event NewReferral(address indexed user, address indexed referrer);
event ReferralReward(address indexed user, address indexed referrer, uint256 value);
event AdminFeeUpdate(uint256 oldFee, uint256 newFee);
event ReferrerFeeUpdate(uint256 oldFee, uint256 newFee);
event MinBetUpdate(uint256 oldMinBet, uint256 newMinBet);
//
constructor (BlockHash blockStorage) public {
_blockStorage = blockStorage;
}
//
function setReferrerFee(uint256 newFee) public onlyOwner {
emit ReferrerFeeUpdate(referrerFee, newFee);
referrerFee = newFee;
}
function setAdminFee(uint256 newFee) public onlyOwner {
emit AdminFeeUpdate(adminFee, newFee);
adminFee = newFee;
}
function setMinBet(uint256 newMinBet) public onlyOwner {
emit MinBetUpdate(minBet, newMinBet);
minBet = newMinBet;
}
/**
* @param referrer Who has invited the user.
*/
function addReferral(address referrer) public {
require(referrer != address(0), "Invalid referrer address");
require(referrer != msg.sender, "Different addresses required");
require(referrers[msg.sender] == address(0), "User has referrer already");
referrers[msg.sender] = referrer;
emit NewReferral(msg.sender, referrer);
}
function addBet(uint256 blockNumber, address option) public payable {
require(msg.value >= minBet, "Bet amount is too low");
require(block.number <= blockNumber.sub(MIN_BLOCKS_BEFORE_ROUND), "It's too late");
Round storage round = rounds[blockNumber];
if (round.records == address(0)) {
round.records = new BetStorage();
emit RoundCreated(blockNumber, round.records);
}
round.betsCount += 1;
round.totalReward = round.totalReward.add(msg.value);
round.records.addBet.value(msg.value)(msg.sender, option);
emit RoundBetAdded(
blockNumber,
msg.sender,
option,
msg.value
);
}
function addBetWithReferrer(
uint256 blockNumber,
address option,
address referrer
)
public
payable
{
addReferral(referrer);
addBet(blockNumber, option);
}
function claimRewardWithBlockData(uint256 blockNumber, bytes blockData) public {
if (blockData.length > 0 && rounds[blockNumber].winner == address(0)) {
addBlockData(blockNumber, blockData);
}
claimRewardForUser(blockNumber, msg.sender);
}
function claimRewardForUser(uint256 blockNumber, address user) public {
Round storage round = rounds[blockNumber];
require(round.winner != address(0), "Round not yet finished");
require(address(round.records).balance > 0, "Round prizes are already distributed");
(uint256 userReward, uint256 referrerReward,) = round.records.rewards(
user,
referrers[user],
referrerFee,
adminFee
);
round.records.claimReward(user, owner(), adminFee, referrers[user], referrerFee);
emit RewardClaimed(blockNumber, user, round.winner, userReward);
if (referrerReward > 0) {
emit ReferralReward(user, referrers[user], referrerReward);
}
}
function addBlockData(uint256 blockNumber, bytes blockData) public {
Round storage round = rounds[blockNumber];
require(round.winner == address(0), "Winner was already submitted");
require(block.number <= blockNumber.add(MAX_BLOCKS_AFTER_ROUND), "It's too late, 256 blocks gone");
require(block.number >= blockNumber.add(MIN_BLOCKS_AFTER_ROUND), "Wait at least 10 blocks");
address blockBeneficiary = _readBlockBeneficiary(blockNumber, blockData);
round.winner = blockBeneficiary;
round.records.finalize(blockBeneficiary, owner());
emit RoundFinalized(blockNumber, blockBeneficiary);
}
function _readBlockBeneficiary(
uint256 blockNumber,
bytes blockData
)
internal
view
returns(address)
{
require(keccak256(blockData) == _blockStorage.blockhashes(blockNumber), "Block data isn't valid");
RLPReader.RLPItem[] memory items = blockData.toRlpItem().toList();
return items[2].toAddress();
}
}