3

I have an ERC20 token MyToken which seems to be working fine. I have another smart contract AirDrop, that is supposed to distribute MyToken. The sendTokens() method takes an array of addresses, and an array of values and distributes values tokens to addresses.

Or at least that's what's supposed to happen. sendTokens() calls sendInternally() which then calls ERC20(token).transfer(). When I run this on truffle, I always get an error with the message: Error: VM Exception while processing transaction: revert.

I used Truffle debug to investigate further and found that the line that does the actual transfer ERC20(token).transfer() throws an error. Stepping through using the debugger, it appears to just get to that line and throw an error for an unknown reason. I am unable to determine a specific reason.

To make sure the problem was not something to do with the transfer method itself, I tried calling MyToken.transferAnyERC20Token() which also uses ERC20(token).transfer(). That works fine over there, but the seemingly same call in the airdrop contract errors out. The AirDrop contract gets initialized with the address of MyToken

I also tried this on both Ganache and Rinkeby with the same results.

I've posted all the code below.:

library SafeMath {

    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }
        uint256 c = a * b;
        assert(c / a == b);
        return c;
    }

    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return a / b;
    }

    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        assert(b <= a);
        return a - b;
    }

    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        assert(c >= a);
        return c;
    }
}

contract ERC20 {
    function totalSupply() public constant returns (uint);
    function balanceOf(address tokenOwner) public constant returns (uint balance);
    function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
    function transfer(address to, uint tokens) public returns (bool success);
    function approve(address spender, uint tokens) public returns (bool success);
    function transferFrom(address from, address to, uint tokens) public returns (bool success);

    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

contract Ownable {
    address public owner;


    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    constructor() public {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    function transferOwnership(address newOwner) public onlyOwner {
        require(newOwner != address(0));
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
    }
}

contract StandardToken is ERC20  {

  using SafeMath for uint256;

  mapping (address => mapping (address => uint256)) internal allowed;

  mapping(address => uint256) public balances;

  uint256 _totalSupply;

  function totalSupply() public view returns (uint256) {
    return _totalSupply;
  }

  function transfer(address _to, uint256 _value) public returns (bool) {
    require(_to != address(0));
    require(_value <= balances[msg.sender]);

    balances[msg.sender] = balances[msg.sender].sub(_value);
    balances[_to] = balances[_to].add(_value);
    emit Transfer(msg.sender, _to, _value);
    return true;
  }

  function balanceOf(address _owner) public view returns (uint256 balance) {
    return balances[_owner];
  }

  function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
    require(_to != address(0));
    require(_value <= balances[_from]);
    require(_value <= allowed[_from][msg.sender]);

    balances[_from] = balances[_from].sub(_value);
    balances[_to] = balances[_to].add(_value);
    allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
    emit Transfer(_from, _to, _value);
    return true;
  }

  function approve(address _spender, uint256 _value) public returns (bool) {
    allowed[msg.sender][_spender] = _value;
    emit Approval(msg.sender, _spender, _value);
    return true;
  }

  function allowance(address _owner, address _spender) public view returns (uint256) {
    return allowed[_owner][_spender];
  }

  function increaseApproval(address _spender, uint _addedValue) public returns (bool) {
    allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue);
    emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
    return true;
  }

  function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) {
    uint oldValue = allowed[msg.sender][_spender];
    if (_subtractedValue > oldValue) {
      allowed[msg.sender][_spender] = 0;
    } else {
      allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
    }
    emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
    return true;
  }

}

Here's the code for MyToken:

contract MyToken is StandardToken, Ownable {
    using SafeMath for uint256;

    uint256 public constant TOTAL_SUPPLY = 10 ** 9;

    string public constant name = "My Token";
    string public constant symbol = "MYT";
    uint8 public constant decimals = 18;

    mapping (address => string) aliases;
    mapping (string => address) addresses;

    constructor() public {
        _totalSupply = TOTAL_SUPPLY * (10**uint256(decimals));
        balances[owner] = _totalSupply;
        emit Transfer(address(0), owner, _totalSupply);
    }

    function availableSupply() public view returns (uint256) {
        return _totalSupply.sub(balances[owner]).sub(balances[address(0)]);
    }

    function approveAndCall(address spender, uint256 tokens, bytes data) public returns (bool success) {
        allowed[msg.sender][spender] = tokens;
        emit Approval(msg.sender, spender, tokens);
        ApproveAndCallFallBack(spender).receiveApproval(msg.sender, tokens, this, data);
        return true;
    }

    function () public payable {
        revert();
    }

    function transferAnyERC20Token(address tokenAddress, uint256 tokens) public onlyOwner returns (bool success) {
        return ERC20(tokenAddress).transfer(owner, tokens);
    }

}

