La transferencia ERC20 se revierte cuando se llama desde un contrato inteligente

Tengo un token ERC20 MyTokenque parece funcionar bien. Tengo otro contrato inteligente AirDrop, que se supone que distribuye MyToken. El sendTokens()método toma una matriz de direcciones y una matriz de valores y distribuye valuestokens a addresses.

O al menos eso es lo que se supone que debe pasar. sendTokens()llama sendInternally()que luego llama ERC20(token).transfer(). Cuando ejecuto esto en truffle, siempre aparece un error con el mensaje: Error: VM Exception while processing transaction: revert.

Utilicé la depuración de Truffle para investigar más a fondo y descubrí que la línea que realiza la transferencia real ERC20(token).transfer()genera un error. Al pasar por el uso del depurador, parece que solo llega a esa línea y arroja un error por un motivo desconocido. No puedo determinar una razón específica.

Para asegurarme de que el problema no tuviera nada que ver con el transfermétodo en sí, intenté llamar, MyToken.transferAnyERC20Token()que también usa ERC20(token).transfer(). Eso funciona bien allí, pero la aparentemente misma llamada en el contrato de lanzamiento aéreo falla. El contrato AirDrop se inicializa con la dirección deMyToken

También probé esto tanto en Ganache como en Rinkeby con los mismos resultados.

He publicado todo el código a continuación.:

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;
  }

}

Aquí está el código para 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);
    }

}

Y el código del airdrop:

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);
  }
}
Después de inicializar AirDropcon la dirección de MyToken, ¿transfiere alguno de los tokens a la AirDropdirección para que tenga algo que distribuir en su saldo? Tenga en cuenta que cuando llama tiene su dirección como MyToken.transferAnyERC20Token(), mientras que en el otro caso es la dirección de la instancia implementada, que puede tener 0 tokens en su saldo. transfermsg.senderAirDrop
! ingrese la descripción de la imagen aquí resuelva los problemas Me enfrento a este problema, ¿puede ayudarme?

Respuestas (2)

Intenté llamar a MyToken.transferAnyERC20Token() que también usa ERC20(token).transfer(). Eso funciona bien allí, pero la aparentemente misma llamada en el contrato de lanzamiento aéreo falla.

No estoy 100% seguro de que esta sea la solución, pero la forma en que describió su proceso sugiere cierta confusión sobre dónde deberían estar los tokens.

Cuando realiza la prueba manualmente, su cuenta de propiedad externa que firma la transacción debe tener tokens para poder enviarlos. Lo mismo ocurre con el Airdrop. No puede enviar lo que no tiene.

¿Recordó transferir de la cuenta del implementador/minter al contrato de Airdrop antes de indicarle al contrato de Airdrop que envíe tokens que no tiene?

Por si no está claro.

  1. Alice implementa los contratos y Alice (generalmente) tiene el 100% del suministro inicial.
  2. Alice quiere que el contrato Airdrop distribuya tokens, por lo que Alice transfiere suficientes tokens al contrato Airdrop.
  3. Alice firma una transacción con el contrato de Airdrop y le indica que reenvíe los tokens bajo la custodia del contrato de Airdrop a los destinatarios.

La implementación de OpenZeppelin de ERC20 que parece estar utilizando generará una excepción en el caso de que el remitente (que es el contrato Airdrop) no tenga tokens suficientes para extraer para el transfermétodo.

Espero eso ayude.

Esto es absolutamente correcto... Olvidé transferir los tokens al airdrop. El problema fue que el contrato de airdrop es el que ejecuta la transferencia y no tiene los tokens para enviar. Para otros que puedan tener este problema en el futuro, hay 2 opciones: 1. Transferir tokens a la AirDropdirección, para que Airdrop pueda enviar tokens. 2. llame MyToken.approve(AirDrop.address)desde la cuenta del titular del token para aprobar la dirección de airdrop para enviar tokens, luego use en su ERC20(address).transferFrom()lugar fotransfer

Este error: VM Exception while processing transaction: revertocurrirá cuando estas declaraciones no se evalúen paratrue

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

y etc... , tienes mucho o requires en tu contrato

también tienes revertaquí:

function () public payable {
    revert();
}

Entonces, todo depende de la entrada que le estés dando al contrato. Revert es una especie de assertC/C++, donde el programa simplemente existe. Lo mismo sucede aquí en la EVM, el contrato revierte la ejecución. Encuentre qué entrada no es válida y esta será la solución a su problema.