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:
- Go to https://cryptotwittos.com/
- Make sure MetaMask is enabled
- Open console
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"}])
Get instance
var CTInstance = CT.at("0xa4b9054417ee4f06453152a45ebeba7786c84c66")
Call method that should return all TwittoIds
CTInstance.getTwittoIds(true, console.log)
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.
CTInstance.getTwittoIds(true, console.log)
sometimes returns an non empty array. I've tried setting the gas as well like thisCTInstance.getTwittoIds(true, {gas: 990000000}, console.log)
but it's still "random" (sometimes empty sometimes not)