Solidity Struct – Why is My Struct Not Updating Using Ethers?

ethers.jshardhatsoliditystruct

I have an array of struct Campaign. The function launch adds a new Campaign to the array. After that when a user sends some ETH using the pledge function, the transaction succeeds and the eth gets sent but the struct doesn't get updated. I'm using alchemy and ethers

Solidity

pragma solidity 0.8.10;

import "./ERC20.sol";
import "hardhat/console.sol";

contract Crowdfund is ERC20 {

    event Launch(
        uint id,
        address indexed creator,
        uint goal,
        uint32 startAt,
        uint32 endAt
    );

    event Cancel(uint id);
    event Pledge(uint indexed id, address indexed caller, uint amount);
    event Unpledge(uint indexed id, address indexed caller, uint amount);
    event Claim(uint id);
    event Refund(uint indexed id, address indexed caller, uint amount);
    event Log(uint amount, uint gas);

    struct Campaign {
        address creator;
        uint numberOfInvestors;
        uint goal;
        uint pledged;
        uint32 startAt;
        uint32 endAt;
        bool claimed;
    }

    IERC20 public immutable token;
    address public immutable tokenAddress;
    uint public count;
    mapping(uint => Campaign) public campaigns;
    mapping(uint => mapping(address => uint)) public pledgedAmount;

    constructor(address _token) {
        token = IERC20(_token);
        tokenAddress = _token;
    }

    receive() external payable {
        emit Log(msg.value, gasleft());
    }

    function launch(
        uint _goal,
        uint32 _startAt,
        uint32 _endAt
    ) external returns (uint) {
        require(_startAt >= block.timestamp, "Start at < now");
        require(_endAt >= _startAt, "end at < start at");
        require(_endAt <= block.timestamp + 90 days, "end at > max duration");
        count += 1;
        Campaign storage c = campaigns[count];
        c.creator = msg.sender;
        c.numberOfInvestors = 0;
        c.goal = _goal;
        c.pledged = 0;
        c.startAt = _startAt;
        c.endAt = _endAt;
        c.claimed = false;
        emit Launch(count, msg.sender, _goal, _startAt, _endAt);
        return count;
    }

    function cancel(uint _id) external {
        Campaign memory campaign = campaigns[_id];
        require(msg.sender == campaign.creator, "Not creator");
        require(block.timestamp < campaign.startAt, "started");
        delete campaigns[_id];
        emit Cancel(_id);
    }

    function pledge(uint _id, uint _amount) external payable {
        Campaign storage campaign = campaigns[_id];
        require(_amount > 0, "Amount should be bigger than 0");

        if (pledgedAmount[_id][msg.sender] == 0) {
            campaign.numberOfInvestors += 1;
        }

        (bool success, ) = address(this).call{value: _amount}("");
        require(success, "Call failed");

        campaign.pledged += _amount;
        pledgedAmount[_id][msg.sender] += _amount;
        //token.transferFrom(msg.sender, address(this), _amount);
        
        emit Pledge(_id, msg.sender, _amount);
    }

    function unpledge(uint _id, uint _amount) external {
        Campaign storage campaign = campaigns[_id];
        require(block.timestamp <= campaign.endAt, "Ended");

        (bool success, ) = msg.sender.call{value: _amount}("");
        require(success, "Call failed");

        campaign.pledged -= _amount;
        pledgedAmount[_id][msg.sender] -= _amount;
        //token.transfer(msg.sender, _amount);

        emit Unpledge(_id, msg.sender, _amount);
    }

    function claim(uint _id) external payable {
        Campaign storage campaign = campaigns[_id];
        require(msg.sender == campaign.creator, "Not creator");
        require(block.timestamp > campaign.endAt, "Not ended");
        require(campaign.pledged >= campaign.goal, "pledge < goal");
        require(!campaign.claimed, "Claimed");

        (bool success, ) = msg.sender.call{value: campaign.pledged}("");     
        require(success, "Call failed");
        campaign.claimed = true;
        //token.transferFrom(msg.sender, campaign.pledged);
        

        emit Claim(_id);
    }

    function claimShares(uint _id) external {
        require(pledgedAmount[_id][msg.sender] > 0, "Nothing pledged");
        //uint amountDue = pledgedAmount[_id][msg.sender] * 100 / campaigns[_id].goal;
        token.sendToAddress(address(this), msg.sender, pledgedAmount[_id][msg.sender] / 10**16);
    }

    function getTokens(uint amount) external payable {
        token.sendToContract(address(this), amount);
    }

    function refund(uint _id) external {
        Campaign storage campaign = campaigns[_id];
        require(block.timestamp > campaign.endAt, "Not ended");
        require(campaign.pledged < campaign.goal, "pledged < goal");

        uint bal = pledgedAmount[_id][msg.sender];
        (bool success, ) = msg.sender.call{value: bal}("");
        require(success, "Call failed");

        pledgedAmount[_id][msg.sender] = 0;
        //token.transfer(msg.sender, bal);
        
        emit Refund(_id, msg.sender, bal);
    }




}

