I’ve been working on a rock, paper, scissors Ethereum DApp using Solidity, Web3 and the Truffle framework. I hit a few difficulties trying to replicate functionality that would normally be trivial in a non blockchain world so I thought I’d share what I learned.
My first thoughts for the DApp was to display a list of existing games that people had created. Normally if I were doing something like this in Django I’d create a game model and save any new games in the database. To display a list of existing games on the front end I’d query the db and iterate over the returned collection. (I realise storage is expensive when using the Ethereum blockchain but I thought trying to replicate this functionality would make sense and would be a good place to start.)
Solidity
Structures
While investigating the various data types that could be used I found the Typing and Your Contracts Storage page from Ethereum useful. I settled on using a struct, a grouping of variables, stored under one reference.
struct Game { string name; uint move; bool isFinished; address ownerAddress; uint stake; uint index; }
That handles one game but I want to store all games. I attempted to do this in a number of different ways but settled on mapping using the games index as the key. Every time a new game is added the index is incremented so I also use gameCount to keep count of the total games.
mapping (uint => Game) games; uint gameCount; struct Game { string name; uint move; bool isFinished; address ownerAddress; uint stake; uint index; }
To add a new game I used this function:
function StartGame(uint moveId, string gameName) payable { require(moveId >= 0 && moveId <= 2); games[gameCount].name = gameName; games[gameCount].move = moveId; games[gameCount].isFinished = false; games[gameCount].ownerAddress = msg.sender; games[gameCount].stake = msg.value; games[gameCount].index = gameCount; gameCount++; }
I also added a function that returns the total number of games:
function GetGamesLength() public returns (uint){ return gameCount; }
Returning A Structure
Next I want to be able to get information about a game using it’s index. In Solidity a structure can only be returned by a function from an internal call so for the front end to get the data I had to find another way. I went with the suggestion here — return the fields of the struct as separate return variables.
function GetGame(uint Index) public returns (string, bool, address, uint, uint) { return (games[Index].name, games[Index].isFinished, games[Index].ownerAddress, games[Index].stake, games[Index].index); }
Front End
On the front end I use Web3 to iterate over each game and display it. To begin I call the GetGamesLength() function. As we saw previously this gives the total number of games. Then I can iterate the index from 0->NoGames to get the data for each game using the GetGame(uint Index) function.
When my page first loads it calls:
getGames: function() { var rpsInstance; App.contracts.RpsFirst.deployed().then(function(instance) { rpsInstance = instance; return rpsInstance.GetGamesLength.call(); }).then(function(gameCount) { App.getAllGames(web3.toDecimal(gameCount), rpsInstance); }).catch(function(err) { console.log(err.message); }); },
Web3 – Promises, Promises & more Promises…
The getAllGames function calls GetGame(uint Index) for each game. To do this I created a sequence of promises using the method described here:
getAllGames: function(NoGames, Instance){ var sequence = Promise.resolve() for (var i=0; i < NoGames; i++){(function(){ var capturedindex = i sequence = sequence.then(function(){ return Instance.GetGame.call(capturedindex); }).then(function(Game){ console.log(Game + ' fetched!' // Do something with game data. console.log(Game[0]); // Name console.log(Game[1]); // isFinished }).catch(function(err){ console.log('Error loading ' + err) }) }()) } }
Conclusion
Looking back at this now it’s all pretty easy looking but it took me a while to get there! I’m still not even sure if it’s the best way to do it. Any advice would be awesome and if it helps someone even better.