Tagged

Upgrading the Argent Wallet to Solidity 0.6

How we upgraded our smart contracts from Solidity 0.5 to 0.6

Elena Gesheva
Oct 23, 2020

The Solidity team releases a major breaking version of the language roughly every year. 0.6 was released in December 2019 and contains around 30 breaking changes amongst many improvements. This post outlines the upgrade process for the smart contracts that underpin the Argent Wallet from Solidity version 0.5 to 0.6 (or, more precisely, from 0.5.4 to 0.6.12, the latest available minor version at the time of writing).

In addition the post examines the approach for supporting gradual migration, that is to partially migrate contracts while leaving others on older major versions. In total, over 4,300 lines of code in 31 contracts were migrated to 0.6, integrated with solc 0.5 based infrastructure contracts and Ethereum subsystems. Although the argent-contracts repo uses etherlime for compilation and testing, I will be drawing parallels to truffle as well because of its popularity.

Start with a well tested solution

It's best practice to maintain as close to 100% branch code coverage as possible on your contracts to ensure no regression bugs are introduced with the upgrade refactoring. Both truffle and etherlime provide code coverage functionality based on solidity-coverage tool. Additionally we run the slither static analyser on contracts.

Determine contract upgrade groups

Ideally a solution is upgraded end-to-end, however that's often not possible or practical. We chose to migrate the Wallet contracts to 0.6 while preserving most of the infrastructure contracts on version 0.5. With such a staged migration approach, we first analysed the contracts structure and dependencies to determine whether different contract groups are sufficiently isolated to allow them to be compiled independently. slither comes with a set of printers to help with analysis of usages, namely the inheritance-graph and call-graph printers, see https://github.com/crytic/slither/wiki/Printer-documentation. Below is an extract of the wallet modules inheritance model generated with slither . --print inheritance-graph.

Subset

Currently, neither etherlime nor truffle allow more granular compile targets than a folder, and selective compilation behaviour is not supported. Essentially we have to segregate contracts that target different solc versions in distinct folders to allow compilation using either framework.

The final contract groups and compile target versions are as follows:

pathsolc
contracts/infrastructure_0.50.5.4
contracts/infrastructure0.6.12
contracts/modules0.6.12
contracts/wallet0.6.12
contracts-legacy/v1.3.00.5.4
contracts-legacy/v1.6.00.5.4
contracts-test0.6.12
lib0.5.4

Note that with truffle you can achieve some selective compilation via regex, e.g. it's possible to exclude contracts from compilation via --contracts_directory option: truffle compile --contracts_directory 'lib/dappsys/[!note][!stop][!proxy][!thing][!token]*.sol'

Integration with 0.5 contracts

With the gradual upgrade approach after the separation of contracts, which we did above, if there are still unresolved cross solc version dependencies between contracts you will get errors similar to this:

{ Error: /argent-contracts/contracts/infrastructure/base/Managed.sol:17:1: ParserError: Source file requires different compiler version (current compiler is 0.6.12+commit.0bbfe453.Emscripten.clang) - note that nightly builds are considered to be strictly less than the released version

pragma solidity ^0.5.4;

^---------------------^

This overlapping dependencies problem can be solved by either setting the contract to compile with wider version range e.g. pragma solidity >=0.5.4 <0.7.0; or, if that is not possible (because of breaking changes), abstracting the called functions to an interface which we can set to the wider version range.

For example, we abstracted IModuleRegistry from ModuleRegistry and imported that new interface in WalletFactory instead of the full ModuleRegistry contract:

loading="lazy"

Another obstacle is the problem with external library imports, which don't allow parallel version installations, e.g. openzeppelin-solidity and git submodules. There's no graceful solution to this and our design allowed us to upgrade the openzeppelin-solidity while keeping the dappsys contracts on its original pragma that allowed compilation with both 0.5 and 0.6.

Once solidity versions no longer clashed we continued to fix the 0.6.12 breaking changes following the documentation.

Tooling support

Throughout the migration work the following blockers and improvements were logged in dependent frameworks we use:

IDE

Using Visual Studio Code with solidity extension when working on large scale refactoring like upgrades wasn't optimal. Switching contracts to a major version with 30 breaking changes potentially raises tens if not hundreds of compiler errors and warnings we have to work through. Since these are normally addressed by type, it will be easier for the reporter to allow us to group it that way.

https://github.com/microsoft/vscode/issues/98819

Test client

ganache-cli was able to successfully run all contract interactions post upgrade. Only issue was found https://github.com/trufflesuite/ganache-cli/issues/759

Solidity linter

We migrated to solhint since ethlint (a.k.a. solium) provided no support for the 0.6 syntax https://github.com/duaraghav8/Ethlint/issues/281

Compile and test framework

There are still gaps in supporting parallel compilation with different solidity versions.

- more granular compile targets

https://github.com/LimeChain/etherlime/issues/325

https://github.com/trufflesuite/truffle/issues/2021#issuecomment-654051052

- support multiple contracts with the same name

This has been an issue for over two years now but the problem is exacerbated with upgrades as the need to support legacy contracts means we have to effectively rename contracts to compile correctly, which becomes tedious to manage. See https://github.com/trufflesuite/truffle/issues/1087 as part of the ongoing rewrite here https://github.com/trufflesuite/truffle/issues/1718

slither provides a printer for detection of reused contract names slither . --ignore-compile --detect name-reused which helps to detect such instances.

Additionally slither itself does not yet fully support 0.6. https://github.com/crytic/slither/issues/405

Solidity

I was surprised by how easy it was switching to the version, helped by the Breaking changes documentation and the compiler messages that provided many fix suggestions. The only issue I bumped into was with the new override keyword which is required when implementing a function from a parent interface. See discussion here https://github.com/ethereum/solidity/issues/8281

Finally, for those using the DelegateProxy (a.k.a. EtherRouter or Proxy) upgrade pattern, since the base Proxy in the Argent Wallet is non-upgradable and designed with minimal code (~20 lines of code) essentially we end up running 0.6 contracts on top of a 0.5 Proxy. Since there are no breaking changes to storage design, this functions correctly.

Summary

Our work on the upgrade for the Argent Wallet can be found here https://github.com/argentlabs/argent-contracts/pull/114. Overall the upgrade was made easier by the Solidity compiler and documentation; it was made harder because tooling support was lacking on both 0.6 features and support for the mix of solidity versions.

Note that while this work was ongoing, Solidity 0.7 was released (in July 2020). However, due to the limited tooling support for that we decided to target 0.6. The work for upgrading to 0.7 and the related tooling and integration issues with the wallet are tracked here.


As always in our process, our contracts were audited by leading independent firms. For more information on this, you can find the audits at: https://github.com/argentlabs/argent-contracts/tree/master/audit

Own It

We use 🍪 cookies to personalise your experience on Argent. Learn more

Accept

Security & Support


HQ London, made with ❤️ across Europe