Modules
Modules are the primary way to add or update functionality in Axis. There are many different "upgradable" or "modular" smart contract designs, so it's important to understand what Modules in Axis are as well as what they can/cannot do.
Axis Modules were initially based on the Module design from the Default Framework, but we modified them heavily to suite our needs.
Modules vs. Proxies
A key goal for many smart contract protocols is to make their systems immutable. Immutability allows users to trust the code and not the administrator of the contract (assuming the user reads and understands the code well enough to be confident there are no security issues). Additionally, it allows other projects to more confidently integrate with the contract(s) and be sure there will be no breaking changes.
However, immutability has a downside: bugs cannot be patched. If a bug is discovered and puts user funds at risk, the team has to hope they can beat any malicious actors (or even MEV bots) to beating them to make a recovery of the funds, if this is even possible. For this reason, many organizations, specifically corporate ones, use upgradeable proxies when they deploy their systems.
Upgradable proxies allow administrators to completely replace the code that is run at a contract address without changing the internal state or having to move any token balances. They can (and do) use this power for good to batch bugs without any impact to the user, but you have to trust them not to take all the funds stored in the contract. Sometimes proxies are used during an initial period after deployment and then "frozen" so they cannot be upgraded anymore, which is a decent compromise. However, any use of proxies means there is some centralization in the smart contract system, and, therefore trust is required in the administrators.
At Axis, we place a high priority on immutability and decentralization of the system. To start with, the AuctionHouse contracts themselves are immutable and require deployment of a new system to make updates. Additionally, we have designed Modules to allow extending and upgrading the functionality of the system without affecting existing users.
Upgradable you say? That doesn't sound immutable! It is where it counts: once an auction is created, the logic for that auction and its derivative cannot be changed by any Module upgrades.
In the Axis system, each Module has a 5-byte Keycode
that tells an AuctionHouse what family of Modules it belongs to. When a Module is installed on an AuctionHouse, the AuctionHouse creates a pointer from the Module's Veecode
to its deployed address. A Veecode
is a 7-byte, versioned keycode, which is the combination of a 2 digit version and the Module's Keycode
. An example is the EncryptedMarginalPrice AuctionModule which has Keycode: "EMPA"
(Keycodes are 3-5 uppercase letters). The first version being deployed has Veecode: 01EMPA
, where the version number is 01
.
If a bug or improvements are made to a module, it can be "upgraded". However, what this really means is that a new version of that Module is installed on the AuctionHouse. The AuctionHouse keeps track of the latest version of a each Module family, but the previous versions can still be referenced by their Veecode
.
Whenever an auction is created, the seller selects what kind of auction they want to run and, optionally, what kind of derivative to sell by specifying a Keycode
for the AuctionModule and DerivativeModule. The AuctionHouse takes these Keycode
s and looks up the most recently installed version of each, and then stores those modules Veecode
s in the auction data. In this way, that specific auction is locked into using the logic in those specific version of the Modules, regardless of if a newer version is installed after the auction is created. Therefore, users have confidence that the code that is present will not change on them in the future.
Additionally, Modules cannot be uninstalled. In this way, auctions cannot be stopped either. However, what if there is a Module family that cannot be fixed or is no longer desired? We can then "sunset" the Module. A sunset Module has no active versions and cannot be used to create new auctions/derivatives. Existing auctions/derivatives will continue to work until they are complete.
We think this design provides a good compromise to the two extremes of a completely immutable and a completely upgradable system.
Here's a table to summarize the main differences between Modules and Proxies:
Feature | Axis Modules | Proxies |
---|---|---|
Immutable | Yes (for a particular auction) | No |
Upgradable | Yes (only applies to new users) | Yes |
Brickable | No (but can prevent new usage) | Yes |
Freeze Version | Yes (for a particular auction) | Yes |
Module Management
Each AuctionHouse has an owner
who has the sole ability to install
, upgrade
, and sunset
Modules on the contract. Thus, the Modules that are installed on the AuctionHouse are permissioned and verified by the owner.
Axis offers a separate framework to permissionlessly extend auction functionality: Callbacks.
Permissioned Functions on Modules
When Modules are deployed, their parent
address is immutably set. This must be the AuctionHouse that the Module is being installed on.
In order for the auction logic to be executed correctly, state-changing functions on Auction Modules are restricted to only being called by the AuctionHouse, using the onlyParent
modifier. As such, any administrative setter functions must be called by the AuctionHouse. To facilitate this in a general way, the AuctionHouse has an execOnSubmodule
function that accepts arbitrary calldata to pass along that is callable by its owner
.
However, it is not intended that any restricted function on the AuctionHouse be callable by the owner
at any time. This could potentially cause issues with auctions if the sequenced logic in the AuctionHouse is bypassed. Therefore, Modules have another modifier, onlyInternal
, which restricts the state-changing functions that are called during the auction process to only calls that originate from outside of execOnSubmodule
.