Issue with ERC20 Minting in mintable Crowdsale: caller does not have the Minter role

Hi,

I have got an issue when I try to mint an ERC20Mintable (GoodLifeToken) token via a MintedToken Sale (GLTMerchantSale).
When I am trying to execute tests implemented in Javascript I am always encountering the following error:
revert MinterRole: caller does not have the Minter role – Reason given: MinterRole: caller does not have the Minter role.

:computer: Environment

Truffle v5.0.24 (core: 5.0.24)
Solidity v0.5.0 (solc-js)
Openzeppelin v2.3.0

:memo:Details
I am assuming that the root cause is the changing msg.sender in contract ERC20Mintable:

contract ERC20Mintable is ERC20, MinterRole {
event MinterCheckedForMintable(address minterApplicant);
function mint(address account, uint256 amount) public onlyMinter returns (bool) {
emit MinterCheckedForMintable(msg.sender); //Added by myself (MarkusM)
_mint(account, amount);
return true;
}
}

Because as you can see here, msg.sender has got the address of accounts[0] (0x14fd85a2aB05Dbb2316f0C9AE283419e396BEB31) in contract GLTMerchantSale (Event TokensPurchased) and MintedCrowdsale (Event LogAddressTokenInCrowdsale):

TokensPurchased(purchaser: 0x14fd85a2aB05Dbb2316f0C9AE283419e396BEB31 (address), beneficiary: 0x7ffC5baA8Ee212d3B46906BCaeeA86Fc746076ee (address), value: 10000000000000000000 (uint256), amount: 200000 (uint256))
LogAddressTokenInCrowdsale(beneficiary: 0x7ffC5baA8Ee212d3B46906BCaeeA86Fc746076ee (address), tokenAmount: 200000 (uint256), tokenAddressInCrowdsale: 0x38eAfAe92E0Da0f3BB7f679415f312Bf4A1f5C79 (address), msgsender: 0x14fd85a2aB05Dbb2316f0C9AE283419e396BEB31 (address))
This account (accounts[0]) is registered for the MinterRole and also owns the contracts GMTMerchantSale and GoodLifeToken.

In contract ERC20Mintable “msg.sender” has suddenly changed to a completely different address:
MinterCheckedForMintable(minterApplicant: 0xd50D89cA5359d6003d95E62097A5F4cF4f75CF72 (address))
Do you have any why msg.sender has changed to this address, which belongs to a contract, that was created during the Truffle test run?

:1234: Code to reproduce
The migration Code of the ERC20Mintable-Token (GoodLifeToken) and the MintedCrowdsale (GLTMerchantSale) looks as follows:

module.exports = async function(deployer, network, accounts) {
    var crowdSale;
    const ethRate = new BigNumber(50000000000000); //1 EUR Cent
    const wallet = accounts[0];
    deployer.deploy(GoodLifeToken, { from: accounts[0] }).then(() => {
      return deployer.deploy(GLTMerchantSale,
                             ethRate,
                             wallet, 
                             GoodLifeToken.address,
                             { from: accounts[0] });
    }).then(inst => {
       crowdSale = inst;
       crowdSale.token().then(async(addr) => { tokenAddress = addr;
            var goodLifeTokenInstance = await GoodLifeToken.at(tokenAddress);
                  goodLifeTokenInstance.addMinter(crowdSale.address)});
               
    });  

This is the implementation of the Truffle Javascript test:

let GLTMerchantSale = artifacts.require(‘GLTMerchantSale’)
let GoodLifeToken = artifacts.require(‘GoodLifeToken’)

contract(‘GLTMerchantSale’, function(accounts) {

const owner = accounts[0]
const wallet = accounts[1]
const merchant = accounts[2]
const rate = "50000000000000"

let merchantSale

beforeEach(async () => {
    var crowdsale = await GLTMerchantSale.deployed({from: accounts[0]});
    console.log("Crowdsale: ")
    console.log(crowdsale.address)
    //crowdsale.token().then(addr => { tokenAddress = addr } )
    var tokenAddress = await crowdsale.token({from: accounts[0]})
    console.log("Token Address: ")
    console.log(tokenAddress)
    merchantSale = await GLTMerchantSale.new(rate, wallet, tokenAddress, {from: accounts[0]})

})

it("should raise an amount of wei by purchachsing a given amount of EUR", async() => {
    const amountInEUR = 2000
    await merchantSale.buyTokens(merchant, amountInEUR, {from: accounts[0]})
    var weiRaised = await merchantSale.weiRaised.call(, {from: accounts[0]})
    var expected = amountInEUR * 50000000000000 * 100 
    assert.equal(weiRaised, expected, "WeiRaised should be 1E+19")
})

The invocation chain looks as follows:

  1. merchantSale.buyTokens
  2. MintedCrowdsale.deliverTokens
  3. ERC20Mintable.mint,
  4. MinterRole.onlyMinter, which checks if “msg.sender” is already registered as Minter. Since this not the case for the provided msg.sender the test runs finally into the error as depicted above („… caller does not have the Minter role…“)

Definition of GLTMerchantSale:

contract GLTMerchantSale is MintedCrowdsale, Ownable, MerchantRole {
….
}

Definition of GoodLifeToken:

contract GoodLifeToken is ERC20, ERC20Detailed, ERC20Mintable, ReentrancyGuard, Ownable, CustomerRole {

}

Is my assumption of the root cause stated above from your point of view correct?
If so, could you please tell me, why the address of msg.sender in ERC20Mintable changed to a different one and how to fix this behavior?

Thanks a lot for your support!

Markus

1 Like

Hi @MarkusM,

Welcome to the community :wave:

The issue in the test is that merchantSale doesn’t have the Minter role.
First the test get’s the deployed crowdsale, then gets the token from this crowdsale and finally creates a new crowdsale using this token, but the new crowdsale isn’t given the Minter role.

beforeEach(async () => {
    var crowdsale = await GLTMerchantSale.deployed({from: accounts[0]});
    console.log("Crowdsale: ")
    console.log(crowdsale.address)
    //crowdsale.token().then(addr => { tokenAddress = addr } )
    var tokenAddress = await crowdsale.token({from: accounts[0]})
    console.log("Token Address: ")
    console.log(tokenAddress)
    merchantSale = await GLTMerchantSale.new(rate, wallet, tokenAddress, {from: accounts[0]})

})

I suggest that in the beforeEach in the test, create a new token, a new crowdsale, and then add the crowdsale as a minter.

Have a look at the MintedCrowdsale.test.js to see how to do this:

Hey Andrew,

thanks a lot for your very helpful reply!!
This is indeed the solution!

I should have posted my question earlier in this forum, although I learned a lot, but it would have saved me lot of debugging efforts.

Thanks
Markus

1 Like

Hi @MarkusM,

Questions are great, as the questions and answers help build the community knowledge base now and in the future. If one person has a problem, then it is more than likely other people will too.

So feel free to ask all the questions that you need (and provide answers to others too).