Deploying and Linking a LinkedList EVM package with ZeppelinOS

tutorial

#1

Note: There is a newer version of this tutorial updated for recent breaking changes in Solidity and Truffle.

EVM package deployment with ZeppelinOS — Part 3

Testing and Publishing

In this section, we will make sure the contract we have deployed to our local network works by testing directly against it in Truffle console. Once we are happy it works, we will publish to the main net and create an NPM package for others to access.

Testing

Now that we have an instance of our contract, we are ready to test it directly in Truffle console.

npx truffle console --network local

Again the network argument tells Truffle to work with our local development network as defined in truffle-config.js .

Create an instance of your contract object:

truffle(local)> myLinkedList = LinkedList.at(‘<your-contract-address>’)

The “<your-contract-address>” is the address returned by the zos create command, but also found in the zos.dev-<<some number here>>.json file. (This command should return a long output which represents our contract object)

Now for the fun!

Let’s check the length of LinkedList:

truffle(local)> myLinkedList.head()
'0x0000000000000000000000000000000000000000000000000000000000000000'

There is no head yet, so this correct. Let us add a node:

truffle(local)> myLinkedList.addNode("Hello World!")

This should return a transaction object and logs. It’s working! (Notice the transactions the window where Ganache is running)

Let’s check the length of the list again:

truffle(local)> myLinkedList.head()
'0xc08d91feb0d1e3c5808af107ca712c167f4998c4eac93670bc98cd627bf2c6d0'
//the above long string is sample output, you will see something different

This should return a bytes32 value as a string which represents the location of the “head” node stored in the LinkedList contract instance. Now try to return the value stored at this “head” node.

truffle(local)> myLinkedList.nodes('0xc08d91feb0d1e3c5808af107ca712c167f4998c4eac93670bc98cd627bf2c6d0')
[ '0x0000000000000000000000000000000000000000000000000000000000000000',
  'Hello World!' ]

This should return back the struct of our node as expected. Note that the struct is returned in an array format. The first value is the value of the “next” node, and the second value is the data value of “this” node.

Finally, try “popping” the head off the list, and call head() again to check if your list is empty again.

truffle(local)> myLinkedList.popHead()
//This returns and prints a transaction

truffle(local)> myLinkedList.head()
'0x0000000000000000000000000000000000000000000000000000000000000000'

It works!

Publishing our Contract:

Great! So now that you have a contract, deployed it on your development network, created an instance of it, and tested it directly with truffle console- what’s next? Publishing!

Publishing your project takes the code you have written, packages it up, and publishes it to the network of your choosing where others can easily reuse it. In the same way that we created an instance of our contract, others can similarly create instances of your contract, independently, and reuse the code for their own projects.

First, before publishing to a public network, let us try publishing to your local development blockchain.

Open a new terminal window and type:

zos publish --network local

In your zos.dev-<<some number here>>.json file you will see that you now have an app , package and provider fields with addresses pointing to their respective contracts.

The big time.

Great, you’ve just published, but only to your local blockchain network- not to NPM and not to the main Ethereum network. This means so far, it’s not very useful as no one can find it. What we need to do now is to publish to a real public network and then to NPM!

Get a mnemonic (and ether!)

To get started, you are going to need a new mnemonic to connect to a public blockchain. Remember THIS IS NOT YOUR DEVELOPMENT MNEMONIC FROM GANACHE if you use that- you will probably lose all your money.

There are a number of different ways to get a mnemonic- some are more secure than others, and some are easier than others. For this tutorial, we will use a simple and secure method: MetaMask.

MetaMask is a browser plugin for Chrome, Firefox, Opera, and Brave. You can get it here: https://metamask.io/.

During the MetaMask installation and setup process, you will be told at a certain point to save your mnemonic- a 12 word long “secret phrase” which can be used to regenerate your entire wallet at any time in the future. This is the mnemonic you will need. Copy it down to a SAFE PLACE , as you will need to copy into your code. This is a good time to also fund your account with a small amount of eth or test-eth (in the case of a test network like Rinkeby, Kovan, or Ropsten).

