Lesson 16 of 34
In Progress

Writing a Simple Smart Contract

Smart contracts are self-executing contracts with the terms of the agreement between buyer and seller being directly written into lines of code. Smart contracts have the potential to revolutionize a wide range of industries and applications, including real estate, supply chain management, insurance, government, and banking and finance. In this article, we’ll take a look at the basics of writing a simple smart contract using Solidity, a programming language specifically designed for writing smart contracts on the Ethereum blockchain.

Getting Started

Before you can write a smart contract, you’ll need to set up a development environment. There are several options available, but one of the most popular is Truffle, a development framework for Ethereum. To install Truffle, you’ll need to have Node.js and npm (the Node Package Manager) installed on your computer.

Once you’ve installed Node.js and npm, you can install Truffle by running the following command:

npm install -g truffle

With Truffle installed, you’re ready to create a new project. To do so, open a terminal and navigate to the directory where you want to create your project. Then run the following command:

truffle init

This will create a new project directory with the following structure:

my-project/
├── contracts/
│   └── Migrations.sol
├── migrations/
│   └── 1_initial_migration.js
├── test/
├── truffle-config.js
└── truffle.js

The contracts directory is where you’ll write your smart contracts. The migrations directory is where you’ll write scripts to deploy your smart contracts to the blockchain. The test directory is where you’ll write tests for your smart contracts. And the truffle-config.js and truffle.js files are configuration files for Truffle.

Writing Your First Smart Contract

Now that you have a project set up, you’re ready to write your first smart contract. To do so, create a new file in the contracts directory called MyContract.sol. This is where you’ll write your contract code.

The first thing you’ll need to do is specify the contract’s version and import any necessary libraries. You can do this at the top of your contract file like this:

pragma solidity ^0.5.11;

import "https://github.com/OpenZeppelin/openzeppelin-solidity/contracts/math/SafeMath.sol";

The pragma solidity line specifies the version of Solidity that you’re using. The import line imports the SafeMath library from OpenZeppelin, which provides functions for safe arithmetic operations.

Next, you’ll need to define your contract. You can do this by writing a contract block like this:

contract MyContract {

}

Inside the contract block, you can define variables, functions, and other elements of your contract. For example, you might define a variable like this:

contract MyContract {
  uint public myVariable;
}

This defines a public variable called myVariable that is of type uint (unsigned integer). The public keyword makes the variable accessible from outside the contract.

You can also define functions in your contract. For example, you might define a function like this:

contract MyContract {
  function myFunction() public {
    // function code goes here
  }
}

This defines a public function called myFunction. The public keyword makes the function accessible from outside the contract.

You can also specify the visibility and mutability of your functions and variables. The visibility of a function or variable determines who can access it, while the mutability determines whether it can be modified. There are four visibility levels in Solidity: public, internal, external, and private. There are two mutability levels: pure and view.

The public visibility level makes the function or variable accessible from outside the contract. The internal visibility level makes the function or variable accessible only within the contract and any contracts that inherit from it. The external visibility level makes the function accessible only from outside the contract, but not from within it. And the private visibility level makes the function or variable accessible only within the contract.

The pure mutability level indicates that the function does not modify the state of the contract and does not depend on any state variables. The view mutability level indicates that the function does not modify the state of the contract, but may depend on state variables.

Here’s an example of a function with both visibility and mutability levels specified:

contract MyContract {
  function myFunction() external pure {
    // function code goes here
  }
}

This function is called myFunction and it is both external (meaning it can be called from outside the contract) and pure (meaning it does not modify the state of the contract and does not depend on any state variables).

Deploying Your Smart Contract

Once you’ve written your smart contract, you’ll need to deploy it to the blockchain. To do so, you’ll need to create a migration script in the migrations directory.

A migration script is a JavaScript file that contains instructions for deploying your smart contract to the blockchain. To create a new migration script, create a new file in the migrations directory with a filename that begins with a number, followed by an underscore and a description of the migration. For example, you might create a file called 2_deploy_my_contract.js.

In your migration script, you’ll need to specify the contract that you want to deploy and any arguments that you want to pass to the contract’s constructor. The constructor is a special function that is executed when the contract is deployed. It is used to initialize the contract’s state.

Here’s an example of a migration script that deploys the MyContract contract:

const MyContract = artifacts.require("MyContract");

module.exports = function(deployer) {
  deployer.deploy(MyContract);
};

This script uses the artifacts.require function to require the compiled version of the MyContract contract. It then exports a function that takes a deployer as an argument. The deployer is an object that provides methods for deploying contracts.

In this example, the deployer.deploy method is used to deploy the MyContract contract. This method takes the contract as an argument and returns a promise that is resolved when the contract is deployed.

To deploy your contract, run the following command in your terminal:

truffle migrate

This will run your migration scripts and deploy your contracts to the blockchain.

Testing Your Smart Contract

Once you’ve deployed your smart contract, you’ll want to test it to make sure it’s working as expected. To do so, you can write tests using the Truffle testing framework.

To create a test, create a new file in the test directory with a filename that ends in .test.js. For example, you might create a file called my_contract.test.js.

In your test file, you’ll need to require the compiled version of your contract and any libraries that you want to use. You can do this at the top of your file like this:

const MyContract = artifacts.require("MyContract");
const truffleAssert = require("truffle-assertions");

The artifacts.require function is used to require the compiled version of the MyContract contract. The truffleAssert library is a collection of assertion functions that make it easier to write tests.

