[Ethereum] “VM Exception while processing transaction: revert” in Truffle test and Truffle dev

crowdsaleerc-20ganache-clitokenstruffle

this is my problem 🙂

Issue

"Error: Returned error: VM Exception while processing transaction: revert" when reaching the Transfer function. I think it is related to the fact that my Crowdsale smart contrat hasn't any TotalSupply of my tokens.

Steps to Reproduce

Token Smart contract :

    pragma solidity   0.5.16; 

    import "openzeppelin-solidity/contracts/token/ERC20/DetailedERC20.sol";
    import "openzeppelin-solidity/contracts/token/ERC20/PausableToken.sol";

    contract TokenSPC is PausableToken, DetailedERC20 
    {

        constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 _amount)
        DetailedERC20(_name, _symbol, _decimals)
        public
        {

            require(_amount > 0, "amount has to be greater than 0");
            totalSupply_ = _amount.mul(10 ** uint256(_decimals));
            balances[msg.sender] = totalSupply_;
            emit Transfer(address(0), msg.sender, totalSupply_);


        }
    } 

Crowdsale Smart Contract :


    pragma solidity 0.5.16;

    import './TokenSPC.sol';
    import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";
    import "openzeppelin-solidity/contracts/token/ERC20/PausableToken.sol";
    import "openzeppelin-solidity/contracts/crowdsale/Crowdsale.sol";
    import "openzeppelin-solidity/contracts/crowdsale/validation/CappedCrowdsale.sol";


    contract TokenCrowdsale is Crowdsale, CappedCrowdsale {

      //Minim invest contrib
      //Max invest contrib
      uint256 public investorMinCap = 2000000000000000;
      uint256 public investorHardCap = 50000000000000000000;
      mapping(address => uint256) public contributions;

      constructor(
        uint256 _rate,
        address payable _wallet,
        ERC20 _token,
        uint256 _cap
       )
      Crowdsale(_rate, _wallet, _token)
      CappedCrowdsale(_cap)
      public
      {

      }

      function getUserContribution(address _beneficiary)
      public view returns(uint256)
      {
        return contributions[_beneficiary];
      }

      function _preValidatePurchase(
        address _beneficiary, 
        uint256 _weiAmount
     )
     internal
     {
        super._preValidatePurchase(_beneficiary,_weiAmount);
        uint256 _existingContribution = contributions[_beneficiary];
        uint256 _newContribution = _existingContribution.add(_weiAmount);
        require(_newContribution >= investorMinCap && _newContribution <= investorHardCap);
        contributions[_beneficiary] = _newContribution;
     } 
   }

Test Crowdsale.test :

import ether from './helpers/ether';
import sendTransaction from './helpers/sendTransaction';
import EVMRevert from './helpers/EVMRevert';

const BN = web3.utils.BN;

require('chai')
  .use(require('chai-as-promised'))
  .use(require('chai-bn')(BN))
  .should();

const Token = artifacts.require('TokenSPC');
const TokenCrowdsale = artifacts.require('TokenCrowdsale');

contract('TokenCrowdsale', function([_, wallet, investor1, investor2]) {

  /*before(async function() {
    // Transfer extra ether to investor1's account for testing
    await web3.eth.sendTransaction({ from: _, to: investor1, value: ether(60) })
    await web3.eth.sendTransaction({ from: _, to: investor2, value: ether(20) })
  });*/

  beforeEach(async function () {
    // Token config
    this.name = "Seed Project Coin";
    this.symbol = "SPC";
    this.decimals = 18;
    this.amount = 380000000;


    // Deploy Token
    this.token = await Token.new(
      this.name,
      this.symbol,
      this.decimals,
      this.amount
    );

    // Crowdsale config
    this.rate = new BN(500);
    this.wallet = wallet;
    this.cap = ether(100);

    //Invest cap
    this.investorMinCap = ether(0.002);
    this.investorHardCap = ether(50);

    this.crowdsale = await TokenCrowdsale.new(
      this.rate,
      this.wallet,
      this.token.address,
      this.cap


    );

    // Transfer token owern to crowdsale
    await this.token.transferOwnership(this.crowdsale.address);


  });

  describe('token', function() {
    it("should check totalSupply", async function() {
      const _supp = await this.token.totalSupply();
      console.log( "     ", "totalSupply =", _supp.toString());
    });

  });

  describe('crowdsale', function() {
    it('tracks the rate', async function() {
      const _rate = await this.crowdsale.rate();
      //console.log( "     ", "Rate =", _rate );
      //console.log( "     ", "this.rate =", this.rate );
      _rate.should.be.a.bignumber.that.equals(this.rate);
    });

    it('tracks the wallet', async function() {
      const wallet = await this.crowdsale.wallet();
      wallet.should.equal(this.wallet);
    });

    it('tracks the token', async function() {
      const token = await this.crowdsale.token();
      token.should.equal(this.token.address);
    });
  });

  //A revoir---------------------------------------------
 /* describe('actualization crowdsale', function() {
    it('actualize total supply of crowdsale after purchase', async function() {
      const originalTotalSupply = await this.token.totalSupply();
      this.token.totalSupply_ -= 1;
      const newTotalSupply = await this.token.totalSupply();
      assert.isTrue(newTotalSupply < originalTotalSupply)
    });
  });*/

  describe('capped crowdsale', async function() {
    it('has the correct hard cap', async function() {
      const _cap = await this.crowdsale.cap();
      _cap.should.be.a.bignumber.that.equals(this.cap);
    });
  });

  //A revoir ---------------------------------------------
  /*describe('accepting payments', function() {
    it('should accept payments', async function() {
      const value = ether(1);
      const purchaser = investor2;
      await this.crowdsale.sendTransaction({ value : value, from : investor1}).should.be.fulfilled;
      await this.crowdsale.buyTokens(investor1, { value: value, from: purchaser }).should.be.fulfilled;
    });
  });*/

  describe('buyTokens()', function() {
    describe('when the contrib is less than min cap', function(){
      it('rejects the transaction', async function() {
        const value = this.investorMinCap - 1;
        await this.crowdsale.buyTokens(investor2, { value: value, from: investor2 }).should.be.rejectedWith(EVMRevert);
      });
    });

     describe('when the invest has already met the min cap', function(){
      it('allows the invest to contrib below the min cap', async function() {
        //isvalid
        const value1 = ether(1);


        await this.crowdsale.buyTokens(investor1, { value: value1, from: investor1 });
        console.log( "     ", "inv =", investor1 );
        console.log( "     ", "value =", value1 );
        console.log( "     ", "inv.value =", await this.crowdsale.buyTokens(investor1, { value: value1, from: investor1 }) );
        //is less then invest cap
        const value2 = 1; //wei
        await this.crowdsale.buyTokens(investor1, { value: value2, from: investor1 }).should.be.fulfilled;

      });
    });

  });

/*---------------A revoir
  describe('when the total contrib exceed the invest hardcap', function(){
    it('reject the transaction', async function() {
      //first contrib in valid range
      const value1 = ether(2);
      await this.crowdsale.buyTokens(investor1, { value: value1, from: investor1});

      //second is over hardcap
      const value2 = ether(49);
      await this.crowdsale.buyTokens(investor1, { value: value2, from: investor1}).should.be.rejectedWith(EVMRevert);
    });
  });

  describe('when the contrib is within the valid range', function() {
    const value = ether(2);
    it('succeeds & updates the contrib amount', async function() {
      await this.crowdsale.buyTokens(investor2, { value: value, from: investor2 }).should.be.fulfilled;
      const contribution = await this.crowdsale.getUserContribution(investor2);
      contribution.should.be.bignumber.equals;
    });
  });
*/

});

