-
Notifications
You must be signed in to change notification settings - Fork 91
23. Smart Contracts Architecture
All of the Ubiquity contracts are upgradeable and divided in 2 parts:
Core contracts use the UUPS proxy standard while diamond facet contracts use the diamond proxy standard.
We decided to keep the core contracts aside from the diamond facets because:
- core contracts won't be frequently upgraded
- UUPS is simpler to implement and maintain. The core contracts are slightly modified ERC-20 and ERC-1155 tokens so moving them to diamond facets seems to be an overkill.
- diamond proxy standard requires unique function selectors (i.e. you can't add multiple ERC-20 token contracts to the diamond because they have equal function selectors)
The following table represents the core contracts:
Contract | Base contract |
---|---|
CreditNft | ERC1155Ubiquity |
StakingShare | ERC1155Ubiquity |
UbiquityCreditToken | ERC20Ubiquity |
UbiquityDollarToken | ERC20Ubiquity |
UbiquityGovernanceToken | ERC20Ubiquity |
When a user calls some method on the Diamond contract then this call uses delegatecall()
to a corresponding diamond facet which stores function selector for the call's msg.sig
. Each facet uses a corresponding library which implements a business logic and stores any state variable in a unique storage slot (i.e. each library stores state variables in a unique storage slot).
There is also a special Modifiers contract which is a base contract for most of the facets. It contains a special state variable which stores an AppStorage struct that is shared across all of the facets. The AppStorage struct occupies slot 0
in all of the facets.
The following table represents the diamond facet contracts:
NOTICE: In order to maintain UUPS and Diamond upgradeability all new state variables must be added to the end of a contract or a storage struct (storage struct example).
The following code refactors must be avoided:
Rule | UUPS related | Diamond related |
---|---|---|
New state variable introduced in the beginning or in the middle of a contract or a storage struct | + | + |
Order of state variables changed | + | + |
Type of state variable changed | + | + |
Existing state variable removed from the middle of a contract or a storage struct | + | + |
Existing state variable removed from the end of a contract or a storage struct | + | + |
Inheritance order changed (ex: "contract MyContract is A,B" != "contract MyContract is B,A" ) |
+ | - |
New state variable added to a base (i.e. derived) contract (unless storage gaps are used properly) | + | - |
Inner struct is added to a storage struct (unless we 100% sure that we will never add new state variables to the inner struct)* | - | + |
* Do not put structs directly in another struct unless you don't plan on ever adding more state variables to the inner structs. You won't be able to add new state variables to inner structs in upgrades without overwriting the storage slot of variables declared after the struct. The solution is to add new state variables to the structs that are stored in mappings, rather than putting structs directly in structs, as the storage slot for variables in mappings is calculated differently and is not continuous in storage.
Some of the contracts are neither part of the core contracts no part of the facets for different reasons:
Contract | Reason |
---|---|
CreditClock | We have plans to move it to facets |
DirectGovernanceFarmer | We have plans to move it to facets |
UbiquiStick contracts | Those contracts are "on hold" right now until we find a real utility for those contracts. Consider those contracts "not under active development". |
Without stability we have nothing. © 2023 Ubiquity DAO.