How to link libraries into Solidity contracts generated by sol-compiler
Liam Zebedee
Posted on April 18, 2019
Solidity smart contracts are generated from source into bytecode. However, much like the days of yore in C, linking in other libraries that may already be deployed is sometimes a necessity.
When a Solidity artifact is generated using Truffle or sol-compiler, there is a field called bytecode
or deployedBytecode
. If you have unlinked libraries and try to deploy this code, you will get an error along the lines of invalid contract bytecode:
{ Error: invalid contract bytecode (arg="bytecode", value="0x608060405260016004554360075543600__$e66798aa9224cd2742272d2e7f8089dbe6$__ffffffffffffffffffffffffffffffffffffffff1660601b81526014018281526020019350505050604051602081830303815290604052805190602001209050939250505056fea165627a7a72305820fdb2e9c02d3de9057e4d439bc377d8c8ae842b83649e495e3303688bfe6f7e930029", version=4.0.26)
If you examine deeper, you can see there is a very non-hexadecimal looking character $
which is the beginning of a link reference. The link reference itself is a string __$e66798aa9224cd2742272d2e7f8089dbe6$__
. This used to have a more human-readable name, such as ____________________$Library$_
, but it was changed to a more arcane algorithm. The libraryHashPlaceholder method creates this format, but what is the input we are giving it? It is of the form, libraryName:lib
.
For example, my MerkleTreeVerifier
library would have the name:
libraryHashPlaceholder('/Users/liamz/Documents/open-source/0dex/packages/contracts/contracts/MerkleTreeVerifier.sol:MerkleTreeVerifier')
// '$1eb98b648b444978ea3820de6fcdeb48d6$'
Integrating with sol-compiler
Now for the 0x compiler, you will need to do two things. Note that I'm also using abi-gen to generate TypeScript wrappers of our contracts.
1) Enable the metadata
option in compiler.json
. For example, here is mine:
{
"contractsDir": "contracts",
"artifactsDir": "build/artifacts",
"contracts": "*",
"compilerSettings": {
"optimizer": { "enabled": false },
"outputSelection": {
"*": {
"*": [
"metadata",
"abi",
"evm.bytecode.object",
"evm.bytecode.sourceMap",
"evm.deployedBytecode.object",
"evm.deployedBytecode.sourceMap"
]
}
}
}
}
2) Add your libraries and link them.
export function addLibrary(name: string, address: string) {
let artifact = require(`@ohdex/contracts/lib/build/artifacts/${name}.json`);
let metadata = JSON.parse(artifact.compilerOutput.metadata)
let [k,v] = Object.entries(metadata.settings.compilationTarget)[0];
let key = `${k}:${v}`;
console.log(`Registered library ${name} to ${key}`)
libraries[key] = address;
}
// Deployment of libraries
// Note that if the library already exists, we just add the address instead
// ....
// Your sol-compiler generated wrapper
import { MerkleTreeVerifierContract } from '@ohdex/contracts/lib/build/wrappers/merkle_tree_verifier';
let merkleTreeVerifier = await MerkleTreeVerifierContract.deployAsync(
...getDeployArgs('MerkleTreeVerifier', pe, account)
)
addLibrary('MerkleTreeVerifier', merkleTreeVerifier.address)
// Linking libraries to your own contracts
let json = require(`@ohdex/contracts/lib/build/artifacts/${name}.json`);
let bytecode = json.compilerOutput.evm.bytecode.object;
bytecode = linker.linkBytecode(bytecode, libraries)
And tada! You've linked your libraries and deployed your contract!
Posted on April 18, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.