Remix IDE - Your first smart contract

The easiest place to start writing smart contracts in Solidity in with the online Remix IDE.

As it's an online IDE, no installation or development environment setup is required, you can navigate to the site and get started!

Remix also provides good tools for debugging, static analysis, and deployment all within the online environment.

You can find the source code used in this tutorial in this GitHub repository.

Before we get started, a quick reminder of what we are building: A dApp which allows any user to issue a bounty in ETH

In Remix, create a new file by selecting the "+" icon in the upper left-hand corner. Name the file: Bounties.sol

The Remix file explorer

In the first line of our Solidity Smart Contract, we tell the compiler which version of Solidity to use:

pragma solidity ^0.5.0;

This line says Solidity compiler version 0.5.0 and above, up to version 0.6.0, can compile the code. The ^ character limits the compiler version up to the next breaking change, being 0.6.0.

To create the contract class, we add the following:

contract Bounties {

}

Next, we add a constructor so that our contract can be instantiated:

constructor() public {}

At this stage, we have the basic skeleton of a Smart Contract, and we can now test it compiles in the Remix IDE.

Your Bounties.sol file should look like this:

pragma solidity ^0.5.0;

contract Bounties {

    constructor() public {}
}

In Remix, select the "Compile" tab in the top right-hand side of the screen, and start the compiler by selecting the "Start to Compile" option.

Compile tab in Remix

If everything is ok, you should see a green label with the name of your contract: "Bounties", this indicates the compilation was successful.

Issuing a Bounty

Now that we have the basic skeleton of our smart contract, we can start adding functions. First, we tackle allowing a user to issue a bounty.

Declare state variables

What are state variables in Solidity? A smart contract instance can maintain a state, which the EVM keeps in a storage area. This state consists of one or more variables of the solidity types. These state variables can only be modified via a function call invoked within a transaction.

You can see a full list of solidity types in the solidity types documentation

First, let's declare an enum which we use to keep track of a bounties state:

enum BountyStatus { CREATED, ACCEPTED, CANCELLED }

Next, we define a struct which defines the data held about an issued bounty:

struct Bounty {
 address issuer;
 uint deadline;
 string data;
 BountyStatus status;
 uint amount;
}

What is a struct? Structs allow us to define custom composite types which allow us to aggregate/organise data.

Now, let's define an array where we store data about each issued bounty:

Bounty[] public bounties;

Issue Bounty Function

Now that we have declared our state variables we can now add functions to allow users to interact with our smart contract

function issueBounty(
    string memory _data,
    uint64 _deadline
)

public payable hasValue() validateDeadline(_deadline) returns (uint)
{
    bounties.push(Bounty(msg.sender, _deadline, _data,
    BountyStatus.CREATED, msg.value));
    return (bounties.length - 1);
}

The function issueBounty receives a string memory _data and an integer _deadline as arguments (the requirements as a string, and the deadline as a UNIX timestamp)

As of Solidity version, 0.5.0 explicit data location for all variables of struct, array or mapping types is now mandatory. Read more about Solidity 0.5.0 breaking changes here

Since string is an array of bytes, we must explicitly specify the data location of the argument _data. We specify memory since we do not want to store this data when the transaction has completed.

Solidity requires that you define the return type(s) We specify: returns(uint) Which means we are returning a uint (the array index of the bounty as the ID)

We define the visibility of this function as public.Read more about solidity function visibility

In order to send ETH to our contract we need to add the payable keyword to our function. Without this payable keyword, the contract will reject all attempts to send ETH to it via this function.

The body of our function has two lines:

bounties.push(Bounty(msg.sender, _deadline, _data,
BountyStatus.CREATED, msg.value));

First, we insert a new Bounty struct to out bounties array, setting the BountyStatus to CREATED.

In Solidity, msg.sender is automatically set as the address of the sender, and msg.value is set to the amount of Weis ( 1 ETH = 1000000000000000000 Weis).

