Contract Overview
Files
- EIP-2535 Diamond Proxy related:
Diamond.sol
: proxy contract itselffacets/
: each Diamond Facet contract in the system. These contain everyexternal
function, or every user facing function in the systemlibraries/DataTypes.sol
: all structs usedlibraries/AppStorage.sol
: single struct that contains all data in the diamondlibraries/LibXXX.sol
: shared internal library functions that may be used across any of the facets. These are inlined into each facet when deployed.
contracts/interfaces/
: separate interfaces not auto-generated ininterfaces/
bridges/
: standalone contracts to bridge between between ETH and staked ETH.BridgeReth.sol
: bridge to deposit rETH into dETHBridgeSteth.sol
: bridge to deposit stETH into dETH
governance/
: governance contracts for DAOlibraries/
: bulk of shared logic contained in library files that are inserted into Facetsmocks/
: mock files for Chainlink, Lido, Rocket Pooltokens/
/Asset.sol
: base ERC-20 used in the system.dETH.sol
(viaAsset.sol
): stablecoin for ETH backed by liquid staking derivative (no yield)dUSD.sol
(viaAsset.sol
): stablecoin for USD, backed by dETH
Ditto.sol
(viatokens/Asset.sol
): DAO token
deploy/
: deploy scriptsinterfaces/
: auto generated folder by running a script that reads all Facets and produces interfaces from.sol
files. It's useful to checkIDiamond.sol
which includes all facets.
Modifiers
onlyDAO: Verifies the caller is the owner of the diamond contract. Owner is currently the DittoTimelockController, which is governed by the DittoDAO.
onlyAdminOrDAO: Verifies the caller is the admin or the DittoDAO, which allows for quicker responses to time sensitive actions.
nonReentrantView: Reentrancy guard for view functions. Only applicable for protocols integrating with DittoETH.
onlyDiamond: Checks that the caller is the diamond contract. This is to give privileged access to certain external functions.
onlyDAO onlyAdminOrDAO nonReentrantView onlyDiamond withdrawTapp shutdownMarket getDethTotal deposit (bridge) transferOwnership cancelOrderFarFromOracle getCollateralRatio depositEth (bridge) setVault transferAdminship getCollateralRatioSpotPrice withdraw (bridge) createBridge setTithe getAssetCollateralRatio createForcedBid deleteBridge setDittoMatchedRate getBids createMarket setDittoShorterRate getAsks diamondCut¹ setInitialCR getShorts setLiquidationCR getShortIdAtOracle setMinAskEth getShortRecord setForcedBidPriceBuffer getShortRecords setPenaltyCR getShortRecordCount setWithdrawalFee getDethBalance setMinShortErc getAssetBalance setMinBidEth getUndistributedYield setTappFeePct getYield setCallerFeePct getDittoMatchedReward setRecoveryCR getDittoReward getAssetUserStruct getAssetStruct getVaultUserStruct getVaultStruct getBridgeStruct ¹ onlyDAO is enforced via
enforceIsContractOwner()
inLibDiamond.sol
nonReentrant: Prevents calling another nonReentrant function within the diamond proxy.
isNotFrozen: Checks that the asset is not frozen.
onlyValidAsset: Checks if the address passed as the asset belongs to the system.
isFrozen: Checks that the asset being called is frozen.
onlyValidShortRecord: Checks that a short record is valid for a given asset, user, and id.
isPermanentlyFrozen: Checks that an asset is not permanently frozen.
nonReentrant isNotFrozen isPermanentlyFrozen onlyValidAsset onlyValidShortRecord cancelAsk() ✅ ✅ cancelBid() ✅ ✅ cancelOrderFarFromOracle() ✅ ✅ cancelShort() ✅ ✅ claimDittoMatchedReward() ✅ claimRedemption() ✅ ✅ claimRemainingCollateral() ✅ ✅ combineShorts() ✅ ✅ ✅ createAsk() ✅ ✅ ✅ createBid() ✅ ✅ ✅ createLimitShort() ✅ ✅ ✅ decreaseCollateral() ✅ ✅ ✅ deposit() ✅ depositAsset() ✅ ✅ ✅ depositEth() ✅ disputeRedemption() ✅ ✅ distributeYield() ✅ ✅ ¹ exitShort() ✅ ✅ ✅ exitShortErcEscrowed() ✅ ✅ ✅ exitShortWallet() ✅ ✅ ✅ increaseCollateral() ✅ ✅ ✅ liquidate() ✅ ✅ ✅ liquidateSecondary() ✅ ✅ ✅ proposeRedemption() ✅ ✅ redeemErc() ✅ ✅ shutdownMarket() ✅ ✅ ✅ updateYield() ✅ withdraw() ✅ withdrawAsset() ✅ ✅ withdrawDittoReward() ✅ ¹ Modifier is on internal function call
_distributeYield
, which is inside a loop in the top leveldistributeYield
call.
Testing
Chainlink, Lido, Rocket Pool mocked in the tests
scripts/
: misc scripts for during devtest/
: unit teststest/utils/OBFixture.sol
: setup to create all contracts, helper functions used in tests, init code
Gas Testing
FOUNDRY_PROFILE=gas
to deploy with the optimizer. Specific tests for gas to try to isolate the estimated gas per function by trying to take into account the cost of warm/cold storage slots.
test-gas/
: specific tests for average gas usage. Set profile to be compiled using optimizer.test-gas/GasHelper.sol
: same for the gas tests. Adds thestartMeasuringGas
/stopMeasuringGas
helpers.
.gas.json
: Gas is written to separate temp files, andnpm run gas
updates them to JSON.
Optimizations
DittoETH utilizes several gas saving optimizations to bring down the gas cost of using the system. As a general rule, optimizations were focused on the 80% use case.
- Diamond Proxy: Requires two external calls, but gives internal access to storage and library calls.
- Struct packing: See the
STypes
library inDataTypes.sol
to see how storage slots are allocated for struct variables. Struct variables that are commonly accessed and wrote to are put into the same slot if possible. - Hint system: Users or frontends must pass a hint to accurately find the accurate place in
bidOrders
,askOrders
,shortOrders
linked lists. This reduces the need for iterating over a large number of Orders. - Reuse IDs: Ids are recycled after use for the following storage variables:
bidOrders
,askOrders
,shortOrders
, andshortRecords
. This is applicable for theSTypes.Orders
andSTypes.ShortRecords
structs. The cost to set storage after initial use goes down from 20k to 5k gas because the slots are not cleared after use. - Utilize unused storage:
Constants.HEAD
of thebidOrders
mapping represents the HEAD of the linked list and is never used as an order. This allowsercAmount
andcreationTime
to be repurposed fororaclePrice
andoracleTime
. This is abstracted in the codebase as thesetPriceAndTime
function.- Another instance is using the
Diamond proxy
address, which isaddress(this)
as theTAPP
address in some of the mappings.
- Another instance is using the
- Keep Storage Non-Zero: In
YieldFacet
,dittoMatchedShares
anddittoReward
are left at 1wei
, which effectively serves as zero and avoids the evm penalty of setting storage of a zeroed slot in subsequent calls. - Virtual Accounting: dETH and
stable assets
are tracked virtually, which are only burned and minted when entering and exiting the system. This reduces the need for an external call to the ERC-20 fortransfer
,mint
,burn
, ect. - Store Oracle Data: External (live) oracle data is only queried when certain conditions have been met. Otherwise, the system uses an internally stored price and time.