m-chrzan.xyz
aboutsummaryrefslogtreecommitdiff
path: root/src/blog/extra-contracts-in-truffle-tests.html
blob: 719255ec3dd5433b8e8a4bede4a840a3741f8202 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
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>