This tutorial is part of the series ToDo List Ethereum Dapp. It is the Step 2. Tutorials already published:
- Intro
- Step 1: Writing The ToDo Smart contracts- beginning
- Step 2: Writing The ToDo Smart contracts – end (This tutorial)
- Step 3: Writing The Nodejs Backend
- Step 4: Setup the frontend and read account data
- Step 5: Build a smart contract client with Truffle Contract
- Step 6: Refactor With Webpack, ES6 and truffle-solidity-loader
- Step 7: Read Contract data from Frontend
- Step 8: Create smart contract data from frontend
- Step 9: Toggle task done & keep frontend updated
In this tutorial we are going to finish the Solidity smart contract that we started during the last episode. We are going to learn the following Solidity constructs:
mapping
events
modifiers
Replace data container from array to mapping
In contracts/Todo.sol
, we store tasks in an array: Task[] tasks
. That was the most simple thing to do when we started. However, to get a single task, we need to loop through all the entries of the array until we find the correct id
:
for(uint i = 0; i < tasks.length; i++) {
if(tasks[i].id === id) {
//do something with tasks[i]
}
}
That’s very wasteful in terms of computation cost, and we want to minimize gas spending.
Instead, we are going to use a mapping. A Solidity mapping is a similar to an object in Javascript. Mappings have (key, value)
pairs, and we can access a specific value
with its key
, without caring about the order of insertion.
This will help us a lot when we want to find a specific task: we will just have to know the task id
.
Let’s replace this:
Task[] tasks;
by this:
mapping(uint => Task) tasks;
uint
= ids of tasksTask
= the tasks themselves
Now it’s easier to access a single task.
Still, mappings cannot give a list of all tasks. We need something else to solve this problem.
Keep track of all task ids
Every time we create a new task, we will store its id in an array called taskIds
. When we want to find all tasks, we will:
- get all task ids from
taskIds
array - get the actual tasks from the
tasks
mapping
Add this just below the definition of our tasks
mapping:
uint[] taskIds;
uint lastTaskId = 1;
We also need to create a function to return this taskIds[]
array from outside the smart contract:
function getTaskIds() public constant returns(uint[]) {
return taskIds;
}
We have improved the way we store and keep track of tasks
. But we broke createTask()
. We need to fix this.
Fix the createTask() function
createTask()
still uses the old Task[] tasks;
array. Let’s make it work with the new tasks
mapping. Replace the old definition of createTask()
by this:
function createTask(string _content, string _author) public {
lastTaskId++;
tasks[lastTaskId] = Task(lastTaskId, now, _content, _author, false);
taskIds.push(lastTaskId);
emit TaskCreated(lastTaskId, now, _content, _author, false); //emit keyword is a recent addition to Solidity, you might see old code without it
}
This is what happen:
- First, we increment
lastTaskId
so that we don’t overwrite a previous task. - Next, we create a new
Task
struct with the values that were provided to the function, and we store it in thetasks
mapping. - We also add the
id
of the task created in thetaskIds
array. - Finally we fire an event with the task values. Events allow outside observers to know about the state transition that happened in a smart contract.
We also need to define this event:
- The name of the event
- The types of the event fields
At the top of your smart contract, before function definitions, add this:
event TaskCreated(uint id, uint date, string content, string author, bool false);
createTask()
is now updated to work with the tasks
mapping.. We still need to do a last improvement to our smart contract.
Check that task exist in getTask()
Currently getTask()
will still return something even for task ids that don’t exit: in Solidity, for keys that do not exist, mapping still return the default values of the field types. We want to abort execution and throw an error when this happen instead.
Because we might have to perform this check in other functions as well, we want our code to be reusable. Solidity has a construct called modifier
that will help us. Solidity modifier are like functions that can be applied before executing other functions:
function A(uint param) B(param) { ... }
modifier B(uint _params) {
//perform check
_; //A is executed
}
Add this modifier in our smart contract:
modifier taskExists(uint id) {
if(tasks[id].id == 0) {
revert(); //this will abort execution
}
_;
}
Add this modifier in the function signature of getTasks()
:
function getTask(uint id) taskExists(id) public constant
The end
We have finished our smart contract. Congrats! You just learned about:
- mappings
- event
- function modifier
So far we have only been working on the smart contract. But a Dapp is more than that. It also has a backend outside the smart contract, and a frontend. In the next episode (step3) we will build the Nodejs backend that will serve the frontend of our Dapp.
Dapp
Leave a Reply