5

I've got this dapp on the main net here.

The following contract has been deployed at this address :

pragma solidity ^0.4.19;

import 'zeppelin-solidity/contracts/ownership/Ownable.sol';
import 'zeppelin-solidity/contracts/lifecycle/Destructible.sol';
import 'zeppelin-solidity/contracts/lifecycle/Pausable.sol';
import 'zeppelin-solidity/contracts/math/SafeMath.sol';

contract CryptoTwittos is Ownable, Pausable, Destructible {
  using SafeMath for uint;

  // A Twitto is owned by a stealer and has a price
  struct Twitto {
    address stealer;
    uint price;
  }

  // Look up Twitto by ids
  mapping(uint => Twitto) public twittos;

  // All Twitto ids and counter
  uint[] public twittoIds;
  uint public twittosCounter;


  // Fire event when steal happens
  event stealEvent(
    uint indexed id,
    address indexed owner,
    uint price,
    address indexed stealer,
    uint newPrice
  );


  // Get twittoIds
  function getTwittoIds(bool all) public view returns (uint[]) {
    // Return empty array if counter is zero
    if (twittosCounter == 0) return new uint[](0);

    if (all) {
      // Return all of them
      return twittoIds;

    } else {
      // Create memory array to store filtered ids
      uint[] memory filteredIds = new uint[](twittosCounter);
      // Store number of belongings
      uint twittosCount = 0;

      for (uint i = 0; i < twittosCounter; i++) {
        // Check if stealer is sender
        if (twittos[twittoIds[i]].stealer == msg.sender) {
          filteredIds[twittosCount] = twittoIds[i];
          twittosCount++;
        }
      }

      // Copy the filteredIds array into a smaller array
      uint[] memory trophies = new uint[](twittosCount);
      for (uint j = 0; j < twittosCount; j++) {
        trophies[j] = filteredIds[j];
      }
      return trophies;
    }
  }

  // Steal a Twitto by paying its price and setting a new one
  function steal(uint id, uint256 newPrice) payable whenNotPaused public {

    // look up the twitto and put on storage
    Twitto storage _twitto = twittos[id];

    // Prevent self stealing!
    require(msg.sender != _twitto.stealer);

    // Make sure the sender pays the right price
    require(msg.value == _twitto.price);

    // Make sure that the new price is higher than the old price
    require(newPrice > _twitto.price);

    // Transfer value with the 1% dev fee
    if (msg.value > 0) {
      _twitto.stealer.transfer(msg.value.mul(99).div(100));
    }

    // Push new Twitto if not existing
    if (_twitto.price == 0) {
      twittoIds.push(id);
      twittosCounter++;
    }

    // Trigger event
    stealEvent(id, _twitto.stealer, _twitto.price, msg.sender, newPrice);

    // Store new stealer
    _twitto.stealer = msg.sender;

    // Store new price
    _twitto.price = newPrice;

  }

  function withdraw() public onlyOwner {

    // Transfer balance to owner
    msg.sender.transfer(address(this).balance);
  }


}

The constant method that is causing trouble is getTwittoIds : it was all working fine but then the size of the arrays grew a little bit (as you can see with the number of transactions on Etherscan) and now this constant method call is sometimes returning an empty array and sometimes not.

Reading at questions like this one I thought that this was due to the gas limit being to low (for constant calls) but the issue is still occurring.

The easiest steps to reproduce this "bug" are the following:

  1. Go to https://cryptotwittos.com/
  2. Make sure MetaMask is enabled
  3. Open console
  4. Get Contract with abi:

    var CT = web3.eth.contract([{"constant":true,"inputs":[],"name":"twittosCounter","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"destroy","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"twittos","outputs":[{"name":"stealer","type":"address"},{"name":"price","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"twittoIds","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"}],"name":"destroyAndSend","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":true,"name":"owner","type":"address"},{"indexed":false,"name":"price","type":"uint256"},{"indexed":true,"name":"stealer","type":"address"},{"indexed":false,"name":"newPrice","type":"uint256"}],"name":"stealEvent","type":"event"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"constant":true,"inputs":[{"name":"all","type":"bool"}],"name":"getTwittoIds","outputs":[{"name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"},{"name":"newPrice","type":"uint256"}],"name":"steal","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}])
    
  5. Get instance

    var CTInstance = CT.at("0xa4b9054417ee4f06453152a45ebeba7786c84c66")
    
  6. Call method that should return all TwittoIds

    CTInstance.getTwittoIds(true, console.log)
    
  7. Convince yourself that the method should not return an empty array

    CTInstance.twittosCounter(console.log)
    

After two days of research I still have no clue on what's going on, so any help would be highly appreciated.

Many thanks!

  • What are the cases where it returns a non-empty array? – Eli Drion Apr 2 '18 at 16:19
  • @ElishaDrion CTInstance.getTwittoIds(true, console.log) sometimes returns an non empty array. I've tried setting the gas as well like this CTInstance.getTwittoIds(true, {gas: 990000000}, console.log) but it's still "random" (sometimes empty sometimes not) – teawaterwire Apr 3 '18 at 7:00
  • But when does it return a non-empty array? In which conditions? What's the size of the array when it does? etc... Plus, keep in mind that the read isn't immediate, and the bigger your array, the more time it will take to return something. – Eli Drion Apr 3 '18 at 8:46
  • The conditions is what I'm trying to figure out. Basically I run the same command in the console repeatedly and I get back randomly an empty array or the proper array (~1200 entries). I'm passing a callback, my reads are all asynchronous. – teawaterwire Apr 3 '18 at 10:35
1
+100

I tried running this on a geth node and indeed it returns 0x

However running this on a parity node returned a huge output (I guess this is what you need)

> eth.call({to:"0xa4b9054417ee4f06453152a45ebeba7786c84c66", data:"0xd15e656c0000000000000000000000000000000000000000000000000000000000000001", gas: "440000"})

Output: https://gist.github.com/cleanunicorn/b90fc5141d157e7dcc861522af25cb5a

The gas consumed for this operation is close to 440000 and it will increase.

I haven't checked yet the geth source code but it seems there is a limit to how much it can run before it stops.

Infura has geth nodes and that's why (if you're using them) it returns 0x. Possibly they are also experimenting with parity or custom builds and sometimes you're getting the correct result

  • Interesting! Do you know a way to increase this gas limit so that geth behaves like parity ? I understand that the array is big but not that big... – teawaterwire Apr 5 '18 at 15:57
  • I've tried the same call on geth (gas 400,000) but no response, I've even tried with 1,000,000 gas but geth refuses to process this much data. If you really want to use geth you need to fork it and remove the limit. Otherwise you should use parity – cleanunicorn Apr 6 '18 at 7:21
1

MetaMask uses Infura nodes by default. I tried calling your contract with JSON RPC and I got 0x as the result.

My best guess is that Infura puts limitations on what can be run on the client EVM with eth_call, and upon hitting that limit returns 0x instead of a more descriptive error. To confirm this I'd need to synchronise my local node with the mainnet and see if the API behaves as expected.

Related issues: https://github.com/INFURA/infura/issues/70, https://github.com/ethereum/web3.py/issues/607

0

Same issue for me when I use web3.eth.contract with MetaMask provider.

I resolve the issue using web3.eth.contract after redefining web3 with Infura provider.

In this way all works fine and function's calls never return 0x.

Hope this can help.

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.