Programming Project - Phase 2

Hi @Capplequoppe,

awaiting your kind revert. I am really stuck and need your Help

Thanks and Regards

su.kal Crypto

Hi @dan-i, @filip @thecil @Capplequoppe @everyone , Can someone Please Help ??

I am really really stuck with this ā€œTransaction REVERTā€ issue for the last 10 days, and now i am frustrated and feel like giving up !
I have checked and rechecked but maybe I am just not getting the silly Logic Mistake I am making, so please please Help :pray: :pray: :pray: :pray: :pray:

Thanks and Regards
Su.Kal Crypto

1 Like

Hey @Su.kal.Crypto, hope you are well.

Sorry for the delay on the reply, could you please provide an screenshot of the issue? that can give us get a better clue on how to solve your issue.

This is the project right? If we download it we will face the same issue if you explain us how to replicate it?

If you have any more questions, please let us know so we can help you! :slight_smile:

Carlos Z.

1 Like

Hey guys @thecil @dan-i

Iā€™m having some issue with the ropsten truffle testnet setup.

Iā€™ve followed the instructions in the video, and upon using

truffle migrate --network ropsten
I received the error:
Error: Cannot find module ā€˜@truffle/hdwallet-providerā€™

So I followed the top post in this forum and used

npm install @truffle/hdwallet-provider

and upon trying again

truffle migrate --network ropsten

I receive:
Screen Shot 2021-03-26 at 1.15.03 PM

Also, I keep getting this error when trying to receive ETH from the faucet:
Screen Shot 2021-03-26 at 12.58.03 PM

Thanks for the help!

Hi

Iā€™ve successfully deployed the CoinToss contract onto ganache network and the dapp works as expected. Iā€™ve also successfully deployed the contract onto Ropsten network:

https://ropsten.etherscan.io/address/0xBa8587Ff871d264f10a69006F627592bBf2E9118

However the dapp fails when attempting to place a bet with error:

The method eth_sendTransaction does not exist/is not available

I think the issue lies that Infura does not support the sendTransaction function since they canā€™t sign the transaction as the private key isnā€™t available to them. That sounds reasonable but what extra steps need to be taken? I was expecting web3.js+metamask to take care of signing the transaction and therefore the code would be the same irrespective of the environment the dapp is deployed to?

I canā€™t see in the videos where you demonstrate a working dapp on the Ropsten network.

The call failing is when attempting to send a transaction to the bet(uint side) function

var config = {
        value: web3.utils.toWei(betSize, "ether")
    }
    contractInstance.methods.bet(side).send(config)
    .on("transactionHash", function(hash) {
        console.log(hash);
    })

Please ignore - I misconfigured the web3 provider.

Thanks

1 Like

Hi @mervxxgotti

Please share your migration file, also you truffle config.
Regarding the faucet use this one: https://faucet.dimensions.network/

Cheers,
Dani

1 Like

truffle-config.js

/**
 * Use this file to configure your truffle project. It's seeded with some
 * common settings for different networks and features like migrations,
 * compilation and testing. Uncomment the ones you need or modify
 * them to suit your project as necessary.
 *
 * More information about configuration can be found at:
 *
 * truffleframework.com/docs/advanced/configuration
 *
 * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider)
 * to sign your transactions before they're sent to a remote public node. Infura accounts
 * are available for free at: infura.io/register.
 *
 * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate
 * public/private key pairs. If you're publishing your code to GitHub make sure you load this
 * phrase from a file you've .gitignored so it doesn't accidentally become public.
 *
 */

const HDWalletProvider = require('@truffle/hdwallet-provider');
const infuraKey = "f442bc908a5e4689a34d94ce3ba716b9";
//
const fs = require('fs');
const mnemonic = fs.readFileSync(".secret").toString().trim();

