How to test solidity smart contracts with ruby, RSpec and Etherium.rb

thesmartnik

TheSmartnik

Posted on March 30, 2022

How to test solidity smart contracts with ruby, RSpec and Etherium.rb

I recently tried testing solidity contracts with rspec and wanted to share results since I haven't really found anyone documenting the process.

Why?

Initially, I just wanted to test whether it's possible because I like ruby and try to stick it anywhere I can. Result turned out to be pretty good, though. I haven't found any big differences with truffle suite.

Without further ado. Naive implementation

Prerequisites

  1. Install ganache with npm -g ganache-cli
  2. Install solidity compiler. If you're on mac you can just use brew install solidity. If not here is a link to documentation

Our smart contract

For this tutorial. We'll use a simple hello world contract. located in contracts/greeter.sol

pragma solidity >= 0.8.11;

contract Greeter {
  string private _greeting = "Hello, World!";
  address public owner;

  constructor() {
    owner = msg.sender;
  }

  function greet() external view returns(string memory) {
    return _greeting;
  }

  function setSuperGreeting(string calldata greeting) external {
    require(msg.sender == owner, "Only owner");
    _greeting = greeting;
  }
}
Enter fullscreen mode Exit fullscreen mode

As you can see it allows to read from block and write from a blockchain. Simple but good enough for our tests.

Our test

Spec setup

First, we'd need to start ganache with just

> ganache-cli
Enter fullscreen mode Exit fullscreen mode

Then following etherium.rb documentation all we need to do is

  1. Create local client
client = Ethereum::HttpClient.new('http://localhost:8545')
Enter fullscreen mode Exit fullscreen mode
  1. Create instance of our contract by providing path to a file and instance of a client
Ethereum::Contract.create(file: "greeter.sol", client: client)
Enter fullscreen mode Exit fullscreen mode
  1. Deploy contract
contract.deploy_and_wait
Enter fullscreen mode Exit fullscreen mode

Our initial setup will look like this

require 'spec_helper.rb'

RSpec.describe 'Greeter' do
  let(:client) { Ethereum::HttpClient.new('http://localhost:8545') }
  let(:contract) { Ethereum::Contract.create(file: "contracts/greeter.sol", client: client) }

  before { contract.deploy_and_wait }
Enter fullscreen mode Exit fullscreen mode

Asserts

First, let's test our greet method. To read from a blockchain with etherium.rb we'll need to use call like so contract.call.method_name. Our test case will look like this

it 'reads greeting' do
  expect(contract.call.greet).to eq("Hello, World!")
end
Enter fullscreen mode Exit fullscreen mode

To change state of the blockchain we need to use transaction like so contract.transact_and_wait.method_name.

Here is an example of our next assertion

it 'changes message' do
  contract.transact_and_wait.set_super_greeting("Yo")

  expect(contract.call.greet).to eq("Yo")
end
Enter fullscreen mode Exit fullscreen mode

Our whole spec will look like this

require 'spec_helper'

RSpec.describe 'Greeter' do
  let(:client) { Ethereum::HttpClient.new('http://localhost:8545') }
  let(:contract) { Ethereum::Contract.create(file: "contracts/greeter.sol", client: client) }

  before { contract.deploy_and_wait }

  it 'sets greeting' do
    expect(contract.call.greet).to eq("Hello, World!")
  end

  it 'changes message' do
    contract.transact_and_wait.set_super_greeting("Yo")

    expect(contract.call.greet).to eq("Yo")
  end
end

Enter fullscreen mode Exit fullscreen mode

Running

> bundle exec rspec

2 examples, 0 failures
Enter fullscreen mode Exit fullscreen mode

As you can see it's super easy only a couple drawbacks.

  1. Need to start ganache by hand
  2. No easy access to accounts

Next Iteration

To overcome the above drawbacks I've created a simple gem. It'll start ganache before the tests and provide access to accounts

Require gem

Let's add our gem to a Gemfile

gem 'rspec-eth'
Enter fullscreen mode Exit fullscreen mode

and require it in spec_helper.rb with

require 'rspec/eth'
Enter fullscreen mode Exit fullscreen mode

Change spec

All you need to do now to make code work is add type: :smart_contract. RSpec::Eth provides a few methods that'll make our specs even simple. We now can remove client and contract. As the gem will guess contract location by spec location.

Updated version

require 'spec_helper'

RSpec.describe 'Greeter', type: :smart_contract do
  before { contract.deploy_and_wait }

  it 'sets greeting' do
    expect(contract.call.greet).to eq("Hello, World!")
  end

  it 'changes message' do
    contract.transact_and_wait.set_super_greeting("Yo")

    expect(contract.call.greet).to eq("Yo")
  end
end
Enter fullscreen mode Exit fullscreen mode

Now stop ganache if it's still running and try running specs again

> bundle exec rspec

2 examples, 0 failures
Enter fullscreen mode Exit fullscreen mode

Conclusion

It's still a work in progress and the process can be improved by adding extra configuration options and contract-specific matchers. However, if you like me love using ruby whenever you can, I believe truffle isn't the only toolset you can use for testing smart contracts

💖 💪 🙅 🚩
thesmartnik
TheSmartnik

Posted on March 30, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related