generated from scaffold-eth/scaffold-eth-2
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
cb3bef2
commit 3cb1370
Showing
12 changed files
with
1,219 additions
and
155 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.