And the airdrop code:

contract AirDrop is Ownable {

  ERC20 public token;
  address public tokenWallet;
  address public tokenAddress;

  event TransferredToken(address indexed to, uint256 value);
  event FailedTransfer(address indexed to, uint256 value);

  constructor(address _tokenAddress, address _tokenWallet) public {
      tokenWallet = _tokenWallet;
      tokenAddress = _tokenAddress;
      token = ERC20(_tokenAddress);
  }

  function sendTokens(address[] destinations, uint256[] values, address _tokenAddress, address _tokenWallet) onlyOwner external {
      require(destinations.length == values.length);
      uint256 i = 0;
      while (i < destinations.length) {
          uint256 toSend = values[i] * 10**18;
          sendInternally(destinations[i], toSend, values[i]);
          i++;
      }
  }

  function sendTokensSingleValue(address[] destinations, uint256 value) onlyOwner external {
      uint256 i = 0;
      uint256 toSend = value * 10**18;
      while (i < destinations.length) {
          sendInternally(destinations[i] , toSend, value);
          i++;
      }
  }

  function sendInternally(address recipient, uint256 tokens, uint256 valueToPresent) internal {
    require(recipient != address(0));
    ERC20(tokenAddress).transfer(recipient, tokens);
  }


  function tokensAvailable() constant returns (uint256) {
    return token.allowance(tokenWallet, msg.sender);
  }

  function destroy() onlyOwner public {
    selfdestruct(owner);
  }
}
  • After initializing AirDrop with the address of MyToken, do you transfer any of the tokens to AirDrop address, so it has something to distribute on its balance? Mind that when you call MyToken.transferAnyERC20Token(), transfer has your address as msg.sender, while in the other case it's the address of AirDrop deployed instance, which may have 0 tokens on its balance. – Utgarda Sep 5 '18 at 22:05
4

I tried calling MyToken.transferAnyERC20Token() which also uses ERC20(token).transfer(). That works fine over there, but the seemingly same call in the airdrop contract errors out.

I'm not 100% sure this is the solution, but the way you described your process hints at some confusion about where the tokens should be.

When you test manually, your externally owned account that signs the transaction needs to have tokens in order to send them. The same goes for the Airdrop. It can't send what it doesn't have.

Did you remember to transfer from the deployer/minter account to the Airdrop contract before instructing the Airdrop contract to send tokens it doesn't have?

In case it isn't clear.

  1. Alice deploys the contracts and Alice (usually) has 100% of the initial supply.
  2. Alice wants the Airdrop contract to distribute tokens, so Alice transfers sufficient tokens to the Airdrop contract.
  3. Alice signs a transaction to the Airdrop contract, instructing it to forward tokens in the Airdrop contract's custody to recipients.

The OpenZeppelin implementation of ERC20 which you appear to be using will throw an exception in the case that the sender (which is the Airdrop contract) has insufficient tokens to draw from for the transfer method.

Hope it helps.

  • This is absolutely right... I forgot to transfer the tokens to the airdrop. The problem was the airdrop contract is the one executing the transfer and does not have the tokens to send. For others who might have this problem in the future, there are 2 options: 1. Either transfer tokens to the AirDrop address, so the airdrop can send tokens. 2. call MyToken.approve(AirDrop.address) from the token holder account to approve the airdrop address to send tokens, then use ERC20(address).transferFrom() instead fo transfer – Inn0vative1 Sep 5 '18 at 21:22
1

This error : VM Exception while processing transaction: revert will occur when these statements are not evaluating to true

require(msg.sender == owner);
require(_to != address(0));
require(_value <= balances[msg.sender]);

and etc... , you have a lot or requires in your contract

you also have revert here:

function () public payable {
    revert();
}

So, it all depends on the input you are giving to the contract. Revert is sort of assert in C/C++, where the program just exists. Same happens here in the EVM, the contract reverts execution. Find which input is invalid and this is going to be the solution to your problem.

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.