Unitas Architecture
Last updated
Last updated
ERC20Token.sol: ERC20 contract + (Blacklist + AccessControl + Pausable)
Unitas.sol: Core contract, this contract is primarily used for exchanging tokens, managing reserve assets.
InsurancePool.sol: The funds in our first phase InsurancePool are all contributed by Unitas. So this contract is just a simple vault. The auction module will be supported in the second phase.
SwapFunctions.sol: This is an abstract contract called SwapFunctions that implements the ISwapFunctions interface. It defines several functions for calculating swapping results, including calculating fees, converting amounts, and validating fees. These functions are implemented using the MathUpgradeable library from OpenZeppelin.
TokenManager.sol: This contract is responsible for managing currency pairs that are open for swapping, and setting parameters such as swapping fees and reserve ratio thresholds.
** Deflationary token is not supported.
TokenType.Asset: USDT (Phase1), USDC (TBD).
TokenType.Stable: USD1, USD91, USD971, and USD84. All Unitas stablecoins decimal is 18.
TypeTokens.sol: This is an abstract contract called TypeTokens that implements the ITypeTokens interface. It includes mappings for storing token types and token addresses, and events for adding or removing tokens from the pool. The contract also defines two modifiers for checking if a token is already in the pool or not.
TokenPairs.sol: This abstract contract implements the ITokenPairs interface. It is used to manage pairs, including the enumerable functionality.
PoolBalances.sol: This is an abstract contract includes some generic functions that are used to manage pool assets. It includes mappings for storing token balances and token portfolios, and events are emitted when portfolios change.
XOracle.sol: This contract is an oracle that provides price data for various assets. It allows addresses with the “FEEDER_ROLE” to update the prices of assets and anyone to view the current and previous prices. The “GUARDIAN_ROLE” is a role with higher permissions than the “FEEDER_ROLE” and is used for administrative purposes such as adding or removing feeders.
UnitasProxy.sol: Transparent Proxy
UnitasProxyAdmin.sol: ProxyAdmin
TimelockController.sol (OpenZeppelin Contracts): The TimelockController is configured with a time delay of 24/48 hours. This means that any proposed transactions or function calls through the TimelockController must be scheduled in advance and will only be executed after a waiting period of 24/48 hours. (Only calls from the GOVERNOR_ROLE are allowed.)
We will monitor the execution of Timelock functions on-chain and send notifications to the Unitas Telegram channel or any official Unitas channel, informing everyone about the operation.
MINTER_ROLE (Unitas contract)
TIMELOCK_ROLE (OZ Contract)
TIMELOCK’s admin == (GOVERNOR_ROLE)
FEEDER_ROLE (EOA)
GOVERNOR_ROLE (Multisig wallet)
GUARDIAN_ROLE (Multisig wallet)
PORTFOLIO_ROLE (Multisig wallet)
WITHDRAWER_ROLE (Unitas contract)
ProxyAdmin (OZ Contract)
ProxyAdmin’s admin (GUARDIAN_ROLE)
GUARDIAN_ROLE 2 of 3
PORTFOLIO_ROLE 2 of 3
GOVERNOR_ROLE (DAO) 4 of 7
Phase 1: 2 of 3
Phase2: 4 of 7
we will use the GOVERNOR multi-signature wallet as the SurplusPool in phase1. All protocol transaction fees will be sent to the surplus pool.
In phase 1, as the Insurance Pool does not yet support the auction module, the Unitas Foundation will act as an Insurance Provider (IP) for staking/unstaking USDT. This can be done through the depositCollateral/withdrawCollateral functions, using only the Guardian (multi-signature wallet).
The sendPortfolio function perform a timelock for 24 hours to transfer a portion of the funds to the portfolio wallet (multi-signature wallet) for DeFi staking investments.
Inherits Initializable, PausableUpgradeable, AccessControlUpgradeable, ReentrancyGuardUpgradeable, PoolBalances and SwapFunctions. The contract is upgradeable.
State Variables
usd1: Gets the address of USD1
oracle: Gets the address of oracle
surplusPool: Gets the address of surplus pool (treasury)
insurancePool: Gets the address of insurance pool
tokenManager: Gets the address of token manager
Modifiers
onlyTimelock: This modifier is used to restrict access to a function to only the address that has the TIMELOCK_ROLE. If the msg.sender does not have the TIMELOCK_ROLE, the function will revert with the NotTimelock error.
onlyGuardian: This modifier is used to restrict access to a function to only the address that has the GUARDIAN_ROLE. If the msg.sender does not have the GUARDIAN_ROLE, the function will revert with the NotGuardian error.
onlyPortfolio: This modifier is used to restrict access to a function based on a specific address having the PORTFOLIO_ROLE. The address of the portfolio is passed as an argument to the modifier. If the provided account does not have the PORTFOLIO_ROLE, the function will revert with the NotPortfolio error.
Public or External Functions
initialize: Initializes the states when deploying the proxy contract, e.g., access control, dependent contract addresses. (initializer restriction)
swap: Swaps tokens between USD-Pegs, USD1 or Unitized Local Stablcoins. Users can choose to input the amount to be spent or the amount to be received. The function will check oracle price is in the tolerance range, checks the reserve ratio is sufficient when there has a limitation. It will update the reserve of the token when the user spends or redeems USD-Pegs, and it will send fee to the surplus pool
estimateSwapResult: Estimates the swap result for quoting. It will return the amount to spent, the amount to receive, the fee, the numerator of fee ratio, and the price
getReserve: Gets current reserve of the token
getReserveStatus: Gets the reserve status, total reserves denominated in USD1, total collaterals denominated in USD1, total liabilities denominated in USD1, and the reserve ratio
getPortfolio: Gets the portfolio state of the token
Timelock Functions
setOracle: Updates the address of oracle
setSurplusPool: Updates the address of surplus pool
setInsurancePool: Updates the address of insurance pool
setTokenManager: Updates the address of token manager
sendPortfolio: Transfers the tokens from the pool to the portfolio manager
Guardian Functions
pause: Disables swapping
unpause: Enables swapping
Portfolio Functions
receivePortfolio: Transfers the tokens from the portfolio manager to the pool
Internal or Private Functions
_setOracle: Updates the address of oracle
_setSurplusPool: Updates the address of surplus pool
_setInsurancePool: Updates the address of insurance pool
_setTokenManager: Updates the address of token manager
_setReserve: Updates the reserve of the token
_swapIn: Transfers USD-Pegs to the pool, or burns Unitas stablecoins from the user
_swapOut: Transfers USD-Pegs or mints Unitas stablecoins to the user
_getSwapResult: Gets the swap result
_getReserveStatus: Calculates the reserve ratio by reserves and liabilities, returns the reserve status and the reserve ratio
_getTotalReservesAndCollaterals: Gets total reserves and total collaterals denominated in USD1
_getTotalLiabilities: Gets total liabilities denominated in USD1
_getPriceQuoteToken: Gets the quote currency based on two token address parameters
_checkPrice: Reverts when the price tolerance range of the token is unset, or the price is not in the tolerance range
_checkReserveRatio: When the reserve ratio threshold of the swap type is positive, checks the reserve ratio is greater than the threshold
Inherits AccessControl and ReentrancyGuard and PoolBalances.
Modifiers
onlyGuardian: This modifier is used to restrict access to a function to only the address that has the GUARDIAN_ROLE. If the msg.sender does not have the GUARDIAN_ROLE, the function will revert with the NotGuardian error.
onlyGuardianOrWithdrawer: This modifier allows access to a function for addresses that have either the GUARDIAN_ROLE or the WITHDRAWER_ROLE. If the msg.sender does not have either of these roles, the function will revert with the NotWithdrawer error.
onlyTimelock: This modifier restricts access to a function to only the address that has the TIMELOCK_ROLE. If the msg.sender does not have the TIMELOCK_ROLE, the function will revert with the NotTimelock error.
onlyPortfolio: This modifier restricts access to a function based on a specific address having the PORTFOLIO_ROLE. The address of the portfolio is passed as an argument to the modifier. If the provided account does not have the PORTFOLIO_ROLE, the function will revert with the NotPortfolio error.
Public or External Functions
getCollateral: Gets current collateral of the token
getPortfolio: Gets the portfolio state of the token
Guardian Functions
depositCollateral: Puts the collateral for a given token to the pool
Guardian or Withdrawer Functions
withdrawCollateral: Withdraws the collateral of the token from the pool. The guardian can withdraw the collateral, and Unitas contract (withdrawer) will withdraw the collateral when the reserve is insufficient to redeem
Timelock Functions
sendPortfolio: Transfers the tokens from the pool to the portfolio manager
Portfolio Functions
receivePortfolio: Transfers the tokens from the portfolio manager to the pool
Inherits AccessControl, TypeTokens, TokenPairs.
State Variables
RESERVE_RATIO_BASE: Returns 1018. The denominator of reserve ratio and threshold that have 18 decimal places
SWAP_FEE_BASE: Returns 106. The denominator of swapping fee that has 6 decimal places
_maxPriceTolerance: This is a mapping that associates an address of a token with its corresponding maximum price tolerance. It is used to store the maximum price tolerance for each token.
_minPriceTolerance: This is a mapping that associates an address of a token with its corresponding minimum price tolerance. It is used to store the minimum price tolerance for each token.
_pair: This is a mapping that associates a pair hash with a PairConfig object. The PairConfig contains two token addresses, fee numerators and reserve ratio thresholds
usd1: Gets the address of USD1
Modifiers
onlyTimelock: This modifier is used to restrict access to a function to only the address that has the TIMELOCK_ROLE. If the msg.sender does not have the TIMELOCK_ROLE, the function will revert with the NotTimelock error.
Public or External Functions
listPairsByIndexAndCount: Gets an array of PairConfig, supporting pagination
getPriceTolerance: Gets the price tolerance range of the token
getTokenType: Gets the type of the token. There has two types of tokens, Asset
(USD-Pegs) for calculating reserves, Stable
(USD1, USD91 and USD971) for calculating liabilities
getPair: Gets the pair setting by the two token addresses. Reverts if the pair does not exist
pairByIndex: Gets the pair setting by the index of pair hashes
Timelock Functions
setUSD1: Updates the address of USD1, and adds it to the pool as stablecoin
setMinMaxPriceTolerance: Updates the price tolerance range of the token
addTokensAndPairs: Adds the tokens and the pairs to the pool. The input arrays can be empty, and the update is performed only when any array has values
removeTokensAndPairs: Removes the tokens and the pairs from the pool. The input arrays can be empty, and the update is performed only when the tokens array, or the two arrays of pair token addresses have values
updatePairs: Updates the pair settings based on the input array of PairConfig. Reverts if any PairConfig is invalid or not in the pool
Internal or Private Functions
_setUSD1: Updates the address of USD1, and adds it to the pool as stablecoin
_setMinMaxPriceTolerance: Updates the price tolerance range of the token
_addTokensAndPairs: Adds the tokens and the pairs to the pool. Since _addPair
checks whether the token is already in the pool, this function adds the tokens before the pairs
_removeToken: Removes the token from the pool. The pairs associated with the token must be removed first
_addPair: Adds the pair to the pool. Reverts if the PairConfig is invalid, two tokens are not in the pool, or the pair is already in the pool
_updatePair: Updates the pair setting by PairConfig. Reverts if the PairConfig is invalid, two tokens are not in the pool, or the pair is not in the pool
_removePair: Removes the pair and the PairConfig, reverts if the pair doesn’t exist
_checkPairParameters: Checks whether the parameters of the PairConfig are valid. The two tokens must be added to the pool before adding the pair. It validates the two token addresses are not the same, and one of the tokens must be USD1. It also validates the fee numerators and the reserve ratio thresholds
_isTokenTypeValid: Returns true when the token type is Asset or Stable
_checkSwapFeeNumerator: Checks if the input of the swapping fee numerator is valid. A valid swapping fee numerator must be zero or less than the denominator. which has 6 decimal places
_checkReserveRatioThreshold: Checks if the input of the reserve ratio threshold is valid. A valid threshold must be zero, or greater than or equal to the denominator, which has 18 decimals places
State Variables
_tokenType: This mapping associates a token address with its corresponding token type. It allows you to map a specific token address to a numeric token type, represented as an uint8 value.
_typeTokens: This mapping associates a token type with a set of addresses. It uses an EnumerableSetUpgradeable.AddressSet data structure to store the set of addresses associated with a particular token type. This mapping allows you to group token addresses based on their token types.
Modifiers
tokenInPool: This modifier is used to ensure that a given token is already present in the pool. It checks the condition using the isTokenInPool function. If the token is not found in the pool, the function will revert with the TokenNotInPool error.
tokenNotInPool: This modifier is used to ensure that a given token is not already present in the pool. It also checks the condition using the isTokenInPool function. If the token is found in the pool, the function will revert with the TokenAlreadyInPool error.
Public or External Functions
listTokensByIndexAndCount: Gets token addresses of the token type, supporting pagination
isTokenInPool: Returns true when token is in the pool
tokenLength: Gets the token count of the token type
tokenByIndex: Gets the token address for a given token type and index
Internal or Private Functions
_addToken: Adds a token to the pool for a given token address and token type
_removeToken: Removes the token from the pool
State Variables
_pairTokens: This mapping is used to map the token address to the set of addresses, where the key and each element in the set represents a pair of tokens
_pairHashes: The set of pair hashes that is used to determine whether a pair exists in the pool
Public or External Functions
listPairTokensByIndexAndCount: Gets an array of token addresses that are paired with the specified token, supporting pagination
isPairInPool: Returns true when the pair is in the pool
pairTokenLength: Gets the total number of tokens that are paired with the specified token
pairTokenByIndex: Gets the paired token address by the specified token and index
pairLength: Gets the total number of pairs
getPairHash: Sorts the two token addresses in ascending order, then returns the hash
Internal or Private Functions
_addPairByTokens: Adds a pair to the pool by the two tokens addresses that are sorted in ascending order. Reverts if the pair already exists
_removePairByTokens: Removes the pair from the pool by the two tokens addresses. Reverts if the pair does not exist
_checkPairExists: Returns the hash of the pair by the two token addresses. Reverts if the pair does not exist
_getPairHash: Returns the hash of the two token addresses
_sortTokens: Sorts the two token addresses in ascending order
State Variables
_balance: This mapping is used to associate an address with the balance of a token. It allows you to store and retrieve the balance of a specific token for a given address.
_portfolio: This mapping is used to associate an address with the portfolio of a token. It allows you to store and retrieve the portfolio value of a specific token for a given address.
Internal or Private Functions
_setBalance: Updates the balance state of the token
_setPortfolio: Updates the portfolio state of the token
_receivePortfolio: Transfers the balance of the token from the sender to the pool
_sendPortfolio: Transfers the balance of the token from the pool to the portfolio manager.
_getBalance: Gets the balance state of the token
_getPortfolio: Gets the portfolio state of the token
_checkAmountPositive: Reverts if the amount is not greater than zero
Internal or Private Functions
_calculateSwapResult: Calculates the swap result by SwapRequest
. It will return the amount to spent, the amount to receive, and the fee
_calculateSwapResultByAmountIn: Calculates the swap result when the amount type is In
.
_calculateSwapResultByAmountOut: Calculates the swap result when the amount type is Out
.
_validateFeeFraction: Checks the fee fraction is valid
_getFeeByAmountWithFee: Calculates the fee based on the amount including the fee
_getFeeByAmountWithoutFee: Calculates the fee based on the amount excluding the fee
_convert: Converts the amount of one token to another token based on the price, and allows selecting the rounding mode
_convertByFromPrice: Converts the amount of source token to target token when the price is based on source token
_convertByToPrice: Converts the amount of source token to target token when the price is based on target token
Inherits AccessControl.
State Variables
prices: The mapping prices is defined with the key type address and the value type IOracle.Price.
Public or External Functions
putPrice: This function allows a caller with the “FEEDER_ROLE” to update the price for a specific asset. It checks if the provided timestamp is newer than the previous one and updates the price accordingly.
updatePrices: This function enables a caller with the “FEEDER_ROLE” to update prices for multiple assets in a single transaction. It iterates through an array of NewPrice structs and calls the putPrice function for each entry.
getPrice: A view function that retrieves the timestamp, previous timestamp, current price, and previous price for a given asset.
getLatestPrice: A view function that returns only the latest price for a given asset.
getStalenessThreshold: Gets the price staleness threshold, returns STALENESS_DEFAULT_THRESHOLD
if it is zero.
decimals: A pure function that returns the number of decimal places for the price data, which is hardcoded to 18.