This document outlines the versioning mechanism for the deeptrade-core package. The system is designed to allow for safe upgrades of the contract logic while interacting with a single, persistent Treasury object.
The primary motivation for implementing versioning is to provide a secure and flexible upgrade path for the contract.
Key scenarios where versioning is crucial include:
- Forced Upgrades: In the event of a critical issue or a major feature update, a new contract version can be deployed. The old version can then be disabled, compelling all clients to upgrade to the new, improved version.
- Security Patches: If a vulnerability is discovered, a patched version of the contract can be quickly deployed and enabled, while the vulnerable version is disabled, mitigating the risk promptly.
- Phased Rollouts: Versioning allows for new features to be rolled out gradually. A new version can coexist with an old one, allowing for a smoother transition period for users.
The deeptrade-core package contains following shared objects: Treasury, CreatePoolConfig, TradingFeeConfig and LoyaltyProgram. The versioning mechanism described in this document applies only to the Treasury object.
The CreatePoolConfig, TradingFeeConfig and LoyaltyProgram objects are not versioned. This is because they can only be modified by an administrator, and there are no functions available for users to create new CreatePoolConfig, TradingFeeConfig or LoyaltyProgram objects.
The versioning is built upon four main components:
-
Treasury.allowed_versions: The centralTreasuryobject contains a field namedallowed_versions. This is aVecSet<u16>that stores a list of all package versions permitted to interact with it. -
CURRENT_VERSION: Each deployed version of thedeeptrade-corepackage has aCURRENT_VERSIONconstant (au16) defined inpackages/deeptrade-core/sources/helper.move. This constant uniquely identifies the version of that specific package's code. -
verify_version()Check: Most functions that mutate theTreasurystate begin with a call toverify_version(). This internal function reads its own package'sCURRENT_VERSIONand checks if that version number is present in theTreasury'sallowed_versionsset. If the version is not found, the transaction aborts with the errorEPackageVersionNotEnabled. -
Treasury.disabled_versions: TheTreasuryobject also contains adisabled_versionsVecSet<u16>. This acts as a permanent denylist. Once a version number is added to this set, it can never be re-enabled.
The process for upgrading the contract and managing which versions are active is controlled by an administrator.
The typical process for rolling out a new version follows these steps:
- Make Code Changes: Implement the required features or fixes in the contract source code that necessitate a version update.
- Increment Version: Update the
CURRENT_VERSIONconstant inpackages/deeptrade-core/sources/helper.moveto a new, unique number. - Deploy New Package: Deploy the updated package to the network. This action results in a new package object with a unique ID.
- Enable New Version: Using the newly deployed package, an administrator calls
enable_version. This action adds the new version to theTreasuryobject'sallowed_versionsset and emits aVersionEnabledevent. - Disable Old Version: To complete the upgrade, an administrator calls
disable_versionto remove the old version number. It is crucial that this call is also made from the new package. This is an irreversible action that moves the version to thedisabled_versionsdenylist and emits aVersionDisabledevent.
The decision to make disabling a version permanent is intentional and serves as a critical security measure to prevent human error.
A version might be disabled because it contains a vulnerability.
Over time, the specific reasons might be forgotten. To prevent a bad actor, who could somehow gain access to the AdminCap, from re-enabling a vulnerable version and exploiting it, we make it impossible to re-enable it again.
This security measure does not reduce flexibility. In case we need to use an old implementation for some reason, we can still take the code from an old commit, deploy it as a new package with a new CURRENT_VERSION, and enable that new version.
- A version can never be re-enabled once it has been disabled.
- An administrator cannot disable the currently executing version of the package. An attempt to do so will cause the transaction to revert.
The repository provides example scripts that demonstrate how to perform these administrative actions:
examples/treasury/versions/enable-version.tsexamples/treasury/versions/disable-version.ts
These scripts show how to construct and send the transactions to call the enable_version and disable_version functions.