Javascript

import env from "react-dotenv";
import {BigNumber, ethers} from 'ethers';
import axios from "axios";
const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY;
const { createAlchemyWeb3 } = require("@alch/alchemy-web3");
const web3 = createAlchemyWeb3(alchemyKey);
const contract_abi = require("./crowdfund-abi.json");
const {CROWDFUND_ADDRESS, ETHERSCAN_KEY} = env;

console.log(CROWDFUND_ADDRESS);
export const CrowdfundContract = new web3.eth.Contract(
  contract_abi,
  CROWDFUND_ADDRESS
);


export const getCampaignsCount = async () => {
  const count = await CrowdfundContract.methods.count().call();
  return count;
}


export const connectWallet = async () => {
   if (window.ethereum) {
      try {
        const addressArray = await window.ethereum.request({
          method: "eth_requestAccounts",
        });
        return addressArray
        
      } catch (err) {
        return err
      }
    }
};

export const pledgedAmount = async (id, address) => {
  const res = await CrowdfundContract.methods.pledgedAmount(id, address).call();
  return res
}

export const pledgeAmount = async (id, amount, address) => {
  const total = amount / (10 ** 18);
  const value = total.toString();
 const transactionParameters = {
    to: CROWDFUND_ADDRESS,
    from: address,
    data: CrowdfundContract.methods.pledge(id, amount).encodeABI({from: address}),
    value: ethers.utils.parseEther(value).toHexString()
  }

  try {
    const txHash = await window.ethereum.request({
      method: "eth_sendTransaction",
      params: [transactionParameters]
    })

    const interval = setInterval(function() {
      console.log("Attempting to get transaction receipt...");
      web3.eth.getTransactionReceipt(txHash, async function(err, rec) {
        if (rec) {
          console.log(rec);
          clearInterval(interval);
        }
        if (err) {
          console(err);
          clearInterval(interval);
        }
      });
    }, 1000) 

  } catch (err) {
    console.log(err.message);
  }
}

export const addCampaign = async (goal, startAt, endAt, address, campaign) => {
  const transactionParameters = {
    to: CROWDFUND_ADDRESS,
    from: address,
    data: CrowdfundContract.methods.launch(goal, startAt, endAt).encodeABI()
  }

 try {
    const txHash = await window.ethereum.request({
       method: "eth_sendTransaction",
       params: [transactionParameters]
    });
    const interval = setInterval(function() {
      console.log("Attempting to get transaction receipt...");
      web3.eth.getTransactionReceipt(txHash, async function(err, rec) {
        if (rec) {
          console.log(rec);
          await axios.post("http://localhost:8080/addCampaign", campaign)
          .then((res) => {
            console.log(res.response);
          })
          .catch((error) => {
            console.log(error);
          });
          clearInterval(interval);
        }
      });
    }, 1000)    
 } catch (err) {
    console.log(err.message)
 }
}

export const getCampaignDetails = async (id) => 
{
  const res = await CrowdfundContract.methods.campaigns(id).call();
  return res;
}

The pledge function is supposed to update the pledged property and the numberOfInvestors property.

Am I calling the function incorrectly using ethers or is it a solidity issue?

Best Answer

Thanks for clarifying for me, it looks like you are incrementing numberOfInvestors in this case

require(_amount > 0, "Amount should be bigger than 0");

if (pledgedAmount[_id][msg.sender] == 0) {
    campaign.numberOfInvestors += 1;
}

To me this says that if the pledged amount is 0 then you will add to the numberOfInvestors uint. Should it say

if (pledgedAmount[_id][msg.sender] > 0){
    campaign.numberOfInvestors += 1;
}

instead? This way it increments the value when the

require(_amount > 0, "Amount should be bigger than 0");

line is passed. It should also probably go after this line

pledgedAmount[_id][msg.sender] += _amount;

so that the pledged amount is set for the id before the function calls it? Let me know if this helps.