Created
January 25, 2019 12:55
-
-
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=
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
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); | |
} | |
} |
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
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); | |
} | |
} |
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
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