Developing Ethereum Smart Contracts to Interact with Other Existing Smart Contracts
It’s surprisingly hard to find information about developing smart contracts that interact with existing contracts on the Ethereum blockchain. Figuring out the best approach can be tricky if you haven’t done it before, as there’s seemingly no obvious solution.
One straightforward approach is to use a testnet and just deal with the ~15 second block times. But this isn’t always so easy.
For example, when developing the contracts for Floatify we would receive testnet DAI from Wyre on the Kovan network and wanted to use that DAI with Compound’s protocol. Both contracts we need exist on Kovan, so let’s just use Kovan, right? Well, unfortunately, Compound’s Kovan contracts don’t use the official MakerDAO version of DAI, meaning it’s not compatible with Wyre’s DAI. So, in this case, not so straightforward.
A similar situation is trying to build on top of multiple projects where one project is deployed on Ropsten and mainnet, while the other is deployed on Kovan and mainnet. So how do we resolve that?
Thanks to a few little-known features of ganache-cli, there is a very simple and powerful solution — run a local fork of the Ethereum mainnet on your machine. This will let you quickly set up an environment to develop, test, and iterate using the existing state of the mainnet, including all contracts and token balances. Additionally, it provides the option to unlock any account, so you can send your contract any tokens from, say, an exchange’s account. This makes it a breeze to get any assets your contract needs for testing.
In this article we’ll explain how to use this approach, with the assumption that you already have knowledge of the basics, including Solidity, Truffle, and ganache-cli.
Getting Started
First let’s get our project set up and make sure we have all the required dependencies. We start by installing ganache-cli globally using npm install -g ganache-cli
.
Next, create a new folder on your computer and navigate to it in the terminal. Now, create a Truffle project with the following series of commands:
npm init
npm install truffle
npx truffle init
Note that Truffle can also be installed globally if you prefer, just like we did with ganache-cli. It’s sometimes preferable to have it installed locally to the project, as we did here, for more control over the version number, but either approach is ok.
Our truffle project is now ready to go! If you are having any issues so far, take a look at the documentation for Truffle and ganache-cli.
Ganache-cli is the important part of this approach, so you don’t have to use Truffle here, and should be able to use Embark instead if you prefer.
Ganache Features
Now we’ll explain the two ganache-cli features that make this approach work. These can be readily discovered simply by reading through the ganache-cli README on GitHub, but these features seem to be overlooked.
Forks
Buried in the list of options in the ganache-cli documentation is this flag:
-f
or--fork
: Fork from another currently running Ethereum client at a given block. Input should be the HTTP location and port of the other client, e.g.http://localhost:8545
. You can optionally specify the block to fork from using an@
sign:http://localhost:8545@1599200
It may not be explicitly stated in the description above, but this means we can fork from a node running the mainnet (or Rinkeby, Kovan, etc.) and run our own personal local version of it. So, instead of starting up ganache with a fresh blockchain, we can start ganache with a blockchain that matches the latest state of the Ethereum mainnet!
We can point ganache to our own personal node or an Infura node. For the latter, login to (or create) your Infura account, then create a new project. After clicking View Project to see the details for this project you’ll see a section called Keys. Note that you should keep the information in here private, as this will give other people access to the Infura nodes under your account.
In the Keys section, there will be a header called Endpoint where you can select the network you want to use. When selecting mainnet
— or whichever other network you want to use — underneath it will say something like mainnet.infura.io/v3/yourInfuraProjectId
. This is the URL to the Ethereum client we will fork from. So when starting ganache with the command ganache-cli -f https://mainnet.infura.io/v3/yourInfuaProjectId
, the process that starts will contain the state of the most recent block on the Ethereum mainnet!
Note: When forking from an Infura node, make sure that you follow the above steps of registering and obtaining a project ID. If you try to fork without that, it may still work, but will likely result in some strange behavior.
When starting ganache-cli with this approach, it will not download the full state to your hard drive, but will instead query the mainnet on-demand as required. As a result, tests will inevitably run slower than a true local blockchain, but the simplicity and benefits of this approach make that trade-off worth it.
One last point on this feature worth noting is that, as seen from the documentation quoted above, we can also fork from a certain block if necessary by appending @blockNumber
to the URL. This is another handy feature that might be helpful for your use case.
Unlocking Accounts
The second valuable feature of ganache-cli is the ability to unlock accounts. Sure, we don’t usually have access to an exchange’s assets, but now we do! From the documentation, it works as follows (there’s a bit more documentation than we show here):
-u
or--unlock
: Specify--unlock ...
any number of times passing either an address or an account index to unlock specific accounts.
$ ganache-cli --unlock "0x1234..." --unlock "0xabcd..."
This feature can also be used to impersonate accounts and unlock addresses you wouldn’t otherwise have access to. When used with the
--fork
feature, you can use ganache-cli to make transactions as any address on the blockchain, which is very useful for testing and dynamic analysis.
That pretty much sums it up! The value here is that we can now easily obtain any ERC20 tokens (or really anything else) needed for development. Just head to Etherscan, browse for an address containing the desired assets, and unlock it. For example, if we want to unlock Binance’s account and test on the mainnet, we would start ganache with:
ganache-cli -d -f https://mainnet.infura.io/v3/yourInfuraProjectId -u "0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8"
You may have noticed we also added in the -d
flag there. This is the deterministic setting, meaning the available accounts ganache generates will be consistent between each startup, instead of random. This is not necessary, but can be convenient.
One thing to be careful of with unlocking accounts is when unlocking them to simulate users (such as unlocking your own personal account because you have DAI and want to simulate users who already have DAI). If you are developing to interact with DAI and you have already called a function such as approve()
on the real mainnet, your unlocked account on the local blockchain will mirror that state. Consequently, it can be easy to overlook and neglect the approval()
call during development, resulting in tests that pass for you, but won’t pass in reality.
Resources
That’s about it! Be sure to read through the ganache-cli documentation for other features you may find useful, such as the —-secure
flag.
Below are some relevant articles about these features. Note that the sample code in these articles may not include the Infura Project ID in the fork URL, but it is necessary as explained above.