Programming Project - Phase 1

Hey @mervxxgotti

I think I solved this - every time I make a change to the .sol contract, am I required to update the abi.js file with the new array from the contract build?

If you modify the function name / the function parameter / the function return value and type you have to modify the ABI.
Take some time to read your abi and you will see why itā€™s important to have it updated :slight_smile:

Also - Iā€™m using Brave browser and things started working once I cleared all browsing history, cookies, cache etc. Is this required every time I make a change? Is there a simpler way to do this?

That should not be required but is surely a good try if something does not work in your front end or if changes made to your code are not reflected in your front end.

Lastly, I am now able to call functions using contractInstance.methods.placeBet(), but I am getting this error when sending ETH to the contract:

This is an issue with Metamask, follow these steps and let me know:

  1. Click the account icon on the top-right corner of MetaMask
  2. Select Settings
  3. Select Advanced
  4. Scroll down and click Reset Account

Cheers,
Dani

thank you Dani :slight_smile: youā€™re a life saver!

Hey @dan-i,

So my dapp and contract are functioning based on the requirements of making and placing bets. But I have some questions as there are some extra functionalities I still want to experiment with.

  1. When I place a bet using placeBet(), itā€™s working as expected, except for what it returns. In my .sol contract, I have it defined to return 0 or double the bet value in order to keep track of the results on the frontend. However, when I try to console.log() the returned result, I get the following (screenshot). How can I write this so on the frontend I can receive a uint?
    Screen Shot 2021-03-23 at 1.18.13 PM

  2. In my $(document).ready() function, I am able to get the metamask account balance and address, as well as the contract balance and address. I am using the refresh() function with the refresh button in order to retrieve updated account balances of both the contract and metamask accounts to check the results after placing a bet. However, this.web3.eth.getBalance() works in my document.ready() function but returns the following error when called from the refresh function. Why is this and how can I fix this?
    Screen Shot 2021-03-23 at 1.13.01 PM

Other than these two questions, everything is working!

Here is my code (Iā€™ve commented out the this.web3.eth error in the refresh() for functionality for now):

main.js

var web3 = new Web3(Web3.givenProvider);
var contractInstance;

var bet;
var config;
var result;

var winnings;
var losses;
var roundsWon;
var roundsLost;
var roundsPlayed;
var winFlag;

$(document).ready(function() {
    //brings up metamask ask for permission from browser, then passes accounts in metamask to promise function
    window.ethereum.enable().then(function(accounts) {
      //contract instance of abi template containing function definitions, address of contract, and default sender (metamask account 0)
      contractInstance = new web3.eth.Contract(abi, "0x7961a7dDde5D3B90d4FA4f8c1eA1B62DAE275Ddf", {from: accounts[0]});

      //Get player's Metamask account balance and address
      this.web3.eth.getBalance(accounts[0], (err, balance) => {
        balance = web3.utils.fromWei(balance, "ether");
        $("#metamask_balance_output").text(balance + " ETH");
        console.log("Metamask account balance: " + balance + " ETH");
      });

      contractInstance.methods.getSenderAddress().call().then(function(address) {
        $("#metamask_address_output").text(address);
        console.log("Metamask account (sender) address: " + address);
      });

      //Get Betting contract account balance and address
      contractInstance.methods.getBalance().call().then(function(balance) {
        balance = web3.utils.fromWei(balance, "ether");
        $("#contract_balance_output").text(balance + " ETH");
        console.log("Betting contract balance: " + balance + " ETH");
      });

      contractInstance.methods.getContractAddress().call().then(function(address) {
        $("#contract_address_output").text(address);
        console.log("Betting contract address: " + address);
      });

      $("#round_output").text("Unplayed!");

      winnings = 0;
      losses = 0;
      roundsWon = 0;
      roundsLost = 0;
      roundsPlayed = 0;

      $("#winnings_output").text(winnings);
      $("#losses_output").text(losses);
      $("#rounds_won_output").text(roundsWon);
      $("#rounds_lost_output").text(roundsLost);
      $("#rounds_played_output").text(roundsPlayed);


    });

    //on click of # buttons, call functions
    $("#bet_button").click(placeBet);
    $("#refresh_button").click(refresh);

});

//on add data button click,
function placeBet() {

  bet = $("#bet_input").val();

  console.log("***bet button pressed. bet received from form is: " + bet + " ***");

  config = {
    value: web3.utils.toWei(bet, "ether")
  };

  result = contractInstance.methods.placeBet().send(config).then(async function(result) {
    await console.log(result);
  });

  console.log(result);

}

//get balance from contract to display
function refresh() {
  console.log("***refresh button pressed***");

  /*
  this.web3.eth.getBalance(accounts[0], (err, balance) => {
    balance = web3.utils.fromWei(balance, "ether");
    $("#metamask_balance_output").text(balance + " ETH");
    console.log("Metamask account balance: " + balance + " ETH");
  });
  */

  //Get Betting contract account balance and address
  contractInstance.methods.getBalance().call().then(function(balance) {
    balance = web3.utils.fromWei(balance, "ether");
    $("#contract_balance_output").text(balance + " ETH");
    console.log("Betting contract balance: " + balance + " ETH");
  });

  contractInstance.methods.getWin().call().then(function(win) {

    if(win == true) {
      $("#round_output").text("You won the last round.");
      console.log("Previous round win flag: " + win);
    } else {
      $("#round_output").text("You lost the last round.");
      console.log("Previous round win flag: " + win);
    }
  });
}

Betting.sol

pragma solidity 0.5.12;

contract Betting {

    uint public balance;
    bool public win;

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

    modifier deploymentCosts(uint deploymentCost){
        require(msg.value >= deploymentCost, "Deployment costs 10 ether to initialize betting pot!");
        _;
    }

    modifier notEmpty() {
        require(balance > 0, "The pot is empty! Must wait for refill or donations!");
        _;
    }

    constructor() public payable deploymentCosts(10 ether) {
        require(msg.value >= 10 ether);

        balance += msg.value;
        win = false;
    }

    event betPlaced(uint bet, bool result, uint currentBalance);

    //init pot to 10 ether
    function init() public payable costs(10 ether) {
        require(msg.value >= 10 ether);
        balance += msg.value;
        win = false;
    }

    function placeBet() public payable costs(1 ether) notEmpty returns (uint) {
        //minimum bet is 1 ether
        //require(msg.value >= 1 ether);

        balance += msg.value;

        //if random returns 1, user wins, set flag, transfer double of sent bet
        if(random() == 1) {
            win = true;
            balance -= msg.value*2;
            msg.sender.transfer(msg.value*2);

            emit betPlaced(msg.value, win, balance);

            return msg.value*2;

        //if random return 0, user loses, set flag, contract keeps bet
        } else {
            win = false;

            emit betPlaced(msg.value, win, balance);

            return 0;
        }
   }

    function getBalance() public view returns (uint){
        return balance;
    }

    function getSenderAddress() public view returns (address) {
      return msg.sender;
    }

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

    function getWin() public view returns (bool) {
      return win;
    }

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

    //returns pseudo random 1 or 0
    function random() public view returns (uint) {
        return now % 2;
    }
}

Hi @mervxxgotti

When I place a bet using placeBet(), itā€™s working as expected, except for what it returns. In my .sol contract, I have it defined to return 0 or double the bet value in order to keep track of the results on the frontend. However, when I try to console.log() the returned result, I get the following (screenshot). How can I write this so on the frontend I can receive a uint?

The returned value a .send() function is always the receipt.
If you want to receive the uint you can emit an event and catch it with web3.
You can also save the result somewhere and get it with a .call function.

Regarding your 2nd question, please push your code to github and give the url, I will deploy and check :slight_smile:

Regards,
Dani

Thank you @dan-i I figured it out! To update the metamask account balance on the front end I simply have to move the window.ethereum.enable() into the function itself:

function refreshMetamaskBalance() {
  //Get player's Metamask account balance and address
  window.ethereum.enable().then(function(accounts) {

    this.web3.eth.getBalance(accounts[0], (err, balance) => {
      balance = web3.utils.fromWei(balance, "ether");
      $("#metamask_balance_output").text(balance + " ETH");
      console.log("Metamask account balance: " + balance + " ETH");
    });
  });
}

Could you explain to me why is this?

Also - could you elaborate more on how I would ā€œcatchā€ an event with web3 or "save the result and get it with a .call function? I did emit an event but Iā€™m not sure what you mean by catching it. Is this going into the logs to find values of payable functions because payable functions cannot return values?

Iā€™ve finished this phase of the project and here are my screenshots. Thank you so much for all your help!

0 init 1 place bet 5 2 results lose 3 place bet 10 4 results lose 5 place bet 15 6 results win

Hey @mervxxgotti

Regarding catching events, I wrote a basic FAQ that should clarify your doubts :slight_smile:
Check it out and let me know: FAQ - How to listen for events

About save the result and get it with a .call function what I mean is the following, let me give you an example:

pragma solidity 0.7.5;

contract Test {

    struct User {
        uint number;
    }
    
    mapping(address => User) public users;
    
    
    /**
     * Set number in User struct.
     * @param _n The number to save.
     */ 
    function setNumber (uint _n) public returns (uint) {
        users[msg.sender].number = _n;
        return users[msg.sender].number;
    } 
    
    /**
     * Get number in User struct.
     */ 
    function getNumber () public view returns (uint) {
        return users[msg.sender].number;
    }
}

From your front end, you would call:

instance.method.setNumber(10).send();

If you would save the returned value of the call above, you would end up with the transaction receipt.

So I created another function in the contract getNumber that just returns the number.

instance.method.getNumber().call();

You could do:

function test () {
    instance.method.setNumber(10).send();
    const result = instance.method.getNumber().call();
    console.log(result);
}

You should use events anyway, thatā€™s the right path to follow.

Cheers,
Dani

Hi there.

Source code for the project can be found here: https://github.com/nielvosloo/iot-coinflip
And a short video showing itā€™s functionality can be seen here https://drive.google.com/file/d/1_NZKUxjmW_6KproQwBvITvA20nJTheSX/view?usp=sharing

Hello. I would like for you to review my code and tell me what else I need to do

Thanks
Claude

/*--------------------------------------------Flip.sol----------------------------------------------------------*/
import "./Ownable.sol";
pragma solidity 0.5.12;

contract Flip is Ownable{

    uint public balance;

    modifier costs(uint cost){
        require(msg.value >= cost);
        _;
    }

  //  event flipCreated(uint balance, address creator);

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

    // emit flipCreated(balance, msg.sender);
    }

    function flip() public view returns (uint){
      return now % 2;

    }

   function withdrawAll() public onlyOwner returns(uint) {
       uint toTransfer = balance;
       balance = 0;
       msg.sender.transfer(toTransfer);
       return toTransfer;
   }

   function getBalance() public view returns(uint){
        uint personsBalance = balance;
        return personsBalance;
   }

}


/*-----------------------------------------Main.js-----------------------------------------------------------------*/

var web3 = new Web3(Web3.givenProvider);
var contractInstance;
var wager;


$(document).ready(function() {
    window.ethereum.enable().then(function(accounts){
        contractInstance = new web3.eth.Contract(abi, "0x1B40F0090428a9B81FbC5a9c291eAFA4B068F8AC", {from: accounts[0]});
        console.log(contractInstance);
    });

    $("#coinFlip").click(winLose);
    $("#get_wager").click(inputWager);
  });


    function inputWager(){
        wager = $("#wager").val();

        sendCoins(wager);
    }

    function sendCoins(arg1){

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

        contractInstance.methods.createFlip().send(config)
        .on("transactionHash", function(hash){
          console.log(hash);
        })
        .on("confirmation", function(confirmatinoNr){
          console.log(confirmationNr);
        })
        .on("receipt", function(receipt){
          console.log(receipt);
        })
    }


    function winLose(){

      contractInstance.methods.flip().call().then(function(flip){
        var choice = $("input[type='radio'][name='choices']:checked").val();
        console.log(choice);

        if(flip == 1 && choice == "heads"){

            $("#result_output").text("YOU WIN "+ wager +" coins");
            console.log("heads you win :"+ wager);
            //sendCoins(wager);
        }
        else if (flip == 1 && choice != "heads") {
            //lostCoins = contractInstance.methods.withdrawAll().call();
            $("#result_output").text("YOU LOSE " + wager + " coins");
            console.log("heads you lose :"+ wager);
        }
        else if (flip == 0 && choice == "tail") {
            $("#result_output").text("YOU WIN "+ wager +" coins");
            //sendCoins(wager);
            console.log("tail you win :" + wager);
        }
        else if (flip == 0 && choice != "tail"){
          //  lostCoins = contractInstance.methods.withdrawAll().call();
            $("#result_output").text("YOU LOSE " + wager + " coins");
            console.log("tail you lose :"+ wager);
        }

      });


    }
1 Like

Hey @nielvosloo

Why are you using a for loop to generate a binary random number?
You code uses lots of resources (gas) compared to what it is actually doing :slight_smile:
Because you are coding on a blockchain, make sure to keep you code as light as possibile and avoid loops / array and strings as much as you can.

See you in Phase two :slight_smile:

Happy coding,
Dani

1 Like

This is my solution:

1 3

1 Like

Iā€™m having trouble getting this to work in remix. All functions are working except for my coinFlip function.

getting:

[vm]

from: 0x5B3...eddC4

to: CoinFlip.coinFlip() 0x236...b00D8

value: 1000000000000000000 wei

data: 0x5ac...947cb

logs: 0

hash: 0x9c4...4cc5a

Debug
transact to CoinFlip.coinFlip errored: VM error: invalid opcode. invalid opcode The execution might have thrown. Debug the transaction to get more information.

Iā€™ve been unsuccessful at debugging. @filip can you see any obvious errors in my coinFlip logic? What am I missing? My front end is almost totally wired up, I just canā€™t get this function to work!

pragma solidity ^0.5.12;

import "./Ownable.sol";

import "node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";

// Contract executes the following:
// Allows for ETH deposits
// Allows for ETH withdrawals
// Generates a random number 1 or 0 with 50/50 probability
// Executes a simulated coin flip, adding 100% of wager to balance if won, or deducting 100% of wager if lost
// emits Deposit, Withdrawal and Flip events

contract CoinFlip is Ownable {
  using SafeMath for uint;
  // Variables
  uint private balance;
  uint private bet;
  uint private treasury;
  uint private allPlayersBalance;

  constructor() public payable {
    uint initFund = msg.value;
    treasury = treasury.add(initFund);
    assert(treasury == address(this).balance);
  }

  // mappings
  // Tracks the user balances stored in the contract
  mapping (address => uint256) private playerBalances;

  // players array
  address payable[] public players;

  // events
  event Deposit(address user, uint256 amount, uint256 balance);
  event Withdraw(address user, uint256 withdrawAmount, uint256 currentBalance);
  event Bet(address user, uint256 bet, string outcome);
  event Fund(uint value);
  // structs
  
  // modifiers
  modifier costs(uint cost) {
    require(msg.value >= cost, "The minimum bet is 0.001 Ether.");
    _;
  }

  // functions

  // player deposits funds into contract for betting use
  function deposit() public payable returns(uint){        
      playerBalances[msg.sender] = playerBalances[msg.sender].add(msg.value);
      allPlayersBalance = allPlayersBalance.add(msg.value);
      assert(treasury + allPlayersBalance == address(this).balance);
      emit Deposit(msg.sender, msg.value, playerBalances[msg.sender]);
      return playerBalances[msg.sender];
  }

  // owner funds the treasury balance
  function fundTreasury() public payable onlyOwner returns(uint) {
    require(msg.value != 0);
    treasury = treasury.add(msg.value);
    emit Fund(msg.value);
    assert(treasury + allPlayersBalance == address(this).balance);
    return msg.value;
  }

  // querries the player's available balance in the contract
  function playerBalance() public view returns (uint256){
    return playerBalances[msg.sender];
  }

  // the total balance available in the contract treasury, these funds still belong to owner
  function getBalance() public view returns (address, uint) {
    return(address(this), treasury);
  }

  // the total funds in the contract, including all player funds + treasury funds
  function totalBalance() public view returns (address, uint) {
    assert(treasury + allPlayersBalance == address(this).balance);
    return(address(this), address(this).balance);
  }

  // only player can withdraw their own funds, contract owner cannot withdraw or transfer these funds
  function playerWithdraw(uint256 _amount) public payable {
      require(playerBalances[msg.sender] >= _amount);
      playerBalances[msg.sender] = playerBalances[msg.sender].sub(_amount);
      allPlayersBalance = allPlayersBalance.sub(_amount);
      msg.sender.transfer(_amount);
      assert(treasury + allPlayersBalance == address(this).balance);
      emit Withdraw(msg.sender, _amount, playerBalances[msg.sender]);
  }

  // only owner can withdraw the treasury funds
  function withdraw(uint _amount) public onlyOwner returns(uint) {
    require(_amount <= treasury);
    msg.sender.transfer(_amount);
    treasury = treasury.sub(_amount);
    assert(treasury + allPlayersBalance == address(this).balance);
  }
  
  function random() public view returns (uint) {
    return now % 2;
  }

  function coinFlip() public payable costs(0.001 ether) returns(string memory) {
    require(msg.value <= playerBalances[msg.sender]);
    require(msg.value <= treasury);

    uint256 randomNum = random();
    string memory outcome;
    if(randomNum == 1){
      outcome = "win";
      playerBalances[msg.sender] = playerBalances[msg.sender].add(msg.value);
      allPlayersBalance = allPlayersBalance.add(msg.value);
      treasury = treasury.sub(msg.value);

    } else {
      outcome = "lose";
      playerBalances[msg.sender] = playerBalances[msg.sender].sub(msg.value);
      allPlayersBalance = allPlayersBalance.sub(msg.value);
      treasury = treasury.add(msg.value);
    }
    
    assert(treasury + allPlayersBalance == address(this).balance);
    emit Bet(msg.sender, msg.value, outcome);
    return outcome;
  }
}

Hi @jonsax

The error is in your assert statement, double check that one.
I am able to play by:

  • removing the assert;
  • deploy contract with 10 eth;
  • deposit 5 ether so that player balance is updated;
  • play;

@filip @dan-i
Just had to get this information out here, these courses were my introduction to MetaMask, and while using mainly Ganache + Meta, I started using Meta for some ETH on Mainnet, and also Testnet. in one of the courses, Filip showed how to store a .secret file with a seep phrase, little did I know, that the seed phrase was the same for Main- and Test-net, so I pushed the code + the .secret file to GitHub, two days ago, my funds from ETH MainNet was gone, 630 SAND Tokens and about 0.2 ETH.

This is a very expensive way to learn about MetaMask, so I implore you to include some information about this in the courses. I canā€™t remember if there was any explanation on this, if there is, let this still be a lesson that everyone can learn from :sweat_smile:

Hey @voljumet

I am really sorry for what happened, unfortunately there are bots that scan Github waiting for seeds.
We suggest to check the git ignore faq in the description of this video: https://academy.ivanontech.com/products/old-ethereum-smart-contract-programming-201/categories/2004134/posts/6718484

This is the faq I wrote: FAQ - How to .gitignore

Again I can imagine how frustrating that is.

Cheers,
Dani

Thank you, Iā€™m familiar with git and .gitignore, but I was not familiar with MetaMask and how the seed works across all networks, so that was my error here. :sweat_smile:

Hereā€™s a screen record of my dapp. I created it using React/Redux.

-Deposit ETH into contract to ā€œFund Treasuryā€ (only owner)
-Deposit ETH into contract to play the coinFilp game (owner and public)
-Option to choose a username to see your bets tracked on the ā€œLeaderboardā€
-Withdraw ETH from player accounts (only account owners can transfer funds, owner cannot)
-Play with funds using the CoinFlip function inside contract, players can only bet with deposited funds
-A ā€œLeaderboardā€ tracks all bets placed according to ā€œusernameā€ from highest win to highest loss.
-Implemented a CoinFlip animated spinning coin as the ā€œspinnerā€ to show during async function awaits.

Here is a link to the project in action:
https://drive.google.com/file/d/1950Onrnb3frCnF4dNAxGX0-gpOYBoyrD/view?usp=sharing

Let me know what yā€™all think!!

1 Like

Well done I think itā€™s really cool! I also like the leaderboard :smiley:
Ready for phase two, canā€™t wait to see the final result.

Happy coding,
Dani

Iā€™m having some issues with this contract. When I deployed to Kovan, I am only losing. None of my bets are winning at all. Is there an obvious flaw in my code?

  function random() public view returns (uint) {
    return now % 2;
  }

  function coinFlip(uint _amount) public returns(string memory) {
    require(_amount <= playerBalances[msg.sender]);
    require(_amount <= treasury);

    string memory _username = usernames[msg.sender];
    uint256 randomNum = random();
    string memory outcome;

    if(randomNum == 1){
      outcome = "win";
      playerBalances[msg.sender] = playerBalances[msg.sender].add(_amount);
      allPlayersBalance = allPlayersBalance.add(_amount);
      treasury = treasury.sub(_amount);

    } else {
      outcome = "lose";
      playerBalances[msg.sender] = playerBalances[msg.sender].sub(_amount);
      allPlayersBalance = allPlayersBalance.sub(_amount);
      treasury = treasury.add(_amount);
    }

    assert(treasury + allPlayersBalance == address(this).balance);
    emit Bet(msg.sender, _username, _amount, outcome, now);
    return outcome;
  }

Iā€™m wondering if thereā€™s an issue where due to the longer block confirmations, perhaps the ā€œnowā€ time is always at the start of a block, so itā€™s always even? Maybe for testnets the pseudo-randomness doesnā€™t work? Anyone else have this issue??

1 Like

Hey @jonsax

Testnets and mainned blocktime is different than the Javascript VM one.
The Javascript VM mines a block for each transaction you send, the blockchain has instead a random blocktime (around 10 seconds for each block) therefore you might have to wait some time before having different results (win/ loss).

Copy paste this code and run it many times:

pragma solidity 0.6.0; 

contract Game { 
    

    function coinFlip() public view returns(uint) {
      
       return block.timestamp;

    }

   
}

Check the timestamp :slight_smile:

Cheers,
Dani

I am just gonna share the smart contract source here. I created a simple version of betting Dapp: generate random number (ā€˜0ā€™ or ā€˜1ā€™), pay fund to contract, pay fund to player, contract balance, etc

pragma solidity 0.6.12;

contract FlipBet {
    uint public contractBalance;
    
    constructor() public payable {
        sendFundToContract();
    }

    function random() public view returns(uint) {
        return uint(keccak256(abi.encodePacked(block.difficulty, now))) % 2;
    }

    function sendFundToPlayer(uint amt) public {
        require(amt <= contractBalance, "Contract doesn't have enough balance!");
        msg.sender.transfer(amt);
        contractBalance -= amt;
    }

    function sendFundToContract() public payable {
        require(msg.value <= msg.sender.balance, "Player doesn't have enough balance!");
        contractBalance += msg.value;
    }
    
    function getContractBalance() public view returns(uint) {
        return address(this).balance;
    }
}

Here is the deployed contract on rinkeby: https://rinkeby.etherscan.io/address/0x160f0e828909ec4b9f21e5b4d20c6694d48b0c78

You can try to interact with it using remix. Thanks.

1 Like