Jim's
Tutorials

Spring 2019
course
site

Contract updates

Added some functionality to create games that are best of X and to set a marker when the contract is complete rather than deleting the contract.

pragma solidity ^0.4.17;

contract RockPaperScissorsFactory {
    address[] public deployedRockPaperScissors;

    function createRockPaperScissors(uint wager, string gameName, uint numberOfGames) public {
        address newRockPaperScissors = new RockPaperScissors(wager, msg.sender, gameName, numberOfGames);
        deployedRockPaperScissors.push(newRockPaperScissors);
    }

    function getDeployedRockPaperScissors() public view returns(address[]) {
        return deployedRockPaperScissors;
    }
}

contract RockPaperScissors {

    struct Game {
        string title;
        uint wager;
        address playerOne;
        address playerTwo;
        uint games;
        uint playerOneMove;
        uint playerTwoMove;
        uint playerOneWinCount;
        uint playerTwoWinCount;
        address winner;
        bool completed;
    }

    address public manager;
    uint public gameWager;
    string public gameName;
    Game public game;
    uint bestOfX;


     function RockPaperScissors(uint wager, address creator, string name, uint bestOf) public {
        manager = creator;
        gameWager = wager;
        gameName = name;
        bestOfX = bestOf;
    }

    function createGame() public payable{
        require(msg.sender == manager);
        require(msg.value == gameWager);
        Game memory newGame = Game({
            title: gameName,
            wager: gameWager,
            playerOne: manager,
            playerTwo: 0,
            games: bestOfX,
            playerOneMove: 0,
            playerTwoMove: 0,
            playerOneWinCount: 0,
            playerTwoWinCount: 0,
            winner: 0,
            completed: false
        });
        game = newGame;
    }

    function joinGame() public payable{
        require(msg.value == gameWager);
        game.playerTwo = msg.sender;
    }

    function playerOneMove(uint move) public{
        require(msg.sender == game.playerOne);
        require(move == 1 || move == 2 || move == 3);
        require(game.playerOneMove == 0);
        game.playerOneMove = move;
        if(game.playerTwoMove != 0){
            selectWinner();
        }
    }

    function playerTwoMove(uint move) public {
        require(msg.sender == game.playerTwo);
        require(move == 1 || move == 2 || move == 3);
        require(game.playerTwoMove == 0);
        game.playerTwoMove = move;
        if(game.playerOneMove != 0){
            selectWinner();
        }
    }

    function selectWinner() private {
        if(game.playerOneMove == game.playerTwoMove){
            game.playerOneMove = 0;
            game.playerTwoMove = 0;
        }
        else if(game.playerOneMove == 1 && game.playerTwoMove == 2){
            game.playerOneWinCount += 1;
        }
        else if(game.playerOneMove == 1 && game.playerTwoMove == 3){
            game.playerTwoWinCount += 1;
        }
        else if(game.playerOneMove == 2 && game.playerTwoMove == 1){
            game.playerTwoWinCount += 1;
        }
        else if(game.playerOneMove == 2 && game.playerTwoMove == 3){
            game.playerOneWinCount += 1;
        }
        else if(game.playerOneMove == 3 && game.playerTwoMove == 2){
            game.playerTwoWinCount += 1;
        }
        else if(game.playerOneMove == 3 && game.playerTwoMove == 1){
            game.playerTwoWinCount += 1;
        }
        if(game.playerOneWinCount == game.games / 2 + 1){
            game.winner = game.playerOne;
        } else {
            game.playerOneMove = 0;
            game.playerTwoMove = 0;
        }
        if(game.playerTwoWinCount == game.games / 2 +1){
            game.winner = game.playerTwo;
        } else {
            game.playerOneMove = 0;
            game.playerTwoMove = 0;
        }
        if(game.winner != 0){
            game.winner.transfer(this.balance);
            game.completed = true;
        }
    }
}

specific things added:

Contract tests!

const assert = require('assert');
const ganache = require('ganache-cli');
const Web3 = require('web3');
const web3 = new Web3(ganache.provider());

