Contract Overview
Files
- EIP-2535 Diamond Proxy related:
Diamond.sol: proxy contract itselffacets/: each Diamond Facet contract in the system. These contain everyexternalfunction, 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.solfiles. It's useful to checkIDiamond.solwhich 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.solnonReentrant: 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 leveldistributeYieldcall.
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=gasto 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/stopMeasuringGashelpers.
.gas.json: Gas is written to separate temp files, andnpm run gasupdates 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
STypeslibrary inDataTypes.solto 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,shortOrderslinked 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.OrdersandSTypes.ShortRecordsstructs. 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.HEADof thebidOrdersmapping represents the HEAD of the linked list and is never used as an order. This allowsercAmountandcreationTimeto be repurposed fororaclePriceandoracleTime. This is abstracted in the codebase as thesetPriceAndTimefunction.- Another instance is using the
Diamond proxyaddress, which isaddress(this)as theTAPPaddress in some of the mappings.
- Another instance is using the
- Keep Storage Non-Zero: In
YieldFacet,dittoMatchedSharesanddittoRewardare 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 assetsare 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.
