Using Truffle Boxes with RSK

What we will do in this workshop:

  1. Pre-requisites
  2. Init
  3. Smart contract
  4. Truffle config
  5. Set up RSKj
  6. Configure RSK networks
  7. Deployment to Regtest
  8. Testing
  9. Deployment to Testnet

Part 0 - Set up pre-requisites

You will need the following software installed on your computer in order to work through this tutorial.

POSIX compliant shell

  • Mac OSX and Linux distributions: Use the standard terminal
  • Windows: If you use the standard cmd terminal, or PowerShell, the commands here may not work. Consider installing Git for Windows, which comes with Git Bash bundled. Here’s a great tutorial on installing and using Git Bash.

NodeJs

  • The most fuss-free way to install and manage multiple versions of node on your computer is nvm.
  • This tutorial assumes that you have version 12 or later
nvm install 12
nvm use 12

Java

  • You will need Java 8 in order to run RSKj
  • If java -version displays an error, or displays a version other than 1.8, you will need to install it.

There are a variety of ways to do this, and SDKman is one which allows you to install and switch between multiple versions as needed:

curl -s "https://get.sdkman.io/" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
# to get a filtered list of available java versions
sdk list java  | grep "8\." # copy a selection for use below

# install the version of java copied above
# (replace accordingly)
sdk install java 8.0.242.j9-adpt

# show installed versions, and switch to the selected one
# (replace accordingly)
sdk list java | grep installed
sdk use java 8.0.242.j9-adpt
java -version

curl

  • This is a system command that is likely already installed on your system
  • If curl --version displays an error, download curl.

Code editor

  • Software that is able to edit text files
  • Preferably one that has support for syntax highlighting for both SOlidity and Javascript
  • VS Code is a good choice if you don’t already have one

Part 1 - Init

Let’s install Truffle, which is the main development tool that we’ll be using; as well as mnemonics, which is a simple utility that can be used to generate BIP39 mnemonics.

npm i -g truffle@5.1.22 mnemonics@1.1.3

We’ll create a directory for this new project, and then initialise a git repo in it. In order to be able to git push, you will need to create a new repository, and copy its remote URL.

Change your folder name and git remote URL as appropriate.

mkdir -p ~/code/rsk/workshop-rsk-truffle-box-bguiz-live
cd ~/code/rsk/workshop-rsk-truffle-box-bguiz-live
git init
git remote add origin git@github.com:bguiz/workshop-rsk-truffle-box-bguiz-live.git

The truffle unbox command sets up a project based on a known template. In this workshop, we will be using the “pet shop” Truffle box, which is very commonly used in demos.

truffle unbox pet-shop
git add .
git commit -m "step: 01-01: truffle unbox"

Tell git not to care about the NodeJs dependencies - we don’t want to commit those!

git init
echo "/node_modules" > .gitignore
git add .gitignore
git commit -m "step: 01-02: .gitignore"

Let’s inspect the directory structure and files that were generated by truffle unbox.

tree -I node_modules

Install a dependency that allows Truffle to make use of a Hierarchically Deterministic Wallet (BIP39). We will make use of this shortly.

npm i --save-exact @truffle/hdwallet-provider@1.0.34
git add -p package.json
git commit -m "step: 01-03: npm install dependencies"
git push origin master

Part 2 - Smart contract

We create a new smart contract called Adoption.sol, within the contracts folder.

touch contracts/Adoption.sol
git add contracts/Adoption.sol
code contracts/Adoption.sol

This is an empty smart contract, the minimum for it to be able to compile.

pragma solidity 0.5.2;

contract Adoption {

}

We use truffle compile to run solc, a Solidity compiler, on all the smart contracts within the contracts folder. There should be two of them: Migrations.sol and Adoption.sol. Take note of the compiler version in the output.

truffle compile

git add -p
git commit -m "step: 02-01: smart contract"

We add a state variable which stores the accounts (of type address) for the adopters.

