Import external contracts and libraries: Truffle vs Remix

Nobody wants a project directory packed with copy-pasted code. What if the code we want to reuse depends on another code and so on? Learning to import external libraries and contracts helps you save gas and avoid copy-pasting.

There are two very different situations when you’d want to use an external contract:

  1. To exclusively reuse the code (as for libraries and inherited contracts)
  2. To depend on the contract’s global state (e.g. exchange tokens on a DEX)

For both situations, this post will explain how to import external libraries and contracts. On top of that, it will show you how to go about using the code after importing it.   

The methods might depend on your development environment. Therefore, we will draw a parallel between the two most used ones: Remix and Truffle.

Remix is an online IDE for Ethereum development which is very easy and popular amongst beginners. 

Truffle is the next level in a developer’s learning journey. It helps you with unit testing and deployment. If you are unfamiliar with Truffle, go ahead and read How to work with Truffle and Ganache.

This post will firstly explain how to actually import the code you need. Then, we will go through how to use what you imported depending on the case:

  1. Use External Libraries 
  2. Inherit External Contracts
  3. Interact with an External Contract

Let’s get started.

How to import code

When wanting to use external code, the first step is to import it! 

Github is a great source of smart contracts code. Every good Ethereum project has a Github repository and, if their contracts are deployed, they also provide their addresses on the specific network. 

Remix

Go to the Github repo and navigate to your desired contract. From there, copy the URL and add it after the import statement. Take as an example the SafeMath library from OpenZeppelin:

import “https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeMath.sol”;

Truffle

When working with Truffle, one more step is required. If you wish to import external contracts or libraries you need to install them as a package. 

The easiest way is to use npm.

Always make sure that the repository you aim to import contains a package.json file. Otherwise, npm will not see it as a package.

To install the package, use the following command:

npm install username/repoName

If you want to install a scoped package (more packages grouped together), take this example of OpenZeppelin:

npm install @openzeppelin/contracts

After installation, we must use the import statement in our code.

Inside the package.json file, you should find the name of the package. Import the code of your desired contract using the name and the path:

import “packageName/path/to/Contract.sol”;

Now that we know how to import the contracts/libraries, let’s find out how to use the code in our smart contracts. The following chapters will explain it case by case.


1. Use External Libraries

Libraries are reusable code that is meant to provide help with simple operations and are not meant to change the global state. 

When it comes to Ethereum development, there are two types of libraries:

  • The ones that only have internal functions
  • The ones containing external and public functions

These differ in terms of deployment and this matters when attempting to import and use them.

Only internal functions

These types of libraries do not have to be deployed on the Ethereum network. A great example is the OpenZeppelin’s SafeMath library. 

You can simply import the code and use it directly (it will be attached to your contract).

Take as an example a simple voting game contract, where we use the SafeMath library to avoid overflow in addition:

pragma solidity >=0.4.22 <0.9.0;
 
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeMath.sol"; // REMIX
import "@openzeppelin/contracts/utils/math/SafeMath.sol"; // TRUFFLE
 
contract Vote {
 
  mapping (address => uint) public votes;
  address winner;
  uint public winner_votes;
  address owner;
 
  constructor(){
    owner = msg.sender;
  }
 
  function vote(address user) public {
    votes[user] = SafeMath.add(votes[user], 1); // Use SAFEMATH
    if(votes[user] > winner_votes){
      winner_votes = votes[user];
      winner = user;
    }
  }
 
}

Some public or external functions

The libraries that contain public or external functions must be deployed. Therefore, they all have an address associated with them. They also have to be linked to the contract that uses them.

Make sure the library is deployed on the network you are working on.

Remix

By default, Remix redeploys any imported library and gives its address to the contract using it. 

If you are only concerned about cluttering your directory and not about gas, you can simply leave it like that.

Otherwise, if you wish to avoid spending gas, this is a great article that explains how to do it: Deploying with libraries on Remix.

Truffle

As mentioned above, these libraries have to be linked to the contract using them. In Truffle, we do that during the migration. We only need to know the address where they were deployed. 

Make sure you don’t forget to import the source code into the contract.

As for the migration file, let’s assume we have SimpleContract.sol which uses SimpleLibrary.sol that was already deployed at address “0x0d63365D63C2d431F79182945CC107e589E02538”. The migration file should look like such:

const SimpleContract = artifacts.require("SimpleContract");
const libAddress = '0x0d63365D63C2d431F79182945CC107e589E02538';

module.exports = function (deployer) {
  SimpleContract.link(‘SimpleLibrary’,libAddress);
  deployer.deploy(SimpleContract);
};

The link() function takes as parameters the name of the library and the address at which it was deployed.

2. Inherit External Contracts

Often we need to inherit another contract’s code. 

A very popular example is when we create a new Ethereum token by the ERC20 standard. The ERC20 contract uses a few libraries and the IERC20 interface. Why clutter our code with all that when we could just import it?

Import external contracts and libraries

When inheriting another contract, the EVM does not care about the address at which the contract was deployed. It only cares about the source code.

Therefore, in both Remix and Truffle, no linking or referencing is required. Only the import statement.

3. Interact with an External Contract

In order to interact with the state of an external smart contract, you first have to import it as explained at the beginning of this post.

When you want to use the methods of that contract, you will need its address.

Make sure the contract is deployed on the network you are working on.

For example, let’s say the Favourite.sol contract has already been deployed. It simply remembers a “favourite” address.

pragma solidity >=0.4.22 <0.9.0;

contract Favourite {

  address private favouriteWinner;
  address voteAddress;


  constructor(address _favouriteWinner){
    favouriteWinner = _favouriteWinner;
  }

  function isFavourite(address user) public view returns(bool){
    return (favouriteWinner == user);
  }

}

Then, our contract Vote.sol uses the functionality of Favourite in order to decide winners of the voting contest:

pragma solidity >=0.4.22 <0.9.0;

import "packageName/path/to//Favourite.sol"; //TRUFFLE
import “https://github.com/…./Favourite.sol”; //REMIX

contract Vote {

  mapping (address => uint) public votes;
  address winner;
  address owner;
  uint public winner_votes;
  Favourite favouriteContract;


  constructor(address _favouriteAddress){
    owner = msg.sender;
    // bonus points for the favourite player
    favouriteContract = Favourite(_favouriteAddress);
  }

  function vote(address user) public{
    // favourite player has a head start
    if(favouriteContract.isFavourite(user) && votes[user] == 0){
      votes[user] = 1;
    }
    votes[user] += 1;
    // user wins if it has the most votes
    if(votes[user] > winner_votes){
      winner_votes = votes[user];
      winner = user;
    }
  }

}

You can notice that we modified the constructor of Vote to get as a parameter the address for the Favourite contract. This can be done through a setter function as well.

Then we assigned this address to favouriteContract which we will later use to access the methods of the Favourite contract. 


I hope this was helpful! Leave any suggestions in the comments 😁

Leave a Reply