Programming Project - Phase 2

hey @dan-i,

Thanks for all your help. So I’ve redone my contract to use provable to generate the random number and it’s working great on remix using the ropsten testnet.

Then I followed along with with Filip’s video to try migrating the project with truffle. I remember running into this issue before as you said,

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

but when I use @truffle/hdwallet-provider, I get this error:
Screen Shot 2021-04-21 at 10.51.21 AM

When when I use truffle-hdwallet-provider, it begins compiling. Why is this?

Next, I continue to follow the videos to import the provableAPI.sol file, and it seems like everything is compiling fine, I get this error:
Screen Shot 2021-04-21 at 10.55.25 AM

It says I have insufficient funds but the balance stated there is ~4.7 ETH, which is the amount in the account in my Metamask on the ropsten network. This ran fine in remix and there was no insufficient funds when running on there. Does this have something to do with the hdwallet-provider?

Here’s my contract and config file:

pragma solidity >= 0.5.0 < 0.6.0;

//import "github.com/provable-things/ethereum-api/provableAPI_0.5.sol";
import "./provableAPI_0.5.sol";

contract Betting is usingProvable {

    uint public contractBalance;

    //for generating random number using provable
    uint256 constant NUM_RANDOM_BYTES_REQUESTED = 1;
    uint256 public latestNumber;

    struct player {
        address playerAddress;
        uint balance;
        uint bet;
        bool waiting;           //true if player waiting for oracle request
        bool win;               //true if last bet won
        bytes32 queryId;
    }

    struct query {
        bytes32 id;
        address playerAddress;
        uint256 random;
    }

    mapping(address => player) private players;
    mapping(bytes32 => query) private queries;

    event LogGeneratedRandomNumber(bytes32 requestId, uint256 randomNumber);
    event LogGameState(address playerAddress, uint playerBalance, bytes32 requestId, uint256 random, uint contractBalance);
    event LogPlay(address playerAddress, uint bet, bytes32 requestId);
    event LogLinkBalance(uint256 linkBalance);

    /*
     * MODIFIERS
     ************************************************************************************************
     */
    modifier initCosts(uint initCost){
        require(msg.value >= initCost, "Contract needs minimum ether to initialize contract balance.");
        _;
    }

    modifier costs(uint cost){
        require(msg.value >= cost, "Minimum bet or deposit is 0.01 ether!");
        _;
    }

    modifier ready() {
        require(contractBalance > 0, "Cannot play. Contract balance is empty.");
        require(players[msg.sender].waiting == false, "Cannot play. This player address is already playing and waiting on request.");
        _;
    }

    modifier withdrawable() {
        require(msg.sender != address(0));
        require(players[msg.sender].balance > 0, "This address has no funds to withdraw.");
        require(!players[msg.sender].waiting, "Cannot withdraw while waiting for oracle request.");
        _;
    }

    /***********************************************************************************************/

    //constructor takes initial 0.05 ether (50 finney) from default player
    constructor() public payable initCosts(0.05 ether) {

        contractBalance += msg.value;

        players[msg.sender].playerAddress = msg.sender;
        players[msg.sender].balance = 0;
        players[msg.sender].bet = 0;
        players[msg.sender].waiting = false;
        players[msg.sender].win = false;
        players[msg.sender].queryId = 0;

        emit LogGameState(players[msg.sender].playerAddress, players[msg.sender].balance, 0, 0, contractBalance);
    }

    function play() public payable costs(0.01 ether) ready {
        players[msg.sender].playerAddress = msg.sender;
        players[msg.sender].bet = msg.value;
        players[msg.sender].waiting = true;

        bytes32 newQueryId = getRandomNumber();

        players[msg.sender].queryId = newQueryId;

        queries[newQueryId].id = newQueryId;
        queries[newQueryId].playerAddress = msg.sender;

        emit LogPlay(players[msg.sender].playerAddress, players[msg.sender].bet, newQueryId);
    }

    function getRandomNumber() payable public returns (bytes32 queryId) {
        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);

        return query_id;
    }

    function __callback(bytes32 _queryId, string memory _result, bytes memory _proof) public {
        uint256 randomNumber = uint256(keccak256(abi.encodePacked(_result)));
        latestNumber = randomNumber;
        updateGame(_queryId);
    }

    function updateGame(bytes32 _queryId) internal {
        address playerAddress = queries[_queryId].playerAddress;

        queries[_queryId].random = latestNumber;

        if(latestNumber % 2 == 1) {
            players[playerAddress].win = true;
        } else {
            players[playerAddress].win = false;
        }

        updateBalances(playerAddress, _queryId);

        players[playerAddress].bet = 0;
        players[playerAddress].waiting = false;

        //delete(requests[requestId]);
    }

    function updateBalances(address playerAddress, bytes32 _queryId) internal {
        if(players[playerAddress].win) {
            players[playerAddress].balance += players[playerAddress].bet * 2;
            contractBalance -= players[playerAddress].bet * 2;
        } else {
            contractBalance += players[playerAddress].bet;
        }

        emit LogGameState(
            playerAddress,
            players[playerAddress].balance,
            _queryId,
            queries[_queryId].random,
            contractBalance
        );
    }

    function withdrawPlayerBalance() public withdrawable {
        uint amount = players[msg.sender].balance;
        delete(players[msg.sender]);
        msg.sender.transfer(amount);

        emit LogGameState(
            msg.sender,
            players[msg.sender].balance,
            players[msg.sender].queryId,
            queries[players[msg.sender].queryId].random,
            contractBalance
        );
    }

    function withdrawContractBalance() public {
        uint256 amount = contractBalance;
        contractBalance = 0;
        msg.sender.transfer(amount);

        emit LogGameState(
            msg.sender,
            players[msg.sender].balance,
            players[msg.sender].queryId,
            queries[players[msg.sender].queryId].random,
            contractBalance
        );
    }

    function deposit() public payable costs(0.01 ether){
        contractBalance += msg.value;
        emit LogGameState(
            msg.sender,
            players[msg.sender].balance,
            players[msg.sender].queryId,
            queries[players[msg.sender].queryId].random,
            contractBalance
        );
    }

    function getPlayer() public view returns (address playerAddress, uint balance, uint bet, bool waiting, bool win, bytes32 queryId, uint256 random) {

        return (players[msg.sender].playerAddress,
                players[msg.sender].balance,
                players[msg.sender].bet,
                players[msg.sender].waiting,
                players[msg.sender].win,
                players[msg.sender].queryId,
                queries[players[msg.sender].queryId].random
                );
    }

    function getPlayerBalance() public view returns (uint) {
        return players[msg.sender].balance;
    }

    function getContractBalance() public view returns (uint){
        return contractBalance;
    }

    function getContractAddress() public view returns (address) {
        return address(this);
    }

    function getSenderAddress() public view returns (address) {
        return msg.sender;
    }
}
/**
 * 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"
      // }
    }
  }
}

Can you help me with this? Thank you!

hey @dan-i, I think I’m almost there! However, I have a “refresh” button on my dapp in order to check whether or not the callback function fired yet which then determines whether or not the player won or lost depending on the returned random number.

I’ve been able to get returned values from the contract into the dapp using contractInstance.methods.functionName().call().then(function(result) { … });, as well as getting return values from contractInstance.events.eventName(function(error, result) { … }); after an event has been emitted from a recently called function.

But how would I use contractInstance.events.eventName() if I want to get the event that was emitted within the callback function? Is it possible to do this as if it was a listener on the dapp so the dapp would update itself upon receiving the event from the callback function and without the use of a refresh button? Is there some sort of function to listen for when events fire from callback functions kind of like how $("#button_name).click(functionName); listens for clicks?

Oh and I’m also still wondering about why truffle-hdwallet-provider worked for me even though it’s deprecated? And why using @truffle/hdwallet-provider returned the error above?

Thanks for all the help @dan-i!

Hi @mervxxgotti

but when I use @truffle/hdwallet-provider, I get this error

You need to install the right package: npm i @truffle/hdwallet-provider

About listening event, once you declare your listeners they will work as long as your server is running.
Take look at the example I wrote :smiley:
https://github.com/dani69654/CoinFlip/blob/master/client/main.js

Let me know if you need info,
Dani

1 Like

@dan-i

Using the Chainlink VRF we need to fund the contract with LINK. How do we do that from the Flip Dapp? We fund the contract with ether with this function.

function receiveEther() public payable costs(1 ether){
require(msg.value >= 1 ether);
balance += msg.value;
}

What do we do for LINK?

I also got this error message when I tried to compile the contracts with truffle

flipdapp

Claude

1 Like

Hey @dan-i & @filip

Thanks for all your help. I’ve uploaded my project here https://github.com/mervxxgotti/IvanOnTech-SCP201old

It’s functioning with a refresh button, I couldn’t really understand your examples using the .once for the events, but I think this is as best as I can do at this current time.

Thanks for everything and I hope to improve.

1 Like

Hey @KlodyCodez

Using the Chainlink VRF we need to fund the contract with LINK. How do we do that from the Flip Dapp? We fund the contract with ether with this function.

You could just send LINK tokens to the contract you deployed.
If you want to track a user deposit of ERC20 tokens in your contract, you have to use the ERC function transferFrom.
If you are not familiar with ERC20 standards just send LINK tokens to your contract for now, in our new Solidity 201 course we have a full explanation of ERC tokens and how they work, so you can follow the new courses (once finished this one) and come back later to improve this project.

Regarding the error with Truffle, in which Solidity version are you trying to compile?

Cheers,
Dani

Hey @mervxxgotti

Well done! Really happy to see that you’ve successfully completed the project :slight_smile:
If you haven’t already, take the new Sol 101 and 201 courses, they have a lot of knowledge that you will surely love to learn.

Also don’t forget the Smart Contract Security course.

See you soon!!
Dani

1 Like

Hello @dan-i

When I type the command truffle version, this is what I get.

Truffle v5.3.3 (core: 5.3.3)
Solidity v0.5.16 (solc-js)
Node v 10.21.0
Web3.js v1.3.5

Claude

I’m onto those next! Sorry if I got frustrated at times, you were a great help!

1 Like

Hey @KlodyCodez

The LINK guide I’ve posted uses Solidity 0.6.6 if I am not wrong.
Can you please push your project to github and share the link, I will take a look.

Cheers,
Dani

Hello @dan-i

Here is the link to my project
https://github.com/KlodeEdwarCodez/FlipDapp

Claude

1 Like

Hi @KlodyCodez

Compile using Solidity 0.6.6 as in their example and that issue will be gone.
There are many other errors which you will need to fix before compiling the project, let me know if you need help.

Cheers,
Dani

@dan-i
I tried to find instruction on how to update the compliler and didn’t find it. Do you have a link for the instructions to update the compiler?

Thanks
Claude

1 Like

@dani

I got it working now.
Thanks
Claude

1 Like

Hi @dan-i, @filip!

Thanks for this very interesting course. I tried to do something nice for this project, but between my limited knowledge, what’s outdated, the Provable API not working, the switch to Chainlink API, and a too ambitious front-end (forked from @Erno project), it took me forever haha!
There are still a few bugs in the UI (some styling not working properly and an ugly crash when betting multiple times without waiting for each bet to complete), but that’s all I can do for now! Planning on following with the new Ethereum201 course, and then Ethereum security.

Here is my repo on Github: https://github.com/Pedrojok01/CoinFlip_2.0
And a live link on kovan: https://coinflip-double-up-your-eth.netlify.app/

Preview

2 Likes

Well done man your dapp is really cool :smile:
I love the UI really well built and user friendly, I’ve played couple of games too!

You got a start from me on your GitHub project.

Keep going, you are in the right path.

Thanks, much appreciated!

1 Like

Hi everyone.
I’m running into some difficulty with phase 2 of the project. I set mapping “inProgress” to true when query is made by the user. The user / frontend can then check the mapping repeatedly to see if the query has been fulfilled. However, it won’t fulfill, I’ve waited 30 mins to be sure :rofl: . Here’s my code:

pragma solidity >= 0.5.0 < 0.6.0;

import "./usingProvable.sol";


contract CoinFlip is usingProvable {
    
    event Flipped(address indexed player, bool success, uint256 amount);

    uint256 constant NUM_RANDOM_BYTES_REQUESTED = 1;
    uint256 public betFunds;
    
    // Current bet from the player
    mapping(address => uint256) public playerBet;
    
    // The sender of the query.
    mapping(bytes32 => address payable) public querySender;
    
    // The last request from the player.
    mapping(address => bytes32) public playerId;
    
    // Set to true when the query is made, set to false when result is returned.
    mapping(address => bool) public inProgress;
    
    // Random true or false.
    mapping(address => bool) public randomResult;


    function setBet() public payable {
        require(msg.value != 0, "Cannot bet zero.");
        playerBet[msg.sender] += msg.value;
    }


    // Player "flips the coin" and makes a random number query.
    function update() payable public {
        require(msg.value >= 0.5 ether, "Not enough gas.");
        uint256 QUERY_EXECUTION_DELAY = 0;
        uint256 GAS_FOR_CALLBACK = 200000;
        bytes32 queryId = provable_newRandomDSQuery(
            QUERY_EXECUTION_DELAY,
            NUM_RANDOM_BYTES_REQUESTED,
            GAS_FOR_CALLBACK
        );
        playerId[msg.sender] = queryId;
        querySender[queryId] = msg.sender;
        inProgress[msg.sender] = true;
    }

    
    // Called by the frontend until it returns false when the query is resolved.
    function checkStatus() public view returns(bool status) {
        return inProgress[msg.sender];
    }
    
    
    // Random number gets set, calls sendWin.
    function __callback(bytes32 _queryId, string memory _result, bytes memory _proof) public {
        require(msg.sender == provable_cbAddress());

        uint256 randomNumber = uint256(keccak256(abi.encodePacked(_result))) % 2;
        bool result = randomNumber == 1;
        
        address payable player = querySender[_queryId];
        randomResult[player] = result;
        sendWin(player);
    }
    
    
    // Check the random boolean and either sends the prize, or doesn't.
    function sendWin(address payable player) public {
        require(playerBet[player] != 0, "No bet placed.");
        uint256 maxWin = betFunds > playerBet[player] * 2 ? playerBet[player] * 2 : betFunds;

        if(randomResult[player]) {
            player.transfer(maxWin);
            betFunds -= maxWin;
            emit Flipped(player, true, maxWin);
        }
        else {
            emit Flipped(player, false, 0);
        }

        inProgress[player] = false;
        playerBet[player] = 0;
    }
    
    
    // Deployer can send funds that can be won.
    function fundPool() public payable {
        require(msg.value != 0, "Cannot send zero.");
        betFunds += msg.value;
    }
}

And here are my versions:

node: v10.19.0
npm: v7.6.3
truffle: v5.3.1
hdwallet-provider: v1.2.2
solc: v0.5.12
provable: >= 0.5.0 < 0.6.0

I know the withdraw function should be separate from the __callback.
Also I have tried it with the frontend with a hardcoded outcome, it works fine.
Thanks in advance,
Paddy

@dan-i, @filip!

Here is my betting app.
Please let me know if there is anything I should fix/work on.

https://github.com/KlodeEdwarCodez/FlipDapp

flipdapp1 flipdapp2

Thanks
Claude

1 Like

Hi @paddyc1

Can you share the contract address so that we can verify if you actually received the callback from the oracle?

thanks,
Dani