module.exports = {
  /**
   * Networks define how you connect to your ethereum client and let you set the
   * defaults web3 uses to send transactions. If you don't specify one truffle
   * will spin up a development blockchain for you on port 9545 when you
   * run `develop` or `test`. You can ask a truffle command to use a specific
   * network from the command line, e.g
   *
   * $ truffle test --network <network-name>
   */

  networks: {
    // Useful for testing. The `development` name is special - truffle uses it by default
    // if it's defined here and no other network is specified at the command line.
    // You should run a client (like ganache-cli, geth or parity) in a separate terminal
    // tab if you use this network and you must also set the `host`, `port` and `network_id`
    // options below to some value.
    //
    ganache: {
    host: "127.0.0.1",     // Localhost (default: none)
    port: 7545,            // Standard Ethereum port (default: none)
    network_id: "5777",       // Any network (default: none)
    },

    // Another network with more advanced options...
    // advanced: {
      // port: 8777,             // Custom port
      // network_id: 1342,       // Custom network
      // gas: 8500000,           // Gas sent with each transaction (default: ~6700000)
      // gasPrice: 20000000000,  // 20 gwei (in wei) (default: 100 gwei)
      // from: <address>,        // Account to send txs from (default: accounts[0])
      // websockets: true        // Enable EventEmitter interface for web3 (default: false)
    // },

    // Useful for deploying to a public network.
    // NB: It's important to wrap the provider as a function.
    ropsten: {
      provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/83d1095ef3f9413dbe7bf4be8d6c003c`),
      network_id: 3,       // Ropsten's id
      gas: 5500000,        // Ropsten has a lower block limit than mainnet
      confirmations: 2,    // # of confs to wait between deployments. (default: 0)
      timeoutBlocks: 200,  // # of blocks before a deployment times out  (minimum/default: 50)
      skipDryRun: true     // Skip dry run before migrations? (default: false for public nets )
    },

    // Useful for private networks
    // private: {
      // provider: () => new HDWalletProvider(mnemonic, `https://network.io`),
      // network_id: 2111,   // This network is yours, in the cloud.
      // production: true    // Treats this network as if it was a public net. (default: false)
    // }
  },

  // Set default mocha options here, use special reporters etc.
  mocha: {
    // timeout: 100000
  },

  // Configure your compilers
  compilers: {
    solc: {
      version: "0.5.12",    // Fetch exact version from solc-bin (default: truffle's version)
      // docker: true,        // Use "0.5.1" you've installed locally with docker (default: false)
      // settings: {          // See the solidity docs for advice about optimization and evmVersion
      //  optimizer: {
      //    enabled: false,
      //    runs: 200
      //  },
      //  evmVersion: "byzantium"
      // }
    }
  }
}

1_initial_migrations.js

const Migrations = artifacts.require("Migrations");

module.exports = function(deployer) {
  deployer.deploy(Migrations);
};

hey @dan-i,

I noticed in the video that there was a discrepancy when uncommenting a line in the truffle-config file.

In mine, I uncommented:

const HDWalletProvider = require('@truffle/hdwallet-provider');

But in Filipā€™s video, I noticed the line was:

const HDWalletProvider = require('truffle-hdwallet-provider');

The migration worked after I changed my line to the one in the video. Why was it different? Is this a version issue?

Thanks!

1 Like

Hi @mervxxgotti

You have to use const HDWalletProvider = require('@truffle/hdwallet-provider'); as truffle-hdwallet-provider is deprecated.

Cheers,
Dani

1 Like

Hi @thecil
Sorry for my Late reply too. I was just trying and trying to read each line of code carefully and understand where I am going wrong. But unfortunately, I am just getting stuck on Events , because my events are not getting broadcasted and hence when i try to play the game again it shows the below message :point_down:
Screenshot 2021-03-29 at 7.50.06 PM

The first time I play the FlipCoin Game/ Bet with any Metamask account, the transaction goes through , but the next time it shows the above message :fu:

Now, I have tried tried many many many times and written different codes but I am still unable to wrap my head around this

Below is my Solidity Code :point_down:

pragma solidity 0.5.12;

import "./provableAPI.sol";
import { SafeMath } from "./SafeMath.sol";


contract Coinflip is usingProvable{

  using SafeMath for uint;

  //Events to listen for :
  event RandomNumberGenerated(uint randomNumber);
  event LogNewProvableQuery(address indexed player);
  event FlipResult(address indexed player, uint amountWon, bool won);
  event BalanceUpdated(address user, uint transferredAmt, uint newContractBalance);


  // Constructors and Modifiers :
  constructor() public {
    provable_setProof(proofType_Ledger);
    contractBalance = 0;
    contractOwner = msg.sender;
    }

  modifier onlyOwner() {
      require(msg.sender == contractOwner, "You are not the owner");
      _;
  }


  //addresses and other such state Variables:
  address public contractOwner;
  uint private contractBalance;
  address public contractAddress;
  uint private constant NUM_RANDOM_BYTES_REQUESTED = 1;

    struct Temp {
      bytes32 id;
      address playerAddress;
    }

    struct PlayerByAddress {
      address playerAddress;
      uint playerBalance;
      bool inGame;
      uint betAmount;
    }

    mapping(bytes32 => Temp) public temps;
    mapping(address => PlayerByAddress) public playersByAddress;

    //Flip the Coin and check whether user won or lost;
    function flipCoin() public payable  {
        require(msg.value <= getBalance().div(2) && msg.value > 0, "The contract doesnt have enough balance to play right now. Come Back later");
        require(_isPlaying(msg.sender) == false, "The User currently is in Game");

        playersByAddress[msg.sender].playerAddress = msg.sender;
        playersByAddress[msg.sender].betAmount = msg.value.sub(provable_getPrice("random")); // Contract keeps oracle's fee.
        playersByAddress[msg.sender].inGame = true;



        _update();
    }



      // @dev               Calls the oracle random function.
      //                    Sets Temp for the given player.
      function _update() internal {
        uint QUERY_EXECUTION_DELAY = 0;
        uint GAS_FOR_CALLBACK =200000;
        bytes32 query_id = provable_newRandomDSQuery(QUERY_EXECUTION_DELAY, NUM_RANDOM_BYTES_REQUESTED, GAS_FOR_CALLBACK);

        temps[query_id].id = query_id;
        temps[query_id].playerAddress = msg.sender;

        emit LogNewProvableQuery(msg.sender);
      }

      // @dev               The callback function called by the oracle once the random number is created.
      // @param _queryId    The query unique identifier that is the key to the player Temp.
      // @param _result     The random number generated by the oracle.
      // @param _proof      Used to check if the result has been tampered.
      function __callback(bytes32 _queryId, string memory _result, bytes memory _proof) public {
        require(msg.sender == provable_cbAddress());

        if (provable_randomDS_proofVerify__returnCode(_queryId, _result, _proof) == 0){
          uint randomNumber = uint(keccak256(abi.encodePacked(_result)))%2;
          _verifyResult (randomNumber, _queryId);
          emit RandomNumberGenerated(randomNumber);

        }
      }


      function _verifyResult(uint _randomNumber, bytes32 _queryId) internal {
        address player = temps[_queryId].playerAddress;
        if(_randomNumber == 1){
          playersByAddress[player].playerBalance += playersByAddress[player].betAmount;
          emit FlipResult(player, playersByAddress[player].betAmount, true);
        }
        else {
          contractBalance = contractBalance.add(playersByAddress[player].betAmount);
          emit FlipResult(player,0, false );
        }

        delete(temps[_queryId]);
        playersByAddress[player].betAmount = 0;
        playersByAddress[player].inGame = false;

      }

    // Get Contract's Balance:
    function getBalance() public view returns(uint) {
      return contractBalance;
    }

    // Get Player's Balance:
    function getPlayerBalance() public view returns (uint) {
        return playersByAddress[msg.sender].playerBalance;
      }


    // withdraw all funds possible only though contractOwner address;
    /*function withdrawAll() internal onlyOwner {

        uint amt = contractBalance;
        contractBalance = 0;

        msg.sender.transfer(amt);
        emit BalanceUpdated(msg.sender, amt, contractBalance);

    }*/


    //Withdraw Funds Won from the Contract
    function withdrawFunds() public {
      require(msg.sender != address(0));
      require(playersByAddress[msg.sender].playerBalance > 0);
      require(!_isPlaying(msg.sender));

      uint amt = playersByAddress[msg.sender].playerBalance;

      delete(playersByAddress[msg.sender]);
      msg.sender.transfer(amt);
      emit BalanceUpdated(msg.sender, amt, contractBalance);
    }





    function fundContract() public payable {
        require(msg.value != 0);
        contractBalance = contractBalance.add(msg.value);
        emit BalanceUpdated(msg.sender, msg.value, contractBalance);

    }



    function _isPlaying(address _player) internal view returns(bool){
      _player = msg.sender;
      return playersByAddress[_player].inGame;
    }
}

and Below is my main.js file :point_down:

var web3 = new Web3(Web3.givenProvider);
var contractInstance;
const contractAddress = "0x849452bDc16B523152E7d4248FF06cd55F28dE8A";


$(document).ready(async function(){
  window.ethereum.enable().then(function(accounts){
    contractInstance = new web3.eth.Contract(abi,contractAddress, {from: accounts[0]});
    console.log(contractInstance);
    console.log(`Use Contract address: ${contractInstance._address}`);
    jQuery("#playerAddress").text(accounts[0]);
  });

  $("#flip_button").click(async ()=> {
    await flipCoin().then( async ()=> {
      //EVENT LISTENERS
      contractInstance.once('LogNewProvableQuery',
      {

        fromBlock: 'latest'
      }, (error, event) => {
        if(error) throw("Error fetching events");
        jQuery("#events").text(`User ${event.returnValues.player} is waiting for the flip result`);
      });

      contractInstance.once('FlipResult',
      {

        fromBlock: 'latest'
      }, (error, event) => {
        if(error) throw("Error fetching events");
        jQuery("#events").text(`User ${event.returnValues.player} won: ${event.returnValues.won}`);
      });
    });
  });

  $("#get_balance").click(async function() {
    await fetchAndDisplay();
  });

  $("#fund_contract_button").click(async function(){
    await fundContract();
  });
});




  async function flipCoin(){
        var bet = $("#bet_input").val();
        var config = {
            value: web3.utils.toWei(bet,"ether")
        }
        await contractInstance.methods.flipCoin().send(config);
  };


      async function fetchAndDisplay(){
          await contractInstance.methods.getBalance().call().then(function(res){
            $("#jackpot_output").text("The Contract has : " + web3.utils.fromWei(res[0], "ether") + "Ether");

          });
      };


      async function fundContract(){
        var fund = $("#fund_contract").val();
        var config = {
          value : web3.utils.toWei(fund, "ether")
        }
        await contractInstance.methods.fundContract().send(config);
     
        await fetchAndDisplay();
      };

**Even my fetchAndDisplay function shows the following error :point_down: **
Screenshot 2021-03-29 at 8.11.11 PM

Please Let me know where I am going wrong ??
Event Listeners are just getting a nightmare for me, especially how to use them and where to put them in my lines of code in main.js ( I have even read the FAQā€™s as @dan-i has provided in Phase 1 Documentation)

:pray: :pray: :pray:

Thanks and Regards

Su.Kal Crypto

Thanks @dan-i!

Iā€™ve gone through the videos now and am preparing to start phase 2, but I am a bit overwhelmed in general. Could you give me a starting point? Right now my betting game is only for one player, should I start trying to implement a bet struct and mapping to keep track of multiple player accounts?

Any hints or instructions would greatly help! :slight_smile:

1 Like

Hey @mervxxgotti

Right now my betting game is only for one player, should I start trying to implement a bet struct and mapping to keep track of multiple player accounts?

Yes you should, and you will need it because of the way the oracle works.

Start small and try to call the oracle and get the random number, then start from there :slight_smile:

This it the project I did long ago, in case you need hints (do not check it immediately): https://github.com/dani69654/CoinFlip/tree/master/contracts

Good luck,
Dani

1 Like

Hi

Iā€™m attempting to run my smart contract using Provable on the Ropsten network.

When the bet() function is executed I receive a LogNewProvableQuery event as well as a Play event and the queryId is populated. However, the __callback() function never executes.

I commented out the require() statement to eliminate issues with this check. Iā€™ve also added an 'emit Callback()event as the first line of the__callback()` function but it never fires.

There shouldnā€™t be any issues with having not sufficient ether to pay for gas charges used by the callback since the contract account is funded with 0.5 ether as part of the deployment process.

Thereā€™s no evidence of any error being thrown in my contract and since thereā€™s nothing coming back from Provable iā€™m not sure how i can progress?

Below is the source code for my smart contact, mock smart contract which is used for testing, test and migration script as well as javascript and html for dapp.

Thanks

Jon

CoinTossOracle.sol

///SPDX-License-Identifier: MIT
pragma solidity 0.6.9;

import "../node_modules/@openzeppelin/contracts/math/SafeMath.sol";
import "../node_modules/@openzeppelin/contracts/access/Ownable.sol";
import "./provableAPI.sol";

contract CoinTossOracle is Ownable, usingProvable {

  using SafeMath for uint;

  uint constant NUM_RANDOM_BYTES_REQUESTED = 1;
  uint private multiplier = 10;
  uint private maximumBetInPlay;

  event Deposited(address indexed payee, uint amount);
  event Withdrawal(address indexed recipient, uint amount);
  event Payout(address indexed winner, uint amount);
  event Play(address indexed player, uint betSize, Side side, uint maxBet, bytes32 indexed queryId);
  event MaxBet(uint amount);
  event GeneratedToss(Side side);
  event LogNewProvableQuery(string description);
  event Callback(address indexed caller, bytes32 indexed queryId, string result);

  enum Side {
      HEADS,
      TAILS
    }

  mapping(bytes32 => Bet) betsPlaced;

  struct Bet {
    address account;
    Side side;
    uint betSize;
    bool inPlay;
  }

  constructor(uint _multiplier) public {
    multiplier = _multiplier;  
  }

  function kill() external onlyOwner {
      selfdestruct(payable(msg.sender));
  }

  function depositFunds() public payable onlyOwner {
      maximumBetInPlay = getMaximumBet();
      emit Deposited(msg.sender, msg.value);
  }

  function withdrawFunds(uint amount) public onlyOwner {
    require(address(this).balance <= amount, "Not enough funds!");
    payable(msg.sender).transfer(amount);
    emit Withdrawal(msg.sender, amount);
  }

  function payout(address payable winner, uint amount) internal {
      assert(amount > 0);
      assert(amount <= address(this).balance);

      winner.transfer(amount);
      emit Payout(winner, amount);
  }

  function getMaximumBet() public view returns (uint) {
      return address(this).balance.div(multiplier);
  }

  function getBalance() public view onlyOwner returns (uint) {
      return address(this).balance;
  }

  function bet(uint side) payable public {
      require(msg.value > 0, "you must bet some Ether!");
      require(maximumBetInPlay >= msg.value, "bet is too large!");
      bytes32 queryId = toss();
      betsPlaced[queryId] = Bet(msg.sender, Side(side), msg.value, true);
      emit Play(msg.sender, msg.value, Side(side), maximumBetInPlay, queryId);
  }

  function __callback(bytes32 _queryId, string memory _result, bytes memory _proof) public override {
    //require(msg.sender == getCBAddress(), "call not originating from Provable Smart Contract!");
    emit Callback(msg.sender, _queryId, _result);
    uint randomToss= uint(keccak256(abi.encodePacked(_result))) % 2;
    emit GeneratedToss(Side(randomToss));
    Bet storage _bet = betsPlaced[_queryId];
    if (randomToss == uint(_bet.side) && _bet.inPlay) {
      //transfer winnings to user
      uint startingBalance = address(this).balance;
      uint winnings = _bet.betSize.mul(2);
      payout(payable(_bet.account), winnings);
      assert(address(this).balance == startingBalance - winnings);
    }
    _bet.inPlay = false;
    maximumBetInPlay = getMaximumBet();
    emit MaxBet(maximumBetInPlay);
  }

  function toss() internal returns (bytes32 queryId) {
        
        uint QUERY_EXECUTION_DELAY = 0;
        //gas required to call __callback sometime in the future.
        uint GAS_FOR_CALLBACK = 200000;
        //queryId can be used as a correlation key to understand which player originally called it.
        queryId = randomQuery(QUERY_EXECUTION_DELAY, 
                              NUM_RANDOM_BYTES_REQUESTED, 
                              GAS_FOR_CALLBACK);
        emit LogNewProvableQuery("Provable Query Sent.  Waiting for answer...");

  }
  
  function randomQuery(uint queryDelay, 
                       uint bytesRequested, 
                       uint gasForCallback) internal virtual returns (bytes32 queryId) {
    queryId = provable_newRandomDSQuery(queryDelay, bytesRequested, gasForCallback); 
  }

  function getCBAddress() virtual internal returns (address) {
      return provable_cbAddress();
  }




}

CoinTossOracleMock (Used in tests)

///SPDX-License-Identifier: MIT
pragma solidity 0.6.9;

import "./CoinTossOracle.sol";

contract CoinTossOracleMock is CoinTossOracle {

    bytes32 constant public QUERY_ID = bytes32(keccak256("TEST"));

    constructor(uint multiplier) CoinTossOracle(multiplier) public {
    }

    function randomQuery(uint queryDelay, uint bytesRequested, uint gasForCallback) internal override returns (bytes32) {
        return QUERY_ID;
    }

    function callback() public {
      __callback(QUERY_ID, "239", bytes("TEST"));
    }

    function getCBAddress() internal override returns (address) {
      return msg.sender;
  }


}

cointossoracle_test.js

const CoinTossOracle = artifacts.require("CoinTossOracle");
const CoinTossOracleMock = artifacts.require("CoinTossOracleMock");
const truffleAssert = require("truffle-assertions");

//Truffle uses an extended version of Mocha test framework.  
//Provides a contract function that takes in name of contract and a function as arguments.
contract("CoinTossOracle", accounts => {

  let cointoss; 
  let fundingAccount = accounts[0];
  let bettingAccount = accounts[1];
  let betMultiplier = 10;
  let fundingSize = web3.utils.toWei("1000", "wei");

  beforeEach(async function() {
    cointoss = await CoinTossOracle.new(betMultiplier);
    let tx = await cointoss.depositFunds({value: fundingSize, from: fundingAccount});
    assert.equal(await web3.eth.getBalance(cointoss.address), fundingSize);
  })

  afterEach(async function() {
    await cointoss.kill({from: fundingAccount});
  })


  it("should be true that the contract already has a positive balance", async () => {
      let balance = await cointoss.getBalance();
      assert(balance == fundingSize, "cointoss balance is not correct");
  })

  it("should fail if a non-owner attempt to retrieve contract balance", async() => {
    await truffleAssert.reverts(cointoss.getBalance({from: bettingAccount}), "Ownable: caller is not the owner");
  })

  it("should return correct maximum bet", async() => {
      let maxBet = await cointoss.getMaximumBet();
      assert(maxBet == fundingSize/betMultiplier, "maximum bet is incorrect");
  })

  it("should fail if a user attempts to bet without sending any ether", async() => {
    await truffleAssert.reverts(cointoss.bet(1, {from: bettingAccount}), "you must bet some Ether!");
  })

  it("should fail if a user attempts to bet with a too large stake", async() => {
    await truffleAssert.reverts(cointoss.bet(0, {value: web3.utils.toWei("0.2", "ether"), from:bettingAccount}), "bet is too large!");
  })

  it("should fail if a user attempts to bet with a stake slightly larger but when added pot would be accepted", async() => {
    await truffleAssert.reverts(cointoss.bet(0, {value: web3.utils.toWei("110", "wei"), from:bettingAccount}), "bet is too large!");
  })

  it("should be possible for the owner of the contract to withdraw funds from the contract", async() => {
    let tx = await cointoss.withdrawFunds(web3.utils.toWei(fundingSize, "wei"));
    truffleAssert.eventEmitted(tx, 'Withdrawal', (ev) => {
      return (ev.recipient === fundingAccount) && (ev.amount == fundingSize);
    })
  })

  it("should not be possible for a non-owner of the contract to withdraw funds from the contract", async() => {
     await truffleAssert.reverts(cointoss.withdrawFunds(web3.utils.toWei(fundingSize, "wei"), {from: bettingAccount}), "Ownable: caller is not the owner");
  })

})

contract("CoinTossOracleMock", accounts => {

  let cointoss; 
  let fundingAccount = accounts[0];
  let bettingAccount = accounts[1];
  let betMultiplier = 10;
  let fundingSize = web3.utils.toWei("1000", "wei");
  let betSize = web3.utils.toWei("100", "wei");

  beforeEach(async function() {
    cointoss = await CoinTossOracleMock.new(betMultiplier, {from: fundingAccount});
    let tx = await cointoss.depositFunds({value: fundingSize, from: fundingAccount});
    assert.equal(await web3.eth.getBalance(cointoss.address), fundingSize);
    truffleAssert.eventEmitted(tx, 'Deposited', (ev) => {
      return (ev.payee === fundingAccount) && (ev.amount == fundingSize);
    })
  })

  afterEach(async function() {
    await cointoss.kill({from: fundingAccount});
  })
  
  it("should not payout and balance of account should increase if user bets wrong side", async() => {
    //bet tails against mock that always returns heads.
    let tx = await cointoss.bet(1, {value: betSize, from:bettingAccount});
    let balance = await web3.eth.getBalance(cointoss.address);
    //console.log("balance = " +  balance);
    //console.log(parseInt(fundingSize) + parseInt(betSize));
    truffleAssert.eventEmitted(tx, 'Play', (ev) => {
      return (ev.player === bettingAccount) && 
             (ev.betSize == betSize) && 
             (ev.side == 1);
    })
    tx = await cointoss.callback();
    truffleAssert.eventNotEmitted(tx, 'Payout');
    assert(balance == (parseInt(fundingSize) + parseInt(betSize)), "contract address balance not correct");
  })

  it("should payout and balance of account decrease if user bets correctly", async () => {
    //bet heads against mock that always returns heads.
    let tx = await cointoss.bet(0, {value: betSize, from:bettingAccount});
    //console.log("balance = " +  balance);
    //console.log(parseInt(fundingSize) - parseInt(betSize));
    truffleAssert.eventEmitted(tx, 'Play', (ev) => {
      return (ev.player === bettingAccount) && 
             (ev.betSize == betSize) && 
             (ev.side == 0);
    })

    tx = await cointoss.callback();
    truffleAssert.eventEmitted(tx, 'Payout', (ev) => {
      return (ev.winner === bettingAccount) &&
              (ev.amount == betSize*2);
    });
    let balance = await web3.eth.getBalance(cointoss.address);
    assert(balance == (parseInt(fundingSize) - parseInt(betSize)), "contract address balance not correct");
  })
})

2_cointoss_migration.js

const CoinToss = artifacts.require("CoinTossOracle");

module.exports = function (deployer) {
  deployer.deploy(CoinToss, 10).then(function (instance) {
        instance.depositFunds({value: web3.utils.toWei("0.5", "ether")});
  });
};

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <title>Coin Toss</title>
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"
      integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
      crossorigin="anonymous"></script>
    <!--script type="text/javascript" src="./web3.min.js"></script-->
    <script src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script>
    <script type="text/javascript" src="./main.js"></script>
    <script type="text/javascript" src="./abi_oracle.js"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">

  </head>
  <body>
    <div class="jumbotron jumbotron-fluid">
      <div class="container">
        <h1 class="display-4">Coin Toss Dapp</h1>
        <p class="lead">Feeling Lucky Punk! </p>
        Your Balance is <label id="account_balance"></label> ether
        <div>Bets placed in this session: <label id="bets_placed">0</label></div>
        <div>Bets won: <label id="bets_won">0</label></div>
      </div>
    </div>
    <div class="container">
      <div>
        <h2>Place Your Bet</h2>
        <div>
          Maximum Bet is <label id="max_bet"></label> ether
        </div>
        <div class="input-group mb-3">
          <input name="side_input" type="radio" id="side_input" class="form-control" value="0">
          <label for="0">Heads</label>
          <input name="side_input" type="radio" id="side_input" class="form-control" value="1">
          <label for="1">Tails</label>
        </div>
        <div class="input-group mb-3">
          <input type="text" class="form-control" id="bet_input" placeholder="Amount to bet (Eth)">
        </div>
        <button type="button" id="place_bet_button" class="btn btn-primary">Place Bet!</button>
      </div>
    </div>
  </body>
</html>

main.js

var web3 = new Web3(Web3.givenProvider);
var contractInstance;
//Ropsten Contract Address for CoinTossOracle
var address = "redacted";

$(document).ready(function() {
    window.ethereum.enable().then(function(accounts){
        contractInstance = new web3.eth.Contract(abi, address,{from: accounts[0]});
        console.log(contractInstance);
        console.log(address);
        console.log("wallet address is " + accounts[0]);
        let playId;
        let payoutId;
        let provableQueryId;
        //initialise values
        contractInstance.methods.getMaximumBet().call().then(function(val) {
            console.log(val);
            $("#max_bet").text(web3.utils.fromWei(val, "ether"));
        });
      
        web3.eth.getBalance(accounts[0]).then(function(val) {
            console.log("wallet balance is " + val);
            $("#account_balance").text(web3.utils.fromWei(val, "ether"));
        });

        contractInstance.events.Payout({filter: {winner: [accounts[0]]}})
        .on("data", event => {
            newPayoutId = event.blockHash + event.logIndex + event.transactionHash;
            if (payoutId != newPayoutId) {
                console.log(event);
                alert("You Won " + web3.utils.fromWei(event.returnValues.amount, "ether") + " ether!!");
                let bets_won = parseInt($("#bets_won").text()) + 1;
                $("#bets_won").text(bets_won);
                getBalance(accounts[0]);
                payoutId = newPayoutId;
            }
        })

        contractInstance.events.Play({filter: {player: [accounts[0]]}})
        .on("data", function(event) {
            newPlayId = event.blockHash + event.logIndex + event.transactionHash;
            if (playId != newPlayId) {
                console.log(event);
                console.log("Playing a coin toss game");
                let bets_placed = parseInt($("#bets_placed").text()) + 1;
                $("#bets_placed").text(bets_placed);
                getBalance(accounts[0]);
                playId = newPlayId;
            } 
        })

        contractInstance.events.MaxBet()
        .on("data", event => {
            console.log(event)
            $("#max_bet").text(web3.utils.fromWei(event.returnValues.amount, "ether"));
        })

        contractInstance.events.LogNewProvableQuery()
        .on("data", event => {
            newProvableQueryId = event.blockHash + event.logIndex + event.transactionHash;
            if (provableQueryId != newProvableQueryId) {
                console.log(event);
                provableQueryId = newProvableQueryId;
            }
            
        })
        .on("error", error => {
            console.log(error);
        })

        contractInstance.events.GeneratedToss()
        .on("data", event => {
            console.log();
        })

        contractInstance.events.Callback()
        .on("data", event => {
            console.log();
        })

    });

    $("#place_bet_button").click(placeBet);
});

function getBalance(account) {
    web3.eth.getBalance(account).then(function(val) {
        console.log("wallet balance is " + val);
        $("#account_balance").text(web3.utils.fromWei(val, "ether"));
    });

}

function placeBet() {
    var side = $("input[name=side_input]:checked").val();
    var betSize = $("#bet_input").val();

    console.log("betting " + betSize + " Eth for coin to land on  " + side);

    var config = {
        value: web3.utils.toWei(betSize, "ether")
    }

    contractInstance.methods.bet(side).send(config)
    .on("transactionHash", function(hash) {
        console.log(hash);
    })
}

Also @thecil

Now i made the following changes to my code :point_down:

CoinFlip.sol

pragma solidity 0.5.12;

import "./provableAPI.sol";
import { SafeMath } from "./SafeMath.sol";


contract Coinflip is usingProvable{

  using SafeMath for uint;

  //Events to listen for :
  event RandomNumberGenerated(uint randomNumber);
  event LogNewProvableQuery(string description);
  event FlipResult(address indexed player, uint amountWon, bool result);
  event BalanceUpdated(address user, uint transferredAmt, uint newContractBalance);


  // Constructors and Modifiers :
  constructor() public {
    provable_setProof(proofType_Ledger);
    contractBalance = 0;
    contractOwner = msg.sender;
    }



  //addresses and other such state Variables:
  address public contractOwner;
  uint private contractBalance;
  address public contractAddress;
  uint private constant NUM_RANDOM_BYTES_REQUESTED = 1;

    struct Temp {
      bytes32 id;
      address playerAddress;
    }

    struct PlayerByAddress {
      address playerAddress;
      uint playerBalance;
      bool inGame;
      uint betAmount;
    }
    mapping(address => uint) public balances;
    mapping(bytes32 => Temp) public temps;
    mapping(address => PlayerByAddress) public playersByAddress;

    //Flip the Coin and check whether user won or lost;
    function flipCoin() public payable  {
        require(msg.value <= getBalance() && msg.value > 0 && contractBalance != 0, "The contract doesnt have enough balance to play right now. Come Back later");
        require(_isPlaying(msg.sender) == false, "The User currently is in Game");

        playersByAddress[msg.sender].playerAddress = msg.sender;
        playersByAddress[msg.sender].betAmount = msg.value.sub(provable_getPrice("random")); // Contract keeps oracle's fee.
        playersByAddress[msg.sender].inGame = true;



        _update();
    }



      // @dev               Calls the oracle random function.
      //                    Sets Temp for the given player.
      function _update() internal {
        uint QUERY_EXECUTION_DELAY = 0;
        uint GAS_FOR_CALLBACK =200000;
        bytes32 query_id = provable_newRandomDSQuery(QUERY_EXECUTION_DELAY, NUM_RANDOM_BYTES_REQUESTED, GAS_FOR_CALLBACK);

        temps[query_id].id = query_id;
        temps[query_id].playerAddress = msg.sender;

        emit LogNewProvableQuery("Provable Query was sent, waiting for answer...");
      }

      // @dev               The callback function called by the oracle once the random number is created.
      // @param _queryId    The query unique identifier that is the key to the player Temp.
      // @param _result     The random number generated by the oracle.
      // @param _proof      Used to check if the result has been tampered.
      function __callback(bytes32 _queryId, string memory _result, bytes memory _proof) public {
        require(msg.sender == provable_cbAddress());

        if (provable_randomDS_proofVerify__returnCode(_queryId, _result, _proof) == 0){
          uint randomNumber = uint(keccak256(abi.encodePacked(_result)))%2;
          _verifyResult (randomNumber, _queryId);
          emit RandomNumberGenerated(randomNumber);

        }
      }


      function _verifyResult(uint _randomNumber, bytes32 _queryId) internal {
        address player = temps[_queryId].playerAddress;
        if(_randomNumber == 1){
          playersByAddress[player].playerBalance += playersByAddress[player].betAmount;
          emit FlipResult(player, playersByAddress[player].betAmount, true);
        }
        else {
          contractBalance = contractBalance.add(playersByAddress[player].betAmount);
          emit FlipResult(player,0, false );
        }

        delete(temps[_queryId]);
        playersByAddress[player].betAmount = 0;
        playersByAddress[player].inGame = false;

      }

    // Get Contract's Balance:
    function getBalance() public view returns(uint) {
      return contractBalance;
    }

    // Get Player's Balance:
    function getPlayerBalance() public view returns (uint) {
        return playersByAddress[msg.sender].playerBalance;
      }




    //Withdraw Funds Won from the Contract
    function withdrawFunds() public {
      require(playersByAddress[msg.sender].playerBalance > 0);
      require(!_isPlaying(msg.sender));

      uint amt = playersByAddress[msg.sender].playerBalance;
      playersByAddress[msg.sender].playerBalance = 0;
      delete(playersByAddress[msg.sender]);
      msg.sender.transfer(amt);
      emit BalanceUpdated(msg.sender, amt, contractBalance);
    }





    function fundContract() public payable {
        require(msg.value != 0);
        contractBalance = contractBalance.add(msg.value);
        emit BalanceUpdated(msg.sender, msg.value, contractBalance);

    }



    function _isPlaying(address _player) internal view returns(bool){
      _player = msg.sender;
      return playersByAddress[_player].inGame;
    }
}

and my main.js file :point_down:

var web3 = new Web3(Web3.givenProvider);
var contractInstance;
const contractAddress = "0x21293f4b4Bfc0fF43845958AB4cb12DDEf37c607";


$(document).ready(async function(){
  window.ethereum.enable().then(function(accounts){
    contractInstance = new web3.eth.Contract(abi,contractAddress, {from: accounts[0]});
    console.log(contractInstance);
    console.log(`Use Contract address: ${contractInstance._address}`);
    jQuery("#playerAddress").text(accounts[0]);
    

  });

  $("#flip_button").click(async ()=> {
    await flipCoin();
  });

  $("#get_balance").click(async ()=> {
    await fetchAndDisplay();
  });

  $("#fund_contract_button").click(async () => {
    await fundContract();
  });
  $("#withdraw_funds").click(async () => {
    await withdrawFunds();
  });

});




  async function flipCoin(){
        var bet = $("#bet_input").val();
        var config = {
            value: web3.utils.toWei(bet,"ether")
        }
        await contractInstance.methods.flipCoin().send(config)
        .on("transactionHash", function(hash){
            console.log(hash);
        })
        .on("confirmation", function(confirmationNr){
            //console.log(confirmationNr);
        })
        .on("receipt", function(receipt){
            //console.log(receipt);
        });

            //EVENT LISTENERS



            contractInstance.events.FlipResult(function(error, event){
            console.log(event.returnValues);
            if(event.returnValues.result === true ) {
                $("#events").append(
                    "<div class='alert alert-success' role='alert'>You Won!</div>"
                )
            } else {
                $("#events").append(
                    "<div class='alert alert-danger' role='alert'>You Lost!</div>"
                )
            }

        })

      };


      async function fetchAndDisplay(){
          await contractInstance.methods.getBalance().call().then(function(res){
            $("#jackpot_output").text("The Contract has : " + web3.utils.fromWei(res, "ether") + "Ether");

          });
      };


      async function fundContract(){
        var fund = $("#fund_contract").val();
        var config = {
          value : web3.utils.toWei(fund, "ether")
        }
        await contractInstance.methods.fundContract().send(config)
        .on("transactionHash", function(hash){
          console.log(hash);
        })
        .on("confirmation", function(confirmationNr){
          //console.log(confirmationNr);
        })
        .on("receipt", function(receipt){
          //console.log(receipt);

        });
      };


      async function withdrawFunds(){
        await contractInstance.methods.withdrawFunds().send();
    };

**But still I get the below Problem :point_down: **

Screenshot 2021-03-30 at 10.13.00 PM

Uncaught (in promise) TypeError: contractInstance.events.FlipResult is not a function
    at flipCoin (main.js:56)
    at async HTMLButtonElement.<anonymous> (main.js:17)

main.js:101 Uncaught (in promise) TypeError: contractInstance.methods.withdrawFunds is not a function
    at withdrawFunds (main.js:101)
    at HTMLButtonElement.<anonymous> (main.js:28)
    at HTMLButtonElement.dispatch (jquery-3.4.1.min.js:2)
    at HTMLButtonElement.v.handle (jquery-3.4.1.min.js:2)

*So This Basically again means that my events are not getting Pushed and because of that **my this below :point_down: line of code isnā€™t getting executed and hence when i try to play again with the same Metamask account it doesnā€™t go through ***


        delete(temps[_queryId]);
        playersByAddress[player].betAmount = 0;
        playersByAddress[player].inGame = false;

**Because the playersByAddress[player].inGame doesnā€™t turn False and because of this I get the etherscan revert by EVM message ** :point_down:

Screenshot 2021-03-29 at 7.50.06 PM

I am literally so frustrated now that i feel like giving up, having gone after line after line after each code and still not being able to figure it out for almost 15 days.

Please help :pray: :pray: :pray: :pray: :pray: :pray: :pray: :pray: :pray: :pray:

Thanks and Regards

Su.kal Crypto

1 Like

Hey @jonp

I have tested the oracle and indeed it does not respond.
It happens sometimes as ropsten is a testnet and this contract can be down, usually the outage lasts for few days.
We will need to try again later this week.

Cheers,
Dani

Hi @Su.kal.Crypto

Is it possible that your abi is not updated and does not contain the event FlipResult and withdrawFunds?
Migrate your contract and use the new abi, then try again and let us know.

Cheers,
Dani

@dan-i

Thanks for the heads up! Saved me hours of unnecessary debugging.

Iā€™ve now deployed to Rinkeby and still have the same issue.

Provable site https://provable.xyz/ only seems to provide status and check query for mainnet.

Having zero visibility in the state of the service on testnet makes it unusable in my view.

Thanks for your help - iā€™ll keep trying.

Jon

1 Like

Hey @jonp

I used to ask them info via this chat :slight_smile:
https://gitter.im/provable/ethereum-api

Check it out.

Cheers,
Dani

Thanks @dan-i
You are a true saviour. What i could not accomplish in 20 days, you solved in 20 minutes ( i was just trying to figure out where my lines of code were wrong because i was constantly getting revert errors, whereas actually my abi.js was not not correct ) :grinning: :pray: :pray: :pray:

Although this is making me a better coder because i realise the implications of each line of code better than before. :grinning:

But now i am stuck again, because now my ā€œLogNewProvavbleQueryā€ Event is getting broadcasted on etherscan, But still my FlipResult event isnā€™t getting broadcasted and I am not able to understand now :thinking:, what could possibly be wrong.

Because of this above error ( WHICH IS BY THE WAY NOT SHOWING ON THE CONSOLE), MY BELOW CODE ISNā€™T GOING THROUGH :point_down:


            contractInstance.events.FlipResult(function(error, event){
              console.log(event.returnValues);
              if(event.returnValues.result === true ) {
                  $("#events").append("<div class='alert alert-success' role='alert'>You Won!</div>")
                  }
              else {
                $("#events").append("<div class='alert alert-danger' role='alert'>You Lost!</div>")
                   }
            });

ALSO THE BELOW SOLIDITY CODE :point_down:

function _verifyResult(uint _randomNumber, bytes32 _queryId) internal {
        address player = temps[_queryId].playerAddress;
        if(_randomNumber == 1){
          playersByAddress[player].playerBalance += playersByAddress[player].betAmount;
          contractBalance = contractBalance.sub(playersByAddress[player].betAmount);
          emit FlipResult(player, playersByAddress[player].betAmount, true);
        }
        else {
          contractBalance = contractBalance.add(playersByAddress[player].betAmount);
          emit FlipResult(player,0, false );
        }

        delete(temps[_queryId]);
        playersByAddress[player].betAmount = 0;
        playersByAddress[player].inGame = false;

      }

AND BECAUSE OF THIS, WHENEVER I TRY TO PLAY AGAIN, THERE IS A TRANSACTION REVERT BY THE EVM, CITING REASON : :point_down: and it happens with all 3 metamask addresses , the first time each one goes through but because _verifyResult is not being executed (because no Response from the Oracle), hence playersByAddress[player].inGame never turns ā€œfalseā€

Screenshot 2021-04-01 at 12.35.02 PM
Screenshot 2021-04-01 at 12.55.49 PM

AM I DOING SOMETHING WRONG OR IS THIS IS AN ISSUE WITH THE ORACLE ITSELF ?
AND CAN YOU GUIDE ME WITH THE NETWORK CONFIGURATIONS OF RINKEBY AND KOVAN ( SO I CAN CHANGE IN MY TRUFFLE-CONFIG FILE ) AND MAYBE I CAN TRY ON THOSE NETWORKS, IN CASE THE ORACLE ISNā€™T WORKING ON ROPSTEN ?

thanks and regards

su.kal Crypto