We also add a function that updates this state variable to remember which account adopted which pet.

Finally, we add another function that allows us to retrieve the full list of adopters.

pragma solidity 0.5.2;

contract Adoption {
  address[16] public adopters;

  // Adopting a pet
  function adopt(uint petId) public returns (uint) {
    require(petId >= 0 && petId <= 15);

    adopters[petId] = msg.sender;

    return petId;
  }

  // Retrieving the adopters
  function getAdopters() public view returns (address[16] memory) {
    return adopters;
  }

}

git add -p
git commit -m "step: 02-02: state variables, setter, getter"
git push origin master

Part 3 - Truffle config

Open up the config file used by Truffle in your code editor. Specify the version of the solidity compiler that you wish to use.

code truffle-config.js

  compilers: {
    solc: {
      version: '0.5.2',
      // ...
    },
  },

git add -p
git commit -m "step: 03-01: specify compiler version"

Now run truffle compile again, and take note of the compiler version in the output. Did it differ from the previous time you ran truffle compile?

truffle compile

git add build/contracts
git commit -m "step: 03-02: compiled contracts output"
git push origin master

Set up RSKj

Get RSKj running locally, this will provide you with a localhost-only network, for fast testing.

For this part, open up a new shell, as you will need to leave processes running in the background while you continue with the rest of your tutorial.

Now you’re ready to download and install RSKj, which is the RSK node. This enables you to run an instance locally, connecting various RSK networks: Mainnet, Testnet, and Regtest.

cd ~/code/rsk
mkdir -p ~/code/rsk/rskj-node
cd ~/code/rsk/rskj-node
curl \
  -L \
  https://github.com/rsksmart/rskj/releases/download/IRIS-3.1.0/rskj-core-3.1.0-IRIS-all.jar \
  > ./rskj-core-3.1.0-IRIS-all.jar
sha256sum rskj-core-3.1.0-IRIS-all.jar
# 43149abce0a737341a0b063f2016a1e73dae19b8af8f2e54657326ac8eedc8a0  rskj-core-3.1.0-IRIS-all.jar

Note: When installing and running the RSKj node, it is always a good idea to verify that your copy is legitimate. Full instructions on how to do this.

For the purposes of this workshop, we will run RSKj on Regtest.

java -cp rskj-core-3.1.0-IRIS-all.jar co.rsk.Start --regtest

If you see no output - that is a good thing.

Leave this running in an open shell, and switch back to your original shell for the rest of this workshop.

Part 4 - Configure RSK networks

Now we have to fund the accounts, so that we know that they exist

Generate a 12-word BIP39 mnemonic using iancoleman.io/bip39, and save to .secret. Alternatively, use mnemonics to do the same.

mnemonics > .secret
git add .secret
git commit -m "step: 04-01: save BIP39 mnemonic"

Get the current gas price of the network, and save to .gas-price.json.

curl \
  https://public-node.testnet.rsk.co/ \
  -X POST -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"eth_gasPrice","params":[],"id":1}' \
  > .gas-price-testnet.json
curl \
  http://localhost:4444/2.0.1/ \
  -X POST -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"eth_gasPrice","params":[],"id":1}' \
  > .gas-price-regtest.json
git add .gas-price-testnet.json .gas-price-regtest.json
git commit -m "step: 04-02: save gas price JSON_RPC responses for testnet and regtest"

Modify the Truffle config again so that we can do the following:

  • Use a hierarchically deterministic wallet with a BIP39 mnemonic phrase
  • Use the updated gas price
  • Configure it to connect to the RSK Regtest
  • Configure it to connect to the RSK Testnet
code truffle-config.js

This part reads in the BIP39 mnemonic phrase and the gas price.

const HDWalletProvider = require('@truffle/hdwallet-provider');
const fs = require('fs');

const gasPriceTestnetRaw = fs.readFileSync(".gas-price-testnet.json").toString().trim();
const gasPriceTestnet = parseInt(JSON.parse(gasPriceTestnetRaw).result, 16);
if (typeof gasPriceTestnet !== 'number' || isNaN(gasPriceTestnet)) {
  throw new Error('unable to retrieve network gas price from .gas-price-testnet.json');
}
const gasPriceRegtestRaw = fs.readFileSync(".gas-price-regtest.json").toString().trim();
const gasPriceRegtest = parseInt(JSON.parse(gasPriceRegtestRaw).result, 16);
console.log(gasPriceRegtest);
if (typeof gasPriceRegtest !== 'number' || isNaN(gasPriceRegtest)) {
  throw new Error('unable to retrieve network gas price from .gas-price-regtest.json');
}
const mnemonic = fs.readFileSync(".secret").toString().trim();
if (!mnemonic || mnemonic.split(' ').length !== 12) {
  throw new Error('unable to retrieve mnemonic from .mnemonic');
}
console.log({
  mnemonic,
  gasPriceTestnet,
  gasPriceRegtest,
});
// NOTE only do the above in demo code.
// This is not, by far, secure enough for a real use scenario.

git add -p truffle-config.js
git commit -m "step: 04-03: read gas prices and BIP39 mnemonic in from files"

This part configures a connection to the RSK Testnet. Note that we specify a gas price that is slightly higher than the minimum specified by the network. For example, the gas price at the time of creating this workshop was 60 million, and we configure a gas price of 61 million. The effect that this has is to get a slightly higher priority for our transactions being added to blocks.

  networks: {
    testnet: {
      provider: () => new HDWalletProvider(
        mnemonic,
        'https://public-node.testnet.rsk.co/',
      ),
      network_id: 31,
      gasPrice: gasPriceTestnet + 1e6,
      networkCheckTimeout: 1e9
    },
    // ...
  },

Test the connection to RSK Testnet.

truffle console --network testnet

(await web3.eth.getBlockNumber()).toString()

(await web3.eth.net.getId()).toString()

.exit

git add -p truffle-config.js
git commit -m "step: 04-04: configure RSK Testnet connection"

This part configures a connection to the RSK Regtest.

  networks: {
    regtest: {
      host: '127.0.0.1',
      port: 4444,
      network_id: 33,
      gasPrice: gasPriceRegtest,
      networkCheckTimeout: 1e3
    },
    // ...
  },

Test the connection to RSK Regtest.

truffle console --network regtest

(await web3.eth.getBlockNumber()).toString()

(await web3.eth.net.getId()).toString()

.exit

git add -p truffle-config.js
git commit -m "step: 04-05: configure RSK Regtest connection"
git push origin master

Part 5 - Deployment to Regtest

Let’s create a deployment script for our adoption smart contract.

touch migrations/2_deploy_contracts.js
code migrations/2_deploy_contracts.js

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

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

git add migrations/2_deploy_contracts.js
git commit -m "step: 05-01: add deployment script for adoption contract"

Use truffle migrate to run the deployment script. You should notice that Truffle has updated the contents of build/contracts.

truffle migrate --network regtest
git add -p build
git commit -m "step: 05-02: run truffle migrate on regtest"
git push origin master

Part 6 - Testing

Let’s create a file that tests whether our smart contract works properly.

touch test/adoption.test.js
code test/adoption.test.js

const Adoption = artifacts.require('Adoption');

contract('Adoption', (accounts) => {

});

We run this empty test file, and should be able to see zero tests.

truffle test --network regtest
#   0 passing (1ms)

git add test/adoption.test.js
git commit -m "step: 06-01: add empty test file for adoption"

Let’s add a test for whether an account can adopt a pet.

const Adoption = artifacts.require('Adoption');

const assert = require('assert');

