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);
}
}
AirDrop
with the address ofMyToken
, do you transfer any of the tokens toAirDrop
address, so it has something to distribute on its balance? Mind that when you callMyToken.transferAnyERC20Token()
,transfer
has your address asmsg.sender
, while in the other case it's the address ofAirDrop
deployed instance, which may have 0 tokens on its balance.