const compiledFactory = require('../etherium/build/RockPaperScissorsFactory.json')
const compiledrockPaperScissors = require('../etherium/build/RockPaperScissors.json')

let accounts;
let factory;
let rockPaperScissors;
let game;

beforeEach(async () => {
  accounts = await web3.eth.getAccounts();

  factory = await new web3.eth.Contract(JSON.parse(compiledFactory.interface))
    .deploy({ data: compiledFactory.bytecode })
    .send({ from: accounts[0], gas: '5000000' });

  await factory.methods.createRockPaperScissors(10000, 'game one', 3).send({
    from: accounts[0],
    gas: '5000000'
  });

  [rockPaperScissors] = await factory.methods.getDeployedRockPaperScissors().call();
  game = await new web3.eth.Contract(
    JSON.parse(compiledrockPaperScissors.interface),
    rockPaperScissors
  );

  await game.methods.createGame().send({
    value: 10000,
    gas: 500000,
    from: accounts[0]
  });

  await game.methods.joinGame().send({
    value: 10000,
    gas: 100000,
    from: accounts[1]
  })
});

describe('rockPaperScissors', () => {
  it('deploys a factory and a game', () => {
    assert.ok(factory.options.address);
    assert.ok(game.options.address);
  });

  it('marks contract creator as playerOne', async () => {
    const gameOne = await game.methods.game().call()
    assert.equal(accounts[0], gameOne.playerOne);
  });

  it('marks the first person to join a game as playerTwo', async () => {
    const gameOne = await game.methods.game().call();
    assert.equal(accounts[1], gameOne.playerTwo);
  });

  it('picks the proper winner if playerone and playertwo choose 1 and 2', async () => {
    await game.methods.playerOneMove(1).send({
      gas: 100000,
      from: accounts[0]
    })
    await game.methods.playerTwoMove(2).send({
      gas: 100000,
      from: accounts[1]
    })
    const gameOne = await game.methods.game().call();
    assert.equal(0, gameOne.playerTwoWinCount);
    assert.equal(1, gameOne.playerOneWinCount);
    assert.equal(0, gameOne.winner);
    assert.equal(false, gameOne.completed)
  })

  it('picks the proper winner after player 1 wins twice and sets completed true', async () => {
    await game.methods.playerOneMove(1).send({
      gas: 100000,
      from: accounts[0]
    })
    await game.methods.playerTwoMove(2).send({
      gas: 100000,
      from: accounts[1]
    })
    await game.methods.playerOneMove(1).send({
      gas: 100000,
      from: accounts[0]
    })
    await game.methods.playerTwoMove(2).send({
      gas: 90000,
      from: accounts[1]
    })
    const gameOne = await game.methods.game().call();
    assert.equal(accounts[0], gameOne.winner)
    assert.equal(true, gameOne.completed)
  })
});

The general outline of these tests is that we are using ganache to create a local etherium blockchain, then using web3 to create an array of fake accounts and interact with our local blockchain, our contracts and our fake accounts. These lines import the Web3 and ganache libraries:

const ganache = require('ganache-cli');
const Web3 = require('web3');

The first line of code in our tests is a beforeEach loop that declares some actions that happen before each one of our tests take place.

beforeEach(async () => {
  accounts = await web3.eth.getAccounts();

  factory = await new web3.eth.Contract(JSON.parse(compiledFactory.interface))
    .deploy({ data: compiledFactory.bytecode })
    .send({ from: accounts[0], gas: '5000000' });

  await factory.methods.createRockPaperScissors(10000, 'game one', 3).send({
    from: accounts[0],
    gas: '5000000'
  });

accounts will be an array of fake accounts. factory will be a deployed instance of our factory contract, we have to send along gas to deploy it from the first account in our array of fake accounts. We we also be deploying a rockpaperscissors contract by calling the createRockPaperScissors method from our factory contract with the proper arguments, a bet limit denoted in wei, the title of the game, and how many matches, in this case the game will be a best of 3. Again we must send gas from the first account to run the code on the blockchain.

[rockPaperScissors] = await factory.methods.getDeployedRockPaperScissors().call();
game = await new web3.eth.Contract(
    JSON.parse(compiledrockPaperScissors.interface),
    rockPaperScissors
  );

  await game.methods.createGame().send({
    value: 10000,
    gas: 500000,
    from: accounts[0]
  });

  await game.methods.joinGame().send({
    value: 10000,
    gas: 100000,
    from: accounts[1]
  })
});

