TL;DR — Enzyme funds are now fully upgradeable through a process that allows managers to opt-in to the latest release, while providing investors a window during which to opt-out. The technical approach involves transplanting the “soul” of the fund (ownership, asset holdings, and shares) into a new shell (release configuration).
Since late 2019, the core development team at Avantgarde Finance has been tirelessly working towards the second major release of the Enzyme Protocol (formerly Melon).
In mid-2020, our team was entering a push for a Q3 release of the first major version since the original launch of Melon in Q1 2019. The protocol was on track to deliver significant improvements such as single-tx fund setup, lending integrations, across-the-board gas savings, and abstracted and extensible fees, policies, and DeFi adapters. Yet, as the target date approached, our team was haunted by a persistent user experience concern: funds were not upgradable.
Non-upgradable funds are a user experience nightmare
Generally, when there is a new release of the Enzyme Protocol, a fund manager would likely want to move to that release to take advantage of new features, improvements, and continuously-maintained infrastructure (e.g., price feeds and the official Avantgarde Finance UI).
Previously, such a fund manager would have needed to create a new fund on the new release, then somehow communicate to their investors to redeem shares from the old fund and invest in the new fund. This creates a user-experience nightmare:
- how to communicate the move with mostly pseudo-anonymous investors
- funnel drop-off between investors redeeming shares and re-investing
- gas and protocol fee costs of this multi-step process
We were aware that several potential power users were waiting on the sidelines for fund upgradability, and it became clear that if we wanted to both win them over and roll out new functionality continuously and aggressively (we do!), then we would need upgradable funds.
Upgrading Enzyme funds in a way that allows user agency is extremely complicated
Finding the appropriate upgradability paradigm for Enzyme was extremely complicated because of three factors:
- Complexity of code base
- Coupling of contracts
- Desire to allow users to optionally upgrade
The Enzyme Protocol has a sprawling contract universe (currently 32 deployed contracts) that includes core logic for accounting and participation, extended functionality to handle fees, policies, and DeFi actions, and infrastructure to price and convert asset values.
Furthermore, these contracts were tightly coupled; the protocol was hardcoded with knowledge about specific fees, a specific price feed, specific types of extended functionality, and so on. (A big focus of this release cycle has been decoupling and abstracting these contracts).
Independently, none of these factors would necessarily make for a difficult upgradability paradigm. For example, if code complexity (or even tight coupling) were the only issue, the Enzyme Council could still upgrade the entire protocol and force all users to use the same versions of all contracts. Indeed, this is a generally-accepted paradigm for upgrading DeFi protocols.
However, it was philosophically important to us to allow our users agency in deciding whether or not to go along with any proposed upgrade.
From a “code is law” perspective, when users decide to start using a protocol, they are agreeing to the rules of how that protocol operates. While the argument can be made that agreeing to the rules of a proxy contract constitutes such an agreement, this gives carte blanche to the governance structure. Additionally, there could be practical or legal ramifications for some portfolio managers if there are changes to the way that assets are custodied, shares are bought or redeemed, assets are valued, etc.
This desire for optional upgrades was made all the more difficult by the fact that we have two user groups to consider: portfolio managers (of investment products) and investors (in those investment products).
The confluence of these three factors made a traditional approach to upgrades daunting and dangerous. Multiple user groups would need to participate in upgrading different types of contracts created during different release cycles, yielding limitless conformations of versions that would somehow need to be tested to guarantee cross-compatibility of all contracts within each conformation.
Fund in the shell: finding the “soul” of Enzyme funds
In trying to make sense of upgrading within this sprawling complexity, one question that emerged early on in our team was: what is the soul of an Enzyme fund?
For me, this question evokes cyborg imagery from cyberpunk sci-fi such as Ghost in the Shell or the more recent Altered Carbon. Such dystopian futuristic universes focus on upgrading or completely substituting the physical body while maintaining the integrity of the mind/soul. These settings are modern extrapolations of classical Cartesian philosophical discussions of mind-body duality.
So what is the “soul”, the essence, of Enzyme funds? Roughly ordered from most to least essential:
- asset holdings
- ownership and access
- mandate (what the fund is and is not allowed to do)
- accounting and valuation logic
- other configuration
We also needed to consider which of these components were abstract enough so as to not handcuff us to particular architectural structures for future releases of the Enzyme Protocol. For example, persisting policies (the mandate) and fees would require each subsequent release to have the same understanding of not only what policies and fees are, but also to maintain consistency of what each instance in storage represents (e.g., use of a PerformanceFee with particular settings).
Ultimately, we decided that the soul of a fund is its asset holdings and its shares, along with a basic access control structure.
Now, it was time to create a paradigm for transplanting these ghosts to new shells.
Upgrading funds via transplant migration
In traditional database-driven systems, “migration” generally refers to using a mapping between schemas to read from a legacy database and write to the subsequent database.
As is frequently pointed out, a blockchain is not a database (and should not be used as one), and indeed it would be cumbersome and expensive to copy and write entire storage layouts to new contracts for each upgrade.
Transplanting an entire contract contract is a much more practical approach.
The above image is the architectural pattern that we settled on for this transplant-based upgradability process. The diagram is crude, complicated, and not 100% accurate, but it serves to illustrate a few key points below. Note, for example, that the colors of group boxes represent different categories of lifespan: the purple boxes represent perpetual contracts that continue to exist across all releases, the blue boxes represent architecture that can potentially be recycled between releases, and the green boxes represent release-specific architecture.
The ghost and the shell: VaultProxy and VaultLib
We decided to implement the oft-used proxy upgradability pattern (following EIP-1822 and EIP-1967) by deploying a
VaultProxy proxy contract per fund that points to a shared
VaultLib logic contract particular to the current release. This
VaultProxy is then upgraded by pointing it to the
VaultLib of the next release.
VaultProxy persists across releases, and thus provides a canonical fund address at which to house asset holdings. Meanwhile, the essential storage layout for the remaining elements of this soul (ownership and an ERC20 layout for shares) are defined within a
VaultLibBaseCore contract that is the foundation of each
VaultLib. Subsequent releases can add to this core storage layout by extending the base of the previous release. For example, this first release adds a storage pattern and events for tracking assets held by the fund.
This is all a fairly common upgradability pattern.
The real magic comes from the architecture to transplant these ghosts to new shells.
The transplant surgeon: Dispatcher
There is a non-upgradable contract called the
Dispatcher that sits atop all releases. Its primary responsibilities are to deploy new
VaultProxy instances, understand which release each
VaultProxy instance is currently on, and to facilitate the migration of
VaultProxy instances to the current release.
Dispatcher is ostensibly the surgeon for transplanting
VaultProxy instances to new releases, and only accepts a command to do so from the latest release. As far as this overarching infrastructure goes, the Enzyme Council only needs to keep the
Dispatcher up-to-date with the current release.
The transplant itself is incredibly simple: the
Dispatcher simply updates two storage variables on the
- The next
- The next
accessor(an abstract reference to the account that is allowed to change
This abstract handoff between releases allows for release architecture to evolve almost limitlessly, as the
Dispatcher is completely indifferent to how releases operate.
In order to grant users agency whether to upgrade to the newest release, the
Dispatcher follows a signal-execute pathway:
- The manager signals that they will upgrade to the newest release
- The manager must wait for a timelock to expire (currently 48 hours)
- The manager executes the upgrade, finalizing the move
This pathway affords both managers and investors the agency to upgrade. The fund manager opts-in by actively signaling and executing the upgrade, and investors are given a window during which they can opt-out, i.e., rage quit.
For a much more detailed (and technically correct) overview of the migration path that includes how it works within the current release, see the audited Enzyme General Spec
The full codebase will be made public along with the forthcoming audit report.
Protocol upgrades that fit the soul
Upgradability paradigms are by no means perfect or a one-size-fits-all solution for all projects. Finding novel approaches to upgradability will be a continuous theme of smart contracts development for the foreseeable future.
For the Enzyme Protocol, it was a helpful exercise to think about what is philosophically important in an upgrade, as well as what constitutes the “soul” to be upgraded. This led to a bespoke paradigm of transplant migration that will be the bedrock of (hopefully!) many releases to come.