0

I try to test this contract with Truffle but the test of checkGoalReached function Fail and I do not understand why.

  • The code to reproduce this problem is available here
  • With Remix Solidity IDE, the behavior of the contract is ok
  • TestRPC v3.0.5
  • Truffle v3.2.4

The contract

pragma solidity ^0.4.10;

contract CrowdFunding {
    // Defines a new type with two fields.
    struct Funder {
        address addr;
        uint amount;
    }

    struct Campaign {
        address beneficiary;
        uint fundingGoal;
        uint numFunders;
        uint amount;
        mapping (uint => Funder) funders;
    }

    uint numCampaigns;
    mapping (uint => Campaign) campaigns;

    function newCampaign(address beneficiary, uint goal) returns (uint campaignID) {
        campaignID = numCampaigns++; // campaignID is return variable
        // Creates new struct and saves in storage. We leave out the mapping type.
        campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
    }

    function contribute(uint campaignID) payable {
        Campaign c = campaigns[campaignID];
        // Creates a new temporary memory struct, initialised with the given values
        // and copies it over to storage.
        // Note that you can also use Funder(msg.sender, msg.value) to initialise.
        c.funders[c.numFunders++] = Funder(msg.sender, msg.value);
        c.amount += msg.value;
    }

    function checkGoalReached(uint campaignID) returns (bool reached) {
        Campaign c = campaigns[campaignID];
        if (c.amount < c.fundingGoal)
            return false;
        uint amount = c.amount;
        c.amount = 0;
        c.beneficiary.transfer(amount);
        return true;
    }
}

Javascript Tests

var CrowdFunding = artifacts.require("./CrowdFunding.sol");

contract('CrowdFunding', function(accounts) {

      var account_one = accounts[0];
      var account_two = accounts[2];
      var campaign;
      var id;

        it("should create a new campaign", function() {
          return CrowdFunding.deployed().then(function(instance) {
            campaign = instance;
            return campaign.newCampaign.call(account_two, 2)
            .then(function (campaignID) {
              id = campaignID;
              return campaign.contribute(campaignID, {from: account_one, value: 1});
            })
            .then(function() {
              assert.equal(web3.eth.getBalance(campaign.address).toNumber(), 1, "Balance isn't 1 after one contribution of 1");
              return campaign.checkGoalReached.call(id);
            })
            .then(function(reached) {
                assert.equal(reached, false, "Campaign with goal 2 is reached with balance 1");
            });
        });
      });
    });

Truffle test Result

enter image description here

0

I think you missed something here. In the context of a Blockchain environment, you must consider a method/function which writes data and a method which reads data in a completely different manner.

Read data (call):

That's really easy to read data on the Blockchain, you don't have to go too far, your node already contains the whole blockchain. It's kind of accessing the local storage.

In the Ethereum ecosystem, You will have to write a read-only function that doesn't modify the state (variables).

For example

Solidity

function getCampaignDetails(uint campaignID) constant returns (uint256, uint256) {
    Campaign c = campaigns[campaignID];
    return (c.fundingGoal, c.amount);
}

Web3

CrowdFunding.deployed().then(function(instance) {
    return instance.getDetails.call(id);
 }).then(function(details) {
    console.log(details);
})

I use the keyword constant to enforce the compiler that's a read-only function (optional). And a Web3 call returns a promise that contains the result of the function (see doc).

A call doesn't cost any gas, can't receive ether, is synchronous and the return value of the contract function is returned immediately.

Write data (transaction):

To write data on the blockchain, you have to send a transaction, it's way more complicated because the transaction has to be broadcast on the network, mined, validated and published.

For example

Solidity

function newCampaign(address beneficiary, uint goal)  {
    campaignID = numCampaigns++;
    campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
}

Web3

CrowdFunding.deployed().then(function(instance) {
    return instance.newCampaign(account_two, 2);
}).then(function (transactionHash) {
    console.log(transactionHash); // useful for tracking
})

In this example, the function newCampaign changes the state by adding a new campaign to the map. A transaction is asynchronous, the success of execution depends on the miner so you can't expect any data in return. However, you can send an event to share some data.


So basically, your code can't work because you need this campaignID. I would store the campaign creator and the campaignID in the Campaign struct, build a call function that returns my campaigns (Filter using campaignCreator == msg.sender) and then select a campaign and run your test.

  • Thanks for your detailed response, I have modified the code github.com/vincentlg/projectforreproduce/blob/master/test/… and using event and now the tests are working. The example of the smart contract that simultaneously manages several campaigns is not very practical to use, this version (1 instance of contract = 1 campaign) ethereum.org/crowdsale seems more relevant. – vincentLg May 21 '17 at 21:57
  • 1
    Your solution using event works like a charm. I agree about the disadvantage of a mono-contract, the pattern Contract registy / Contract instances is definitely better and cost effective (in terms of gas consumption) – Greg Jeanmart May 21 '17 at 22:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.