The first line of code establishes an array of deployed RockPaperScissors contracts, in this case just one. The next lines establish the variable game as a deployed instance of a rockPaperScissors contract. The next two blocks create a new game of rockpaperscissors from the owner of the contract accounts[0], then join the game from a new account, account[1]. These actions are then tested later and have to take place before we can test things like establishing a winner and making moves in a game.

describe('rockPaperScissors', () => {
  it('deploys a factory and a game', () => {
    assert.ok(factory.options.address);
    assert.ok(game.options.address);
  });

  it('marks contract creator as playerOne', async () => {
    const gameOne = await game.methods.game().call()
    assert.equal(accounts[0], gameOne.playerOne);
  });

  it('marks the first person to join a game as playerTwo', async () => {
    const gameOne = await game.methods.game().call();
    assert.equal(accounts[1], gameOne.playerTwo);
  });

  it('picks the proper winner if playerone and playertwo choose 1 and 2', async () => {
    await game.methods.playerOneMove(1).send({
      gas: 100000,
      from: accounts[0]
    })
    await game.methods.playerTwoMove(2).send({
      gas: 100000,
      from: accounts[1]
    })
    const gameOne = await game.methods.game().call();
    assert.equal(0, gameOne.playerTwoWinCount);
    assert.equal(1, gameOne.playerOneWinCount);
    assert.equal(0, gameOne.winner);
    assert.equal(false, gameOne.completed)
  })

  it('picks the proper winner after player 1 wins twice and sets completed true', async () => {
    await game.methods.playerOneMove(1).send({
      gas: 100000,
      from: accounts[0]
    })
    await game.methods.playerTwoMove(2).send({
      gas: 100000,
      from: accounts[1]
    })
    await game.methods.playerOneMove(1).send({
      gas: 100000,
      from: accounts[0]
    })
    await game.methods.playerTwoMove(2).send({
      gas: 90000,
      from: accounts[1]
    })
    const gameOne = await game.methods.game().call();
    assert.equal(accounts[0], gameOne.winner)
    assert.equal(true, gameOne.completed)
  })
});

The last section of my tests test the things in the describe blocks. Namely that it deploys a factory and game contract, it makes the creator of the contract as player one, it marks the person to join a game as player two, it picks the proper winner based on chosen moves, it picks the proper winner after one player wins two games.

Deploy script

The below script is code to deploy my rockpaperscissors factory contract to the rinkeby test network. This functions exactly like the main etherium network except getting ether for exchange on the network is free. I used some tools to do this, they are the Web3.js library (like always), and the truffle HD wallet provider to make transactions on the network. There are different ways to upload a contract to the blockchain. The most basic would be to host an entire blockchain node on your local machine and upload a contract, that would then proliferate through the blockchain. What I did was to hook up to the infura network, which is essentially just hosting their own node and deploying stuff for you.

const HDWalletProvider = require('truffle-hdwallet-provider');
const Web3 = require('web3');
const compiledFactory = require('./build/RockPaperScissorsFactory.json')

const provider = new HDWalletProvider(
  'robot april fatigue siren blade unfold normal minimum mercy series obvious bread',
  'https://rinkeby.infura.io/v3/9319195d62034106b0e7b86277444e17'
);
const web3 = new Web3(provider)

const deploy = async () => {
  const accounts = await web3.eth.getAccounts();

  console.log('attempting to deploy from account', accounts[0])

  const result = await new web3.eth.Contract(JSON.parse(compiledFactory.interface))
      .deploy({ data: compiledFactory.bytecode })
      .send({ gas: '5000000', from: accounts[0] });
  console.log('contract deployed to', result.options.address);
};
deploy();