title: Extra contracts in Truffle tests date: August 26, 2020 --- <p> 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 <code>contracts/</code> directory. Just <code>artifacts.require</code> and call it a day. But what if you want to interact with a different contract? </p> <p> 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 <code>contracts/</code>. </p> <p> Unfortunately, not all parts of the Truffle API are very well documented, but here's how I got my sweet, sweet Truffle contract wrappers. </p> <h3>Getting the contract object</h3> <pre> import truffleContract = require('truffle-contract') const Contract = truffleContract({ abi: artifact.abi, unlinked_binary: artifact.bytecode, }) </pre> where <code>artifact</code> is a standard solc build artifact, say parsed from a JSON file that <code>truffle compile</code> outputs. The thing to note is that the bytecode is under an <code>unlinked_binary</code> property. <h3>Making it usable</h3> <p> Before you go run off playing with your new <code>Contract</code>, a few last housekeeping things to take care of. If you try, say, deploying this contract with <code>Contract.new()</code>, you'll get a nasty error message saying <pre> Error: Contract error: Please call setProvider() first before calling new(). </pre> </p> <p> Actually not that nasty since it tells you what you need to do to fix things! Call <code>Contract.setProvider()</code> with a standard Web3 provider. If you already have a Web3 instance lying around, <pre> Contract.setProvider(web3.currentProvider) </pre> should do. </p> <p> Last things last, at this point you would start seeing the following: <pre> 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. </pre> I resolved this by calling <code>Contract.setNetwork('development')</code>, given that this was in a unit tests context and would always execute against the <code>'development'</code> 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. </p> <p> At this point you'll be able to call <code>Contract.new()</code> to get a handle to a freshly deployed contract, or <code>Contract.at(address)</code> to interact with an already deployed instance of the contract. </p> <h3>TL;DR</h3> <p> Here's the little helper function I wrote to construct these Truffle objects: <pre> 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 } </pre> </p> <h3>Final note on linking</h3> <p> If you're using any linked libraries in your contract, first deploy the libraries, then link them with <code>Contract.link('LibraryName', libraryAddress)</code>. </p>