Next, you’ll need to write a test function. A test function is a function that contains one or more test cases. To write a test function, use the test keyword followed by a description of the test case. For example:

test("My test case", async () => {
  // test code goes here
});

Inside the test function, you can use the truffleAssert library to make assertions about the state of your contract. For example, you might assert that a function returns the expected value like this:

truffleAssert.equal(actualValue, expectedValue, "Values should be equal");

You can also use the truffleAssert library to assert that a transaction was successful or that an event was emitted.

To run your tests, run the following command in your terminal:

truffle test

This will run all the test files in your test directory and report the results.

Conclusion

In this article, we’ve covered the basics of writing a simple smart contract using Solidity. We’ve looked at how to set up a development environment, how to define variables and functions in a contract, how to deploy the contract to the blockchain, and how to write tests for the contract.

There is much more to learn about Solidity and smart contracts, including advanced concepts like contract inheritance, contract interfaces, and security considerations. With the knowledge you’ve gained from this article, you can continue learning and experimenting with Solidity and the Ethereum blockchain to build more complex and powerful contracts.

Exercises

To review these concepts, we will go through a series of exercises designed to test your understanding and apply what you have learned.

Write a smart contract that defines a variable called “name” and a function called “setName” that sets the value of the name variable. Deploy the contract and test that the setName function works as expected.

pragma solidity ^0.8.0;

contract NameContract {
  string name;

  function setName(string memory _name) public {
    name = _name;
  }
}

To test this contract, you can use the following test code:

const NameContract = artifacts.require("NameContract");
const truffleAssert = require("truffle-assertions");

contract("NameContract", () => {
  it("should set the name correctly", async () => {
    const contract = await NameContract.deployed();
    await contract.setName("Alice");
    const result = await contract.name();
    assert.equal(result, "Alice", "Name was not set correctly");
  });
});

Write a smart contract that defines a function called “add” that takes two arguments and returns their sum. Deploy the contract and test that the add function works as expected.

pragma solidity ^0.8.0;

contract AddContract {
  function add(uint a, uint b) public pure returns (uint) {
    return a + b;
  }
}

To test this contract, you can use the following test code:

const AddContract = artifacts.require("AddContract");
const truffleAssert = require("truffle-assertions");

contract("AddContract", () => {
  it("should add two numbers correctly", async () => {
    const contract = await AddContract.deployed();
    const result = await contract.add(2, 3);
    assert.equal(result, 5, "Numbers were not added correctly");
  });
});

Write a smart contract that defines a struct called “Person” with fields for a name and an age. Write a function called “createPerson” that takes a name and an age as arguments and returns a Person struct. Deploy the contract and test that the createPerson function works as expected.

pragma solidity ^0.8.0;

contract PersonContract {
  struct Person {
    string name;
    uint age;
  }

  function createPerson(string memory _name, uint _age) public pure returns (Person memory) {
    Person memory person;
    person.name = _name;
    person.age = _age;
    return person;
  }
}

To test this contract, you can use the following test code:

const PersonContract = artifacts.require("PersonContract");
const truffleAssert = require("truffle-assertions");

contract("PersonContract", () => {
  it("should create a person correctly", async () => {
    const contract = await PersonContract.deployed();
    const result = await contract.createPerson("Alice", 30);
    assert.equal(result.name, "Alice", "Name was not set correctly");
    assert.equal(result.age, 30, "Age was not set correctly");
  });
});

Write a smart contract that defines a mapping called “ages” that maps addresses to ages. Write a function called “setAge” that takes an address and an age as arguments and sets the age for the given address in the mapping. Write a function called “getAge” that takes an address as an argument and returns the age for the given address from the mapping. Deploy the contract and test that the setAge and getAge functions work as expected.

pragma solidity ^0.8.0;

contract AgeContract {
  mapping(address => uint) public ages;

  function setAge(address _address, uint _age) public {
    ages[_address] = _age;
  }

  function getAge(address _address) public view returns (uint) {
    return ages[_address];
  }
}

To test this contract, you can use the following test code:

const AgeContract = artifacts.require("AgeContract");
const truffleAssert = require("truffle-assertions");

contract("AgeContract", () => {
  it("should set and get an age correctly", async () => {
    const contract = await AgeContract.deployed();
    await contract.setAge(accounts[0], 30);
    const result = await contract.getAge(accounts[0]);
    assert.equal(result, 30, "Age was not set or retrieved correctly");
  });
});

Write a smart contract that defines an enum called “Color” with values “Red”, “Green”, and “Blue”. Write a function called “setColor” that takes a Color as an argument and sets a variable called “favoriteColor” to the given Color. Write a function called “getColor” that returns the value of the favoriteColor variable. Deploy the contract and test that the setColor and getColor functions work as expected.

pragma solidity ^0.8.0;

contract ColorContract {
  enum Color { Red, Green, Blue }
  Color public favoriteColor;

  function setColor(Color _color) public {
    favoriteColor = _color;
  }

  function getColor() public view returns (Color) {
    return favoriteColor;
  }
}

To test this contract, you can use the following test code:

const ColorContract = artifacts.require("ColorContract");
const truffleAssert = require("truffle-assertions");

contract("ColorContract", () => {
  it("should set and get a color correctly", async () => {
    const contract = await ColorContract.deployed();
    await contract.setColor(1);
    const result = await contract.getColor();
    assert.equal(result, 1, "Color was not set or retrieved correctly");
  });
});