Once MetaMask is installed and you have copied your mnemonic you will need to create an account (and address) with MetaMask that you will use as your “deployment address” as-per the transparent proxy issue mentioned earlier. This can be the default account that MetaMask creates, or you can generate a new account to use specifically for deployment. Once you have this, you should load the account with some ETH for deployment.

A note on safety:

As a personal preference, I tend to generate mnemonics and fund them with only enough eth to complete whatever deployment or development task I am working on. When I finish, I send whatever remains back to my personal account. It can be very easy to accidentally commit secret data to public GitHub accounts. Enterprising individuals have created scripts that specifically scan git repos for mistakenly hardcoded private keys and mnemonics to steal users funds. By limiting the amount of money you put on your address you can limit your losses in the event your code (and mnemonic) ever inadvertently become public. Similarly, if you do use MetaMask to generate your mnemonic, be sure to reset it to a new account when you are done with development- do not forget and mistakenly go forward using the MetaMask account you created for development as your main wallet.

Setup :

If you open your truffle-config.js file, you will see there is only your development blockchain under “local”. You will need to add here the network you wish to deploy to.

(For more information about deploying to the main net you can check here)

Return to your code editor, open up truffle-config.js and edit it to look like this:

"use strict";
var HDWalletProvider = require("truffle-hdwallet-provider");

var mnemonic =
  "Your Twelve Word Mnemonic";

module.exports = {
  networks: {
    local: {
      host: "localhost",
      port: 9545,
      gas: 5000000,
      gasPrice: 5e9,
      network_id: "*"
    },
    mainnet: {
      provider: function() {
        return new HDWalletProvider(
          mnemonic,
          "https://mainnet.infura.io/v3/<<Your INFURA API TOKEN>>", 2
        );
      },
      gas: 200000000,
      gasPrice: 5e7,
      network_id: 1
    },
    ropsten: {
      provider: function() {
        return new HDWalletProvider(
          mnemonic,
          "https://ropsten.infura.io/<<Your INFURA API TOKEN>>"
        );
      },
      gas: 5000000,
      gasPrice: 5e9,
      network_id: 3
    }
  }
};

Notice I am including both the Ethereum main-net and ropsten test-net for conveneince, feel free to use either.

You will need to install HDWalletProvider for this to work. (Learn more about HDWalletProvider). You will want to review this for the nuances of deployment.

npm install truffle-hdwallet-provider

HDWalletProvider gives you a javascript object that will behave like an Ethereum wallet connected to a network. In this case, you will use Infura, a free service that acts as a gateway to the main Ethereum network. You will need an API token to connect, get that here by signing up.

Once you’ve signed up to Infura, found your token (You will need to create a project, and then copy-paste the endpoint), deposited some ETH in your wallet, you now need to push your project to the main net before publishing. Use the address of the MetaMask account you created earlier as your from address.

zos push --network mainnet --from &lt;&lt;your from address&gt;&gt;

Now you can now proceed to publish .

zos publish --network mainnet --from &lt;&lt;your from address&gt;&gt;

A note on Gas & deployment troubleshooting

Gas prices for executing or deploying contracts on public networks can vary wildly at times and figuring out the accurate cost of deployment can sometimes be a guessing game. Before deploying you should check:

ETH Gas Station
ETH gas price recommendations ethgasstation.info

To see what current gas prices are to get a recommendation on how much to use. The above settings for the main net might not work for you and you might get the following (among other) gas errors:

Cowardly refusing to execute transaction with excessively high default gas price of 100 gwei. Consider explicitly setting a different gasPrice value in your truffle.js config file. You can check reasonable values for gas price in https://ethgasstation.info/.

In this case, the message is referring to your truffle-config.js file. Using the format of your “local” network settings, add gas and gasPrice and adjust the numbers until they work using https://ethgasstation.info/ as a starting point.

Troubleshooting:

There are a number of possible errors that can creep up during this process so don’t be discouraged. Most of the time it will be a question of using enough gas based on network conditions, the size of your contracts to deploy and the address you are deploying from (check the HDWallet provider documents closely).

One problem you can potentially run into is the following error message:

Error: Contract transaction couldn’t be found after 50 blocks

