23. Smart Contracts Architecture - ubiquity/ubiquity-dollar GitHub Wiki

Overview

All of the Ubiquity contracts are upgradeable and divided in 2 parts:

  1. Core contracts
  2. Diamond facet contracts

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)

Proxy diagram

Core contracts

The following table represents the core contracts:

Contract Base contract
CreditNft ERC1155Ubiquity
StakingShare ERC1155Ubiquity
UbiquityCreditToken ERC20Ubiquity
UbiquityDollarToken ERC20Ubiquity
UbiquityGovernanceToken ERC20Ubiquity

Diamond facet contracts

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:

Facet contract Library Unique storage slot
Modifiers (base class for most of the facets) LibAppStorage Slot (0)
AccessControlFacet LibAccessControl Slot
BondingCurveFacet LibBondingCurve Slot
ChefFacet LibChef Slot
CollectableDustFacet LibCollectableDust Slot
CreditNftManagerFacet LibCreditNftManager Slot
CreditNftRedemptionCalculatorFacet LibCreditNftRedemptionCalculator -
CreditRedemptionCalculatorFacet LibCreditRedemptionCalculator Slot
CurveDollarIncentiveFacet LibCurveDollarIncentive Slot
DiamondCutFacet LibDiamond Slot
DiamondLoupeFacet LibDiamond Slot
DollarMintCalculatorFacet LibDollarMintCalculator -
DollarMintExcessFacet LibDollarMintExcess -
ManagerFacet - -
OwnershipFacet LibDiamond Slot
StakingFacet LibStaking Slot
StakingFormulasFacet LibStakingFormulas -
TWAPOracleDollar3poolFacet LibTWAPOracle Slot
UbiquityPoolFacet LibUbiquityPool Slot

Upgradeability

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.

Other contracts

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".