Deployement script :


const Token = artifacts.require("./TokenSPC.sol");
const TokenCrowdsale = artifacts.require("./TokenCrowdsale.sol");

const ether = (n) => new web3.utils.BN(web3.utils.toWei(n.toString(), 'ether'));

const duration = {
  seconds: function (val) { return val; },
  minutes: function (val) { return val * this.seconds(60); },
  hours: function (val) { return val * this.minutes(60); },
  days: function (val) { return val * this.hours(24); },
  weeks: function (val) { return val * this.days(7); },
  years: function (val) { return val * this.days(365); },
};

module.exports = async function(deployer, network, accounts) {
  const _name = "Seed Project Coin";
  const _symbol = "SPC";
  const _decimals = 18;
  const _amount = 380000000;

  await deployer.deploy(Token , _name, _symbol, _decimals, _amount );
  const deployedToken = await Token.deployed();



  const _rate           = 1;
  const _wallet         = accounts[0]; // TODO: Replace me
  const _token          = deployedToken.address;
  const _cap            = ether(100);

  await deployer.deploy(
    TokenCrowdsale,
    _rate,
    _wallet,
    _token,
    _cap

  );

  return true;
};

First method to have the error :

Launch ganache-cli via cmd : ganache-cli

Compile : truffle compile

Launch test : truffle test ./test/TokenCrowdsale.test.js

Second method to have the error :

Launch ganache-cli via cmd : ganache-cli

Compile : truffle compile

Migrate : truffle migrate

Use console : truffle console

Commands in truffle console :

    - TokenSPC.deployed().then(instance => token = instance)
    - TokenCrowdsale.deployed().then(instance => crowdsale = instance)

    - web3.eth.getAccounts().then(function(acc){ accounts = acc }) 
    -  var tokenamount = 100 * 10**18 
    - token.transfer(accounts[1], tokenamount.toString())

    - crowdsale.buyTokens(accounts[1], { from: accounts[1], value: 10000000000000000000 })

Expected Behavior

The Crowdsale smart contract have a fixed totalSupply, i don't want any minted token. So i expect the crowdsale to do the transactions to buy the token and transfer it to the user.

Actual Results

2


1


Sans titre

Environment

  • Operating System: Windows 10
  • Ethereum client: Ganache-cli => v6.9.1, Ganache-core => v2.10.2
  • Truffle version (truffle version): v5.1.20
  • node version (node --version): v12.16.1
  • npm version (npm --version): v6.13.4

More Informations

I'm following a tutorial from DappUniversity : Real World Ico to help me building my smart contract functionnalities. I've done a lot of research about a lot of errors that i've encountered and almost find everytime a solution. But the VM Error is crap and doesn't tell anything. Also, i didn't find any issue similar to mine. There's only one topic pointing out the fact that most of the time if it's not out-of-gas issue, it's because we don't have any token to buy, and that's true for my part. I really think it's because i don't have any totalSupply in my crowdsale so he can't buy / transfer / ect… and i'm stuck because i can't find a tutorial or topic explaing how to have a fixed total supply. Actually there is one really sticking to my situation which is that topic :

Crowdsale Tokens

But he is creating the token contract with the help of the crowdsale contract, and i'm not doing this neither i want to. I need to find a way to transfer the totalSupply from the token contract to the crowdsale contract, and to test it with truffle test.

Thanks a lot for your help peeps !

Best Answer

Your crowdsale contract has no tokens to transfer to the buyer.

You transfer ownership of the Token Contract in your test file, but the total supply is still in control of the Token Contract deployer address . From the looks of it should be your account[0].

After both your contract have been instantiated, you will need to do something like:

await this.token.transfer(desiredAmountOfTokens, this.crowdsale.address)

Related Topic