Lesson 3 of 13
In Progress

Writing Solidity Contracts with Truffle

Now that you have set up a development environment with Truffle, you are ready to start writing Solidity contracts. In this tutorial, we will show you how to write, test, and deploy Solidity contracts using Truffle.

What is Solidity?

Solidity is a high-level, contract-oriented programming language for writing smart contracts on the Ethereum platform. It is designed to be statically typed, compiled to bytecode that can be deployed on the Ethereum Virtual Machine (EVM), and to have a syntax similar to that of JavaScript.

Writing a Solidity Contract

To write a Solidity contract with Truffle, you need to create a new Solidity file in the contracts directory of your Truffle project. For example, to create a contract called MyContract, you would create a file called MyContract.sol in the contracts directory.

Here is an example of a simple Solidity contract:

pragma solidity ^0.6.0;

contract MyContract {
  string public message;

  constructor() public {
    message = "Hello, world!";
  }

  function setMessage(string memory _message) public {
    message = _message;
  }
}

This contract has a single state variable called “message” which is a string that is publicly accessible. It also has a constructor function that is executed when the contract is deployed, and a function called “setMessage” that allows the message to be changed.

Compiling a Solidity Contract

To compile a Solidity contract with Truffle, you need to run the truffle compile command in the Truffle console. This will compile your contracts and generate contract abstractions in the build/contracts directory.

For example, to compile the MyContract contract that we defined earlier, you would enter the following command in the Truffle console:

truffle compile

This will generate a JSON file called MyContract.json in the build/contracts directory, which contains the contract abstraction for MyContract. The contract abstraction is a JavaScript object that contains information about the contract’s ABI (Application Binary Interface), bytecode, and deployed address.

Testing a Solidity Contract

To test a Solidity contract with Truffle, you need to create a test file in the test directory of your Truffle project. The test file should export a test function that calls functions on the contract and checks the output to ensure that the contract is behaving as expected.

Here is an example of a test file for the MyContract contract:

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

contract("MyContract", function() {
  it("should set the message correctly", async function() {
    const instance = await MyContract.deployed();
    await instance.setMessage("Hello, Truffle!");
    const message = await instance.message();
    assert.equal(message, "Hello, Truffle!");
  });
});

This test file imports the MyContract contract abstraction, deploys an instance of the contract, and calls the setMessage and message functions to check that the message is correctly set.

To run the test, you can use the truffle test command in the Truffle console. This will compile your contracts, deploy them to a simulated Ethereum network, and run the tests.

For example, to run the test for the MyContract contract, you would enter the following command in the Truffle console:

truffle test

This will compile your contracts, deploy them to a simulated Ethereum network, and run the tests. If the tests pass, you will see a message indicating that the tests have passed. If the tests fail, you will see an error message indicating which test has failed and why.

Deploying a Solidity Contract

To deploy a Solidity contract with Truffle, you need to create a migration script in the migrations directory. A migration script is a JavaScript file that exports a function that handles the deployment of your contracts.

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

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

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

To deploy the contract using this script, you can use the truffle migrate command in the Truffle console. This will run the migration scripts and deploy your contracts to the network specified in the Truffle configuration file (e.g. the development network).

For example, to deploy the MyContract contract, you would enter the following command in the Truffle console:

truffle migrate

This will deploy the contract to the network and output the contract’s deployed address. You can then use the contract’s address to interact with the contract from your application or from the Truffle console.

Conclusion

In this tutorial, we showed you how to write, test, and deploy Solidity contracts with Truffle. You learned how to create a Solidity contract, compile it with Truffle, write tests for it, and deploy it to the Ethereum network. With these skills, you are now ready to start building your own smart contracts with Truffle!

