Skip to content

Instantly share code, notes, and snippets.

@Alonski
Created January 25, 2019 12:55
Show Gist options
  • Save Alonski/d494503e2b19f5645d5b388e47409f0c to your computer and use it in GitHub Desktop.
Save Alonski/d494503e2b19f5645d5b388e47409f0c to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.5.1+commit.c8a2cb62.js&optimize=false&gist=
pragma solidity 0.5;
pragma experimental "ABIEncoderV2";
import "./Transfer.sol";
/// @title High Roller App
/// @notice This contract allows the playing of a dice rolling game.
/// Two players take turns rolling two dice each.
/// The winner is the player whose sum of dice outcomes is highest.
/// @dev This contract is an example of a dApp built to run on
/// the CounterFactual framework
contract HighRollerApp {
enum ActionType {
START_GAME,
COMMIT_TO_HASH,
COMMIT_TO_NUM
}
enum Stage {
PRE_GAME,
COMMITTING_HASH,
COMMITTING_NUM,
DONE
}
enum Player {
FIRST,
SECOND
}
struct AppState {
address[2] playerAddrs;
Stage stage;
bytes32 salt;
bytes32 commitHash;
uint256 playerFirstNumber;
uint256 playerSecondNumber;
}
struct Action {
ActionType actionType;
uint256 number;
bytes32 actionHash;
}
function isStateTerminal(AppState memory state)
public
pure
returns (bool)
{
return state.stage == Stage.DONE;
}
function getTurnTaker(AppState memory state)
public
pure
returns (Player)
{
return state.stage == Stage.COMMITTING_NUM ? Player.SECOND : Player.FIRST;
}
function applyAction(AppState memory state, Action memory action)
public
pure
returns (bytes memory)
{
AppState memory nextState = state;
if (action.actionType == ActionType.START_GAME) {
require(
state.stage == Stage.PRE_GAME,
"Cannot apply START_GAME on PRE_GAME"
);
nextState.stage = Stage.COMMITTING_HASH;
} else if (action.actionType == ActionType.COMMIT_TO_HASH) {
require(
state.stage == Stage.COMMITTING_HASH,
"Cannot apply COMMIT_TO_HASH on COMMITTING_HASH"
);
nextState.stage = Stage.COMMITTING_NUM;
nextState.commitHash = action.actionHash;
} else if (action.actionType == ActionType.COMMIT_TO_NUM) {
require(
state.stage == Stage.COMMITTING_NUM,
"Cannot apply COMMITTING_NUM on COMMITTING_NUM"
);
nextState.stage = Stage.DONE;
nextState.playerSecondNumber = action.number;
} else {
revert("Invalid action type");
}
return abi.encode(nextState);
}
function resolve(AppState memory state, Transfer.Terms memory terms)
public
pure
returns (Transfer.Transaction memory)
{
uint256[] memory amounts = new uint256[](2);
address[] memory to = new address[](2);
to[0] = state.playerAddrs[0];
to[1] = state.playerAddrs[1];
bytes32 expectedCommitHash = keccak256(
abi.encodePacked(state.salt, state.playerFirstNumber)
);
if (expectedCommitHash == state.commitHash) {
amounts = getWinningAmounts(
state.playerFirstNumber, state.playerSecondNumber, terms.limit
);
} else {
amounts[0] = 0;
amounts[1] = terms.limit;
}
bytes[] memory data = new bytes[](2);
return Transfer.Transaction(
terms.assetType,
terms.token,
to,
amounts,
data
);
}
function getWinningAmounts(uint256 num1, uint256 num2, uint256 termsLimit)
public
pure
returns (uint256[] memory)
{
uint256[] memory amounts = new uint256[](2);
bytes32 randomSalt = calculateRandomSalt(num1, num2);
(uint8 playerFirstTotal, uint8 playerSecondTotal) = highRoller(randomSalt);
if (playerFirstTotal > playerSecondTotal) {
amounts[0] = termsLimit;
amounts[1] = 0;
} else if (playerFirstTotal < playerSecondTotal) {
amounts[0] = 0;
amounts[1] = termsLimit;
} else {
amounts[0] = termsLimit / 2;
amounts[1] = termsLimit / 2;
}
return amounts;
}
function highRoller(bytes32 randomness)
public
pure
returns(uint8 playerFirstTotal, uint8 playerSecondTotal)
{
(bytes8 hash1, bytes8 hash2,
bytes8 hash3, bytes8 hash4) = cutBytes32(randomness);
playerFirstTotal = bytes8toDiceRoll(hash1) + bytes8toDiceRoll(hash2);
playerSecondTotal = bytes8toDiceRoll(hash3) + bytes8toDiceRoll(hash4);
}
function calculateRandomSalt(uint256 num1, uint256 num2)
public
pure
returns (bytes32)
{
return keccak256(abi.encodePacked(num1 * num2));
}
/// @notice Splits a bytes32 into 4 bytes8 by cutting every 8 bytes
/// @param h The bytes32 to be split
/// @dev Takes advantage of implicitly recognizing the length of each bytes8
/// variable when being read by `mload`. We point to the start of each
/// string (e.g., 0x08, 0x10) by incrementing by 8 bytes each time.
function cutBytes32(bytes32 h)
public
pure
returns (bytes8 q1, bytes8 q2, bytes8 q3, bytes8 q4)
{
assembly {
let ptr := mload(0x40)
mstore(add(ptr, 0x00), h)
q1 := mload(add(ptr, 0x00))
q2 := mload(add(ptr, 0x08))
q3 := mload(add(ptr, 0x10))
q4 := mload(add(ptr, 0x18))
}
}
/// @notice Converts a bytes8 into a uint64 between 1-6
/// @param q The bytes8 to convert
/// @dev Splits this by using modulas 6 to get the uint
function bytes8toDiceRoll(bytes8 q)
public
pure
returns (uint8)
{
return uint8(uint64(q) % 6);
}
}
pragma solidity 0.5;
pragma experimental "ABIEncoderV2";
contract HighRollerApp {
function getWinningAmounts(uint256 num1, uint256 num2, uint256 termsLimit)
public
pure
returns (uint256[] memory)
{
uint256[] memory amounts = new uint256[](2);
bytes32 randomSalt = calculateRandomSalt(num1, num2);
(uint8 playerFirstTotal, uint8 playerSecondTotal) = highRoller(randomSalt);
if (playerFirstTotal > playerSecondTotal) {
amounts[0] = termsLimit;
amounts[1] = 0;
} else if (playerFirstTotal < playerSecondTotal) {
amounts[0] = 0;
amounts[1] = termsLimit;
} else {
amounts[0] = termsLimit / 2;
amounts[1] = termsLimit / 2;
}
return amounts;
}
function highRoller(bytes32 randomness)
public
pure
returns(uint8 playerFirstTotal, uint8 playerSecondTotal)
{
(bytes8 hash1, bytes8 hash2,
bytes8 hash3, bytes8 hash4) = cutBytes32(randomness);
playerFirstTotal = bytes8toDiceRoll(hash1) + bytes8toDiceRoll(hash2);
playerSecondTotal = bytes8toDiceRoll(hash3) + bytes8toDiceRoll(hash4);
}
function calculateRandomSalt(uint256 num1, uint256 num2)
public
pure
returns (bytes32)
{
return keccak256(abi.encodePacked(num1 * num2));
}
/// @notice Splits a bytes32 into 4 bytes8 by cutting every 8 bytes
/// @param h The bytes32 to be split
/// @dev Takes advantage of implicitly recognizing the length of each bytes8
/// variable when being read by `mload`. We point to the start of each
/// string (e.g., 0x08, 0x10) by incrementing by 8 bytes each time.
function cutBytes32(bytes32 h)
public
pure
returns (bytes8 q1, bytes8 q2, bytes8 q3, bytes8 q4)
{
assembly {
let ptr := mload(0x40)
mstore(add(ptr, 0x00), h)
q1 := mload(add(ptr, 0x00))
q2 := mload(add(ptr, 0x08))
q3 := mload(add(ptr, 0x10))
q4 := mload(add(ptr, 0x18))
}
}
/// @notice Converts a bytes8 into a uint64 between 1-6
/// @param q The bytes8 to convert
/// @dev Splits this by using modulas 6 to get the uint
function bytes8toDiceRoll(bytes8 q)
public
pure
returns (uint8)
{
return uint8(uint64(q) % 6);
}
}
pragma solidity 0.5;
pragma experimental "ABIEncoderV2";
import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";
/// @title Transfer - A library to encode a generic asset transfer data type
/// @author Liam Horne - <[email protected]>
/// @notice This library defines `Transfer.Transaction` and `Transfer.Terms`, two structures
/// which are used in state channel applications to represent a kind of "resolution" and
/// a commitment to how much can be resolved respectively. A `Transfer.Transaction` object
/// should be able to encode any arbitrary Ethereum-based asset transfer.
library Transfer {
enum Asset {
ETH,
ERC20
}
struct Terms {
uint8 assetType;
uint256 limit;
address token;
}
struct Transaction {
uint8 assetType;
address token;
address[] to;
uint256[] value;
bytes[] data;
}
/// @notice A delegate target for executing transfers of an arbitrary Transfer.Detail
/// @param txn A `Transfer.Transaction` struct
/// TODO: Add support for an OTHER Asset type and do a (to, value, data) CALL
function execute(Transfer.Transaction memory txn) public {
for (uint256 i = 0; i < txn.to.length; i++) {
address payable to = address(uint160(txn.to[i]));
uint256 value = txn.value[i];
if (txn.assetType == uint8(Transfer.Asset.ETH)) {
to.transfer(value);
} else if (txn.assetType == uint8(Transfer.Asset.ERC20)) {
require(
ERC20(txn.token).transfer(to, value),
"Execution of ERC20 Transaction failed."
);
}
}
}
/// @notice Verifies whether or not a `Transfer.Transaction` meets the terms set by a
/// `Transfer.Terms` object based on the limit information of how much can be transferred
/// @param txn A `Transfer.Transaction` struct
/// @return A boolean indicating if the terms are met
function meetsTerms(
Transfer.Transaction memory txn,
Transfer.Terms memory terms
)
public
pure
returns (bool)
{
uint256 sum = 0;
for (uint256 i = 0; i < txn.value.length; i++) {
sum += txn.value[i];
}
return sum <= terms.limit;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment