(Day 15 DAO)Unable to access a getter function in a smart contract from my frontend (react)

MacOS Catalina 10.15.3 (19D76)
Truffle v5.1.5 (core: 5.0.40)
Node v12.12.0

Description (code below)
When I create a getter function on my frontend for a variable on the smart contract, I’m unable to receive any data.

Steps to reproduce:

  1. Clone Day 15 DAO frontend into local env
  2. Edit DAO smart contract to include the following code:

Smart Contract
pragma solidity >= 0.5.0 <= 0.7.0;

contract DAO {
  struct Proposal {
    uint id;
    string name;
    uint amount;
    address payable recipient;
    uint votes;
    uint end;
    bool executed;
  }

  mapping(address => bool) public investors;
  mapping(address => uint) public shares;
  mapping(address => mapping(uint => bool)) public votes;
  mapping(uint => Proposal) public proposals;
  uint public totalShares;
  uint public availableFunds;
  uint public contributionEnd;
  uint public nextProposalId;
  uint public voteTime;
  uint public quorum;
  address public admin;

  constructor(
    uint contributionTime,
    uint _voteTime,
    uint _quorum)
    public {
    require(_quorum > 0 && _quorum < 100, 'quorum must be between 0 and 100');
    contributionEnd = now + contributionTime;
    voteTime = _voteTime;
    quorum = _quorum;
    admin = msg.sender;
  }

**// Added getter function as an added exercise**
  function getTotalShares() public view returns(uint) {
    return totalShares;
  }

  function contribute() external payable {
    require(now < contributionEnd, 'cannot contribute after contributionEnd');
    investors[msg.sender] = true;
    shares[msg.sender] += msg.value;
    totalShares += msg.value;
    availableFunds += msg.value;
  }

  function redeemShare(uint amount) external {
    require(shares[msg.sender] >= amount, 'not enough shares');
    require(availableFunds >= amount, 'not enough available funds');
    shares[msg.sender] -= amount;
    availableFunds -= amount;
    msg.sender.transfer(amount);
  }
    
  function transferShare(uint amount, address to) external {
    require(shares[msg.sender] >= amount, 'not enough shares');
    shares[msg.sender] -= amount;
    shares[to] += amount;
    investors[to] = true;
  }

  function createProposal(
    string memory name,
    uint amount,
    address payable recipient) 
    public 
    onlyInvestors() {
    require(availableFunds >= amount, 'amount too big');
    proposals[nextProposalId] = Proposal(
      nextProposalId,
      name,
      amount,
      recipient,
      0,
      now + voteTime,
      false
    );
    availableFunds -= amount;
    nextProposalId++;
  }

  function vote(uint proposalId) external onlyInvestors() {
    Proposal storage proposal = proposals[proposalId];
    require(votes[msg.sender][proposalId] == false, 'investor can only vote once for a proposal');
    require(now < proposal.end, 'can only vote until proposal end date');
    votes[msg.sender][proposalId] = true;
    proposal.votes += shares[msg.sender];
  }

  function executeProposal(uint proposalId) external onlyAdmin() {
    Proposal storage proposal = proposals[proposalId];
    require(now >= proposal.end, 'cannot execute proposal before end date');
    require(proposal.executed == false, 'cannot execute proposal already executed');
    //BUG 1: Because of integer division,
    //the require condition failed even when we had enough votes
    //require((proposal.votes / totalShares) * 100 >= quorum, 'cannot execute proposal with votes # below quorum');
    require(((proposal.votes * 100) / totalShares) >= quorum, 'cannot execute proposal with votes # below quorum');
    //BUG2: Added this missing code, otherwise can execute same proposal twice
    proposal.executed = true;
    _transferEther(proposal.amount, proposal.recipient);
  }

  function withdrawEther(uint amount, address payable to) external onlyAdmin() {
    _transferEther(amount, to);
  }
  
  function _transferEther(uint amount, address payable to) internal {
    require(amount <= availableFunds, 'not enough availableFunds');
    availableFunds -= amount;
    to.transfer(amount);
  }

  //For ether returns of proposal investments
  fallback() external payable {
    availableFunds += msg.value;
  }

  modifier onlyInvestors() {
    require(investors[msg.sender] == true, 'only investors');
    _;
  }

  modifier onlyAdmin() {
    require(msg.sender == admin, 'only admin');
    _;
  }
}
  1. Edit the App.js to include the following code:

App.js
import React, { useEffect, useState } from ‘react’;
import DAO from ‘./contracts/DAO.json’;
import { getWeb3 } from ‘./utils.js’;

