1
votes

I am developing an Ethereum based Card Game. A user can collect n amount of individual/unique Cards. Everything is up and running, I am using the following balance mapping:

mapping (address => mapping (uint256 => uint256)) balances;

The first uint is the Card ID, the second uint is the Card count. I will have up to 1000 Cards, right now I am testing with 700 Cards.

I retrieve the balances on DApp Start by calling:

function balanceOf(address _owner, uint256 _id) view external returns(uint256) {
    return balances[_owner][_id];
}

for every single ID. On balance changes I do partial balance updates. This generally works. It is free, but it is also extremely slow as the initial retrieval call has to be done 640 times. I have researched a lot and also tried various implementations, but the main problem is that I need to retrieve an address mapped array holding the Card ID and Count information. Currently you can not easily retrieve dynamic sized Arrays or Structs.

What would be the proposal to resolve the issue? Am I stuck with up to 1000 balanceOf calls on DApp Start until Solidity introduces simple Array calls?

I thought about caching data on my WebServer, but for this to work I would need to run a node on the WebServer which I would like to avoid. A Client based caching, where the Client posts the balance to the WebServer may also run into an inconsistent state because of the asynchronous nature of the Blockchain.

3
What's wrong with function balanceOf(address _owner) view external returns (uint256[] ids, uint256[] count)? - Adam Kipnis
How would the whole return function and the declaration look like? An example would be very helpful. - Chi

3 Answers

0
votes

You can also use struct for managing data more easily. I made one contract using struct and retained data too. Please let me know if this approach didnt work for you.

pragma solidity ^0.4.18;

contract Test{

struct User{
    uint cardId;
    uint cardCount;
}
address user_address;

mapping (address => User) public Users;

function add_user() public {
    user_address = msg.sender ;
    var new_user = Users[user_address];
    new_user.cardId =2;
    new_user.cardCount = 50;
}

function get() public returns(uint,uint)
{
    return(Users[user_address].cardId,Users[user_address].cardCount);
}


}
0
votes

Your best chance is to use a secondary mapping to store the card IDs some user has, query it first, and than, for each ID, check the count for that user and that card ID.

Here is the code, tested on Remix:

pragma solidity 0.4.24;

contract Test {

    mapping (address => uint[]) cardsOwned;
    mapping (address => mapping (uint => uint)) cardsCounter;

    function cardsOwnedBy(address _owner) view public returns (uint[]) {
        return (cardsOwned[_owner]);
    }

    function cardsCounterFor(address _owner, uint _id) view public returns (uint) {
        return cardsCounter[_owner][_id];
    }

}
0
votes

I kept on trying various different solutions but couldn't find any good way to handle this. I found a setup which will work for me for now, until Solidity is updated to be more functional when it comes to array handling and especially dynamically sized variables.

Fastest solution for my requirements:

mapping (address => uint256[1000]) public balances;

The mapping now assigns addresses to a fixed size array. I can now retrieve the full list on DApp Start by using:

function balancesOf(address _owner) view external returns (uint256[1000]) {
  return balances[_owner];
}

The main advantage is that it is extremely fast, compared to any other solution. The main disadvantage is, that I lost my dynamically sized array and I have to know the maximum Card Count in advance - which I do not. I used a safety buffer now but if I hit the 1000 Cards mark I will have to update the contract. Hopefully there will be better solutions in the future.