"VM Exception while processing transaction: revert" in Truffle test and Truffle dev in Crowdsale

Hey guys, here is my issue !


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.

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 :

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 !

1 Like

Hi @Exctazy,

Welcome to the community :wave:

I assume that the issue is that the test changes the owner of the contract (using transferOwnership but doesn’t transfer any tokens to the crowdsale (using transfer). The crowdsale doesn’t have a supply of tokens to sell.

To see this in action, we can look at the OpenZeppelin Contract tests (using OpenZeppelin Test Environment)


As an aside, your ERC20 token could be simpler (I wasn’t sure of the code in the constructor). See the following example: Help me write an erc20 token and a crowdsale contract

Feel free to ask all the questions that you need.

Hello sir,

Yes, i’m pretty sure it comes from the fact that my Crowdsale contract have no supply has you said. I was expecting to do the transfer from the Token.sol Smartcontract, but in fact, i need to do it in the Test script ? So i can’t make it an automatism ? I will need to transfer manually the supply from the token to the crowdsale after their deployments ?

Thank you really much for your time !

Edit : I’ve tried to input the context part in my test.js, with another describe that allows me to check the balance of the crowdsale and it appears that my crowdsale have 0 after the transfer. How can i check if the token transfer is working ? It should have work since my token contract have a supply.

1 Like

Hi @Exctazy,

When you deploy your token and crowdsale, you can use a single migration script to deploy which includes transferring tokens to the crowdsale.

Another way is to have a deployer contract which does this. There is an example in the documentation for a minted crowdsale:
https://docs.openzeppelin.com/contracts/2.x/crowdsales#minted-crowdsale


To check that the transfer has worked in your test you can check the token balance of the crowdsale.

I have created an example test, token and crowdsale below:

SimpleCrowdsale.test.js

// test/SimpleCrowdsale.test.js

// Load dependencies
const { balance, BN, constants, ether, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');

const { expect } = require('chai');

// Load compiled artifacts
const SimpleToken = artifacts.require('SimpleToken');
const SimpleCrowdsale = artifacts.require('SimpleCrowdsale');

// Start test block
contract('SimpleCrowdsale', function ([owner, beneficiary]) {

  beforeEach(async function () {
    // Deploy a new token and crowdsale contract for each test
    this.token = await SimpleToken.new({ from: owner });
    this.crowdsale = await SimpleCrowdsale.new(1, beneficiary, this.token.address, { from: owner });
    tokenSupply = await this.token.totalSupply();
    this.token.transfer(this.crowdsale.address, tokenSupply);
  });

  // Test case
  it('should have entire token supply', async function () {
    tokenSupply = await this.token.totalSupply();
    expect(await this.token.balanceOf(this.crowdsale.address)).to.be.bignumber.equal(tokenSupply);
  });
});

SimpleToken.sol

pragma solidity ^0.5.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol";

/**
 * @title SimpleToken
 * @dev Very simple ERC20 Token example, where all tokens are pre-assigned to the creator.
 * Note they can later distribute these tokens as they wish using `transfer` and other
 * `ERC20` functions.
 */
contract SimpleToken is Context, ERC20, ERC20Detailed {

    /**
     * @dev Constructor that gives _msgSender() all of existing tokens.
     */
    constructor () public ERC20Detailed("SimpleToken", "SIM", 18) {
        _mint(_msgSender(), 1000000 * (10 ** uint256(decimals())));
    }
}

SimpleCrowdsale.sol

pragma solidity ^0.5.0;

import "@openzeppelin/contracts/crowdsale/Crowdsale.sol";


/**
 * @title SimpleCrowdsale
 * @dev This is an example of a fully fledged crowdsale.
 */
contract SimpleCrowdsale is Crowdsale {
    constructor (
        uint256 rate,
        address payable wallet,
        IERC20 token
    )
        public
        Crowdsale(rate, wallet, token)
    {
    }
}
1 Like

Okay, i was pretty close for the test of supply. I was using console.log and not expect though.

Wait a minute, you have a mint function in the construction of your token. Does it has anything special beside my part where i create and assign a total supply ? and how can you mint without deriving Token from Mintable? Are we forced to use the minting to get a supply of token ?

1 Like

Hi @Exctazy,

The OpenZeppelin Contracts ERC20 implementation has an internal _mint that you should use in your constructor when creating a fixed supply token.

We don’t need to make the token mintable, as _mint is being called only in the constructor so it is only being called once by the deployer of the token.

For more details on why this is done see the documentation on Fixed Supply


I wasn’t sure how in your token you were able to access what in OpenZeppelin Contracts 2.5 are private state variables (_totalSupply and _balances)?

Alright i’ll check that and see if that change anything by using _mint !

I’ve modify the variable to public !

1 Like

Hi @Exctazy,

Modifying OpenZeppelin Contracts is not recommended.

The documentation has the following note:

To keep your system secure, you should always use the installed code as-is, and neither copy-paste it from online sources, nor modify it yourself.

OpenZeppelin Contracts should be extended through inheritance (see the documentation: Extending Contracts)

I have reset as it was the OZ contract by making it private. In fact, i’ve seen in a thread someone saying that you can’t access private variable even through inheritance (he was solving a problem with a totalsupply too) and it was making sense.

Anyway, i’ve found the mint function but i’m forced to use the OZ contract “CappedToken”. It’s the only one to have the _mint fonction. And he is deriving from “MintableToken”. Can i copy / paste the mint function else where ? So i dont have inheritance with mintable ?

1 Like

Hi @Exctazy,

If you want a mintable token you could use the Preset ERC20 token.
Have a look at the following tutorial: