Coding Smart Contracts — Tutorial Part I
How to Write, Deploy and Test a Smart Contract
In this article, I will give you a smart contract tutorial. It will tell you how to quickly write, test and deploy Ethereum smart contracts. My motivation is to help people to make the first steps. There are several good tutorials which helped me to get started.
But I missed kind of a “cookbook recipe” for the entire journey, starting with the installation of tools and frameworks and ending with deployment to Ethereum and usage out of an application.
And so, I decided to write down all the steps involved and hope that you will find it helpful!
I’m working on a Mac, but I’ll provide links to the documentation of all tools and frameworks so that you’ll be able to find fitting instructions for your personal environment.
Today we will:
- Setup an environment that allows you to write production-ready smart contracts
- Write a simple smart contract
- Test security and style guide issues with solhint
- Write unit tests with a Truffle framework
- Deploy the contract on the Rinkeby testnet using MetaMask and Remix
- Execute calls on the deployed smart contract
1. Prepare
Before you start you will need the following tools, frameworks, and resources. Some of them need to be installed, some of them are available online. Once you have the full setup, you are good to go. This setup is also the starting point for my second tutorial, where you will learn to use the smart contract from a java application.
npm install -g truffletruffle versionbrew update
brew upgrade
brew tap Ethereum/Ethereum
brew install soliditybrew tap web3j/web3j
brew install web3jnpm install -g solhint
Verify installation:
solhint -V
2. Write the Smart Contract Code
Let’s start with writing a simple smart contract code. For testing purposes we will just deploy a very simple smart contract Ownable which has knowledge of its owner address and defines a couple of functions that we can use for testing:
pragma solidity ^0.5.0;/**
* @title Ownable
* @dev A basic contract for all contracts that need to be administered by the contract owner.
* It provides a function modifier 'onlyOwner' that can be used in
* the derived contracts for functions executable only by the
* contract owner.
*/
contract Ownable { // Keeps the owner of the contract
address private contractOwner; // C'tor
constructor() public {
contractOwner = msg.sender;
} modifier onlyOwner() {
require(msg.sender == contractOwner);
_;
} /**
* @dev determine if this address is the owner of this contract
* @return bool
*/
function isContractOwner(address _address) view public returns (bool) {
return _address == contractOwner;
} /**
* @dev returns the address of the owner of this contract
* @return address
*/
function getContractOwner() view public returns (address){
return contractOwner;
}
}
You can copy and paste this code into a file and name it Ownable.sol. Please create a directory myproject/ (which we will use as the main directory) and a subdirectory contracts/ to store the file ( myproject/contracts/). Now you can quickly check with solhint whether the contract has some issues. Solhint expects to find a config .json file in the directory.
$ cd myproject/contracts/
$ vi .solhint.json
Insert:
{
"extends": "solhint:default"
}
And save the .solhint.json file.
Check the contract:
$ solhint Ownable.sol
Ideally the output of solhint should be empty. Here is an example of how the solhint would report a formatting issue:
$ solhint Ownable.sol Ownable.sol
37:7 error Expected indentation of 8 spaces but found 6 indent✖ 1 problem (1 error, 0 warnings)
If the output is empty, there is nothing more to do in this step. If not, you will need to fix the issues pointed out by solhint and then continue.
Let’s develop some tests with Truffle.
3. Write the Truffle Tests
With the Truffle framework you can easily develop and run tests for your smart contract locally. Actually, Truffle allows you to compile and run tests on different blockchains (testnets or the livenet) using configuration in truffle-config.js. See http://truffleframework.com/docs/advanced/configuration for more detailed documentation.
I’m going to show you how to use the truffle’s built-in development blockchain. For this, you won´t need to configure anything, the truffle-config.js shall exist, but can remain empty:
module.exports = { // See <http://truffleframework.com/docs/advanced/configuration> for more information
// We can use truffle to compile and deploy contracts to different networks and chains
// To be configured};
Create a truffle-config.js and store it in your main directory myproject/.
Now let’s write a simple test for our contract Ownable.sol.
Create a subdirectory test in myproject/, create a file called OwnableTest.js there.
Add some tests:
// The contract to test
const Ownable = artifacts.require("Ownable");// Start with a clean test state
contract('Ownable', accounts => { // Keep Ownable instance
let OwnableContract;
// Keep the account of the contract owner
let Owner;
// Keep the address of the first account that was created by truffle for you
const Account1 = accounts[1];//******************************************************************
// Test cases for the isContractOwner method
//****************************************************************** // Test for the isContractOwner method - true
it("IsContractOwner should return true", () => {
return Ownable.deployed()
.then(function(instance) {
OwnableContract = instance;
// Get contract owner
return OwnableContract.getContractOwner.call();
}).then(function(resultOwner) {
Owner = resultOwner;
// Call isContractOwner for the returned owner address
return OwnableContract.isContractOwner.call(Owner);
}).then(function(resultIsOwner) {
// Check that the result is true
assert.equal(resultIsOwner.valueOf(), true, "Expected the result to be true");
return true;
}).catch(function(e) {
// We don't expect any error here
console.log(e);
});
}); // Test for the isContractOwner method - false
it("IsContractOwner should return false", () => {
return Ownable.deployed()
.then(function(instance) {
OwnableContract = instance;
// Call isContractOwner for the non-owner address
return (OwnableContract.isContractOwner.call(Account1));
}).then(function(resultIsOwner) {
// Check that the result is true
assert.equal(resultIsOwner.valueOf(), false, "Expected the result to be true");
return true;
}).catch(function(e) {
// We don't expect any error here
console.log(e);
});
});});
Now, we need to deploy our contract to the chain where we are going to perform the testing. For this, Truffle needs to know what contracts have to be deployed. Create a subdirectory migrations/ in myproject/and place 2 files in there:
1_initial_migration.js
2_deploy_ownable.js
Content of the 1_initial_migration.js
var Migrations = artifacts.require("./Migrations.sol");
module.exports = function(deployer) {
deployer.deploy(Migrations);
};
Content of the 2_deploy_ownable.js
// Define contracts to be deployed
var Ownable = artifacts.require("Ownable");// Deploy contracts
module.exports = function(deployer) {
deployer.deploy(Ownable);
};
These files will be picked up by truffle when you run the migrate command and executed in the order how they are prefixed: 1_, 2_…
Now we need to add a Migrations smart contract for truffle, that is referenced in the 1_initial_migration.js. The Migrations contract keeps track of what migrations (as listed in the migrations/ directory) were done on the current network.
Go to contracts/, create and store a Migrations.sol file there:
pragma solidity ^0.5.0;contract Migrations { address public owner;
uint public last_completed_migration; modifier restricted() {
if (msg.sender == owner) _;
} constructor() public {
owner = msg.sender;
} function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
} function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}
At the end you should have the following structure in your main directory myproject/:
myproject
├── contracts
│ ├── Migrations.sol
│ └── Ownable.sol
├── migrations
│ ├── 1_initial_migration.js
│ └── 2_deploy_ownable.js
├── test
│ └── OwnableTest.js
└── truffle-config.js
To run the tests with the built-in blockchain, do the following in your main directory myproject/:
$ truffle develop
This will start a test environment and create a couple of accounts for you which you can easily reference in your tests (see the test example above).
truffle(develop)> compiletruffle(develop)> migratetruffle(develop)> test
You should see the following output:
truffle(develop)> test
Using network 'develop'.Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile. Contract: Ownable
✓ IsContractOwner should return true (54ms)
✓ IsContractOwner should return false (50ms) 2 passing (137ms)
Hooray!
4. Deploy to Testnet (Rinkeby)
Now you have written and tested your smart contract locally and you are ready to deploy it on the testnet. For this, I will be using , but you are free to choose any other testnet.
First of all you will need to create an Ethereum account on rinkeby and get some test coins to be able to deploy your smart contract to the testnet.
We are going to use to create and manage your accounts, and to deploy and test the contract.
Use MetaMask to create an account. You will be provided with an Ethereum address and you will also be allowed to switch between different networks (test networks or mainnet). To get some test ETH go to the rinkeby faucet and follow the instructions.
The following instructions only apply to the old Remix interface.
Compile your contract in Remix. Add your file with the Solidity code ( Ownable.sol). Go to the Compile tab of Remix, select the appropriate compiler version (in your case it will be 0.5.0). Push Start to compile button.
Now you are ready to deploy the contract. Select Rinkeby as the network in MetaMask, go to Run tab, and choose Injected Web3J as environment option. Remix will pick up your MetaMask account automatically and show your balance in ETH. Now you can choose your compiled contract and deploy it to the Rinkeby testnet. You will need to confirm the deployment transaction in MetaMask.
You will see a similar output in the remix console:
creation of Ownable pending...
https://rinkeby.etherscan.io/tx/0x936c531caaa06a97c4416f4daa91d44287d1c30f6316c7807eff33d28a9a3832
Once the transaction was successfully executed and recorded on the blockchain, you will get something like:
[block:4622611 txIndex:3] from:0x4cc...ed751 to:Ownable.(constructor) value:0 wei data:0x608...e0029 logs:0 hash:0x936...a3832
A click on
block:4622611 txIndex:3
Your contract can now be seen under Deployed contracts and can be tested by calling getContractOwner and isContractOwner.
In Part II of this smart contract tutorial, I will show how to use a deployed contract from a java application.
Originally published at https://trimplement.com on July 2, 2019.