If you have any questions or need further assistance, you can refer to the Solidity documentation (https://solidity.readthedocs.io/) or ask for help in the Ethereum community (https://ethereum.stackexchange.com/). Happy coding!

Exercises

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

Create a Solidity contract called “HelloWorld” that has a single function called “sayHello” that returns the string “Hello, world!”.

pragma solidity ^0.6.0;

contract HelloWorld {
    function sayHello() public pure returns (string memory) {
        return "Hello, world!";
    }
}

Create a Solidity contract called “Bank” that has the following functions:
-“deposit” that allows the caller to deposit ether into the contract
-“withdraw” that allows the caller to withdraw ether from the contract
-“balance” that returns the balance of the contract
-“transfer” that allows the caller to transfer a specified amount of ether to another address

pragma solidity ^0.6.0;

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

contract Bank {
    using SafeMath for uint;

    mapping(address => uint) public balances;
    uint public totalBalance;

    function deposit() public payable {
        require(msg.value > 0, "Cannot deposit 0 or less ether.");
        balances[msg.sender] = balances[msg.sender].add(msg.value);
        totalBalance = totalBalance.add(msg.value);
    }

    function withdraw(uint amount) public {
        require(amount <= balances[msg.sender], "Insufficient balance.");
        require(amount > 0, "Cannot withdraw 0 or less ether.");
        balances[msg.sender] = balances[msg.sender].sub(amount);
        totalBalance = totalBalance.sub(amount);
        msg.sender.transfer(amount);
    }

    function balance(address account) public view returns (uint) {
        return balances[account];
    }

    function transfer(address recipient, uint amount) public {
        require(amount <= balances[msg.sender], "Insufficient balance.");
        require(amount > 0, "Cannot transfer 0 or less ether.");
        balances[msg.sender] = balances[msg.sender].sub(amount);
        balances[recipient] = balances[recipient].add(amount);
    }
}

Modify the “Bank” contract from the previous exercise to include the following functions:
-“kill” that allows the owner to kill the contract and send the remaining balance to their account
-“owner” that returns the owner of the contract

pragma solidity ^0.6.0;

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

contract Bank {
    using SafeMath for uint;

    mapping(address => uint) public balances;
    uint public totalBalance;
    address public owner;

    constructor() public {
        owner = msg.sender;
    }

    function deposit() public payable {
        require(msg.value > 0, "Cannot deposit 0 or less ether.");
        balances[msg.sender] = balances[msg.sender].add(msg.value);
        totalBalance = totalBalance.add(msg.value);
    }

    function withdraw(uint amount) public {
        require(amount <= balances[msg.sender], "Insufficient balance.");
        require(amount > 0, "Cannot withdraw 0 or less ether.");
        balances[msg.sender] = balances[msg.sender].sub(amount);
        totalBalance = totalBalance.sub(amount);
        msg.sender.transfer(amount);
    }

    function balance(address account) public view returns (uint) {
        return balances[account];
    }

    function transfer(address recipient, uint amount) public {
        require(amount <= balances[msg.sender], "Insufficient balance.");
        require(amount > 0, "Cannot transfer 0 or less ether.");
        balances[msg.sender] = balances[msg.sender].sub(amount);
        balances[recipient] = balances[recipient].add(amount);
    }

    function kill() public {
        require(msg.sender == owner, "Only the owner can kill the contract.");
        selfdestruct(owner);
    }
}

Create a Solidity contract called “Token” that has the following functions:
-“mint” that allows the owner to mint a specified amount of tokens and assign them to an account
-“transfer” that allows a token holder to transfer a specified amount of tokens to another account
-“balanceOf” that returns the balance of a specified account
-“totalSupply” that returns the total supply of tokens

pragma solidity ^0.6.0;

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

contract Auction {
    using SafeMath for uint;

    struct Item {
        address owner;
        uint price;
    }

    Item public item;
    address public highestBidder;
    uint public highestBid;

    constructor(address _owner, uint _price) public {
        item.owner = _owner;
        item.price = _price;
    }

    function bid() public payable {
        require(msg.value > highestBid, "Bid must be higher than current highest bid.");
        require(msg.value > item.price, "Bid must be higher than the minimum price.");

        if (highestBidder != address(0)) {
            highestBidder.transfer(highestBid);
        }
        highestBidder = msg.sender;
        highestBid = msg.value;
    }

    function withdraw() public {
        require(msg.sender == highestBidder, "Only the current highest bidder can withdraw their bid.");
        msg.sender.transfer(highestBid);
        highestBidder = address(0);
        highestBid = 0;
    }

    function auctionEnd() public {
        require(msg.sender == item.owner, "Only the owner can end the auction.");
        require(highestBidder != address(0), "There must be a highest bidder to end the auction.");
        item.owner.transfer(highestBid);
        delete item;
    }
}

Create a Solidity contract called “DonationBox” that has the following functions:
-“donate” that allows a donor to make a donation to the box.
-“withdraw” that allows the owner of the contract to withdraw the total balance of the box.
-“setGoal” that allows the owner of the contract to set a goal for the amount of funds they want to collect.
-“fundsNeeded” that returns the difference between the current balance and the goal.

pragma solidity ^0.6.0;

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

contract DonationBox {
    using SafeMath for uint;

    address public owner;
    uint public balance;
    uint public goal;

    constructor() public {
        owner = msg.sender;
    }

    function donate() public payable {
        balance = balance.add(msg.value);
    }

    function withdraw() public {
        require(msg.sender == owner, "Only the owner can withdraw funds.");
        msg.sender.transfer(balance);
        balance = 0;
    }

    function setGoal(uint _goal) public {
        require(msg.sender == owner, "Only the owner can set the goal.");
        goal = _goal;
    }

    function fundsNeeded() public view returns (uint) {
        return goal.sub(balance);
    }
}