const BN = web3.utils.BN;

  it('account can adopt pet', async () => {
    const inst = await Adoption.deployed();

    const adopterAccount = accounts[5];
    const expectedPetId = new BN(8);
    const adoptTxInfo = await inst.adopt(
      expectedPetId,
      {
        from: adopterAccount,
      },
    );

    assert.equal(
      adoptTxInfo.receipt.status,
      true,
      'adoption transaction failed',
    );
  });

This time, when we run the tests, we should see one passing test.

truffle test --network regtest
#  1 passing (2s)

git add -p test/adoption.test.js
git commit -m "step: 06-02: test for adopt function"

Let’s add another test for that checks whether the state of the smart contract was modified correctly in the previous function invocation.

  it('remembers adopter account', async () => {
    inst = await Adoption.deployed();

    const adopterAccount = accounts[5];
    const expectedPetId = new BN(8);
    const returnedAccount = await inst.adopters(
      expectedPetId,
    );

    assert.equal(
      returnedAccount,
      adopterAccount,
      'returned adopter account mismatch',
    );
  });

When you run the tests, you should get a 2 passing tests this time.

truffle test --network regtest
#  2 passing (3s)

git add -p test/adoption.test.js
git commit -m "step: 06-03: test for state change after adopt function called"
git push origin master

Part 7 - Deployment to Testnet

Thus far we have only connected to a blockchain that runs using just 1 node, that runs on your own computer. Let’s now switch to interacting with a “real” blockchain, which is running on multiple nodes distributed across multiple computers!

Start the Truffle console, and this time, interact with the RSK Testnet.

truffle console --network testnet

Wait for a second, and you should see a prompt which looks like: truffle(testnet)> . Test that the connection is OK by attempting to get the block number, like so:

(await web3.eth.getBlockNumber()).toString()
'791905'

The addresses of the first 10 wallets in our hierarchically deterministic wallet can be obtained now, and we write them to a file named .accounts

const accounts = Object.keys(web3.currentProvider.wallets)

accounts

await require('fs').promises.writeFile('.accounts', accounts.join('\n'))

.exit

(Typing .exit quits the Truffle console.)

Save the list of account addresses.

git add .accounts
git commit .accounts -m "step: 07-01: save list of account addresses"

Fund your first Testnet account with some tRBTC using the RSK Testnet faucet - faucet.rsk.co. Use the address which is in the first line of the .accounts file.

You will need this in order to pay for the gas need for smart contract deployment.

head -n 1 < .accounts

Check that you have tRBTC

truffle console --network testnet

const accounts = Object.keys(web3.currentProvider.wallets)

web3.eth.getBalance(accounts[0])

.exit

Deploy the contracts, this time to Testnet instead of Regtest.

truffle migrate --network testnet

git add -p build
git commit -m "step: 07-02: truffle migrate on testnet"
git push origin master

Where to go from here

Congratulations on making it through till the end of this workshop!

You are now able to use Truffle like a pro: Unbox, compile, test, and migrate (to multiple networks)!

Here are a few things that you can explore next:

  • You now have a copy of RSKj running locally, you can try interacting with it using geth. We have a tutorial for that.
  • In this workshop we focused on the bigger picture, and did not go into a lot of details about the Solidity language for smart contracts. We did not get into much detail about testing smart contracts either. Check out our webinars for more workshops like this one that we have planned for you.
  • Take a look at the src folder that was generated during truffle unbox, it contains a stubbed implementation of a front end to interact with the smart contract that you just created. We have a basic tutorial on developing a front end for a smart contract. Experiment with this.
    ├── src
    │   ├── css
    │   │   <...>
    │   ├── fonts
    │   │   <...>
    │   ├── images
    │   │   ├── boxer.jpeg
    │   │   ├── french-bulldog.jpeg
    │   │   ├── golden-retriever.jpeg
    │   │   └── scottish-terrier.jpeg
    │   ├── index.html
    │   ├── js
    │   │   ├── app.js
    │   │   ├── bootstrap.min.js
    │   │   ├── truffle-contract.js
    │   │   └── web3.min.js
    │   └── pets.json