title: Extra contracts in Truffle tests date: August 26, 2020 ---
Sometimes you just need a Truffle contract object. If you're developing a
full-blown Truffle project, this is easy for your "core" contracts, the ones you
keep in your contracts/
directory. Just
artifacts.require
and call it a day. But what if you want to
interact with a different contract?
I ran into this while working on meta-tooling around smart contracts,
specifically scripts for running upgrades of proxied contracts. To this end, I
wanted to write unit tests that would use minimal example contracts with
edge cases that might not easily appear "in nature". I want to keep these dummy
contracts in a test resources directory rather than cluttering up the main
contracts/
.
Unfortunately, not all parts of the Truffle API are very well documented, but here's how I got my sweet, sweet Truffle contract wrappers.
import truffleContract = require('truffle-contract') const Contract = truffleContract({ abi: artifact.abi, unlinked_binary: artifact.bytecode, })where
artifact
is a standard solc build artifact, say parsed from a
JSON file that truffle compile
outputs. The thing to note is that
the bytecode is under an unlinked_binary
property.
Before you go run off playing with your new Contract
, a few last
housekeeping things to take care of. If you try, say, deploying this contract
with Contract.new()
, you'll get a nasty error message saying
Error: Contract error: Please call setProvider() first before calling new().
Actually not that nasty since it tells you what you need to do to fix things!
Call Contract.setProvider()
with a standard Web3 provider. If you
already have a Web3 instance lying around,
Contract.setProvider(web3.currentProvider)should do.
Last things last, at this point you would start seeing the following:
Error: Contract has no network id set, cannot lookup artifact data. Either set the network manually using Contract.setNetwork(), run Contract.detectNetwork(), or use new(), at() or deployed() as a thenable which will detect the network automatically.I resolved this by calling
Contract.setNetwork('development')
,
given that this was in a unit tests context and would always execute against the
'development'
Truffle network. You might need to pass in a
different network name, or feel free to look into one of the other solutions
suggested by the above error message.
At this point you'll be able to call Contract.new()
to get a handle
to a freshly deployed contract, or Contract.at(address)
to interact
with an already deployed instance of the contract.
Here's the little helper function I wrote to construct these Truffle objects:
import truffleContract = require('truffle-contract') const makeTruffleContract = (artifact) => { const Contract = truffleContract({ abi: artifact.abi, unlinked_binary: artifact.bytecode, }) Contract.setProvider(web3.currentProvider) Contract.setNetwork('development') return Contract }
If you're using any linked libraries in your contract, first deploy the
libraries, then link them with
Contract.link('LibraryName', libraryAddress)
.