To help get around this you will want to check out the options for the zos publish command and use the --timeout flag. The default timeout for each blockchain transaction is 600 seconds, but in some cases, you might need more (an example). You can also try to raise your gas amount to get included into blocks faster. Expect a moderate amount of trial and error to get your settings right.

zos publish .... --timeout 6000 //just an example

Once you get your deployment to a public network such as the main net to succeed, you will find a new file has been created for you: zos.mainnet.json (or zos.<<networkname>>.json) this file keeps track of your project contracts as deployed on that particular network.

Once you have been able to publish to the main net (or other public networks) you should see something similar to this:

Publishing project to mainnet…
Deploying new App…
Deployed App at 0x2f759.......
Deploying new Package…
Deployed Package 0x706........
Adding new version…
Deploying new ImplementationDirectory…
Deployed ImplementationDirectory at 0x9ded..........
Updated zos.mainnet.json

Whew! You did it! Congratulations are in order!

NPM

You have now deployed your EVM package to the main net where anyone can access it. The final step is to add your EVM package to the NPM repository so others can easily find your package and install your code via npm install .

At this point, if you haven’t already, you will need to sign up for an NPM account.

Your NPM package should include the source code and compiled contracts as well as the ZeppelinOS configuration files. To do this, we will need to add a top-level field to our package.json :

{
…,
“files”: [
“build”,
“contracts”,
“test”,
“zos.json”,
“zos.*.json”
]
}

Your package.json will look similar to this (customized for your project of course)

{
“name”: “linkedlist”,
“version”: “0.0.1”,
“description”: “On chain solidity linked list”,
“main”: “index.js”,
“scripts”: {
“test”: “echo \”Error: no test specified\” &amp;&amp; exit 1&quot;
},
“keywords”: [
“solidity”,
“ethereum”,
“linked”,
“list”
],
“author”: “Dennison Bertram”,
“license”: “MIT”,
“dependencies”: {
“truffle-hdwallet-provider”: “0.0.6”,
“zos-lib”: “2.0.1”
},
 “files”: [
“build”,
“contracts”,
“test”,
“zos.json”,
“zos.*.json”
]
}

The ZeppelinOS configuration files keep track of your contracts and the addresses where you have published them. This way when someone installs your NPM package, ZeppelinOS already knows where the on-chain code is deployed and can link their project to your on-chain code. (This link does not affect your contract in any way, it just allows others to reuse your code via the ZeppelinOS system- they still will need to deploy their own instance of the code via the proxy system).

Now we just need to clean a few things up before we publish to NPM.

Double check the rest of your package.json fields to be sure they describe your package correctly. Feel free to remove the “main” field, if present, because for pure EVM packages it doesn’t serve a purpose. At this point, you can also delete your zos.dev-<<some number here>>.json file as it describes your particular local test environment that others won’t have access to.

Once you’re ready simply:

npm login

And publish:

npm publish

That’s it! You’ve published your first EVM package! Others can link to your package by via:

zos link <<your-project-name>>

As a note — you’re not going to be able to publish under the LinkedList title- as it already exists as an NPM package and you will get an error:

You do not have permission to publish “LinkedList”.

You should check to see if there isn’t by chance already a package with the same name as your package. If so, simply go back to your package.json file and change the “name” field to something else. Whatever name you decide to use, remember it as that is the name people will need to use when they later type:

npm install YOUR-PROJECT-NAME

Pulling it all together

Now that you’ve created your first EVM package, let us go through the steps you would take to link it to your project as if you were to link directly from NPM. These commands you already know, so I will run through them quickly:

mkdir myproject2

cd myproject2

npm init -y
//the -y just skips all the dialogue questions and fills with defaults

zos init myproject2

Now we need to link the NPM package:

zos link <<name of your npm package>>

This will install your NPM package and add it to your zos.json file as a dependency. You will also now find your linked contract in the node modules folder. Open it up and you will see the ZeppelinOS project configuration files along with your contract and build folders.

Testing

Return to your myproject2 top level, and create a new contract to test the linked LinkedList contract. In your contracts folder make a new contract called QuickContract.sol. Not that for the import of the LinkedList NPM module I refer to the folder by the name “LinkedList”. As you will not be able to publish your NPM package under that name, be sure to reference the module name according to the name you gave your NPM package.