We set the msg.sender as the issuer and the msg.value as the bounty amount.

return (bounties.length - 1);

Validation with Modifiers

Modifiers in Solidity allow you to attach additional pieces of code to run before or after the execution of a function. It is a common practice in Solidity to use modifiers to perform argument validation for functions.

Validate Deadline

validateDeadline(_deadline) is added to ensure the deadline argument is in the future; it should not be possible for a user to issue a bounty with a deadline in the past.

modifier validateDeadline(uint _newDeadline) {
 require(_newDeadline > now);
 _;
}

We use the modifier keyword to declare a modifier, modifiers like functions can receive arguments of their own.

The position of the _; symbol is key within a modifier. This body of the function modified is inserted where this symbol appears.

The validateDeadline modifier essentially says, execute this line:

require(_newDeadline > now);

Then execute the main function.

For validation, the require keyword allows you to set conditionals If they are not met, the execution is halted, reverted, and remaining gas returned to the user.

In general require should be used to validate user inputs, responses from external contracts, and state conditions before execution.

You can read more about assert, require, and revert here.

The modifier validateDeadline reads as follows:

If the deadline > now continue and execute function body, else revert and refund remaining gas to the caller.

Has Value

hasValue() is added to ensure msg.value is a non zero value. Even though as previously discussed, the payable keyword ensures msg.value is set, it can still be sent as zero.

Similar to validateDeadline we use require to ensure msg.value input is valid e.g >0

modifier hasValue() {
 require(msg.value > 0);
 _;
}

payable is a pre-defined modifier in Solidity. It validates that ETH is sent when calling a function which requires the smart contract to be funded.

You can read more about how modifiers can be used to restrict access and guard against incorrect usage in the solidity documentation

Issue Bounty Event

It is best practice when modifying state in Solidity to emit an event. Events allow blockchain clients to subscribe to state changes and perform actions based on those changes.

For example, a user interface showing a list of transfers in and out of an account, i.e., etherscan, could listen to a "transfer" event to update the user on the latest transfers in and out of an account.

Read more about solidity events here.

Since when issuing a bounty, we change the state of our Bounties.sol contract, we issue a BountyIssued event. First, we need to declare our event:

event BountyIssued(uint bounty_id, address issuer, uint amount, string data);

Our BountyIssued event emits the following information about the bounty data stored:

Then in our issueBounty function, we need to emit the BountyIssued event:

bounties.push(Bounty(msg.sender, _deadline, _data, BountyStatus.CREATED, msg.value));
*emit BountyIssued(bounties.length - 1,msg.sender, msg.value, _data);*
return (bounties.length - 1);

Now that we have added our issueBounty function our Bounties.sol file should look like the following:

pragma solidity ^0.5.0;
/**
* @title Bounties
* @author Joshua Cassidy- <[email protected]>
* @dev Simple smart contract which allows any user to issue a bounty in ETH linked to requirements
* which anyone can fulfil by submitting the evidence of their fulfilment
*/
contract Bounties {
    /*
    * Enums
    */
    enum BountyStatus { CREATED, ACCEPTED, CANCELLED }

    /*
    * Storage
    */
    Bounty[] public bounties;

    /*
    * Structs
    */
    struct Bounty {
        address issuer;
        uint deadline;
        string data;
        BountyStatus status;
        uint amount; //in wei
    }
    /**
    * @dev Contructor
    */
    constructor() public {}

    /**
    * @dev issueBounty(): instantiates a new bounty
    * @param _deadline the unix timestamp after which fulfillments will no longer be accepted
    * @param _data the requirements of the bounty
    */
    function issueBounty(
    string memory _data,
    uint64 _deadline
    )
    public
    payable
    hasValue()
    validateDeadline(_deadline)
    returns (uint)
    {
        bounties.push(Bounty(msg.sender, _deadline, _data, BountyStatus.CREATED, msg.value));
        emit BountyIssued(bounties.length - 1,msg.sender, msg.value, _data);
        return (bounties.length - 1);
    }

    /**
    * Modifiers
    */
    modifier hasValue() {
        require(msg.value > 0);
        _;
    }
    modifier validateDeadline(uint _newDeadline) {
        require(_newDeadline > now);
        _;
    }

    /**
    * Events
    */
    event BountyIssued(uint bounty_id, address issuer, uint amount, string data);
}

