Skip to content

Commit

Permalink
Dice game using future randao
Browse files Browse the repository at this point in the history
  • Loading branch information
damianmarti committed Nov 22, 2023
1 parent cb3bef2 commit 3cb1370
Show file tree
Hide file tree
Showing 12 changed files with 1,219 additions and 155 deletions.
111 changes: 111 additions & 0 deletions packages/hardhat/contracts/DiceGame.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import "./RLPReader.sol";

contract DiceGame {
using RLPReader for RLPReader.RLPItem;
using RLPReader for bytes;

struct BetStruct {
uint8 number;
uint256 blockNumber;
bool rolled;
uint8 rolledNumber;
}

mapping(address => BetStruct) public bets;

uint256 public constant futureBlocks = 2;
uint256 public constant betValue = 0.001 ether;
uint256 public constant prize = 0.015 ether;

event Bet(
address indexed player,
uint256 indexed blockNumber,
uint8 number
);
event Roll(
address indexed player,
uint256 indexed blockNumber,
uint8 number
);
event Winner(
address indexed winner,
uint256 indexed blockNumber,
uint8 number
);

constructor() payable {}

function bet(uint8 _number) public payable {
require(msg.value >= betValue, "Failed to send enough value");

require(_number < 16, "Number must be smaller than 16");

BetStruct storage userBet = bets[msg.sender];

require(
userBet.blockNumber < block.number - futureBlocks,
"Already played"
);

userBet.blockNumber = block.number;
userBet.number = _number;
userBet.rolled = false;

emit Bet(msg.sender, block.number, _number);
}

function rollTheDice(bytes memory rlpBytes) public {
BetStruct storage userBet = bets[msg.sender];

require(userBet.blockNumber > 0, "No played");
require(!userBet.rolled, "Already rolled");
require(
block.number >= userBet.blockNumber + futureBlocks,
"Future block not reached"
);
require(
block.number < userBet.blockNumber + futureBlocks + 256,
"You miss the roll window"
);

RLPReader.RLPItem[] memory ls = rlpBytes.toRlpItem().toList();

// uint256 difficulty = ls[7].toUint();
// we have to use mixHash on PoS networks -> https://eips.ethereum.org/EIPS/eip-4399
bytes memory difficulty = ls[13].toBytes();

uint256 blockNumber = ls[8].toUint();

require(
blockNumber == userBet.blockNumber + futureBlocks,
"Wrong block"
);

require(
blockhash(blockNumber) == keccak256(rlpBytes),
"Wrong block header"
);

bytes32 hash = keccak256(
abi.encodePacked(difficulty, address(this), msg.sender)
);
uint8 roll = uint8(uint256(hash) % 16);

userBet.rolled = true;
userBet.rolledNumber = roll;

emit Roll(msg.sender, userBet.blockNumber, roll);

if (roll == userBet.number) {
(bool sent, ) = address(msg.sender).call{ value: prize }("");
require(sent, "Failed to send Ether");

emit Winner(msg.sender, userBet.blockNumber, roll);
}
}

receive() external payable {}
}
234 changes: 234 additions & 0 deletions packages/hardhat/contracts/RLPReader.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
// SPDX-License-Identifier: Apache-2.0

/*
* @author Hamdi Allam [email protected]
* Please reach out with any questions or concerns
*/
pragma solidity 0.8.17;

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) {
uint memPtr;
assembly {
memPtr := add(item, 0x20)
}

return RLPItem(item.length, memPtr);
}

/*
* @param the RLP item.
*/
function rlpLen(RLPItem memory item) internal pure returns (uint) {
return item.len;
}

/*
* @param the RLP item.
* @return (memPtr, len) pair: location of the item's payload in memory.
*/
function payloadLocation(
RLPItem memory item
) internal pure returns (uint, uint) {
uint offset = _payloadOffset(item.memPtr);
uint memPtr = item.memPtr + offset;
uint len = item.len - offset; // data length
return (memPtr, len);
}

/*
* @param the RLP item.
*/
function payloadLen(RLPItem memory item) internal pure returns (uint) {
(, uint len) = payloadLocation(item);
return len;
}

/*
* @param the RLP item containing the encoded list.
*/
function toList(
RLPItem memory item
) internal pure returns (RLPItem[] memory) {
require(isList(item));

uint items = numItems(item);
RLPItem[] memory 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;
}

return result;
}

// @return indicator whether encoded payload is a list. negate this function call for isData.
function isList(RLPItem memory item) internal pure returns (bool) {
if (item.len == 0) return false;

uint8 byte0;
uint memPtr = item.memPtr;
assembly {
byte0 := byte(0, mload(memPtr))
}

if (byte0 < LIST_SHORT_START) return false;
return true;
}

/** RLPItem conversions into data types **/

function toUint(RLPItem memory item) internal pure returns (uint) {
require(item.len > 0 && item.len <= 33);

(uint memPtr, uint len) = payloadLocation(item);

uint result;
assembly {
result := mload(memPtr)

// shfit to the correct location if neccesary
if lt(len, 32) {
result := div(result, exp(256, sub(32, len)))
}
}

return result;
}

function toBytes(RLPItem memory item) internal pure returns (bytes memory) {
require(item.len > 0);

(uint memPtr, uint len) = payloadLocation(item);
bytes memory result = new bytes(len);

uint destPtr;
assembly {
destPtr := add(0x20, result)
}

copy(memPtr, destPtr, len);
return result;
}

/*
* Private Helpers
*/

// @return number of payload items inside an encoded list.
function numItems(RLPItem memory item) private pure returns (uint) {
if (item.len == 0) return 0;

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) private pure returns (uint) {
uint itemLen;
uint byte0;
assembly {
byte0 := byte(0, mload(memPtr))
}

if (byte0 < STRING_SHORT_START) itemLen = 1;
else if (byte0 < STRING_LONG_START)
itemLen = 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
itemLen := add(dataLen, add(byteLen, 1))
}
} else if (byte0 < LIST_LONG_START) {
itemLen = 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
itemLen := add(dataLen, add(byteLen, 1))
}
}

return itemLen;
}

// @return number of bytes until the data
function _payloadOffset(uint memPtr) private 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;
}

/*
* @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) private pure {
if (len == 0) return;

// 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;
}

if (len > 0) {
// 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))
}
}
}
}
Loading

0 comments on commit 3cb1370

Please sign in to comment.