pragma solidity ^0.4.24;
import “<<your NPM package name here>>/contracts/LinkedList.sol”;
//The NPM package name will have it's own folder under modules

contract QuickContract {
LinkedList private _linkedlist;

function setLinkedList(LinkedList linkedlist) external {
require(linkedlist != address(0), “You must provide a non-0x0 address”);

_linkedlist = linkedlist;
}

function getHead() public view returns (bytes32) {
bytes32 _node = _linkedlist.head();
return _node;
}

function addNode(string _data) public {
_linkedlist.addNode(_data);
}
}

This is a fairly simple contract designed to test the basic functions of the LinkedList.sol contract. Note the first function setLinkedList() takes an instance of LinkedList as an address. This is how the QuickContract knows where the LinkedList contract is deployed, but remember- this address will actually be the proxy contract, which will serve as the permanent address for the contract, but actually points to the proxy. When we call into the proxy, the proxy will forward the contract call to the logic code as managed by ZeppelinOS. To make an upgradable contract, you can tell the proxy to point to a new logic contract, keeping the same address, but with new logic. As upgradable smart-contracts are outside of this tutorial, I encourage you to see this for more information.

Make sure you have your development blockchain running:

ganache-cli — port 9545 — deterministic

And start a new session:

zos session — network local — from 0x1df62f291b2e969fb0849d99d9ce41e2f137006e — expires 3600

Now we’re going to add QuickContract to the project

zos add QuickContract

If any of the following commands fail with an “A network name must be provided to execute the requested action error”, it means our session has expired. In that case, renew it by running the command:

zos session — network local — from 0x1df62f291b2e969fb0849d99d9ce41e2f137006e — expires 3600

The next command will push our changes to the blockchain.

zos push --deploy-dependencies

Let’s take a quick look at the flag --deploy-dependencies while we have deployed our NPM contract to the Ethereum main net, for testing purposes we are testing again on our local blockchain. ZeppelinOS will need to also deploy its own copy of the LinkedList.sol to this development blockchain in order to link to it. We only need to do in the case that the dependencies are not already deployed on our blockchain, and we only need to do it once. If you have successfully deployed to the main net, you will not need to do this again.

Like before, you need to create an instance of our contract:

zos create QuickContract

Be sure to call the LinkedList.sol contract initializer. (ZeppelinOS will remind you if you forget)

zos create LinkedList/LinkedList — init initialize

Note after zos create I have included the node_modules folder where LinkedList.sol has been installed via NPM.

Now we can test the contract.

npx truffle console — network local

First, link the contract by setting the address of our LinkedList contract.

truffle(local)> QuickContract.at(‘<QuickContract-contract-address>’).setLinkedList(‘<LinkedList-address>’)

Remember you can find your ‘<QuickContract-contract-address>’ and ‘<LinkedList-address>’ inside your zos.<networkname>.json file.

Now create an instance of the contract in truffle:

truffle(local)> quickContract = QuickContract.at(‘<QuickContract-contract-address>’)

Time to test! First check if a head exists:

truffle(local)> quickContract.getHead()
‘0x0000000000000000000000000000000000000000000000000000000000000000’

Now try adding a node:

truffle(local)> quickContract.addNode(“Hello World!”)

And now get the head again:

truffle(local)> quickContract.getHead()
‘0xcfbf767713316a02071074289829821d60f486eee26333765c344cea90478d1e’ 
//The actual bytes32 response will be unique to your project

It works! We’ve linked to an NPM deployed Ethereum main net contract!

Recap

That brings us to the end, just to recap in this tutorial you:

  1. Learned what EVM packages are for, how they can be used and how to deploy them.
  2. Wrote a basic, (but very useful!) linked list data structure with solidity.
  3. Added the LinkedList.sol contract to your ZeppelinOS project.
  4. Tested your linked list by deploying the LinkedList.sol contract and creating an instance to test against using ZeppelinOS and Truffle.
  5. Published your LinkedList.sol contract to the Ethereum main net and published it on NPM for others to use.
  6. Created a new project from scratch, linking your deployed LinkedList to our test project.
  7. Tested your new project using Truffle, and sure enough, it works!

Good luck with your projects!