Deploy & interact in Remix

Now that we have our smart contract we can deploy to a local development blockchain running in the RemixIDE (browser), and test our issueBounty function.

First, let's compile our Bounties.sol contract to ensure we have no errors. In Remix, select the "Compile" tab in the top right-hand side of the screen, and start the compiler by selecting the "Start to Compile" option.

You will notice a few static analysis warnings in the static analysis tab of the IDE. Remix runs a set of static analysers to help avoid known security vulnerabilities and follow best practices. You can read more about Remix Analysis here. We can ignore these warnings for now and move on to deploying and interacting with our smart contract.

In Remix, select the Run tab in the top right-hand side of the screen. Within the Environment dropdown section, select the Javascript VM option.

Deploy and run panel in Remix

The "JavaScript VM" option, runs a Javascript VM blockchain within the browser, this allows you to deploy and send transactions to a blockchain within the RemixIDE in the browser. This is particularly useful for prototyping, especially since no dependencies are required to be installed locally. You can read more about running transactions within Remix here.

Within the Run tab in Remix, with the JavaScript VM environment option selected. Click the Deploy button.

This button executes a transaction to deploy the contract to the local blockchain environment running in the browser. We'll talk more about contract creation transactions later on in the series.

Within the RemixIDE console, which is located directly below the editor panel, you will see the log output of the contract creation transaction.

Log output in Remix

The "green" tick indicates that the transaction itself was successful.

Within the "Run" tab in Remix, we can now select our deployed Bounties contract so that we can invoke the issueBounty function. Under the "Deployed Contracts" section, we see a list of function which can be invoked on the deployed smart contract.

Here we have the following options:

Deploy and run panel in Remix

To invoke the issueBounty function, we need to first set the arguments in the "issueBounty" input box.

Set the string _data argument to some string "some requirements" and set the uint64 _deadline argument to a UNIX timestamp in the future e.g. "1691452800" August 8th 2023.

Since our issueBounty function is payable we must ensure msg.value is set, we do this by setting the values at the top of the "Run" tab with the RemixIDE.

Here we have the following options:

Go ahead and set "Value" to some number > 0, but less than the current amount available in the selected account. In this example, we set it to 1 ETH

Clicking the "issueBounty" button in the "Deployed Contracts" section, within the "Run" tab, sends a transaction invoking the issueBounty function, on the deployed Bounties contract.

Within the console, you will see the log output of the issueBounty transaction.

Console output in Remix

The "Green" tick indicates the transaction was successful.

The decoded output gives you the return value of the function call, here it is 0.

This should be the index of our "Bounty" data within the bounties array in our smart contract data store. We can double-check the storage was correct by invoking the "bounties" method in the "Deployed Contracts" section.

Set the uint256 argument of the bounties function to 0 and click the "blue" bounties button.

Set arguments in Remix

Here we confirm that the data inputs for our issuedBounty are retrieved correctly from the "bounties" array with deployed smart contracts storage.

Try it yourself

Now that you have seen how to add a function to issue a bounty, try adding the following functions to the Bounties contract:

Note: For acceptFulfilment you need to use the address.transfer(uint amount) function to send the ETH to the fulfiller. You can read more about the address.transfer member here.

You can find the complete Bounties.sol file here for reference.

Next Steps

If you enjoyed this guide, or have any suggestions or questions, let me know in the comments.

If you have found any errors, feel free to update this guide by selecting the 'Update Article' option in the right-hand menu, and/or update the code