function App() {
  const [web3, setWeb3] = useState(undefined);
  const [accounts, setAccounts] = useState(undefined);
  const [contract, setContract] = useState(undefined);
  const [admin, setAdmin] = useState(undefined);
  const [shares, setShares] = useState(undefined);

**// Added totalShares state to support the new function**
  const [totalShares, setTotalShares] = useState(undefined);

  const [proposals, setProposals] = useState([]);

  useEffect(() => {
const init = async () => {
  const web3 = await getWeb3();
  const accounts = await web3.eth.getAccounts();
  const networkId = await web3.eth.net.getId();
  const deployedNetwork = DAO.networks[networkId];
  const contract = new web3.eth.Contract(
    DAO.abi,
    deployedNetwork && deployedNetwork.address,
  );

  const admin = await contract.methods
    .admin()
    .call();

  setWeb3(web3);
  setAccounts(accounts);
  setContract(contract);
  setAdmin(admin);
}
init();
window.ethereum.on('accountsChanged', accounts => {
  setAccounts(accounts);
});
  }, []);

  const isReady = () => {
return (
  typeof contract !== 'undefined' 
  && typeof web3 !== 'undefined'
  && typeof accounts !== 'undefined'
  && typeof admin !== 'undefined'
);
  }

  useEffect(() => {
if(isReady()) {
  updateShares();
  updateProposals();
}
  }, [accounts, contract, web3, admin]);
  
  async function updateShares() {
const shares = await contract.methods
  .shares(accounts[0])
  .call();
setShares(shares);
  }

**// Added function to retrieve total shares** 
 async function updateTotalShares() {
const totalShares = await contract.methods
  .getTotalShares()
  .call();
setTotalShares(totalShares);
  }

  async function updateProposals() {
const nextProposalId = parseInt(await contract.methods
  .nextProposalId()
  .call());

const proposals = [];
for(let i = 0; i < nextProposalId; i++) { 
  const [proposal, hasVoted] = await Promise.all([
    contract.methods.proposals(i).call(),
    contract.methods.votes(accounts[0], i).call()
  ]);
  proposals.push({...proposal, hasVoted});
}
setProposals(proposals);
  }

  async function executeProposal(proposalId) {
await contract.methods
  .executeProposal(proposalId)
  .send({from: accounts[0]});
await updateProposals();
  };

  async function withdrawEther(e) {
e.preventDefault();
const amount = e.target.elements[0].value;
const to = e.target.elements[1].value;
await contract.methods
  .withdraw(amount, to)
  .send({from: accounts[0]});
  };

  async function contribute(e) {
e.preventDefault();
const amount = e.target.elements[0].value;
await contract.methods
  .contribute()
  .send({from: accounts[0], value: amount});
await updateShares();
await updateTotalShares();
  };

  async function redeemShares(e) {
e.preventDefault();
const amount = e.target.elements[0].value;
await contract.methods
  .redeemShares(amount)
  .send({from: accounts[0]});
await updateShares();
await updateTotalShares();
  };

  async function transferShares(e) {
e.preventDefault();
const amount = e.target.elements[0].value;
await contract.methods
  .redeemShares(amount)
  .send({from: accounts[0]});
await updateShares();
await updateTotalShares();
  };

  async function vote(ballotId) {
await contract.methods
  .vote(ballotId)
  .send({from: accounts[0]});
await updateProposals();
  };

  async function createProposal(e) {
e.preventDefault();
const name = e.target.elements[0].value;
const amount = e.target.elements[1].value;
const recipient = e.target.elements[2].value;
await contract.methods
  .createProposal(name, amount, recipient)
  .send({from: accounts[0]});
await updateProposals();
  };

  function isFinished(proposal) {
const now = new Date().getTime();
const proposalEnd =  new Date(parseInt(proposal.end) * 1000);
return (proposalEnd > now) > 0 ? false : true;
  }

  if (!isReady()) {
return <div>Loading...</div>;
  }

return (

DAO

My Shares: {shares}

**// Added totalShares to the DOM **

Total Shares: {totalShares}

**totalShares is not displayed. At the very least, I expect a zero value.**

{accounts[0].toLowerCase() === admin.toLowerCase() ? (
<>



Withdraw ether


<form onSubmit={e => withdrawEther(e)}>

Amount



To


Submit



) : null}

Contribute

contribute(e)}>
Amount
Submit

Redeem shares

redeemShares(e)}>
Amount
Submit

Transfer shares

transferShares(e)}>
Amount
Submit

Create proposal

createProposal(e)}>
Name
Amount
Recipient
Submit

Proposals

{proposals.map(proposal => ( ))}
Id Name Amount Recipient Votes Vote Ends on Executed
{proposal.id} {proposal.name} {proposal.amount} {proposal.recipient} {proposal.votes} {isFinished(proposal) ? 'Vote finished' : ( proposal.hasVoted ? 'You already voted' : ( vote(proposal.id)} type="submit" className="btn btn-primary"> Vote ))} {(new Date(parseInt(proposal.end) * 1000)).toLocaleString()} {proposal.executed ? 'Yes' : ( admin.toLowerCase() === accounts[0].toLowerCase() ? ( executeProposal(proposal.id)} type="submit" className="btn btn-primary"> Execute ) : 'No' )}
); }

export default App;

Expected: Total Shares is displayed (3100)
Actual: Value is null

If you want an initial value of 0 for totalShares, you need to initialize the state like this:

const [totalShares, setTotalShares] = useState(0);

Then, have you called contribute() with a non-zero value for amount? totalShares is incremented only after.

You can put a console.log statement in updateTotalShares() to help for debugging:

 async function updateTotalShares() {
  const totalShares = await contract.methods
    .getTotalShares()
    .call();
  console.log('updateTotalShares()');
  console.log(totalShares);
  setTotalShares(totalShares);
  }
1 Like