DEV Community

Liam Zebedee
Liam Zebedee

Posted on

How to link libraries into Solidity contracts generated by sol-compiler

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!

Top comments (1)

Collapse
 
leckylao profile image
Lecky Lao

Is it the same as what Hardhat doing it here?
hardhat.org/plugins/nomiclabs-hard...