ToDo List Ethereum Dapp (Step2) | Writing the smart contract – part II

This tutorial is part of the series ToDo List Ethereum Dapp. It is the Step 2. Tutorials already published:

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 tasks
  • Task = 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 the tasks mapping.
  • We also add the id of the task created in the taskIds 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.

Leave a Reply

Your email address will not be published. Required fields are marked *