DittoETH - albahaca's results

A decentralized stablecoin protocol with an order book design for supercharged staking yield.

General Information

Platform: Code4rena

Start Date: 15/03/2024

Pot Size: $60,500 USDC

Total HM: 16

Participants: 43

Period: 21 days

Judge: hansfriese

Total Solo HM: 5

Id: 348

League: ETH

DittoETH

Findings Distribution

Researcher Performance

Rank: 13/43

Findings: 2

Award: $428.40

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

Awards

191.7284 USDC - $191.73

Labels

bug
grade-a
QA (Quality Assurance)
sufficient quality report
Q-15

External Links

Quality Assurance Report For DittoETH Contest

Total Low Risk Issues : 10

Total Non-critical Issues : 54

Low Risk Issues

IssueInstances
[L001]Array lengths not checked1
[L002]Unsafe downcast7
[L003]No limits when setting state variable amounts4
[L004]Double type casts create complexity within the code1
[L005]Constructor contains no validation2
[L006]For loops in public or external functions should be avoided due to high gas costs and possible DOS3
[L007]Function calls within for loops2
[L008]Multiplication on the result of a division3
[L009]Missing checks for address(0x0) in the constructor3
[L010]Prefer continue over revert model in iteration2

L001 - Array lengths not checked:

If the length of the arrays are not required to be of the same length, user operations may not be fully executed due to a mismatch in the number of items iterated over, versus the number of items provided in the second array.

File: facets/ShortOrdersFacet.sol


35          function createLimitShort(
36              address asset,
37              uint80 price,
38              uint88 ercAmount,
39              MTypes.OrderHint[] memory orderHintArray,
40              uint16[] memory shortHintArray,
41              uint16 shortOrderCR
42          ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant {
43              MTypes.CreateLimitShortParam memory p;
44              STypes.Asset storage Asset = s.asset[asset];
45              STypes.Order memory incomingShort;
46      
47              // @dev create "empty" SR. Fail early.
48              incomingShort.shortRecordId = LibShortRecord.createShortRecord(asset, msg.sender, SR.Closed, 0, 0, 0, 0, 0);
49      
50              uint256 cr = LibOrders.convertCR(shortOrderCR);
51              if ((shortOrderCR + C.BID_CR) < Asset.initialCR || cr >= C.CRATIO_MAX_INITIAL) {
52                  revert Errors.InvalidCR();
53              }
54      
55              // @dev minShortErc needs to be modified (bigger) when cr < 1
56              p.minShortErc = cr < 1 ether ? LibAsset.minShortErc(asset).mul(1 ether + cr.inv()) : LibAsset.minShortErc(asset);
57              p.eth = price.mul(ercAmount);
58              p.minAskEth = LibAsset.minAskEth(asset);
59              if (ercAmount < p.minShortErc || p.eth < p.minAskEth) revert Errors.OrderUnderMinimumSize();
60      
61              // For a short, need enough collateral to cover minting ERC (calculated using initialCR)
62              if (s.vaultUser[Asset.vault][msg.sender].ethEscrowed < p.eth.mul(cr)) revert Errors.InsufficientETHEscrowed();
63      
64              incomingShort.addr = msg.sender;
65              incomingShort.price = price;
66              incomingShort.ercAmount = ercAmount;
67              incomingShort.id = Asset.orderIdCounter;
68              incomingShort.orderType = O.LimitShort;
69              incomingShort.creationTime = LibOrders.getOffsetTime();
70              incomingShort.shortOrderCR = shortOrderCR; // 170 -> 1.70x
71      
72              p.startingId = s.bids[asset][C.HEAD].nextId;
73              STypes.Order storage highestBid = s.bids[asset][p.startingId];
74              // @dev if match and match price is gt .5% to saved oracle in either direction, update startingShortId
75              if (highestBid.price >= incomingShort.price && highestBid.orderType == O.LimitBid) {
76                  LibOrders.updateOracleAndStartingShortViaThreshold(asset, LibOracle.getPrice(asset), incomingShort, shortHintArray);
77              }
78      
79              p.oraclePrice = LibOracle.getSavedOrSpotOraclePrice(asset);
80              if (LibSRUtil.checkRecoveryModeViolation(asset, cr, p.oraclePrice)) {
81                  revert Errors.BelowRecoveryModeCR();
82              }
83      
84              // @dev reading spot oracle price
85              if (incomingShort.price < p.oraclePrice) {
86                  LibOrders.addShort(asset, incomingShort, orderHintArray);
87              } else {
88                  LibOrders.sellMatchAlgo(asset, incomingShort, orderHintArray, p.minAskEth);
89              }
90          }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/ShortOrdersFacet.sol#L35:90

L002 - Unsafe downcast:

When a type is downcast to a smaller type, the higher order bits are truncated, effectively applying a modulo to the original value. Without any other checks, this wrapping will lead to unexpected behavior and bugs.

<details> <summary>Click to show 7 findings</summary>
File: facets/BridgeRouterFacet.sol


87              uint88 dethAmount = uint88(IBridge(bridge).depositEth{value: msg.value}()); // Assumes 1 ETH = 1 DETH


68              uint88 dethAmount = uint88(IBridge(bridge).deposit(msg.sender, amount)); // @dev(safe-cast)

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L68:68

File: facets/PrimaryLiquidationFacet.sol


231             return a < b ? uint88(a) : b;

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L231:231

File: libraries/LibOracle.sol


151             s.bids[asset][C.HEAD].ercAmount = uint80(oraclePrice);


164             return uint80(s.bids[asset][C.HEAD].ercAmount);

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L164:164

File: libraries/LibOrders.sol


903                 uint88 minShortErc = uint88(LibAsset.minShortErc(asset));

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L903:903

File: libraries/UniswapOracleLibrary.sol


62              int24 tick = int24(tickCumulativesDelta / int32(secondsAgo));

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L62:62

</details>

L003 - No limits when setting state variable amounts:

It is important to ensure state variables numbers are set to a reasonable value.

<details> <summary>Click to show 4 findings</summary>
File: libraries/LibOracle.sol


152             s.bids[asset][C.HEAD].creationTime = oracleTime;

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L152:152

File: libraries/LibOrders.sol


334             orders[asset][C.HEAD].prevId = id;


488                 orders[asset][C.HEAD].nextId = id;


542                 orders[asset][nextAskId].prevId = headId;

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L542:542

</details>

L004 - Double type casts create complexity within the code:

Double type casting should be avoided in Solidity contracts to prevent unintended consequences and ensure accurate data representation. Performing multiple type casts in succession can lead to unexpected truncation, rounding errors, or loss of precision, potentially compromising the contract's functionality and reliability. Furthermore, double type casting can make the code less readable and harder to maintain, increasing the likelihood of errors and misunderstandings during development and debugging. To ensure precise and consistent data handling, developers should use appropriate data types and avoid unnecessary or excessive type casting, promoting a more robust and dependable contract execution.

File: facets/RedemptionFacet.sol


bytes8(uint64(p.currentCR)),

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L133:133

L005 - Constructor contains no validation:

In Solidity, when values are being assigned in constructors to unsigned or integer variables, it's crucial to ensure the provided values adhere to the protocol's specific operational boundaries as laid out in the project specifications and documentation. If the constructors lack appropriate validation checks, there's a risk of setting state variables with values that could cause unexpected and potentially detrimental behavior within the contract's operations, violating the intended logic of the protocol. This can compromise the contract's security and impact the maintainability and reliability of the system. In order to avoid such issues, it is recommended to incorporate rigorous validation checks in constructors. These checks should align with the project's defined rules and constraints, making use of Solidity's built-in require function to enforce these conditions. If the validation checks fail, the require function will cause the transaction to revert, ensuring the integrity and adherence to the protocol's expected behavior.

File: facets/BridgeRouterFacet.sol


29          constructor(address _rethBridge, address _stethBridge) {
30              rethBridge = _rethBridge;
31              stethBridge = _stethBridge;
32          }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L29:32

File: facets/PrimaryLiquidationFacet.sol


30          constructor(address _dusd) {
31              dusd = _dusd;
32          }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L30:32

L006 - For loops in public or external functions should be avoided due to high gas costs and possible DOS:

In Solidity, for loops can potentially cause Denial of Service (DoS) attacks if not handled carefully. DoS attacks can occur when an attacker intentionally exploits the gas cost of a function, causing it to run out of gas or making it too expensive for other users to call. Below are some scenarios where for loops can lead to DoS attacks: Nested for loops can become exceptionally gas expensive and should be used sparingly.

File: facets/RedemptionFacet.sol


56          function proposeRedemption(
57              address asset,
58              MTypes.ProposalInput[] calldata proposalInput,
59              uint88 redemptionAmount,
60              uint88 maxRedemptionFee
61          ) external isNotFrozen(asset) nonReentrant {
62              if (proposalInput.length > type(uint8).max) revert Errors.TooManyProposals();
63              MTypes.ProposeRedemption memory p;
64              p.asset = asset;
65              STypes.AssetUser storage redeemerAssetUser = s.assetUser[p.asset][msg.sender];
66              uint256 minShortErc = LibAsset.minShortErc(p.asset);
67      
68              if (redemptionAmount < minShortErc) revert Errors.RedemptionUnderMinShortErc();
69      
70              if (redeemerAssetUser.ercEscrowed < redemptionAmount) revert Errors.InsufficientERCEscrowed();
71      
72              // @dev redeemerAssetUser.SSTORE2Pointer gets reset to address(0) after actual redemption
73              if (redeemerAssetUser.SSTORE2Pointer != address(0)) revert Errors.ExistingProposedRedemptions();
74      
75              p.oraclePrice = LibOracle.getPrice(p.asset);
76      
77              bytes memory slate;
78              for (uint8 i = 0; i < proposalInput.length; i++) {
79                  p.shorter = proposalInput[i].shorter;
80                  p.shortId = proposalInput[i].shortId;
81                  p.shortOrderId = proposalInput[i].shortOrderId;
82                  // @dev Setting this above _onlyValidShortRecord to allow skipping
83                  STypes.ShortRecord storage currentSR = s.shortRecords[p.asset][p.shorter][p.shortId];
84      
85                  /// Evaluate proposed shortRecord
86      
87                  if (!validRedemptionSR(currentSR, msg.sender, p.shorter, minShortErc)) continue;
88      
89                  currentSR.updateErcDebt(p.asset);
90                  p.currentCR = currentSR.getCollateralRatio(p.oraclePrice);
91      
92                  // @dev Skip if proposal is not sorted correctly or if above redemption threshold
93                  if (p.previousCR > p.currentCR || p.currentCR >= C.MAX_REDEMPTION_CR) continue;
94      
95                  // @dev totalAmountProposed tracks the actual amount that can be redeemed. totalAmountProposed <= redemptionAmount
96                  if (p.totalAmountProposed + currentSR.ercDebt <= redemptionAmount) {
97                      p.amountProposed = currentSR.ercDebt;
98                  } else {
99                      p.amountProposed = redemptionAmount - p.totalAmountProposed;
100                     // @dev Exit when proposal would leave less than minShortErc, proxy for nearing end of slate
101                     if (currentSR.ercDebt - p.amountProposed < minShortErc) break;
102                 }
103     
104                 /// At this point, the shortRecord passes all checks and will be included in the slate
105     
106                 p.previousCR = p.currentCR;
107     
108                 // @dev Cancel attached shortOrder if below minShortErc, regardless of ercDebt in SR
109                 // @dev All verified SR have ercDebt >= minShortErc so CR does not change in cancelShort()
110                 STypes.Order storage shortOrder = s.shorts[asset][p.shortOrderId];
111                 if (currentSR.status == SR.PartialFill && shortOrder.ercAmount < minShortErc) {
112                     if (shortOrder.shortRecordId != p.shortId || shortOrder.addr != p.shorter) revert Errors.InvalidShortOrder();
113                     LibOrders.cancelShort(asset, p.shortOrderId);
114                 }
115     
116                 p.colRedeemed = p.oraclePrice.mulU88(p.amountProposed);
117                 if (p.colRedeemed > currentSR.collateral) {
118                     p.colRedeemed = currentSR.collateral;
119                 }
120     
121                 currentSR.collateral -= p.colRedeemed;
122                 currentSR.ercDebt -= p.amountProposed;
123     
124                 p.totalAmountProposed += p.amountProposed;
125                 p.totalColRedeemed += p.colRedeemed;
126     
127                 // @dev directly write the properties of MTypes.ProposalData into bytes
128                 // instead of usual abi.encode to save on extra zeros being written
129                 slate = bytes.concat(
130                     slate,
131                     bytes20(p.shorter),
132                     bytes1(p.shortId),
133                     bytes8(uint64(p.currentCR)),
134                     bytes11(p.amountProposed),
135                     bytes11(p.colRedeemed)
136                 );
137     
138                 LibSRUtil.disburseCollateral(p.asset, p.shorter, p.colRedeemed, currentSR.dethYieldRate, currentSR.updatedAt);
139                 p.redemptionCounter++;
140                 if (redemptionAmount - p.totalAmountProposed < minShortErc) break;
141             }
142     
143             if (p.totalAmountProposed < minShortErc) revert Errors.RedemptionUnderMinShortErc();
144     
145             // @dev SSTORE2 the entire proposalData after validating proposalInput
146             redeemerAssetUser.SSTORE2Pointer = SSTORE2.write(slate);
147             redeemerAssetUser.slateLength = p.redemptionCounter;
148             redeemerAssetUser.oraclePrice = p.oraclePrice;
149             redeemerAssetUser.ercEscrowed -= p.totalAmountProposed;
150     
151             STypes.Asset storage Asset = s.asset[p.asset];
152             Asset.ercDebt -= p.totalAmountProposed;
153     
154             uint32 protocolTime = LibOrders.getOffsetTime();
155             redeemerAssetUser.timeProposed = protocolTime;
156             // @dev Calculate the dispute period
157             // @dev timeToDispute is immediate for shorts with CR <= 1.1x
158     
159             /*
160             +-------+------------+
161             | CR(X) |  Hours(Y)  |
162             +-------+------------+
163             | 1.1   |     0      |
164             | 1.2   |    .333    |
165             | 1.3   |    .75     |
166             | 1.5   |    1.5     |
167             | 1.7   |     3      |
168             | 2.0   |     6      |
169             +-------+------------+
170     
171             Creating fixed points and interpolating between points on the graph without using exponentials
172             Using simple y = mx + b formula
173             
174             where x = currentCR - previousCR
175             m = (y2-y1)/(x2-x1)
176             b = previous fixed point (Y)
177             */
178     
179             uint256 m;
180     
181             if (p.currentCR > 1.7 ether) {
182                 m = uint256(3 ether).div(0.3 ether);
183                 redeemerAssetUser.timeToDispute = protocolTime + uint32((m.mul(p.currentCR - 1.7 ether) + 3 ether) * 1 hours / 1 ether);
184             } else if (p.currentCR > 1.5 ether) {
185                 m = uint256(1.5 ether).div(0.2 ether);
186                 redeemerAssetUser.timeToDispute =
187                     protocolTime + uint32((m.mul(p.currentCR - 1.5 ether) + 1.5 ether) * 1 hours / 1 ether);
188             } else if (p.currentCR > 1.3 ether) {
189                 m = uint256(0.75 ether).div(0.2 ether);
190                 redeemerAssetUser.timeToDispute =
191                     protocolTime + uint32((m.mul(p.currentCR - 1.3 ether) + 0.75 ether) * 1 hours / 1 ether);
192             } else if (p.currentCR > 1.2 ether) {
193                 m = uint256(0.417 ether).div(0.1 ether);
194                 redeemerAssetUser.timeToDispute =
195                     protocolTime + uint32((m.mul(p.currentCR - 1.2 ether) + C.ONE_THIRD) * 1 hours / 1 ether);
196             } else if (p.currentCR > 1.1 ether) {
197                 m = uint256(C.ONE_THIRD.div(0.1 ether));
198                 redeemerAssetUser.timeToDispute = protocolTime + uint32(m.mul(p.currentCR - 1.1 ether) * 1 hours / 1 ether);
199             }
200     
201             redeemerAssetUser.oraclePrice = p.oraclePrice;
202             redeemerAssetUser.timeProposed = LibOrders.getOffsetTime();
203     
204             uint88 redemptionFee = calculateRedemptionFee(asset, p.totalColRedeemed, p.totalAmountProposed);
205             if (redemptionFee > maxRedemptionFee) revert Errors.RedemptionFeeTooHigh();
206     
207             STypes.VaultUser storage VaultUser = s.vaultUser[Asset.vault][msg.sender];
208             if (VaultUser.ethEscrowed < redemptionFee) revert Errors.InsufficientETHEscrowed();
209             VaultUser.ethEscrowed -= redemptionFee;
210             emit Events.ProposeRedemption(p.asset, msg.sender);
211         }


224         function disputeRedemption(address asset, address redeemer, uint8 incorrectIndex, address disputeShorter, uint8 disputeShortId)
225             external
226             isNotFrozen(asset)
227             nonReentrant
228         {
229             if (redeemer == msg.sender) revert Errors.CannotDisputeYourself();
230             MTypes.DisputeRedemption memory d;
231             d.asset = asset;
232             d.redeemer = redeemer;
233     
234             STypes.AssetUser storage redeemerAssetUser = s.assetUser[d.asset][d.redeemer];
235             if (redeemerAssetUser.SSTORE2Pointer == address(0)) revert Errors.InvalidRedemption();
236     
237             if (LibOrders.getOffsetTime() >= redeemerAssetUser.timeToDispute) revert Errors.TimeToDisputeHasElapsed();
238     
239             MTypes.ProposalData[] memory decodedProposalData =
240                 LibBytes.readProposalData(redeemerAssetUser.SSTORE2Pointer, redeemerAssetUser.slateLength);
241     
242             for (uint256 i = 0; i < decodedProposalData.length; i++) {
243                 if (decodedProposalData[i].shorter == disputeShorter && decodedProposalData[i].shortId == disputeShortId) {
244                     revert Errors.CannotDisputeWithRedeemerProposal();
245                 }
246             }
247     
248             STypes.ShortRecord storage disputeSR = s.shortRecords[d.asset][disputeShorter][disputeShortId];
249             // Match continue (skip) conditions in proposeRedemption()
250             uint256 minShortErc = LibAsset.minShortErc(d.asset);
251             if (!validRedemptionSR(disputeSR, d.redeemer, disputeShorter, minShortErc)) revert Errors.InvalidRedemption();
252     
253             MTypes.ProposalData memory incorrectProposal = decodedProposalData[incorrectIndex];
254             MTypes.ProposalData memory currentProposal;
255             STypes.Asset storage Asset = s.asset[d.asset];
256     
257             uint256 disputeCR = disputeSR.getCollateralRatio(redeemerAssetUser.oraclePrice);
258     
259             if (disputeCR < incorrectProposal.CR && disputeSR.updatedAt + C.DISPUTE_REDEMPTION_BUFFER <= redeemerAssetUser.timeProposed)
260             {
261                 // @dev All proposals from the incorrectIndex onward will be removed
262                 // @dev Thus the proposer can only redeem a portion of their original slate
263                 for (uint256 i = incorrectIndex; i < decodedProposalData.length; i++) {
264                     currentProposal = decodedProposalData[i];
265     
266                     STypes.ShortRecord storage currentSR = s.shortRecords[d.asset][currentProposal.shorter][currentProposal.shortId];
267                     currentSR.collateral += currentProposal.colRedeemed;
268                     currentSR.ercDebt += currentProposal.ercDebtRedeemed;
269     
270                     d.incorrectCollateral += currentProposal.colRedeemed;
271                     d.incorrectErcDebt += currentProposal.ercDebtRedeemed;
272                 }
273     
274                 s.vault[Asset.vault].dethCollateral += d.incorrectCollateral;
275                 Asset.dethCollateral += d.incorrectCollateral;
276                 Asset.ercDebt += d.incorrectErcDebt;
277     
278                 // @dev Update the redeemer's SSTORE2Pointer
279                 if (incorrectIndex > 0) {
280                     redeemerAssetUser.slateLength = incorrectIndex;
281                 } else {
282                     // @dev this implies everything in the redeemer's proposal was incorrect
283                     delete redeemerAssetUser.SSTORE2Pointer;
284                     emit Events.DisputeRedemptionAll(d.asset, redeemer);
285                 }
286     
287                 // @dev Penalty is based on the proposal with highest CR (decodedProposalData is sorted)
288                 // @dev PenaltyPct is bound between CallerFeePct and 33% to prevent exploiting primaryLiquidation fees
289                 uint256 penaltyPct = LibOrders.min(
290                     LibOrders.max(LibAsset.callerFeePct(d.asset), (currentProposal.CR - disputeCR).div(currentProposal.CR)), 0.33 ether
291                 );
292     
293                 uint88 penaltyAmt = d.incorrectErcDebt.mulU88(penaltyPct);
294     
295                 // @dev Give redeemer back ercEscrowed that is no longer used to redeem (penalty applied)
296                 redeemerAssetUser.ercEscrowed += (d.incorrectErcDebt - penaltyAmt);
297                 s.assetUser[d.asset][msg.sender].ercEscrowed += penaltyAmt;
298             } else {
299                 revert Errors.InvalidRedemptionDispute();
300             }
301         }


310         function claimRedemption(address asset) external isNotFrozen(asset) nonReentrant {
311             uint256 vault = s.asset[asset].vault;
312             STypes.AssetUser storage redeemerAssetUser = s.assetUser[asset][msg.sender];
313             STypes.VaultUser storage redeemerVaultUser = s.vaultUser[vault][msg.sender];
314             if (redeemerAssetUser.SSTORE2Pointer == address(0)) revert Errors.InvalidRedemption();
315             if (LibOrders.getOffsetTime() < redeemerAssetUser.timeToDispute) revert Errors.TimeToDisputeHasNotElapsed();
316     
317             MTypes.ProposalData[] memory decodedProposalData =
318                 LibBytes.readProposalData(redeemerAssetUser.SSTORE2Pointer, redeemerAssetUser.slateLength);
319     
320             uint88 totalColRedeemed;
321             for (uint256 i = 0; i < decodedProposalData.length; i++) {
322                 MTypes.ProposalData memory currentProposal = decodedProposalData[i];
323                 totalColRedeemed += currentProposal.colRedeemed;
324                 _claimRemainingCollateral({
325                     asset: asset,
326                     vault: vault,
327                     shorter: currentProposal.shorter,
328                     shortId: currentProposal.shortId
329                 });
330             }
331             redeemerVaultUser.ethEscrowed += totalColRedeemed;
332             delete redeemerAssetUser.SSTORE2Pointer;
333             emit Events.ClaimRedemption(asset, msg.sender);
334         }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L310:334

L007 - Function calls within for loops:

Making function calls within loops in Solidity can lead to inefficient gas usage, potential bottlenecks, and increased vulnerability to attacks. Each function call or external call consumes gas, and when executed within a loop, the gas cost multiplies, potentially causing the transaction to run out of gas or exceed block gas limits. This can result in transaction failure or unpredictable behavior.

File: facets/RedemptionFacet.sol


321             for (uint256 i = 0; i < decodedProposalData.length; i++) {
322                 MTypes.ProposalData memory currentProposal = decodedProposalData[i];
323                 totalColRedeemed += currentProposal.colRedeemed;
324                 _claimRemainingCollateral({
325                     asset: asset,
326                     vault: vault,
327                     shorter: currentProposal.shorter,
328                     shortId: currentProposal.shortId
329                 });
330             }


78              for (uint8 i = 0; i < proposalInput.length; i++) {
79                  p.shorter = proposalInput[i].shorter;
80                  p.shortId = proposalInput[i].shortId;
81                  p.shortOrderId = proposalInput[i].shortOrderId;
82                  // @dev Setting this above _onlyValidShortRecord to allow skipping
83                  STypes.ShortRecord storage currentSR = s.shortRecords[p.asset][p.shorter][p.shortId];
84      
85                  /// Evaluate proposed shortRecord
86      
87                  if (!validRedemptionSR(currentSR, msg.sender, p.shorter, minShortErc)) continue;
88      
89                  currentSR.updateErcDebt(p.asset);
90                  p.currentCR = currentSR.getCollateralRatio(p.oraclePrice);
91      
92                  // @dev Skip if proposal is not sorted correctly or if above redemption threshold
93                  if (p.previousCR > p.currentCR || p.currentCR >= C.MAX_REDEMPTION_CR) continue;
94      
95                  // @dev totalAmountProposed tracks the actual amount that can be redeemed. totalAmountProposed <= redemptionAmount
96                  if (p.totalAmountProposed + currentSR.ercDebt <= redemptionAmount) {
97                      p.amountProposed = currentSR.ercDebt;
98                  } else {
99                      p.amountProposed = redemptionAmount - p.totalAmountProposed;
100                     // @dev Exit when proposal would leave less than minShortErc, proxy for nearing end of slate
101                     if (currentSR.ercDebt - p.amountProposed < minShortErc) break;
102                 }
103     
104                 /// At this point, the shortRecord passes all checks and will be included in the slate
105     
106                 p.previousCR = p.currentCR;
107     
108                 // @dev Cancel attached shortOrder if below minShortErc, regardless of ercDebt in SR
109                 // @dev All verified SR have ercDebt >= minShortErc so CR does not change in cancelShort()
110                 STypes.Order storage shortOrder = s.shorts[asset][p.shortOrderId];
111                 if (currentSR.status == SR.PartialFill && shortOrder.ercAmount < minShortErc) {
112                     if (shortOrder.shortRecordId != p.shortId || shortOrder.addr != p.shorter) revert Errors.InvalidShortOrder();
113                     LibOrders.cancelShort(asset, p.shortOrderId);
114                 }
115     
116                 p.colRedeemed = p.oraclePrice.mulU88(p.amountProposed);
117                 if (p.colRedeemed > currentSR.collateral) {
118                     p.colRedeemed = currentSR.collateral;
119                 }
120     
121                 currentSR.collateral -= p.colRedeemed;
122                 currentSR.ercDebt -= p.amountProposed;
123     
124                 p.totalAmountProposed += p.amountProposed;
125                 p.totalColRedeemed += p.colRedeemed;
126     
127                 // @dev directly write the properties of MTypes.ProposalData into bytes
128                 // instead of usual abi.encode to save on extra zeros being written
129                 slate = bytes.concat(
130                     slate,
131                     bytes20(p.shorter),
132                     bytes1(p.shortId),
133                     bytes8(uint64(p.currentCR)),
134                     bytes11(p.amountProposed),
135                     bytes11(p.colRedeemed)
136                 );
137     
138                 LibSRUtil.disburseCollateral(p.asset, p.shorter, p.colRedeemed, currentSR.dethYieldRate, currentSR.updatedAt);
139                 p.redemptionCounter++;
140                 if (redemptionAmount - p.totalAmountProposed < minShortErc) break;
141             }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L78:141

L008 - Multiplication on the result of a division:

Dividing an integer by another integer will often result in loss of precision. When the result is multiplied by another number, the loss of precision is magnified, often to material magnitudes. (X / Z) * Y should be re-written as (X * Y) / Z.

File: libraries/LibOracle.sol


93                      uint256 twapPriceNormalized = twapPrice * (1 ether / C.DECIMAL_USDC);


141             uint256 twapPriceNormalized = twapPrice * (1 ether / C.DECIMAL_USDC);

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L141:141

File: libraries/LibOrders.sol


47                  uint88 shares = eth * (timeTillMatch / 1 days);

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L47:47

L009 - Missing checks for address(0x0) in the constructor:

File: facets/BridgeRouterFacet.sol


30              rethBridge = _rethBridge;


31              stethBridge = _stethBridge;

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L31:31

File: facets/PrimaryLiquidationFacet.sol


31              dusd = _dusd;

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L31:31

L010 - Prefer continue over revert model in iteration:

Preferably, it's better to skip operations on array indices when a condition is not met instead of reverting the entire transaction. Reverting could be exploited by malicious actors who might intentionally introduce array objects failing conditional checks, disrupting group operations. It's advisable to skip array indices rather than revert, unless there are valid security or logic reasons for doing otherwise

File: facets/RedemptionFacet.sol


78              for (uint8 i = 0; i < proposalInput.length; i++) {
79                  p.shorter = proposalInput[i].shorter;
80                  p.shortId = proposalInput[i].shortId;
81                  p.shortOrderId = proposalInput[i].shortOrderId;
82                  // @dev Setting this above _onlyValidShortRecord to allow skipping
83                  STypes.ShortRecord storage currentSR = s.shortRecords[p.asset][p.shorter][p.shortId];
84      
85                  /// Evaluate proposed shortRecord
86      
87                  if (!validRedemptionSR(currentSR, msg.sender, p.shorter, minShortErc)) continue;
88      
89                  currentSR.updateErcDebt(p.asset);
90                  p.currentCR = currentSR.getCollateralRatio(p.oraclePrice);
91      
92                  // @dev Skip if proposal is not sorted correctly or if above redemption threshold
93                  if (p.previousCR > p.currentCR || p.currentCR >= C.MAX_REDEMPTION_CR) continue;
94      
95                  // @dev totalAmountProposed tracks the actual amount that can be redeemed. totalAmountProposed <= redemptionAmount
96                  if (p.totalAmountProposed + currentSR.ercDebt <= redemptionAmount) {
97                      p.amountProposed = currentSR.ercDebt;
98                  } else {
99                      p.amountProposed = redemptionAmount - p.totalAmountProposed;
100                     // @dev Exit when proposal would leave less than minShortErc, proxy for nearing end of slate
101                     if (currentSR.ercDebt - p.amountProposed < minShortErc) break;
102                 }
103     
104                 /// At this point, the shortRecord passes all checks and will be included in the slate
105     
106                 p.previousCR = p.currentCR;
107     
108                 // @dev Cancel attached shortOrder if below minShortErc, regardless of ercDebt in SR
109                 // @dev All verified SR have ercDebt >= minShortErc so CR does not change in cancelShort()
110                 STypes.Order storage shortOrder = s.shorts[asset][p.shortOrderId];
111                 if (currentSR.status == SR.PartialFill && shortOrder.ercAmount < minShortErc) {
112                     if (shortOrder.shortRecordId != p.shortId || shortOrder.addr != p.shorter) revert Errors.InvalidShortOrder();
113                     LibOrders.cancelShort(asset, p.shortOrderId);
114                 }
115     
116                 p.colRedeemed = p.oraclePrice.mulU88(p.amountProposed);
117                 if (p.colRedeemed > currentSR.collateral) {
118                     p.colRedeemed = currentSR.collateral;
119                 }
120     
121                 currentSR.collateral -= p.colRedeemed;
122                 currentSR.ercDebt -= p.amountProposed;
123     
124                 p.totalAmountProposed += p.amountProposed;
125                 p.totalColRedeemed += p.colRedeemed;
126     
127                 // @dev directly write the properties of MTypes.ProposalData into bytes
128                 // instead of usual abi.encode to save on extra zeros being written
129                 slate = bytes.concat(
130                     slate,
131                     bytes20(p.shorter),
132                     bytes1(p.shortId),
133                     bytes8(uint64(p.currentCR)),
134                     bytes11(p.amountProposed),
135                     bytes11(p.colRedeemed)
136                 );
137     
138                 LibSRUtil.disburseCollateral(p.asset, p.shorter, p.colRedeemed, currentSR.dethYieldRate, currentSR.updatedAt);
139                 p.redemptionCounter++;
140                 if (redemptionAmount - p.totalAmountProposed < minShortErc) break;
141             }


242             for (uint256 i = 0; i < decodedProposalData.length; i++) {
243                 if (decodedProposalData[i].shorter == disputeShorter && decodedProposalData[i].shortId == disputeShortId) {
244                     revert Errors.CannotDisputeWithRedeemerProposal();
245                 }
246             }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L242:246

Non-critical Issues

IssueInstances
[NC001]The nonReentrant modifier should occur before all other modifiers6
[NC002]Imports could be organized more systematically4
[NC003]Constants in comparisons should appear on the left side41
[NC004]else-block not required32
[NC005]If-statement can be converted to a ternary4
[NC006]Variable names that consist of all capital letters should be reserved for constant/immutable variables4
[NC007]Consider using delete rather than assigning false/zero to clear values14
[NC008]Variable names for immutables should use CONSTANT_CASE3
[NC009]Lines are too long53
[NC010]File is missing NatSpec comments4
[NC011]Function declarations should have NatSpec descriptions36
[NC012]Contract declarations should have @notice tags10
[NC013]Invalid NatSpec comment style93
[NC014]Inconsistent spacing in comments2
[NC015]Not using the named return variables anywhere in the function is confusing15
[NC016]Contracts should have full test coverage1
[NC017]Large or complicated code bases should implement invariant tests1
[NC018]Consider using block.number instead of block.timestamp6
[NC019]Consider bounding input array length2
[NC020]Variables should be named in mixedCase style46
[NC021]Function names should use lowerCamelCase3
[NC022]Consider adding a deny-list4
[NC023]Events are missing sender information1
[NC024]Zero as a function argument should have a descriptive meaning23
[NC025]It is standard for all external and public functions to be override from an interface12
[NC026]Consider adding formal verification proofs1
[NC027]Setters should prevent re-setting of the same value1
[NC028]Consider splitting long calculations6
[NC029]High cyclomatic complexity9
[NC030]Unused import11
[NC031]Unsafe conversion from unsigned to signed values2
[NC032]Style guide: State and local variables should be named using lowerCamelCase82
[NC033]Function definitions should have NatSpec @dev annotations57
[NC034]Function definitions should have NatSpec @notice annotations55
[NC035]Interface declarations should have NatSpec @title annotations1
[NC036]Interface declarations should have NatSpec @author annotations1
[NC037]Interface declarations should have NatSpec @notice annotations1
[NC038]Interface declarations should have NatSpec @dev annotations1
[NC039]Library declarations should have Natspec @title annotations5
[NC040]Library declarations should have Natspec @author annotations6
[NC041]Library declarations should have Natspec @notice annotations5
[NC042]Library declarations should have Natspec @dev annotations6
[NC043]Contract definitions should have Natspec @title annotations4
[NC044]Contract definitions should have Natspec @author annotations4
[NC045]Contract definitions should have Natspec @notice annotations4
[NC046]Contract definitions should have Natspec @dev annotations4
[NC047]State variable declarations should have Natspec @notice annotations3
[NC048]State variable declarations should have Natspec @dev annotations3
[NC049]Functions should have Natspec @return annotations31
[NC050]Functions should have Natspec @param annotations54
[NC051]Missing events in sensitive functions1
[NC052]Unused file6
[NC053]Consider only defining one library/interface/contract per sol file1
[NC054]Use a struct to encapsulate multiple function parameters14

NC001 - The nonReentrant modifier should occur before all other modifiers:

This is a best-practice to protect against reentrancy in other modifiers.

<details> <summary>Click to show 6 findings</summary>
File: facets/PrimaryLiquidationFacet.sol


47          function liquidate(address asset, address shorter, uint8 id, uint16[] memory shortHintArray, uint16 shortOrderId)
48              external
49              isNotFrozen(asset)
50              nonReentrant
51              onlyValidShortRecord(asset, shorter, id)
52              returns (uint88, uint88)
53          {
54              if (msg.sender == shorter) revert Errors.CannotLiquidateSelf();
55              // @dev TAPP partially reimburses gas fees, capped at 10 to limit arbitrary high cost
56              if (shortHintArray.length > 10) revert Errors.TooManyHints();
57      
58              // @dev Ensures SR has enough ercDebt/collateral to make caller fee worthwhile
59              LibSRUtil.checkCancelShortOrder({
60                  asset: asset,
61                  initialStatus: s.shortRecords[asset][shorter][id].status,
62                  shortOrderId: shortOrderId,
63                  shortRecordId: id,
64                  shorter: shorter
65              });
66      
67              // @dev liquidate requires more up-to-date oraclePrice
68              LibOrders.updateOracleAndStartingShortViaTimeBidOnly(asset, shortHintArray);
69      
70              MTypes.PrimaryLiquidation memory m = _setLiquidationStruct(asset, shorter, id, shortOrderId);
71      
72              // @dev Can liquidate as long as CR is low enough
73              if (m.cRatio >= LibAsset.liquidationCR(m.asset)) {
74                  // If CR is too high, check for recovery mode and violation to continue liquidation
75                  if (!LibSRUtil.checkRecoveryModeViolation(m.asset, m.cRatio, m.oraclePrice)) revert Errors.SufficientCollateral();
76              }
77      
78              // revert if no asks, or price too high
79              _checklowestSell(m);
80      
81              _performForcedBid(m, shortHintArray);
82      
83              _liquidationFeeHandler(m);
84      
85              _fullorPartialLiquidation(m);
86      
87              emit Events.Liquidate(asset, shorter, id, msg.sender, m.ercDebtMatched);
88      
89              return (m.gasFee, m.ethFilled);
90          }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L47:90

File: facets/RedemptionFacet.sol


56          function proposeRedemption(
57              address asset,
58              MTypes.ProposalInput[] calldata proposalInput,
59              uint88 redemptionAmount,
60              uint88 maxRedemptionFee
61          ) external isNotFrozen(asset) nonReentrant {
62              if (proposalInput.length > type(uint8).max) revert Errors.TooManyProposals();
63              MTypes.ProposeRedemption memory p;
64              p.asset = asset;
65              STypes.AssetUser storage redeemerAssetUser = s.assetUser[p.asset][msg.sender];
66              uint256 minShortErc = LibAsset.minShortErc(p.asset);
67      
68              if (redemptionAmount < minShortErc) revert Errors.RedemptionUnderMinShortErc();
69      
70              if (redeemerAssetUser.ercEscrowed < redemptionAmount) revert Errors.InsufficientERCEscrowed();
71      
72              // @dev redeemerAssetUser.SSTORE2Pointer gets reset to address(0) after actual redemption
73              if (redeemerAssetUser.SSTORE2Pointer != address(0)) revert Errors.ExistingProposedRedemptions();
74      
75              p.oraclePrice = LibOracle.getPrice(p.asset);
76      
77              bytes memory slate;
78              for (uint8 i = 0; i < proposalInput.length; i++) {
79                  p.shorter = proposalInput[i].shorter;
80                  p.shortId = proposalInput[i].shortId;
81                  p.shortOrderId = proposalInput[i].shortOrderId;
82                  // @dev Setting this above _onlyValidShortRecord to allow skipping
83                  STypes.ShortRecord storage currentSR = s.shortRecords[p.asset][p.shorter][p.shortId];
84      
85                  /// Evaluate proposed shortRecord
86      
87                  if (!validRedemptionSR(currentSR, msg.sender, p.shorter, minShortErc)) continue;
88      
89                  currentSR.updateErcDebt(p.asset);
90                  p.currentCR = currentSR.getCollateralRatio(p.oraclePrice);
91      
92                  // @dev Skip if proposal is not sorted correctly or if above redemption threshold
93                  if (p.previousCR > p.currentCR || p.currentCR >= C.MAX_REDEMPTION_CR) continue;
94      
95                  // @dev totalAmountProposed tracks the actual amount that can be redeemed. totalAmountProposed <= redemptionAmount
96                  if (p.totalAmountProposed + currentSR.ercDebt <= redemptionAmount) {
97                      p.amountProposed = currentSR.ercDebt;
98                  } else {
99                      p.amountProposed = redemptionAmount - p.totalAmountProposed;
100                     // @dev Exit when proposal would leave less than minShortErc, proxy for nearing end of slate
101                     if (currentSR.ercDebt - p.amountProposed < minShortErc) break;
102                 }
103     
104                 /// At this point, the shortRecord passes all checks and will be included in the slate
105     
106                 p.previousCR = p.currentCR;
107     
108                 // @dev Cancel attached shortOrder if below minShortErc, regardless of ercDebt in SR
109                 // @dev All verified SR have ercDebt >= minShortErc so CR does not change in cancelShort()
110                 STypes.Order storage shortOrder = s.shorts[asset][p.shortOrderId];
111                 if (currentSR.status == SR.PartialFill && shortOrder.ercAmount < minShortErc) {
112                     if (shortOrder.shortRecordId != p.shortId || shortOrder.addr != p.shorter) revert Errors.InvalidShortOrder();
113                     LibOrders.cancelShort(asset, p.shortOrderId);
114                 }
115     
116                 p.colRedeemed = p.oraclePrice.mulU88(p.amountProposed);
117                 if (p.colRedeemed > currentSR.collateral) {
118                     p.colRedeemed = currentSR.collateral;
119                 }
120     
121                 currentSR.collateral -= p.colRedeemed;
122                 currentSR.ercDebt -= p.amountProposed;
123     
124                 p.totalAmountProposed += p.amountProposed;
125                 p.totalColRedeemed += p.colRedeemed;
126     
127                 // @dev directly write the properties of MTypes.ProposalData into bytes
128                 // instead of usual abi.encode to save on extra zeros being written
129                 slate = bytes.concat(
130                     slate,
131                     bytes20(p.shorter),
132                     bytes1(p.shortId),
133                     bytes8(uint64(p.currentCR)),
134                     bytes11(p.amountProposed),
135                     bytes11(p.colRedeemed)
136                 );
137     
138                 LibSRUtil.disburseCollateral(p.asset, p.shorter, p.colRedeemed, currentSR.dethYieldRate, currentSR.updatedAt);
139                 p.redemptionCounter++;
140                 if (redemptionAmount - p.totalAmountProposed < minShortErc) break;
141             }
142     
143             if (p.totalAmountProposed < minShortErc) revert Errors.RedemptionUnderMinShortErc();
144     
145             // @dev SSTORE2 the entire proposalData after validating proposalInput
146             redeemerAssetUser.SSTORE2Pointer = SSTORE2.write(slate);
147             redeemerAssetUser.slateLength = p.redemptionCounter;
148             redeemerAssetUser.oraclePrice = p.oraclePrice;
149             redeemerAssetUser.ercEscrowed -= p.totalAmountProposed;
150     
151             STypes.Asset storage Asset = s.asset[p.asset];
152             Asset.ercDebt -= p.totalAmountProposed;
153     
154             uint32 protocolTime = LibOrders.getOffsetTime();
155             redeemerAssetUser.timeProposed = protocolTime;
156             // @dev Calculate the dispute period
157             // @dev timeToDispute is immediate for shorts with CR <= 1.1x
158     
159             /*
160             +-------+------------+
161             | CR(X) |  Hours(Y)  |
162             +-------+------------+
163             | 1.1   |     0      |
164             | 1.2   |    .333    |
165             | 1.3   |    .75     |
166             | 1.5   |    1.5     |
167             | 1.7   |     3      |
168             | 2.0   |     6      |
169             +-------+------------+
170     
171             Creating fixed points and interpolating between points on the graph without using exponentials
172             Using simple y = mx + b formula
173             
174             where x = currentCR - previousCR
175             m = (y2-y1)/(x2-x1)
176             b = previous fixed point (Y)
177             */
178     
179             uint256 m;
180     
181             if (p.currentCR > 1.7 ether) {
182                 m = uint256(3 ether).div(0.3 ether);
183                 redeemerAssetUser.timeToDispute = protocolTime + uint32((m.mul(p.currentCR - 1.7 ether) + 3 ether) * 1 hours / 1 ether);
184             } else if (p.currentCR > 1.5 ether) {
185                 m = uint256(1.5 ether).div(0.2 ether);
186                 redeemerAssetUser.timeToDispute =
187                     protocolTime + uint32((m.mul(p.currentCR - 1.5 ether) + 1.5 ether) * 1 hours / 1 ether);
188             } else if (p.currentCR > 1.3 ether) {
189                 m = uint256(0.75 ether).div(0.2 ether);
190                 redeemerAssetUser.timeToDispute =
191                     protocolTime + uint32((m.mul(p.currentCR - 1.3 ether) + 0.75 ether) * 1 hours / 1 ether);
192             } else if (p.currentCR > 1.2 ether) {
193                 m = uint256(0.417 ether).div(0.1 ether);
194                 redeemerAssetUser.timeToDispute =
195                     protocolTime + uint32((m.mul(p.currentCR - 1.2 ether) + C.ONE_THIRD) * 1 hours / 1 ether);
196             } else if (p.currentCR > 1.1 ether) {
197                 m = uint256(C.ONE_THIRD.div(0.1 ether));
198                 redeemerAssetUser.timeToDispute = protocolTime + uint32(m.mul(p.currentCR - 1.1 ether) * 1 hours / 1 ether);
199             }
200     
201             redeemerAssetUser.oraclePrice = p.oraclePrice;
202             redeemerAssetUser.timeProposed = LibOrders.getOffsetTime();
203     
204             uint88 redemptionFee = calculateRedemptionFee(asset, p.totalColRedeemed, p.totalAmountProposed);
205             if (redemptionFee > maxRedemptionFee) revert Errors.RedemptionFeeTooHigh();
206     
207             STypes.VaultUser storage VaultUser = s.vaultUser[Asset.vault][msg.sender];
208             if (VaultUser.ethEscrowed < redemptionFee) revert Errors.InsufficientETHEscrowed();
209             VaultUser.ethEscrowed -= redemptionFee;
210             emit Events.ProposeRedemption(p.asset, msg.sender);
211         }


224         function disputeRedemption(address asset, address redeemer, uint8 incorrectIndex, address disputeShorter, uint8 disputeShortId)
225             external
226             isNotFrozen(asset)
227             nonReentrant
228         {
229             if (redeemer == msg.sender) revert Errors.CannotDisputeYourself();
230             MTypes.DisputeRedemption memory d;
231             d.asset = asset;
232             d.redeemer = redeemer;
233     
234             STypes.AssetUser storage redeemerAssetUser = s.assetUser[d.asset][d.redeemer];
235             if (redeemerAssetUser.SSTORE2Pointer == address(0)) revert Errors.InvalidRedemption();
236     
237             if (LibOrders.getOffsetTime() >= redeemerAssetUser.timeToDispute) revert Errors.TimeToDisputeHasElapsed();
238     
239             MTypes.ProposalData[] memory decodedProposalData =
240                 LibBytes.readProposalData(redeemerAssetUser.SSTORE2Pointer, redeemerAssetUser.slateLength);
241     
242             for (uint256 i = 0; i < decodedProposalData.length; i++) {
243                 if (decodedProposalData[i].shorter == disputeShorter && decodedProposalData[i].shortId == disputeShortId) {
244                     revert Errors.CannotDisputeWithRedeemerProposal();
245                 }
246             }
247     
248             STypes.ShortRecord storage disputeSR = s.shortRecords[d.asset][disputeShorter][disputeShortId];
249             // Match continue (skip) conditions in proposeRedemption()
250             uint256 minShortErc = LibAsset.minShortErc(d.asset);
251             if (!validRedemptionSR(disputeSR, d.redeemer, disputeShorter, minShortErc)) revert Errors.InvalidRedemption();
252     
253             MTypes.ProposalData memory incorrectProposal = decodedProposalData[incorrectIndex];
254             MTypes.ProposalData memory currentProposal;
255             STypes.Asset storage Asset = s.asset[d.asset];
256     
257             uint256 disputeCR = disputeSR.getCollateralRatio(redeemerAssetUser.oraclePrice);
258     
259             if (disputeCR < incorrectProposal.CR && disputeSR.updatedAt + C.DISPUTE_REDEMPTION_BUFFER <= redeemerAssetUser.timeProposed)
260             {
261                 // @dev All proposals from the incorrectIndex onward will be removed
262                 // @dev Thus the proposer can only redeem a portion of their original slate
263                 for (uint256 i = incorrectIndex; i < decodedProposalData.length; i++) {
264                     currentProposal = decodedProposalData[i];
265     
266                     STypes.ShortRecord storage currentSR = s.shortRecords[d.asset][currentProposal.shorter][currentProposal.shortId];
267                     currentSR.collateral += currentProposal.colRedeemed;
268                     currentSR.ercDebt += currentProposal.ercDebtRedeemed;
269     
270                     d.incorrectCollateral += currentProposal.colRedeemed;
271                     d.incorrectErcDebt += currentProposal.ercDebtRedeemed;
272                 }
273     
274                 s.vault[Asset.vault].dethCollateral += d.incorrectCollateral;
275                 Asset.dethCollateral += d.incorrectCollateral;
276                 Asset.ercDebt += d.incorrectErcDebt;
277     
278                 // @dev Update the redeemer's SSTORE2Pointer
279                 if (incorrectIndex > 0) {
280                     redeemerAssetUser.slateLength = incorrectIndex;
281                 } else {
282                     // @dev this implies everything in the redeemer's proposal was incorrect
283                     delete redeemerAssetUser.SSTORE2Pointer;
284                     emit Events.DisputeRedemptionAll(d.asset, redeemer);
285                 }
286     
287                 // @dev Penalty is based on the proposal with highest CR (decodedProposalData is sorted)
288                 // @dev PenaltyPct is bound between CallerFeePct and 33% to prevent exploiting primaryLiquidation fees
289                 uint256 penaltyPct = LibOrders.min(
290                     LibOrders.max(LibAsset.callerFeePct(d.asset), (currentProposal.CR - disputeCR).div(currentProposal.CR)), 0.33 ether
291                 );
292     
293                 uint88 penaltyAmt = d.incorrectErcDebt.mulU88(penaltyPct);
294     
295                 // @dev Give redeemer back ercEscrowed that is no longer used to redeem (penalty applied)
296                 redeemerAssetUser.ercEscrowed += (d.incorrectErcDebt - penaltyAmt);
297                 s.assetUser[d.asset][msg.sender].ercEscrowed += penaltyAmt;
298             } else {
299                 revert Errors.InvalidRedemptionDispute();
300             }
301         }


310         function claimRedemption(address asset) external isNotFrozen(asset) nonReentrant {
311             uint256 vault = s.asset[asset].vault;
312             STypes.AssetUser storage redeemerAssetUser = s.assetUser[asset][msg.sender];
313             STypes.VaultUser storage redeemerVaultUser = s.vaultUser[vault][msg.sender];
314             if (redeemerAssetUser.SSTORE2Pointer == address(0)) revert Errors.InvalidRedemption();
315             if (LibOrders.getOffsetTime() < redeemerAssetUser.timeToDispute) revert Errors.TimeToDisputeHasNotElapsed();
316     
317             MTypes.ProposalData[] memory decodedProposalData =
318                 LibBytes.readProposalData(redeemerAssetUser.SSTORE2Pointer, redeemerAssetUser.slateLength);
319     
320             uint88 totalColRedeemed;
321             for (uint256 i = 0; i < decodedProposalData.length; i++) {
322                 MTypes.ProposalData memory currentProposal = decodedProposalData[i];
323                 totalColRedeemed += currentProposal.colRedeemed;
324                 _claimRemainingCollateral({
325                     asset: asset,
326                     vault: vault,
327                     shorter: currentProposal.shorter,
328                     shortId: currentProposal.shortId
329                 });
330             }
331             redeemerVaultUser.ethEscrowed += totalColRedeemed;
332             delete redeemerAssetUser.SSTORE2Pointer;
333             emit Events.ClaimRedemption(asset, msg.sender);
334         }


347         function claimRemainingCollateral(address asset, address redeemer, uint8 claimIndex, uint8 id)
348             external
349             isNotFrozen(asset)
350             nonReentrant
351         {
352             STypes.AssetUser storage redeemerAssetUser = s.assetUser[asset][redeemer];
353             if (redeemerAssetUser.SSTORE2Pointer == address(0)) revert Errors.InvalidRedemption();
354             if (redeemerAssetUser.timeToDispute > LibOrders.getOffsetTime()) revert Errors.TimeToDisputeHasNotElapsed();
355     
356             // @dev Only need to read up to the position of the SR to be claimed
357             MTypes.ProposalData[] memory decodedProposalData =
358                 LibBytes.readProposalData(redeemerAssetUser.SSTORE2Pointer, claimIndex + 1);
359             MTypes.ProposalData memory claimProposal = decodedProposalData[claimIndex];
360     
361             if (claimProposal.shorter != msg.sender && claimProposal.shortId != id) revert Errors.CanOnlyClaimYourShort();
362     
363             STypes.Asset storage Asset = s.asset[asset];
364             _claimRemainingCollateral({asset: asset, vault: Asset.vault, shorter: msg.sender, shortId: id});
365         }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L347:365

File: facets/ShortOrdersFacet.sol


35          function createLimitShort(
36              address asset,
37              uint80 price,
38              uint88 ercAmount,
39              MTypes.OrderHint[] memory orderHintArray,
40              uint16[] memory shortHintArray,
41              uint16 shortOrderCR
42          ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant {
43              MTypes.CreateLimitShortParam memory p;
44              STypes.Asset storage Asset = s.asset[asset];
45              STypes.Order memory incomingShort;
46      
47              // @dev create "empty" SR. Fail early.
48              incomingShort.shortRecordId = LibShortRecord.createShortRecord(asset, msg.sender, SR.Closed, 0, 0, 0, 0, 0);
49      
50              uint256 cr = LibOrders.convertCR(shortOrderCR);
51              if ((shortOrderCR + C.BID_CR) < Asset.initialCR || cr >= C.CRATIO_MAX_INITIAL) {
52                  revert Errors.InvalidCR();
53              }
54      
55              // @dev minShortErc needs to be modified (bigger) when cr < 1
56              p.minShortErc = cr < 1 ether ? LibAsset.minShortErc(asset).mul(1 ether + cr.inv()) : LibAsset.minShortErc(asset);
57              p.eth = price.mul(ercAmount);
58              p.minAskEth = LibAsset.minAskEth(asset);
59              if (ercAmount < p.minShortErc || p.eth < p.minAskEth) revert Errors.OrderUnderMinimumSize();
60      
61              // For a short, need enough collateral to cover minting ERC (calculated using initialCR)
62              if (s.vaultUser[Asset.vault][msg.sender].ethEscrowed < p.eth.mul(cr)) revert Errors.InsufficientETHEscrowed();
63      
64              incomingShort.addr = msg.sender;
65              incomingShort.price = price;
66              incomingShort.ercAmount = ercAmount;
67              incomingShort.id = Asset.orderIdCounter;
68              incomingShort.orderType = O.LimitShort;
69              incomingShort.creationTime = LibOrders.getOffsetTime();
70              incomingShort.shortOrderCR = shortOrderCR; // 170 -> 1.70x
71      
72              p.startingId = s.bids[asset][C.HEAD].nextId;
73              STypes.Order storage highestBid = s.bids[asset][p.startingId];
74              // @dev if match and match price is gt .5% to saved oracle in either direction, update startingShortId
75              if (highestBid.price >= incomingShort.price && highestBid.orderType == O.LimitBid) {
76                  LibOrders.updateOracleAndStartingShortViaThreshold(asset, LibOracle.getPrice(asset), incomingShort, shortHintArray);
77              }
78      
79              p.oraclePrice = LibOracle.getSavedOrSpotOraclePrice(asset);
80              if (LibSRUtil.checkRecoveryModeViolation(asset, cr, p.oraclePrice)) {
81                  revert Errors.BelowRecoveryModeCR();
82              }
83      
84              // @dev reading spot oracle price
85              if (incomingShort.price < p.oraclePrice) {
86                  LibOrders.addShort(asset, incomingShort, orderHintArray);
87              } else {
88                  LibOrders.sellMatchAlgo(asset, incomingShort, orderHintArray, p.minAskEth);
89              }
90          }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/ShortOrdersFacet.sol#L35:90

</details>

NC002 - Imports could be organized more systematically:

This issue arises when the contract's interface is not imported first, followed by each of the interfaces it uses, followed by all other files.

<details> <summary>Click to show 4 findings</summary>
File: facets/BridgeRouterFacet.sol


6       import {IBridge} from "contracts/interfaces/IBridge.sol";

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L6:6

File: facets/PrimaryLiquidationFacet.sol


6       import {IDiamond} from "interfaces/IDiamond.sol";

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L6:6

File: libraries/LibOracle.sol


8       import {IDiamond} from "interfaces/IDiamond.sol";

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L8:8

File: libraries/LibOrders.sol


6       import {IDiamond} from "interfaces/IDiamond.sol";

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L6:6

</details>

NC003 - Constants in comparisons should appear on the left side:

This issue arises when constants in comparisons appear on the right side, which can lead to typo bugs.

<details> <summary>Click to show 41 findings</summary>
File: facets/BridgeRouterFacet.sol


102             if (dethAmount == 0) revert Errors.ParameterIsZero();


109                 if (dethAssessable > 0) {


111                     if (withdrawalFeePct > 0) {


134             if (dethAmount == 0) revert Errors.ParameterIsZero();


164                 if (vault == 0) revert Errors.InvalidBridge();

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L164:164

File: facets/PrimaryLiquidationFacet.sol


56              if (shortHintArray.length > 10) revert Errors.TooManyHints();

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L56:56

File: facets/RedemptionFacet.sol


181             if (p.currentCR > 1.7 ether) {


184             } else if (p.currentCR > 1.5 ether) {


188             } else if (p.currentCR > 1.3 ether) {


192             } else if (p.currentCR > 1.2 ether) {


196             } else if (p.currentCR > 1.1 ether) {


279                 if (incorrectIndex > 0) {


371             if (shortRecord.ercDebt == 0 && shortRecord.status == SR.FullyFilled) {


397             assert(newBaseRate > 0); // Base rate is always non-zero after redemption

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L397:397

File: facets/ShortOrdersFacet.sol


56              p.minShortErc = cr < 1 ether ? LibAsset.minShortErc(asset).mul(1 ether + cr.inv()) : LibAsset.minShortErc(asset);

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/ShortOrdersFacet.sol#L56:56

File: libraries/LibBytes.sol


14              require(slate.length % 51 == 0, "Invalid data length");

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBytes.sol#L14:14

File: libraries/LibOracle.sol


30                      uint256 basePriceInEth = basePrice > 0 ? uint256(basePrice * C.BASE_ORACLE_DECIMALS).inv() : 0;


60                      if (roundID == 0 || price == 0 || timeStamp > block.timestamp) revert Errors.InvalidPrice();


76              bool invalidFetchData = roundId == 0 || timeStamp == 0 || timeStamp > block.timestamp || chainlinkPrice <= 0


80              bool priceDeviation = protocolPrice > 0 && chainlinkDiff.div(protocolPrice) > 0.5 ether;


89                      if (twapPrice == 0) {


104                         if (wethBal < 100 ether) {


125             bool invalidFetchData = roundId == 0 || timeStamp == 0 || timeStamp > block.timestamp || chainlinkPrice <= 0


126                 || baseRoundId == 0 || baseTimeStamp == 0 || baseTimeStamp > block.timestamp || baseChainlinkPrice <= 0;


134             if (twapPrice == 0) revert Errors.InvalidTwapPrice();


139             if (wethBal < 100 ether) revert Errors.InsufficientEthInLiquidityPool();


169             if (LibOrders.getOffsetTime() - getTime(asset) < 15 minutes) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L169:169

File: libraries/LibOrders.sol


371             if (hintId == 0 || nextId == 0) {


501             if (b.matchedAskId != 0) {


505             if (b.matchedShortId != 0) {


576                     if (incomingAsk.ercAmount.mul(highestBid.price) == 0) {


677             SR status = incomingShort.ercAmount == 0 ? SR.FullyFilled : SR.PartialFill;


792                 orderPriceGtThreshold = (incomingOrder.price - savedPrice).div(savedPrice) > 0.005 ether;


794                 orderPriceGtThreshold = (savedPrice - incomingOrder.price).div(savedPrice) > 0.005 ether;


805             if (timeDiff >= 15 minutes) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L805:805

File: libraries/LibSRUtil.sol


35              if (yield > 0) {


113                 if (Asset.ercDebt > 0) {


131             if (short.ercDebt == 0) revert Errors.OriginalShortRecordRedeemed();


158             if (ercDebt > 0) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L158:158

File: libraries/UniswapOracleLibrary.sol


52              if (secondsAgo <= 0) revert Errors.InvalidTWAPSecondsAgo();


65              if (tickCumulativesDelta < 0 && (tickCumulativesDelta % int32(secondsAgo) != 0)) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L65:65

</details>

NC004 - else-block not required:

One level of nesting can be removed by not having an else block when the if-block returns

<details> <summary>Click to show 32 findings</summary>
File: facets/BridgeRouterFacet.sol


173             if (dethTotalNew >= dethTotal) {
174                 // when yield is positive 1 deth = 1 eth
175                 return amount;
176             } else {
177                 // negative yield means 1 deth < 1 eth
178                 // @dev don't use mulU88 in rare case of overflow
179                 return amount.mul(dethTotalNew).divU88(dethTotal);
180             }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L173:180

File: facets/RedemptionFacet.sol


38              if (shortRecord.status == SR.Closed || shortRecord.ercDebt < minShortErc || proposer == shorter) {
39                  return false;
40              } else {
41                  return true;
42              }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L38:42

File: libraries/LibBridgeRouter.sol


48              if (bridgePointer == VAULT.BRIDGE_RETH) {
49                  // Withdraw RETH
50                  creditReth = VaultUser.bridgeCreditReth;
51                  if (creditReth >= amount) {
52                      VaultUser.bridgeCreditReth -= amount;
53                      return 0;
54                  }
55      
56                  VaultUser.bridgeCreditReth = 0;
57                  amount -= creditReth;
58                  creditSteth = VaultUser.bridgeCreditSteth;
59                  if (creditSteth < C.ROUNDING_ZERO) {
60                      // Valid withdraw when no STETH credits
61                      return amount;
62                  } else {
63                      if (IBridge(stethBridge).getDethValue() < C.ROUNDING_ZERO) {
64                          // Can withdraw RETH using STETH credit when STETH bridge is empty
65                          if (creditSteth >= amount) {
66                              VaultUser.bridgeCreditSteth -= amount;
67                              return 0;
68                          } else {
69                              VaultUser.bridgeCreditSteth = 0;
70                              return amount - creditSteth;
71                          }
72                      } else {
73                          // Must use available bridge credits on withdrawal
74                          // @dev Prevents abusing bridge for arbitrage
75                          revert Errors.MustUseExistingBridgeCredit();
76                      }
77                  }
78              } else {
79                  // Withdraw STETH
80                  creditSteth = VaultUser.bridgeCreditSteth;
81                  if (creditSteth >= amount) {
82                      VaultUser.bridgeCreditSteth -= amount;
83                      return 0;
84                  }
85      
86                  VaultUser.bridgeCreditSteth = 0;
87                  amount -= creditSteth;
88                  creditReth = VaultUser.bridgeCreditReth;
89                  if (creditReth < C.ROUNDING_ZERO) {
90                      // Valid withdraw when no RETH credits
91                      return amount;
92                  } else {
93                      if (IBridge(rethBridge).getDethValue() < C.ROUNDING_ZERO) {
94                          // Can withdraw STETH using RETH credit when RETH bridge is empty
95                          if (creditReth >= amount) {
96                              VaultUser.bridgeCreditReth -= amount;
97                              return 0;
98                          } else {
99                              VaultUser.bridgeCreditReth = 0;
100                             return amount - creditReth;
101                         }
102                     } else {
103                         // Must use available bridge credits on withdrawal
104                         // @dev Prevents abusing bridge for arbitrage
105                         revert Errors.MustUseExistingBridgeCredit();
106                     }
107                 }
108             }


59                  if (creditSteth < C.ROUNDING_ZERO) {
60                      // Valid withdraw when no STETH credits
61                      return amount;
62                  } else {
63                      if (IBridge(stethBridge).getDethValue() < C.ROUNDING_ZERO) {
64                          // Can withdraw RETH using STETH credit when STETH bridge is empty
65                          if (creditSteth >= amount) {
66                              VaultUser.bridgeCreditSteth -= amount;
67                              return 0;
68                          } else {
69                              VaultUser.bridgeCreditSteth = 0;
70                              return amount - creditSteth;
71                          }
72                      } else {
73                          // Must use available bridge credits on withdrawal
74                          // @dev Prevents abusing bridge for arbitrage
75                          revert Errors.MustUseExistingBridgeCredit();
76                      }
77                  }


63                      if (IBridge(stethBridge).getDethValue() < C.ROUNDING_ZERO) {
64                          // Can withdraw RETH using STETH credit when STETH bridge is empty
65                          if (creditSteth >= amount) {
66                              VaultUser.bridgeCreditSteth -= amount;
67                              return 0;
68                          } else {
69                              VaultUser.bridgeCreditSteth = 0;
70                              return amount - creditSteth;
71                          }
72                      } else {
73                          // Must use available bridge credits on withdrawal
74                          // @dev Prevents abusing bridge for arbitrage
75                          revert Errors.MustUseExistingBridgeCredit();
76                      }


65                          if (creditSteth >= amount) {
66                              VaultUser.bridgeCreditSteth -= amount;
67                              return 0;
68                          } else {
69                              VaultUser.bridgeCreditSteth = 0;
70                              return amount - creditSteth;
71                          }


89                  if (creditReth < C.ROUNDING_ZERO) {
90                      // Valid withdraw when no RETH credits
91                      return amount;
92                  } else {
93                      if (IBridge(rethBridge).getDethValue() < C.ROUNDING_ZERO) {
94                          // Can withdraw STETH using RETH credit when RETH bridge is empty
95                          if (creditReth >= amount) {
96                              VaultUser.bridgeCreditReth -= amount;
97                              return 0;
98                          } else {
99                              VaultUser.bridgeCreditReth = 0;
100                             return amount - creditReth;
101                         }
102                     } else {
103                         // Must use available bridge credits on withdrawal
104                         // @dev Prevents abusing bridge for arbitrage
105                         revert Errors.MustUseExistingBridgeCredit();
106                     }
107                 }


93                      if (IBridge(rethBridge).getDethValue() < C.ROUNDING_ZERO) {
94                          // Can withdraw STETH using RETH credit when RETH bridge is empty
95                          if (creditReth >= amount) {
96                              VaultUser.bridgeCreditReth -= amount;
97                              return 0;
98                          } else {
99                              VaultUser.bridgeCreditReth = 0;
100                             return amount - creditReth;
101                         }
102                     } else {
103                         // Must use available bridge credits on withdrawal
104                         // @dev Prevents abusing bridge for arbitrage
105                         revert Errors.MustUseExistingBridgeCredit();
106                     }


95                          if (creditReth >= amount) {
96                              VaultUser.bridgeCreditReth -= amount;
97                              return 0;
98                          } else {
99                              VaultUser.bridgeCreditReth = 0;
100                             return amount - creditReth;
101                         }


125             if (factorReth > factorSteth) {
126                 // rETH market premium relative to stETH
127                 if (bridgePointer == VAULT.BRIDGE_RETH) {
128                     // Only charge fee if withdrawing rETH
129                     return factorReth.div(factorSteth) - 1 ether;
130                 }
131             } else if (factorSteth > factorReth) {
132                 // stETH market premium relative to rETH
133                 if (bridgePointer == VAULT.BRIDGE_STETH) {
134                     // Only charge fee if withdrawing stETH
135                     return factorSteth.div(factorReth) - 1 ether;
136                 }
137             } else {
138                 // Withdrawing less premium LST or premiums are equivalent
139                 return 0;
140             }


131             } else if (factorSteth > factorReth) {
132                 // stETH market premium relative to rETH
133                 if (bridgePointer == VAULT.BRIDGE_STETH) {
134                     // Only charge fee if withdrawing stETH
135                     return factorSteth.div(factorReth) - 1 ether;
136                 }
137             } else {
138                 // Withdrawing less premium LST or premiums are equivalent
139                 return 0;
140             }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L131:140

File: libraries/LibOracle.sol


28                  if (oracle == baseOracle) {
29                      // @dev multiply base oracle by 10**10 to give it 18 decimals of precision
30                      uint256 basePriceInEth = basePrice > 0 ? uint256(basePrice * C.BASE_ORACLE_DECIMALS).inv() : 0;
31                      basePriceInEth = baseOracleCircuitBreaker(protocolPrice, baseRoundID, basePrice, baseTimeStamp, basePriceInEth);
32                      return basePriceInEth;
33                  } else {
34                      // prettier-ignore
35                      (
36                          uint80 roundID,
37                          int256 price,
38                          /*uint256 startedAt*/
39                          ,
40                          uint256 timeStamp,
41                          /*uint80 answeredInRound*/
42                      ) = oracle.latestRoundData();
43                      uint256 priceInEth = uint256(price).div(uint256(basePrice));
44                      oracleCircuitBreaker(roundID, baseRoundID, price, basePrice, timeStamp, baseTimeStamp);
45                      return priceInEth;
46                  }


48                  if (oracle == baseOracle) {
49                      return twapCircuitBreaker();
50                  } else {
51                      // prettier-ignore
52                      (
53                          uint80 roundID,
54                          int256 price,
55                          /*uint256 startedAt*/
56                          ,
57                          uint256 timeStamp,
58                          /*uint80 answeredInRound*/
59                      ) = oracle.latestRoundData();
60                      if (roundID == 0 || price == 0 || timeStamp > block.timestamp) revert Errors.InvalidPrice();
61      
62                      uint256 twapInv = twapCircuitBreaker();
63                      uint256 priceInEth = uint256(price * C.BASE_ORACLE_DECIMALS).mul(twapInv);
64                      return priceInEth;
65                  }


83              if (invalidFetchData) {
84                  return twapCircuitBreaker();
85              } else if (priceDeviation) {
86                  // Check valid twap price
87                  try IDiamond(payable(address(this))).estimateWETHInUSDC(C.UNISWAP_WETH_BASE_AMT, 30 minutes) returns (uint256 twapPrice)
88                  {
89                      if (twapPrice == 0) {
90                          return chainlinkPriceInEth;
91                      }
92      
93                      uint256 twapPriceNormalized = twapPrice * (1 ether / C.DECIMAL_USDC);
94                      uint256 twapPriceInEth = twapPriceNormalized.inv();
95                      uint256 twapDiff = twapPriceInEth > protocolPrice ? twapPriceInEth - protocolPrice : protocolPrice - twapPriceInEth;
96      
97                      // Save the price that is closest to saved oracle price
98                      if (chainlinkDiff <= twapDiff) {
99                          return chainlinkPriceInEth;
100                     } else {
101                         // Check valid twap liquidity
102                         IERC20 weth = IERC20(C.WETH);
103                         uint256 wethBal = weth.balanceOf(C.USDC_WETH);
104                         if (wethBal < 100 ether) {
105                             return chainlinkPriceInEth;
106                         }
107                         return twapPriceInEth;
108                     }
109                 } catch {
110                     return chainlinkPriceInEth;
111                 }
112             } else {
113                 return chainlinkPriceInEth;
114             }


85              } else if (priceDeviation) {
86                  // Check valid twap price
87                  try IDiamond(payable(address(this))).estimateWETHInUSDC(C.UNISWAP_WETH_BASE_AMT, 30 minutes) returns (uint256 twapPrice)
88                  {
89                      if (twapPrice == 0) {
90                          return chainlinkPriceInEth;
91                      }
92      
93                      uint256 twapPriceNormalized = twapPrice * (1 ether / C.DECIMAL_USDC);
94                      uint256 twapPriceInEth = twapPriceNormalized.inv();
95                      uint256 twapDiff = twapPriceInEth > protocolPrice ? twapPriceInEth - protocolPrice : protocolPrice - twapPriceInEth;
96      
97                      // Save the price that is closest to saved oracle price
98                      if (chainlinkDiff <= twapDiff) {
99                          return chainlinkPriceInEth;
100                     } else {
101                         // Check valid twap liquidity
102                         IERC20 weth = IERC20(C.WETH);
103                         uint256 wethBal = weth.balanceOf(C.USDC_WETH);
104                         if (wethBal < 100 ether) {
105                             return chainlinkPriceInEth;
106                         }
107                         return twapPriceInEth;
108                     }
109                 } catch {
110                     return chainlinkPriceInEth;
111                 }
112             } else {
113                 return chainlinkPriceInEth;
114             }


98                      if (chainlinkDiff <= twapDiff) {
99                          return chainlinkPriceInEth;
100                     } else {
101                         // Check valid twap liquidity
102                         IERC20 weth = IERC20(C.WETH);
103                         uint256 wethBal = weth.balanceOf(C.USDC_WETH);
104                         if (wethBal < 100 ether) {
105                             return chainlinkPriceInEth;
106                         }
107                         return twapPriceInEth;
108                     }


169             if (LibOrders.getOffsetTime() - getTime(asset) < 15 minutes) {
170                 return getPrice(asset);
171             } else {
172                 return getOraclePrice(asset);
173             }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L169:173

File: libraries/LibOrders.sol


241             if (check1 && check2) {
242                 return C.EXACT;
243             } else if (!check1) {
244                 return C.PREV;
245             } else if (!check2) {
246                 return C.NEXT;
247             }


243             } else if (!check1) {
244                 return C.PREV;
245             } else if (!check2) {
246                 return C.NEXT;
247             }


272             if (check1 && check2) {
273                 return C.EXACT;
274             } else if (!check1) {
275                 return C.PREV;
276             } else if (!check2) {
277                 return C.NEXT;
278             }


274             } else if (!check1) {
275                 return C.PREV;
276             } else if (!check2) {
277                 return C.NEXT;
278             }


381             if (direction == C.EXACT) {
382                 return hintId;
383             } else if (direction == C.NEXT) {
384                 return getOrderId(orders, asset, C.NEXT, nextId, incomingOrder.price, incomingOrder.orderType);
385             } else {
386                 uint16 prevId = orders[asset][hintId].prevId;
387                 return getOrderId(orders, asset, C.PREV, prevId, incomingOrder.price, incomingOrder.orderType);
388             }


383             } else if (direction == C.NEXT) {
384                 return getOrderId(orders, asset, C.NEXT, nextId, incomingOrder.price, incomingOrder.orderType);
385             } else {
386                 uint16 prevId = orders[asset][hintId].prevId;
387                 return getOrderId(orders, asset, C.PREV, prevId, incomingOrder.price, incomingOrder.orderType);
388             }


412             if (orderType == O.LimitAsk || orderType == O.LimitShort) {
413                 return verifySellId(orders, asset, prevId, newPrice, nextId);
414             } else if (orderType == O.LimitBid) {
415                 return verifyBidId(asset, prevId, newPrice, nextId);
416             }


421             if (o == O.LimitBid || o == O.MarketBid) {
422                 return O.LimitBid;
423             } else if (o == O.LimitAsk || o == O.MarketAsk) {
424                 return O.LimitAsk;
425             } else if (o == O.LimitShort) {
426                 return O.LimitShort;
427             }


423             } else if (o == O.LimitAsk || o == O.MarketAsk) {
424                 return O.LimitAsk;
425             } else if (o == O.LimitShort) {
426                 return O.LimitShort;
427             }


574                 if (incomingAsk.price <= highestBid.price) {
575                     // Consider ask filled if only dust amount left
576                     if (incomingAsk.ercAmount.mul(highestBid.price) == 0) {
577                         updateBidOrdersOnMatch(s.bids, asset, highestBid.id, false);
578                         incomingAsk.ercAmount = 0;
579                         matchIncomingSell(asset, incomingAsk, matchTotal);
580                         return;
581                     }
582                     matchHighestBid(incomingAsk, highestBid, asset, matchTotal);
583                     if (incomingAsk.ercAmount > highestBid.ercAmount) {
584                         incomingAsk.ercAmount -= highestBid.ercAmount;
585                         highestBid.ercAmount = 0;
586                         matchOrder(s.bids, asset, highestBid.id);
587     
588                         // loop
589                         startingId = highestBid.nextId;
590                         if (startingId == C.TAIL) {
591                             matchIncomingSell(asset, incomingAsk, matchTotal);
592     
593                             if (incomingAsk.ercAmount.mul(incomingAsk.price) >= minAskEth) {
594                                 addSellOrder(incomingAsk, asset, orderHintArray);
595                             }
596                             s.bids[asset][C.HEAD].nextId = C.TAIL;
597                             return;
598                         }
599                     } else {
600                         if (incomingAsk.ercAmount == highestBid.ercAmount) {
601                             matchOrder(s.bids, asset, highestBid.id);
602                             updateBidOrdersOnMatch(s.bids, asset, highestBid.id, true);
603                         } else {
604                             highestBid.ercAmount -= incomingAsk.ercAmount;
605                             s.bids[asset][highestBid.id].ercAmount = highestBid.ercAmount;
606                             updateBidOrdersOnMatch(s.bids, asset, highestBid.id, false);
607                             // Check reduced dust threshold for existing limit orders
608                             if (highestBid.ercAmount.mul(highestBid.price) < LibAsset.minBidEth(asset).mul(C.DUST_FACTOR)) {
609                                 cancelBid(asset, highestBid.id);
610                             }
611                         }
612                         incomingAsk.ercAmount = 0;
613                         matchIncomingSell(asset, incomingAsk, matchTotal);
614                         return;
615                     }
616                 } else {
617                     updateBidOrdersOnMatch(s.bids, asset, highestBid.id, false);
618                     matchIncomingSell(asset, incomingAsk, matchTotal);
619     
620                     if (incomingAsk.ercAmount.mul(incomingAsk.price) >= minAskEth) {
621                         addSellOrder(incomingAsk, asset, orderHintArray);
622                     }
623                     return;
624                 }


583                     if (incomingAsk.ercAmount > highestBid.ercAmount) {
584                         incomingAsk.ercAmount -= highestBid.ercAmount;
585                         highestBid.ercAmount = 0;
586                         matchOrder(s.bids, asset, highestBid.id);
587     
588                         // loop
589                         startingId = highestBid.nextId;
590                         if (startingId == C.TAIL) {
591                             matchIncomingSell(asset, incomingAsk, matchTotal);
592     
593                             if (incomingAsk.ercAmount.mul(incomingAsk.price) >= minAskEth) {
594                                 addSellOrder(incomingAsk, asset, orderHintArray);
595                             }
596                             s.bids[asset][C.HEAD].nextId = C.TAIL;
597                             return;
598                         }
599                     } else {
600                         if (incomingAsk.ercAmount == highestBid.ercAmount) {
601                             matchOrder(s.bids, asset, highestBid.id);
602                             updateBidOrdersOnMatch(s.bids, asset, highestBid.id, true);
603                         } else {
604                             highestBid.ercAmount -= incomingAsk.ercAmount;
605                             s.bids[asset][highestBid.id].ercAmount = highestBid.ercAmount;
606                             updateBidOrdersOnMatch(s.bids, asset, highestBid.id, false);
607                             // Check reduced dust threshold for existing limit orders
608                             if (highestBid.ercAmount.mul(highestBid.price) < LibAsset.minBidEth(asset).mul(C.DUST_FACTOR)) {
609                                 cancelBid(asset, highestBid.id);
610                             }
611                         }
612                         incomingAsk.ercAmount = 0;
613                         matchIncomingSell(asset, incomingAsk, matchTotal);
614                         return;
615                     }


765                     if (isExactStartingShort) {
766                         Asset.startingShortId = shortHintId;
767                         return;
768                     } else if (startingShortWithinOracleRange) {
769                         // @dev prevShortPrice >= oraclePrice
770                         Asset.startingShortId = prevId;
771                         return;
772                     } else if (allShortUnderOraclePrice) {
773                         Asset.startingShortId = C.HEAD;
774                         return;
775                     }


768                     } else if (startingShortWithinOracleRange) {
769                         // @dev prevShortPrice >= oraclePrice
770                         Asset.startingShortId = prevId;
771                         return;
772                     } else if (allShortUnderOraclePrice) {
773                         Asset.startingShortId = C.HEAD;
774                         return;
775                     }


838                 } else if (order.creationTime == orderHint.creationTime) {
839                     return orderHint.hintId;
840                 } else if (!anyOrderHintPrevMatched && order.prevOrderType == O.Matched) {
841                     anyOrderHintPrevMatched = true;
842                 }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L838:842

File: libraries/LibSRUtil.sol


59                  if (shorter == msg.sender) {
60                      // If call comes from exitShort() or combineShorts() then always cancel
61                      LibOrders.cancelShort(asset, shortOrderId);
62                      assert(shortRecord.status != SR.PartialFill);
63                      return true;
64                  } else if (shortRecord.ercDebt < LibAsset.minShortErc(asset)) {
65                      // If call comes from liquidate() and SR ercDebt under minShortErc
66                      LibOrders.cancelShort(asset, shortOrderId);
67                      return true;
68                  }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L59:68

</details>

NC005 - If-statement can be converted to a ternary:

The code can be made more compact while also increasing readability by converting the following if-statements to ternaries (e.g. foo += (x > y) ? a : b)

<details> <summary>Click to show 4 findings</summary>
File: libraries/LibOrders.sol


90              if (order.price > s.bids[asset][nextId].price || nextId == C.TAIL) {
91                  hintId = C.HEAD;
92              } else {
93                  hintId = findOrderHintId(s.bids, asset, orderHintArray);
94              }


111             if (order.price < s.asks[asset][nextId].price || nextId == C.TAIL) {
112                 hintId = C.HEAD;
113             } else {
114                 hintId = findOrderHintId(s.asks, asset, orderHintArray);
115             }


132             if (order.price < s.shorts[asset][nextId].price || nextId == C.TAIL) {
133                 hintId = C.HEAD;
134             } else {
135                 hintId = findOrderHintId(s.shorts, asset, orderHintArray);
136             }


791             if (incomingOrder.price >= savedPrice) {
792                 orderPriceGtThreshold = (incomingOrder.price - savedPrice).div(savedPrice) > 0.005 ether;
793             } else {
794                 orderPriceGtThreshold = (savedPrice - incomingOrder.price).div(savedPrice) > 0.005 ether;
795             }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L791:795

</details>

NC006 - Variable names that consist of all capital letters should be reserved for constant/immutable variables:

If the variable needs to be different based on which class it comes from, a view/pure function should be used instead.

<details> <summary>Click to show 4 findings</summary>
File: facets/PrimaryLiquidationFacet.sol


165             STypes.VaultUser storage TAPP = s.vaultUser[m.vault][address(this)];


211             STypes.VaultUser storage TAPP = s.vaultUser[m.vault][address(this)];


241             STypes.VaultUser storage TAPP = s.vaultUser[m.vault][address(this)];

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L241:241

File: libraries/LibBytes.sol


24                  uint64 CR; // bytes8

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBytes.sol#L24:24

</details>

NC007 - Consider using delete rather than assigning false/zero to clear values:

The delete keyword more closely matches the semantics of what is being done, and draws more attention to the changing of state, which may lead to a more thorough audit of its associated logic.

<details> <summary>Click to show 14 findings</summary>
File: libraries/LibBridgeRouter.sol


56                  VaultUser.bridgeCreditReth = 0;


69                              VaultUser.bridgeCreditSteth = 0;


86                  VaultUser.bridgeCreditSteth = 0;


99                              VaultUser.bridgeCreditReth = 0;


167                         VaultUserFrom.bridgeCreditReth = 0;


176                         VaultUserFrom.bridgeCreditSteth = 0;


188                         VaultUserFrom.bridgeCreditReth = 0;


189                         VaultUserFrom.bridgeCreditSteth = 0;

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L189:189

File: libraries/LibOrders.sol


578                         incomingAsk.ercAmount = 0;


585                         highestBid.ercAmount = 0;


612                         incomingAsk.ercAmount = 0;

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L612:612

File: libraries/LibSRUtil.sol


138             short.tokenId = 0;


148             nft.shortOrderId = 0;

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L148:148

File: libraries/UniswapOracleLibrary.sol


56              secondsAgos[1] = 0;

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L56:56

</details>

NC008 - Variable names for immutables should use CONSTANT_CASE:

For immutable variable names, each word should use all capital letters, with underscores separating each word (CONSTANT_CASE).

File: facets/BridgeRouterFacet.sol


26          address private immutable rethBridge;


27          address private immutable stethBridge;

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L27:27

File: facets/PrimaryLiquidationFacet.sol


28          address private immutable dusd;

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L28:28

NC009 - Lines are too long:

Usually lines in source code are limited to 80 characters. Today's screens are much larger so it's reasonable to stretch this in some cases. The solidity style guide recommends a maximumum line length of 120 characters, so the lines below should be split when they reach that length.

Note: these are missed from bots

<details> <summary>Click to show 53 findings</summary>
File: facets/BridgeRouterFacet.sol


// @dev Deters attempts to take advantage of long delays between updates to the yield rate, by creating large temporary positions

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L147:147

File: facets/PrimaryLiquidationFacet.sol

IDiamond(payable(address(this))).createForcedBid(address(this), m.asset, _bidPrice, m.short.ercDebt, shortHintArray);

LibSRUtil.disburseCollateral(m.asset, m.shorter, m.short.collateral, m.short.dethYieldRate, m.short.updatedAt);

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L246:246

File: facets/RedemptionFacet.sol


function validRedemptionSR(STypes.ShortRecord storage shortRecord, address proposer, address shorter, uint256 minShortErc)

// @dev totalAmountProposed tracks the actual amount that can be redeemed. totalAmountProposed <= redemptionAmount

if (shortOrder.shortRecordId != p.shortId || shortOrder.addr != p.shorter) revert Errors.InvalidShortOrder();

LibSRUtil.disburseCollateral(p.asset, p.shorter, p.colRedeemed, currentSR.dethYieldRate, currentSR.updatedAt);

redeemerAssetUser.timeToDispute = protocolTime + uint32((m.mul(p.currentCR - 1.7 ether) + 3 ether) * 1 hours / 1 ether);

function disputeRedemption(address asset, address redeemer, uint8 incorrectIndex, address disputeShorter, uint8 disputeShortId)

if (disputeCR < incorrectProposal.CR && disputeSR.updatedAt + C.DISPUTE_REDEMPTION_BUFFER <= redeemerAssetUser.timeProposed)

STypes.ShortRecord storage currentSR = s.shortRecords[d.asset][currentProposal.shorter][currentProposal.shortId];

LibOrders.max(LibAsset.callerFeePct(d.asset), (currentProposal.CR - disputeCR).div(currentProposal.CR)), 0.33 ether

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L290:290

File: facets/ShortOrdersFacet.sol


p.minShortErc = cr < 1 ether ? LibAsset.minShortErc(asset).mul(1 ether + cr.inv()) : LibAsset.minShortErc(asset);

LibOrders.updateOracleAndStartingShortViaThreshold(asset, LibOracle.getPrice(asset), incomingShort, shortHintArray);

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/ShortOrdersFacet.sol#L76:76

File: libraries/LibBridgeRouter.sol


function withdrawalFeePct(uint256 bridgePointer, address rethBridge, address stethBridge) internal view returns (uint256 fee) {

uint256 unitWstethTWAP = OracleLibrary.estimateTWAP(1 ether, 30 minutes, VAULT.WSTETH_WETH, VAULT.WSTETH, C.WETH);

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L122:122

File: libraries/LibBytes.sol


function readProposalData(address SSTORE2Pointer, uint8 slateLength) internal view returns (MTypes.ProposalData[] memory) {

colRedeemed := add(0xffffffffffffffffffffff, shr(80, fullWord)) // (256-88-88 = 80), mask of bytes11 = 0xff * 11

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBytes.sol#L42:42

File: libraries/LibOracle.sol


try baseOracle.latestRoundData() returns (uint80 baseRoundID, int256 basePrice, uint256, uint256 baseTimeStamp, uint80) {

basePriceInEth = baseOracleCircuitBreaker(protocolPrice, baseRoundID, basePrice, baseTimeStamp, basePriceInEth);

chainlinkPriceInEth > protocolPrice ? chainlinkPriceInEth - protocolPrice : protocolPrice - chainlinkPriceInEth;

try IDiamond(payable(address(this))).estimateWETHInUSDC(C.UNISWAP_WETH_BASE_AMT, 30 minutes) returns (uint256 twapPrice)

uint256 twapDiff = twapPriceInEth > protocolPrice ? twapPriceInEth - protocolPrice : protocolPrice - twapPriceInEth;

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L95:95

File: libraries/LibOrders.sol


function increaseSharesOnMatch(address asset, STypes.Order memory order, MTypes.Match memory matchTotal, uint88 eth) internal {

function addSellOrder(STypes.Order memory incomingOrder, address asset, MTypes.OrderHint[] memory orderHintArray) private {

emit Events.CreateOrder(asset, incomingOrder.addr, incomingOrder.orderType, incomingOrder.id, incomingOrder.ercAmount);

function cancelOrder(mapping(address => mapping(uint16 => STypes.Order)) storage orders, address asset, uint16 id) internal {

function matchOrder(mapping(address => mapping(uint16 => STypes.Order)) storage orders, address asset, uint16 id) internal {

* @dev Instead of canceling each one, just wait till the last match and only swap prevId/nextId there, since the rest are gone

function matchIncomingSell(address asset, STypes.Order memory incomingOrder, MTypes.Match memory matchTotal) private {

function matchIncomingShort(address asset, STypes.Order memory incomingShort, MTypes.Match memory matchTotal) private {

matchTotal.colUsed += incomingSell.price.mulU88(fillErc).mulU88(LibOrders.convertCR(incomingSell.shortOrderCR));

if (shortOrderType == O.Cancelled || shortOrderType == O.Matched || shortOrderType == O.Uninitialized) {

bool startingShortWithinOracleRange = shortPrice <= oraclePrice.mul(1.005 ether) && prevShortPrice >= oraclePrice;

// @dev Update on match if order matches and price diff between order price and oracle > chainlink threshold (i.e. eth .5%)

// @dev Possible for this function to never get called if updateOracleAndStartingShortViaThreshold() gets called often enough

uint88 collateralDiff = shortOrder.price.mulU88(debtDiff).mulU88(LibOrders.convertCR(shortOrder.shortOrderCR));

// Approximates the match price compared to the oracle price and accounts for any discount by increasing dethTithePercent

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L954:954

File: libraries/LibSRUtil.sol


function disburseCollateral(address asset, address shorter, uint88 collateral, uint256 dethYieldRate, uint32 updatedAt)

@dev If somebody exits a short, gets liquidated, decreases their collateral before YIELD_DELAY_SECONDS duration is up,

function checkCancelShortOrder(address asset, SR initialStatus, uint16 shortOrderId, uint8 shortRecordId, address shorter)

if (shortOrder.shortRecordId != shortRecordId || shortOrder.addr != shorter) revert Errors.InvalidShortOrder();

function checkShortMinErc(address asset, SR initialStatus, uint16 shortOrderId, uint8 shortRecordId, address shorter)

if (shortOrder.shortRecordId != shortRecordId || shortOrder.addr != shorter) revert Errors.InvalidShortOrder();

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L84:84

File: libraries/UniswapOracleLibrary.sol


// https://github.com/Uniswap/v3-periphery/blob/b325bb0905d922ae61fcc7df85ee802e8df5e96c/contracts/libraries/OracleLibrary.sol

baseToken < quoteToken ? U256.mulDiv(ratioX192, baseAmount, 1 << 192) : U256.mulDiv(1 << 192, baseAmount, ratioX192);

baseToken < quoteToken ? U256.mulDiv(ratioX128, baseAmount, 1 << 128) : U256.mulDiv(1 << 128, baseAmount, ratioX128);

// @dev Returns the cumulative tick and liquidity as of each timestamp secondsAgo from the current block timestamp

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L58:58

</details>

NC010 - File is missing NatSpec comments:

The file does not contain any of the NatSpec comments (@inheritdoc, @param, @return, @notice), which are important for documentation and user confirmation.

<details> <summary>Click to show 4 findings</summary>
File: libraries/LibBridgeRouter.sol


// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.21;

import {IBridge} from "contracts/interfaces/IBridge.sol";

import {STypes} from "contracts/libraries/DataTypes.sol";
import {AppStorage, appStorage} from "contracts/libraries/AppStorage.sol";
import {OracleLibrary} from "contracts/libraries/UniswapOracleLibrary.sol";
import {C, VAULT} from "contracts/libraries/Constants.sol";
import {Errors} from "contracts/libraries/Errors.sol";

import {U256, U88} from "contracts/libraries/PRBMathHelper.sol";

// import {console} from "contracts/libraries/console.sol";

library LibBridgeRouter {
    using U256 for uint256;
    using U88 for uint88;

    // Credit user account with dETH and bridge credit if applicable
    function addDeth(uint256 vault, uint256 bridgePointer, uint88 amount) internal {
        AppStorage storage s = appStorage();
        STypes.VaultUser storage VaultUser = s.vaultUser[vault][msg.sender];

        if (vault == VAULT.ONE) {
            // Only VAULT.ONE has mixed LST
            if (bridgePointer == VAULT.BRIDGE_RETH) {
                VaultUser.bridgeCreditReth += amount;
            } else {
                VaultUser.bridgeCreditSteth += amount;
            }
        }

        VaultUser.ethEscrowed += amount;
        s.vault[vault].dethTotal += amount;
    }

    // Determine how much dETH is NOT covered by bridge credits during withdrawal
    function assessDeth(uint256 vault, uint256 bridgePointer, uint88 amount, address rethBridge, address stethBridge)
        internal
        returns (uint88)
    {
        AppStorage storage s = appStorage();
        STypes.VaultUser storage VaultUser = s.vaultUser[vault][msg.sender];

        uint88 creditReth;
        uint88 creditSteth;
        if (bridgePointer == VAULT.BRIDGE_RETH) {
            // Withdraw RETH
            creditReth = VaultUser.bridgeCreditReth;
            if (creditReth >= amount) {
                VaultUser.bridgeCreditReth -= amount;
                return 0;
            }

            VaultUser.bridgeCreditReth = 0;
            amount -= creditReth;
            creditSteth = VaultUser.bridgeCreditSteth;
            if (creditSteth < C.ROUNDING_ZERO) {
                // Valid withdraw when no STETH credits
                return amount;
            } else {
                if (IBridge(stethBridge).getDethValue() < C.ROUNDING_ZERO) {
                    // Can withdraw RETH using STETH credit when STETH bridge is empty
                    if (creditSteth >= amount) {
                        VaultUser.bridgeCreditSteth -= amount;
                        return 0;
                    } else {
                        VaultUser.bridgeCreditSteth = 0;
                        return amount - creditSteth;
                    }
                } else {
                    // Must use available bridge credits on withdrawal
                    // @dev Prevents abusing bridge for arbitrage
                    revert Errors.MustUseExistingBridgeCredit();
                }
            }
        } else {
            // Withdraw STETH
            creditSteth = VaultUser.bridgeCreditSteth;
            if (creditSteth >= amount) {
                VaultUser.bridgeCreditSteth -= amount;
                return 0;
            }

            VaultUser.bridgeCreditSteth = 0;
            amount -= creditSteth;
            creditReth = VaultUser.bridgeCreditReth;
            if (creditReth < C.ROUNDING_ZERO) {
                // Valid withdraw when no RETH credits
                return amount;
            } else {
                if (IBridge(rethBridge).getDethValue() < C.ROUNDING_ZERO) {
                    // Can withdraw STETH using RETH credit when RETH bridge is empty
                    if (creditReth >= amount) {
                        VaultUser.bridgeCreditReth -= amount;
                        return 0;
                    } else {
                        VaultUser.bridgeCreditReth = 0;
                        return amount - creditReth;
                    }
                } else {
                    // Must use available bridge credits on withdrawal
                    // @dev Prevents abusing bridge for arbitrage
                    revert Errors.MustUseExistingBridgeCredit();
                }
            }
        }
    }

    // Bridge fees exist only to prevent free arbitrage, fee charged is the premium/discount differential
    // @dev Only applicable to VAULT.ONE which has mixed LST
    function withdrawalFeePct(uint256 bridgePointer, address rethBridge, address stethBridge) internal view returns (uint256 fee) {
        IBridge bridgeReth = IBridge(rethBridge);
        IBridge bridgeSteth = IBridge(stethBridge);

        // Calculate rETH market premium/discount (factor)
        uint256 unitRethTWAP = OracleLibrary.estimateTWAP(1 ether, 30 minutes, VAULT.RETH_WETH, VAULT.RETH, C.WETH);
        uint256 unitRethOracle = bridgeReth.getUnitDethValue();
        uint256 factorReth = unitRethTWAP.div(unitRethOracle);
        // Calculate stETH market premium/discount (factor)
        uint256 unitWstethTWAP = OracleLibrary.estimateTWAP(1 ether, 30 minutes, VAULT.WSTETH_WETH, VAULT.WSTETH, C.WETH);
        uint256 unitWstethOracle = bridgeSteth.getUnitDethValue();
        uint256 factorSteth = unitWstethTWAP.div(unitWstethOracle);
        if (factorReth > factorSteth) {
            // rETH market premium relative to stETH
            if (bridgePointer == VAULT.BRIDGE_RETH) {
                // Only charge fee if withdrawing rETH
                return factorReth.div(factorSteth) - 1 ether;
            }
        } else if (factorSteth > factorReth) {
            // stETH market premium relative to rETH
            if (bridgePointer == VAULT.BRIDGE_STETH) {
                // Only charge fee if withdrawing stETH
                return factorSteth.div(factorReth) - 1 ether;
            }
        } else {
            // Withdrawing less premium LST or premiums are equivalent
            return 0;
        }
    }

    // @dev Only relevant to NFT SR that is being transferred, used to deter workarounds to the bridge credit system
    function transferBridgeCredit(address asset, address from, address to, uint88 collateral) internal {
        AppStorage storage s = appStorage();

        STypes.Asset storage Asset = s.asset[asset];
        uint256 vault = Asset.vault;

        if (vault == VAULT.ONE) {
            STypes.VaultUser storage VaultUserFrom = s.vaultUser[vault][from];
            uint88 creditReth = VaultUserFrom.bridgeCreditReth;
            uint88 creditSteth = VaultUserFrom.bridgeCreditSteth;
            STypes.VaultUser storage VaultUserTo = s.vaultUser[vault][to];

            if (creditReth < C.ROUNDING_ZERO && creditSteth < C.ROUNDING_ZERO) {
                // No bridge credits
                return;
            }

            if (creditReth > C.ROUNDING_ZERO && creditSteth < C.ROUNDING_ZERO) {
                // Only creditReth
                if (creditReth > collateral) {
                    VaultUserFrom.bridgeCreditReth -= collateral;
                    VaultUserTo.bridgeCreditReth += collateral;
                } else {
                    VaultUserFrom.bridgeCreditReth = 0;
                    VaultUserTo.bridgeCreditReth += creditReth;
                }
            } else if (creditReth < C.ROUNDING_ZERO && creditSteth > C.ROUNDING_ZERO) {
                // Only creditSteth
                if (creditSteth > collateral) {
                    VaultUserFrom.bridgeCreditSteth -= collateral;
                    VaultUserTo.bridgeCreditSteth += collateral;
                } else {
                    VaultUserFrom.bridgeCreditSteth = 0;
                    VaultUserTo.bridgeCreditSteth += creditSteth;
                }
            } else {
                // Both creditReth and creditSteth
                uint88 creditTotal = creditReth + creditSteth;
                if (creditTotal > collateral) {
                    creditReth = creditReth.divU88(creditTotal).mulU88(collateral);
                    creditSteth = creditSteth.divU88(creditTotal).mulU88(collateral);
                    VaultUserFrom.bridgeCreditReth -= creditReth;
                    VaultUserFrom.bridgeCreditSteth -= creditSteth;
                } else {
                    VaultUserFrom.bridgeCreditReth = 0;
                    VaultUserFrom.bridgeCreditSteth = 0;
                }
                VaultUserTo.bridgeCreditReth += creditReth;
                VaultUserTo.bridgeCreditSteth += creditSteth;
            }
        }
    }

    // Update user account upon dETH withdrawal
    function removeDeth(uint256 vault, uint88 amount, uint88 fee) internal {
        AppStorage storage s = appStorage();
        s.vaultUser[vault][msg.sender].ethEscrowed -= (amount + fee);
        s.vault[vault].dethTotal -= amount;
    }
}

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L1:1

File: libraries/LibBytes.sol


// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.21;

import {MTypes} from "contracts/libraries/DataTypes.sol";
import {SSTORE2} from "solmate/utils/SSTORE2.sol";

// import {console} from "contracts/libraries/console.sol";

library LibBytes {
    // Custom decode since SSTORE.write was written directly in proposeRedemption
    function readProposalData(address SSTORE2Pointer, uint8 slateLength) internal view returns (MTypes.ProposalData[] memory) {
        bytes memory slate = SSTORE2.read(SSTORE2Pointer);
        // ProposalData is 51 bytes
        require(slate.length % 51 == 0, "Invalid data length");

        MTypes.ProposalData[] memory data = new MTypes.ProposalData[](slateLength);

        for (uint256 i = 0; i < slateLength; i++) {
            // 32 offset for array length, mulitply by each ProposalData
            uint256 offset = i * 51 + 32;

            address shorter; // bytes20
            uint8 shortId; // bytes1
            uint64 CR; // bytes8
            uint88 ercDebtRedeemed; // bytes11
            uint88 colRedeemed; // bytes11

            assembly {
                // mload works 32 bytes at a time
                let fullWord := mload(add(slate, offset))
                // read 20 bytes
                shorter := shr(96, fullWord) // 0x60 = 96 (256-160)
                // read 8 bytes
                shortId := and(0xff, shr(88, fullWord)) // 0x58 = 88 (96-8), mask of bytes1 = 0xff * 1
                // read 64 bytes
                CR := and(0xffffffffffffffff, shr(24, fullWord)) // 0x18 = 24 (88-64), mask of bytes8 = 0xff * 8

                fullWord := mload(add(slate, add(offset, 29))) // (29 offset)
                // read 88 bytes
                ercDebtRedeemed := shr(168, fullWord) // (256-88 = 168)
                // read 88 bytes
                colRedeemed := add(0xffffffffffffffffffffff, shr(80, fullWord)) // (256-88-88 = 80), mask of bytes11 = 0xff * 11
            }

            data[i] = MTypes.ProposalData({
                shorter: shorter,
                shortId: shortId,
                CR: CR,
                ercDebtRedeemed: ercDebtRedeemed,
                colRedeemed: colRedeemed
            });
        }

        return data;
    }
}

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBytes.sol#L1:1

File: libraries/LibOracle.sol


// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.21;

import {U256} from "contracts/libraries/PRBMathHelper.sol";

import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IDiamond} from "interfaces/IDiamond.sol";
import {AppStorage, appStorage} from "contracts/libraries/AppStorage.sol";
import {C} from "contracts/libraries/Constants.sol";
import {LibOrders} from "contracts/libraries/LibOrders.sol";
import {Errors} from "contracts/libraries/Errors.sol";

// import {console} from "contracts/libraries/console.sol";

library LibOracle {
    using U256 for uint256;

    function getOraclePrice(address asset) internal view returns (uint256) {
        AppStorage storage s = appStorage();
        AggregatorV3Interface baseOracle = AggregatorV3Interface(s.baseOracle);
        uint256 protocolPrice = getPrice(asset);

        AggregatorV3Interface oracle = AggregatorV3Interface(s.asset[asset].oracle);
        if (address(oracle) == address(0)) revert Errors.InvalidAsset();

        try baseOracle.latestRoundData() returns (uint80 baseRoundID, int256 basePrice, uint256, uint256 baseTimeStamp, uint80) {
            if (oracle == baseOracle) {
                // @dev multiply base oracle by 10**10 to give it 18 decimals of precision
                uint256 basePriceInEth = basePrice > 0 ? uint256(basePrice * C.BASE_ORACLE_DECIMALS).inv() : 0;
                basePriceInEth = baseOracleCircuitBreaker(protocolPrice, baseRoundID, basePrice, baseTimeStamp, basePriceInEth);
                return basePriceInEth;
            } else {
                // prettier-ignore
                (
                    uint80 roundID,
                    int256 price,
                    /*uint256 startedAt*/
                    ,
                    uint256 timeStamp,
                    /*uint80 answeredInRound*/
                ) = oracle.latestRoundData();
                uint256 priceInEth = uint256(price).div(uint256(basePrice));
                oracleCircuitBreaker(roundID, baseRoundID, price, basePrice, timeStamp, baseTimeStamp);
                return priceInEth;
            }
        } catch {
            if (oracle == baseOracle) {
                return twapCircuitBreaker();
            } else {
                // prettier-ignore
                (
                    uint80 roundID,
                    int256 price,
                    /*uint256 startedAt*/
                    ,
                    uint256 timeStamp,
                    /*uint80 answeredInRound*/
                ) = oracle.latestRoundData();
                if (roundID == 0 || price == 0 || timeStamp > block.timestamp) revert Errors.InvalidPrice();

                uint256 twapInv = twapCircuitBreaker();
                uint256 priceInEth = uint256(price * C.BASE_ORACLE_DECIMALS).mul(twapInv);
                return priceInEth;
            }
        }
    }

    function baseOracleCircuitBreaker(
        uint256 protocolPrice,
        uint80 roundId,
        int256 chainlinkPrice,
        uint256 timeStamp,
        uint256 chainlinkPriceInEth
    ) private view returns (uint256 _protocolPrice) {
        bool invalidFetchData = roundId == 0 || timeStamp == 0 || timeStamp > block.timestamp || chainlinkPrice <= 0
            || block.timestamp > 2 hours + timeStamp;
        uint256 chainlinkDiff =
            chainlinkPriceInEth > protocolPrice ? chainlinkPriceInEth - protocolPrice : protocolPrice - chainlinkPriceInEth;
        bool priceDeviation = protocolPrice > 0 && chainlinkDiff.div(protocolPrice) > 0.5 ether;

        // @dev if there is issue with chainlink, get twap price. Verify twap and compare with chainlink
        if (invalidFetchData) {
            return twapCircuitBreaker();
        } else if (priceDeviation) {
            // Check valid twap price
            try IDiamond(payable(address(this))).estimateWETHInUSDC(C.UNISWAP_WETH_BASE_AMT, 30 minutes) returns (uint256 twapPrice)
            {
                if (twapPrice == 0) {
                    return chainlinkPriceInEth;
                }

                uint256 twapPriceNormalized = twapPrice * (1 ether / C.DECIMAL_USDC);
                uint256 twapPriceInEth = twapPriceNormalized.inv();
                uint256 twapDiff = twapPriceInEth > protocolPrice ? twapPriceInEth - protocolPrice : protocolPrice - twapPriceInEth;

                // Save the price that is closest to saved oracle price
                if (chainlinkDiff <= twapDiff) {
                    return chainlinkPriceInEth;
                } else {
                    // Check valid twap liquidity
                    IERC20 weth = IERC20(C.WETH);
                    uint256 wethBal = weth.balanceOf(C.USDC_WETH);
                    if (wethBal < 100 ether) {
                        return chainlinkPriceInEth;
                    }
                    return twapPriceInEth;
                }
            } catch {
                return chainlinkPriceInEth;
            }
        } else {
            return chainlinkPriceInEth;
        }
    }

    function oracleCircuitBreaker(
        uint80 roundId,
        uint80 baseRoundId,
        int256 chainlinkPrice,
        int256 baseChainlinkPrice,
        uint256 timeStamp,
        uint256 baseTimeStamp
    ) private view {
        bool invalidFetchData = roundId == 0 || timeStamp == 0 || timeStamp > block.timestamp || chainlinkPrice <= 0
            || baseRoundId == 0 || baseTimeStamp == 0 || baseTimeStamp > block.timestamp || baseChainlinkPrice <= 0;

        if (invalidFetchData) revert Errors.InvalidPrice();
    }

    function twapCircuitBreaker() private view returns (uint256 twapPriceInEth) {
        // Check valid price
        uint256 twapPrice = IDiamond(payable(address(this))).estimateWETHInUSDC(C.UNISWAP_WETH_BASE_AMT, 30 minutes);
        if (twapPrice == 0) revert Errors.InvalidTwapPrice();

        // Check valid liquidity
        IERC20 weth = IERC20(C.WETH);
        uint256 wethBal = weth.balanceOf(C.USDC_WETH);
        if (wethBal < 100 ether) revert Errors.InsufficientEthInLiquidityPool();

        uint256 twapPriceNormalized = twapPrice * (1 ether / C.DECIMAL_USDC);
        return twapPriceNormalized.inv();
    }

    /* 
    @dev C.HEAD to marks the start/end of the linked list, so the only properties needed are id/nextId/prevId.
    Helper methods are used to set the values of oraclePrice and oracleTime since they are set to different properties
    */
    function setPriceAndTime(address asset, uint256 oraclePrice, uint32 oracleTime) internal {
        AppStorage storage s = appStorage();
        s.bids[asset][C.HEAD].ercAmount = uint80(oraclePrice);
        s.bids[asset][C.HEAD].creationTime = oracleTime;
    }

    // @dev Intentionally using creationTime for oracleTime.
    function getTime(address asset) internal view returns (uint256 creationTime) {
        AppStorage storage s = appStorage();
        return s.bids[asset][C.HEAD].creationTime;
    }

    // @dev Intentionally using ercAmount for oraclePrice. Storing as price may lead to bugs in the match algos.
    function getPrice(address asset) internal view returns (uint80 oraclePrice) {
        AppStorage storage s = appStorage();
        return uint80(s.bids[asset][C.HEAD].ercAmount);
    }

    // @dev Allows caller to save gas since reading spot price costs ~16K
    function getSavedOrSpotOraclePrice(address asset) internal view returns (uint256) {
        if (LibOrders.getOffsetTime() - getTime(asset) < 15 minutes) {
            return getPrice(asset);
        } else {
            return getOraclePrice(asset);
        }
    }
}

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L1:1

File: libraries/LibSRUtil.sol


// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.21;

import {U88, U256} from "contracts/libraries/PRBMathHelper.sol";

import {STypes, SR} from "contracts/libraries/DataTypes.sol";
import {AppStorage, appStorage} from "contracts/libraries/AppStorage.sol";
import {C} from "contracts/libraries/Constants.sol";
import {LibOrders} from "contracts/libraries/LibOrders.sol";
import {LibShortRecord} from "contracts/libraries/LibShortRecord.sol";
import {LibAsset} from "contracts/libraries/LibAsset.sol";
import {Errors} from "contracts/libraries/Errors.sol";
import {LibBridgeRouter} from "contracts/libraries/LibBridgeRouter.sol";

// import {console} from "contracts/libraries/console.sol";

// extra ShortRecord helpers, similar to LibShortRecord
library LibSRUtil {
    using U88 for uint88;
    using U256 for uint256;

    function disburseCollateral(address asset, address shorter, uint88 collateral, uint256 dethYieldRate, uint32 updatedAt)
        internal
    {
        AppStorage storage s = appStorage();

        STypes.Asset storage Asset = s.asset[asset];
        uint256 vault = Asset.vault;
        STypes.Vault storage Vault = s.vault[vault];

        Vault.dethCollateral -= collateral;
        Asset.dethCollateral -= collateral;
        // Distribute yield
        uint88 yield = collateral.mulU88(Vault.dethYieldRate - dethYieldRate);
        if (yield > 0) {
            /*
            @dev If somebody exits a short, gets liquidated, decreases their collateral before YIELD_DELAY_SECONDS duration is up,
            they lose their yield to the TAPP
            */
            bool isNotRecentlyModified = LibOrders.getOffsetTime() - updatedAt > C.YIELD_DELAY_SECONDS;
            if (isNotRecentlyModified) {
                s.vaultUser[vault][shorter].ethEscrowed += yield;
            } else {
                s.vaultUser[vault][address(this)].ethEscrowed += yield;
            }
        }
    }

    function checkCancelShortOrder(address asset, SR initialStatus, uint16 shortOrderId, uint8 shortRecordId, address shorter)
        internal
        returns (bool isCancelled)
    {
        AppStorage storage s = appStorage();
        if (initialStatus == SR.PartialFill) {
            STypes.Order storage shortOrder = s.shorts[asset][shortOrderId];
            STypes.ShortRecord storage shortRecord = s.shortRecords[asset][shorter][shortRecordId];
            if (shortOrder.shortRecordId != shortRecordId || shortOrder.addr != shorter) revert Errors.InvalidShortOrder();

            if (shorter == msg.sender) {
                // If call comes from exitShort() or combineShorts() then always cancel
                LibOrders.cancelShort(asset, shortOrderId);
                assert(shortRecord.status != SR.PartialFill);
                return true;
            } else if (shortRecord.ercDebt < LibAsset.minShortErc(asset)) {
                // If call comes from liquidate() and SR ercDebt under minShortErc
                LibOrders.cancelShort(asset, shortOrderId);
                return true;
            }
        }
    }

    function checkShortMinErc(address asset, SR initialStatus, uint16 shortOrderId, uint8 shortRecordId, address shorter)
        internal
        returns (bool isCancelled)
    {
        AppStorage storage s = appStorage();

        STypes.ShortRecord storage shortRecord = s.shortRecords[asset][shorter][shortRecordId];
        uint256 minShortErc = LibAsset.minShortErc(asset);

        if (initialStatus == SR.PartialFill) {
            // Verify shortOrder
            STypes.Order storage shortOrder = s.shorts[asset][shortOrderId];
            if (shortOrder.shortRecordId != shortRecordId || shortOrder.addr != shorter) revert Errors.InvalidShortOrder();

            if (shortRecord.status == SR.Closed) {
                // Check remaining shortOrder
                if (shortOrder.ercAmount < minShortErc) {
                    // @dev The resulting SR will not have PartialFill status after cancel
                    LibOrders.cancelShort(asset, shortOrderId);
                    isCancelled = true;
                }
            } else {
                // Check remaining shortOrder and remaining shortRecord
                if (shortOrder.ercAmount + shortRecord.ercDebt < minShortErc) revert Errors.CannotLeaveDustAmount();
            }
        } else if (shortRecord.status != SR.Closed && shortRecord.ercDebt < minShortErc) {
            revert Errors.CannotLeaveDustAmount();
        }
    }

    function checkRecoveryModeViolation(address asset, uint256 shortRecordCR, uint256 oraclePrice)
        internal
        view
        returns (bool recoveryViolation)
    {
        AppStorage storage s = appStorage();

        uint256 recoveryCR = LibAsset.recoveryCR(asset);
        if (shortRecordCR < recoveryCR) {
            // Only check asset CR if low enough
            STypes.Asset storage Asset = s.asset[asset];
            if (Asset.ercDebt > 0) {
                // If Asset.ercDebt == 0 then assetCR is NA
                uint256 assetCR = Asset.dethCollateral.div(oraclePrice.mul(Asset.ercDebt));
                if (assetCR < recoveryCR) {
                    // Market is in recovery mode and shortRecord CR too low
                    return true;
                }
            }
        }
    }

    function transferShortRecord(address from, address to, uint40 tokenId) internal {
        AppStorage storage s = appStorage();

        STypes.NFT storage nft = s.nftMapping[tokenId];
        address asset = s.assetMapping[nft.assetId];
        STypes.ShortRecord storage short = s.shortRecords[asset][from][nft.shortRecordId];
        if (short.status == SR.Closed) revert Errors.OriginalShortRecordCancelled();
        if (short.ercDebt == 0) revert Errors.OriginalShortRecordRedeemed();

        // @dev shortOrderId is already validated in mintNFT
        if (short.status == SR.PartialFill) {
            LibOrders.cancelShort(asset, nft.shortOrderId);
        }

        short.tokenId = 0;
        LibShortRecord.deleteShortRecord(asset, from, nft.shortRecordId);
        LibBridgeRouter.transferBridgeCredit(asset, from, to, short.collateral);

        uint8 id = LibShortRecord.createShortRecord(
            asset, to, SR.FullyFilled, short.collateral, short.ercDebt, short.ercDebtRate, short.dethYieldRate, tokenId
        );

        nft.owner = to;
        nft.shortRecordId = id;
        nft.shortOrderId = 0;
    }

    function updateErcDebt(STypes.ShortRecord storage short, address asset) internal {
        AppStorage storage s = appStorage();

        // Distribute ercDebt
        uint64 ercDebtRate = s.asset[asset].ercDebtRate;
        uint88 ercDebt = short.ercDebt.mulU88(ercDebtRate - short.ercDebtRate);

        if (ercDebt > 0) {
            short.ercDebt += ercDebt;
            short.ercDebtRate = ercDebtRate;
        }
    }
}

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L1:1

</details>

NC011 - Function declarations should have NatSpec descriptions:

Function declarations should be preceded by a NatSpec comment.

<details> <summary>Click to show 36 findings</summary>
File: facets/BridgeRouterFacet.sol


29          constructor(address _rethBridge, address _stethBridge) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L29:29

File: facets/PrimaryLiquidationFacet.sol


30          constructor(address _dusd) {


229         function min88(uint256 a, uint88 b) private pure returns (uint88) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L229:229

File: facets/RedemptionFacet.sol


31          function validRedemptionSR(STypes.ShortRecord storage shortRecord, address proposer, address shorter, uint256 minShortErc)

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L31:31

File: libraries/LibBridgeRouter.sol


21          function addDeth(uint256 vault, uint256 bridgePointer, uint88 amount) internal {


39          function assessDeth(uint256 vault, uint256 bridgePointer, uint88 amount, address rethBridge, address stethBridge)


198         function removeDeth(uint256 vault, uint88 amount, uint88 fee) internal {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L198:198

File: libraries/LibBytes.sol


11          function readProposalData(address SSTORE2Pointer, uint8 slateLength) internal view returns (MTypes.ProposalData[] memory) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBytes.sol#L11:11

File: libraries/LibOracle.sol


19          function getOraclePrice(address asset) internal view returns (uint256) {


69          function baseOracleCircuitBreaker(


117         function oracleCircuitBreaker(


131         function twapCircuitBreaker() private view returns (uint256 twapPriceInEth) {


149         function setPriceAndTime(address asset, uint256 oraclePrice, uint32 oracleTime) internal {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L149:149

File: libraries/LibOrders.sol


35          function convertCR(uint16 cr) internal pure returns (uint256) {


40          function increaseSharesOnMatch(address asset, STypes.Order memory order, MTypes.Match memory matchTotal, uint88 eth) internal {


55          function currentOrders(mapping(address => mapping(uint16 => STypes.Order)) storage orders, address asset)


78          function isShort(STypes.Order memory order) internal pure returns (bool) {


82          function addBid(address asset, STypes.Order memory order, MTypes.OrderHint[] memory orderHintArray) internal {


103         function addAsk(address asset, STypes.Order memory order, MTypes.OrderHint[] memory orderHintArray) internal {


628         function matchIncomingSell(address asset, STypes.Order memory incomingOrder, MTypes.Match memory matchTotal) private {


810         function updateStartingShortIdViaShort(address asset, STypes.Order memory incomingShort) internal {


826         function findOrderHintId(


854         function cancelBid(address asset, uint16 id) internal {


868         function cancelAsk(address asset, uint16 id) internal {


882         function cancelShort(address asset, uint16 id) internal {


955         function handlePriceDiscount(address asset, uint80 price) internal {


985         function min(uint256 a, uint256 b) internal pure returns (uint256) {


989         function max(uint256 a, uint256 b) internal pure returns (uint256) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L989:989

File: libraries/LibSRUtil.sol


22          function disburseCollateral(address asset, address shorter, uint88 collateral, uint256 dethYieldRate, uint32 updatedAt)


49          function checkCancelShortOrder(address asset, SR initialStatus, uint16 shortOrderId, uint8 shortRecordId, address shorter)


72          function checkShortMinErc(address asset, SR initialStatus, uint16 shortOrderId, uint8 shortRecordId, address shorter)


102         function checkRecoveryModeViolation(address asset, uint256 shortRecordCR, uint256 oraclePrice)


124         function transferShortRecord(address from, address to, uint40 tokenId) internal {


151         function updateErcDebt(STypes.ShortRecord storage short, address asset) internal {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L151:151

File: libraries/UniswapOracleLibrary.sol


11          function observe(uint32[] calldata secondsAgos)


47          function estimateTWAP(uint128 amountIn, uint32 secondsAgo, address pool, address baseToken, address quoteToken)

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L47:47

</details>

NC012 - Contract declarations should have @notice tags:

@notice is used to explain to end users what the contract does, and the compiler interprets /// or /** comments as this tag if one wasn't explicitly provided.

<details> <summary>Click to show 10 findings</summary>
File: facets/BridgeRouterFacet.sol


18      contract BridgeRouterFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L18:18

File: facets/PrimaryLiquidationFacet.sol


21      contract PrimaryLiquidationFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L21:21

File: facets/RedemptionFacet.sol


21      contract RedemptionFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L21:21

File: facets/ShortOrdersFacet.sol


18      contract ShortOrdersFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/ShortOrdersFacet.sol#L18:18

File: libraries/LibBridgeRouter.sol


16      library LibBridgeRouter {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L16:16

File: libraries/LibBytes.sol


9       library LibBytes {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBytes.sol#L9:9

File: libraries/LibOracle.sol


16      library LibOracle {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L16:16

File: libraries/LibOrders.sol


20      library LibOrders {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L20:20

File: libraries/LibSRUtil.sol


18      library LibSRUtil {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L18:18

File: libraries/UniswapOracleLibrary.sol


10      interface IUniswapV3Pool {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L10:10

</details>

NC013 - Invalid NatSpec comment style:

NatSpec must begin with ///, or use /* ... */ syntax.

<details> <summary>Click to show 93 findings</summary>
File: facets/BridgeRouterFacet.sol


67              // @dev amount after deposit might be less, if bridge takes a fee


147         // @dev Deters attempts to take advantage of long delays between updates to the yield rate, by creating large temporary positions


178                 // @dev don't use mulU88 in rare case of overflow

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L178:178

File: facets/PrimaryLiquidationFacet.sol


55              // @dev TAPP partially reimburses gas fees, capped at 10 to limit arbitrary high cost


58              // @dev Ensures SR has enough ercDebt/collateral to make caller fee worthwhile


67              // @dev liquidate requires more up-to-date oraclePrice


72              // @dev Can liquidate as long as CR is low enough


95          // @dev startingShortId is updated via updateOracleAndStartingShortViaTimeBidOnly() prior to call


158             // @dev Provide higher price to better ensure it can fully fill the liquidation


164             // @dev Increase ethEscrowed by shorter's full collateral for forced bid


177                 // @dev Max ethDebt can only be the ethEscrowed in the TAPP


186             // @dev Liquidation contract will be the caller. Virtual accounting done later for shorter or TAPP


192             // @dev virtually burning the repurchased debt


197             // @dev manually setting basefee to 1,000,000 in foundry.toml;


198             // @dev By basing gasFee off of baseFee instead of priority, adversaries are prevent from draining the TAPP


217             // @dev TAPP already received the gasFee for being the forcedBid caller. tappFee nets out.

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L217:217

File: facets/RedemptionFacet.sol


36              // @dev Matches check in onlyValidShortRecord but with a more restrictive ercDebt condition


37              // @dev Proposer can't redeem on self


72              // @dev redeemerAssetUser.SSTORE2Pointer gets reset to address(0) after actual redemption


82                  // @dev Setting this above _onlyValidShortRecord to allow skipping


92                  // @dev Skip if proposal is not sorted correctly or if above redemption threshold


95                  // @dev totalAmountProposed tracks the actual amount that can be redeemed. totalAmountProposed <= redemptionAmount


100                     // @dev Exit when proposal would leave less than minShortErc, proxy for nearing end of slate


108                 // @dev Cancel attached shortOrder if below minShortErc, regardless of ercDebt in SR


109                 // @dev All verified SR have ercDebt >= minShortErc so CR does not change in cancelShort()


127                 // @dev directly write the properties of MTypes.ProposalData into bytes


145             // @dev SSTORE2 the entire proposalData after validating proposalInput


156             // @dev Calculate the dispute period


157             // @dev timeToDispute is immediate for shorts with CR <= 1.1x


261                 // @dev All proposals from the incorrectIndex onward will be removed


262                 // @dev Thus the proposer can only redeem a portion of their original slate


278                 // @dev Update the redeemer's SSTORE2Pointer


282                     // @dev this implies everything in the redeemer's proposal was incorrect


287                 // @dev Penalty is based on the proposal with highest CR (decodedProposalData is sorted)


288                 // @dev PenaltyPct is bound between CallerFeePct and 33% to prevent exploiting primaryLiquidation fees


295                 // @dev Give redeemer back ercEscrowed that is no longer used to redeem (penalty applied)


356             // @dev Only need to read up to the position of the SR to be claimed


372                 // @dev Refund shorter the remaining collateral only if fully redeemed and not claimed already


375                 // @dev Shorter shouldn't lose any unclaimed yield because dispute time > YIELD_DELAY_SECONDS


381         // @dev inspired by https://docs.liquity.org/faq/lusd-redemptions#how-is-the-redemption-fee-calculated


391             // @dev Calculate Asset.ercDebt prior to proposal


393             // @dev Derived via this forumula: baseRateNew = baseRateOld + redeemedLUSD / (2 * totalLUSD)

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L393:393

File: facets/ShortOrdersFacet.sol


47              // @dev create "empty" SR. Fail early.


55              // @dev minShortErc needs to be modified (bigger) when cr < 1


74              // @dev if match and match price is gt .5% to saved oracle in either direction, update startingShortId


84              // @dev reading spot oracle price

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/ShortOrdersFacet.sol#L84:84

File: libraries/LibBridgeRouter.sol


74                          // @dev Prevents abusing bridge for arbitrage


104                         // @dev Prevents abusing bridge for arbitrage


112         // @dev Only applicable to VAULT.ONE which has mixed LST


143         // @dev Only relevant to NFT SR that is being transferred, used to deter workarounds to the bridge credit system

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L143:143

File: libraries/LibOracle.sol


29                      // @dev multiply base oracle by 10**10 to give it 18 decimals of precision


82              // @dev if there is issue with chainlink, get twap price. Verify twap and compare with chainlink


155         // @dev Intentionally using creationTime for oracleTime.


161         // @dev Intentionally using ercAmount for oraclePrice. Storing as price may lead to bugs in the match algos.


167         // @dev Allows caller to save gas since reading spot price costs ~16K

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L167:167

File: libraries/LibOrders.sol


29          // @dev in seconds


43              // @dev use the diff to get more time (2159), to prevent overflow at year 2106


138             // @dev: Only need to set this when placing incomingShort onto market


172         // @dev partial addOrder


188             // @dev (ID) is exiting, [ID] is inserted


237             // @dev: TAIL can't be prevId because it will always be last item in list


267             // @dev: TAIL can't be prevId because it will always be last item in list


294             // @dev (ID) is exiting, [ID] is inserted


332             // @dev mark as cancelled instead of deleting the order itself


419         // @dev not used to change state, just which methods to call


507                     // @dev Handles only matching one thing


508                     // @dev If does not get fully matched, b.matchedShortId == 0 and therefore not hit this block


511                     // @dev Handles moving forward only


518                     // @dev Handle going backward and forward


641             // @dev match price is based on the order that was already on orderbook


724             // @dev this happens at the end so fillErc isn't affected in previous calculations


760                     // @dev force hint to be within 0.5% of oraclePrice


769                         // @dev prevShortPrice >= oraclePrice


782         // @dev Update on match if order matches and price diff between order price and oracle > chainlink threshold (i.e. eth .5%)


790             // @dev handle .5% deviations in either directions


802         // @dev Possible for this function to never get called if updateOracleAndStartingShortViaThreshold() gets called often enough


846                 // @dev If hint was prev matched, assume that hint was close to HEAD and therefore is reasonable to use HEAD


899                 // @dev creating shortOrder automatically creates a closed shortRecord which also sets a shortRecordId


900                 // @dev cancelling an unmatched order needs to also handle/recycle the shortRecordId


905                     // @dev prevents leaving behind a partially filled SR is under minShortErc


906                     // @dev if the corresponding short is cancelled, then the partially filled SR's debt will == minShortErc


928                         // @dev update the eth refund amount


931                     // @dev virtually mint the increased debt


962             // @dev tithe is increased only if discount is greater than 1%


964                 // @dev bound the new tithe amount between 25% and 100%


967                 // @dev Vault.dethTitheMod is added onto Vault.dethTithePercent to get tithe amount


970                 // @dev dethTitheMod is only set when setTithe is called.


974                     // @dev change back to original tithe percent


981             // @dev exists because of ShortOrderFacet contract size

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L981:981

File: libraries/LibSRUtil.sol


89                          // @dev The resulting SR will not have PartialFill status after cancel


133             // @dev shortOrderId is already validated in mintNFT

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L133:133

File: libraries/UniswapOracleLibrary.sol


58              // @dev Returns the cumulative tick and liquidity as of each timestamp secondsAgo from the current block timestamp


69              // @dev Gets price using this formula: p(i) = 1.0001**i, where i is the tick

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L69:69

</details>

NC014 - Inconsistent spacing in comments:

Some lines use // x and some use //x. The instances below point out the usages that don't follow the majority, within each file.

File: facets/PrimaryLiquidationFacet.sol


92          //PRIVATE FUNCTIONS

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L92:92

File: libraries/LibOrders.sol


514                     //@handles moving backwards only.

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L514:514

NC015 - Not using the named return variables anywhere in the function is confusing:

Consider changing the return variable to be an unnamed one, since the variable is never assigned, nor is it returned by name

<details> <summary>Click to show 15 findings</summary>
File: facets/RedemptionFacet.sol


384             returns (uint88 redemptionFee)

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L384:384

File: libraries/LibBridgeRouter.sol


113         function withdrawalFeePct(uint256 bridgePointer, address rethBridge, address stethBridge) internal view returns (uint256 fee) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L113:113

File: libraries/LibOracle.sol


75          ) private view returns (uint256 _protocolPrice) {


131         function twapCircuitBreaker() private view returns (uint256 twapPriceInEth) {


156         function getTime(address asset) internal view returns (uint256 creationTime) {


162         function getPrice(address asset) internal view returns (uint80 oraclePrice) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L162:162

File: libraries/LibOrders.sol


30          function getOffsetTime() internal view returns (uint32 timeInSeconds) {


234             returns (int256 direction)


266         ) private view returns (int256 direction) {


409         ) internal view returns (int256 direction) {


420         function normalizeOrderType(O o) private pure returns (O newO) {


447         ) internal view returns (uint16 _hintId) {


830         ) internal view returns (uint16 hintId) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L830:830

File: libraries/LibSRUtil.sol


51              returns (bool isCancelled)


105             returns (bool recoveryViolation)

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L105:105

</details>

NC016 - Contracts should have full test coverage:

While 100% code coverage does not guarantee that there are no bugs, it often will catch easy-to-find bugs, and will ensure that there are fewer regressions when the code invariably has to be modified. Furthermore, in order to get full coverage, code authors will often have to re-organize their code so that it is more modular, so that each component can be tested separately, which reduces interdependencies between modules and layers, and makes for code that is easier to reason about and audit.

File: Various Files


None

NC017 - Large or complicated code bases should implement invariant tests:

Large code bases, or code with lots of inline-assembly, complicated math, or complicated interactions between multiple contracts, should implement invariant fuzzing tests. Invariant fuzzers such as Echidna require the test writer to come up with invariants which should not be violated under any circumstances, and the fuzzer tests various inputs and function calls to ensure that the invariants always hold. Even code with 100% code coverage can still have bugs due to the order of the operations a user performs, and invariant fuzzers, with properly and extensively-written invariants, can close this testing gap significantly.

File: Various Files


None

NC018 - Consider using block.number instead of block.timestamp:

block.timestamp is vulnerable to miner manipulation and creates a potential front-running vulnerability. Consider using block.number instead.

<details> <summary>Click to show 6 findings</summary>
File: libraries/LibOracle.sol


60                      if (roundID == 0 || price == 0 || timeStamp > block.timestamp) revert Errors.InvalidPrice();


76              bool invalidFetchData = roundId == 0 || timeStamp == 0 || timeStamp > block.timestamp || chainlinkPrice <= 0


77                  || block.timestamp > 2 hours + timeStamp;


125             bool invalidFetchData = roundId == 0 || timeStamp == 0 || timeStamp > block.timestamp || chainlinkPrice <= 0


126                 || baseRoundId == 0 || baseTimeStamp == 0 || baseTimeStamp > block.timestamp || baseChainlinkPrice <= 0;

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L126:126

File: libraries/LibOrders.sol


32              return uint32(block.timestamp - C.STARTING_TIME); // @dev(safe-cast)

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L32:32

</details>

NC019 - Consider bounding input array length:

The functions below take in an unbounded array, and make function calls for entries in the array. While the function will revert if it eventually runs out of gas, it may be a nicer user experience to require() that the length of the array is below some reasonable maximum, so that the user doesn't have to use up a full transaction's gas only to see that the transaction reverts.

File: facets/RedemptionFacet.sol


78              for (uint8 i = 0; i < proposalInput.length; i++) {
79                  p.shorter = proposalInput[i].shorter;
80                  p.shortId = proposalInput[i].shortId;
81                  p.shortOrderId = proposalInput[i].shortOrderId;
82                  // @dev Setting this above _onlyValidShortRecord to allow skipping
83                  STypes.ShortRecord storage currentSR = s.shortRecords[p.asset][p.shorter][p.shortId];
84      
85                  /// Evaluate proposed shortRecord
86      
87                  if (!validRedemptionSR(currentSR, msg.sender, p.shorter, minShortErc)) continue;
88      
89                  currentSR.updateErcDebt(p.asset);
90                  p.currentCR = currentSR.getCollateralRatio(p.oraclePrice);
91      
92                  // @dev Skip if proposal is not sorted correctly or if above redemption threshold
93                  if (p.previousCR > p.currentCR || p.currentCR >= C.MAX_REDEMPTION_CR) continue;
94      
95                  // @dev totalAmountProposed tracks the actual amount that can be redeemed. totalAmountProposed <= redemptionAmount
96                  if (p.totalAmountProposed + currentSR.ercDebt <= redemptionAmount) {
97                      p.amountProposed = currentSR.ercDebt;
98                  } else {
99                      p.amountProposed = redemptionAmount - p.totalAmountProposed;
100                     // @dev Exit when proposal would leave less than minShortErc, proxy for nearing end of slate
101                     if (currentSR.ercDebt - p.amountProposed < minShortErc) break;
102                 }
103     
104                 /// At this point, the shortRecord passes all checks and will be included in the slate
105     
106                 p.previousCR = p.currentCR;
107     
108                 // @dev Cancel attached shortOrder if below minShortErc, regardless of ercDebt in SR
109                 // @dev All verified SR have ercDebt >= minShortErc so CR does not change in cancelShort()
110                 STypes.Order storage shortOrder = s.shorts[asset][p.shortOrderId];
111                 if (currentSR.status == SR.PartialFill && shortOrder.ercAmount < minShortErc) {
112                     if (shortOrder.shortRecordId != p.shortId || shortOrder.addr != p.shorter) revert Errors.InvalidShortOrder();
113                     LibOrders.cancelShort(asset, p.shortOrderId);
114                 }
115     
116                 p.colRedeemed = p.oraclePrice.mulU88(p.amountProposed);
117                 if (p.colRedeemed > currentSR.collateral) {
118                     p.colRedeemed = currentSR.collateral;
119                 }
120     
121                 currentSR.collateral -= p.colRedeemed;
122                 currentSR.ercDebt -= p.amountProposed;
123     
124                 p.totalAmountProposed += p.amountProposed;
125                 p.totalColRedeemed += p.colRedeemed;
126     
127                 // @dev directly write the properties of MTypes.ProposalData into bytes
128                 // instead of usual abi.encode to save on extra zeros being written
129                 slate = bytes.concat(
130                     slate,
131                     bytes20(p.shorter),
132                     bytes1(p.shortId),
133                     bytes8(uint64(p.currentCR)),
134                     bytes11(p.amountProposed),
135                     bytes11(p.colRedeemed)
136                 );
137     
138                 LibSRUtil.disburseCollateral(p.asset, p.shorter, p.colRedeemed, currentSR.dethYieldRate, currentSR.updatedAt);
139                 p.redemptionCounter++;
140                 if (redemptionAmount - p.totalAmountProposed < minShortErc) break;
141             }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L78:141

File: libraries/LibOrders.sol


743                 for (uint256 i = 0; i < shortHintArray.length;) {
744                     shortHintId = shortHintArray[i];
745                     unchecked {
746                         ++i;
747                     }
748     
749                     STypes.Order storage short = s.shorts[asset][shortHintId];
750                     {
751                         O shortOrderType = short.orderType;
752                         if (shortOrderType == O.Cancelled || shortOrderType == O.Matched || shortOrderType == O.Uninitialized) {
753                             continue;
754                         }
755                     }
756     
757                     uint80 shortPrice = short.price;
758                     uint16 prevId = short.prevId;
759                     uint80 prevShortPrice = s.shorts[asset][prevId].price;
760                     // @dev force hint to be within 0.5% of oraclePrice
761                     bool startingShortWithinOracleRange = shortPrice <= oraclePrice.mul(1.005 ether) && prevShortPrice >= oraclePrice;
762                     bool isExactStartingShort = shortPrice >= oraclePrice && prevShortPrice < oraclePrice;
763                     bool allShortUnderOraclePrice = shortPrice < oraclePrice && short.nextId == C.TAIL;
764     
765                     if (isExactStartingShort) {
766                         Asset.startingShortId = shortHintId;
767                         return;
768                     } else if (startingShortWithinOracleRange) {
769                         // @dev prevShortPrice >= oraclePrice
770                         Asset.startingShortId = prevId;
771                         return;
772                     } else if (allShortUnderOraclePrice) {
773                         Asset.startingShortId = C.HEAD;
774                         return;
775                     }
776                 }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L743:776

NC020 - Variables should be named in mixedCase style:

As the Solidity Style Guide suggests: arguments, local variables and mutable state variables should be named in mixedCase style.

<details> <summary>Click to show 46 findings</summary>
File: facets/PrimaryLiquidationFacet.sol


210             STypes.VaultUser storage VaultUser = s.vaultUser[m.vault][msg.sender];


241             STypes.VaultUser storage TAPP = s.vaultUser[m.vault][address(this)];


170                 STypes.Asset storage Asset = s.asset[m.asset];


211             STypes.VaultUser storage TAPP = s.vaultUser[m.vault][address(this)];


165             STypes.VaultUser storage TAPP = s.vaultUser[m.vault][address(this)];

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L165:165

File: facets/RedemptionFacet.sol


207             STypes.VaultUser storage VaultUser = s.vaultUser[Asset.vault][msg.sender];


248             STypes.ShortRecord storage disputeSR = s.shortRecords[d.asset][disputeShorter][disputeShortId];


386             STypes.Asset storage Asset = s.asset[asset];


363             STypes.Asset storage Asset = s.asset[asset];


83                  STypes.ShortRecord storage currentSR = s.shortRecords[p.asset][p.shorter][p.shortId];


151             STypes.Asset storage Asset = s.asset[p.asset];


266                     STypes.ShortRecord storage currentSR = s.shortRecords[d.asset][currentProposal.shorter][currentProposal.shortId];


257             uint256 disputeCR = disputeSR.getCollateralRatio(redeemerAssetUser.oraclePrice);


255             STypes.Asset storage Asset = s.asset[d.asset];


394             uint256 redeemedDUSDFraction = ercDebtRedeemed.div(totalAssetErcDebt);

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L394:394

File: facets/ShortOrdersFacet.sol


41              uint16 shortOrderCR


44              STypes.Asset storage Asset = s.asset[asset];

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/ShortOrdersFacet.sol#L44:44

File: libraries/LibBridgeRouter.sol


23              STypes.VaultUser storage VaultUser = s.vaultUser[vault][msg.sender];


44              STypes.VaultUser storage VaultUser = s.vaultUser[vault][msg.sender];


154                 STypes.VaultUser storage VaultUserTo = s.vaultUser[vault][to];


151                 STypes.VaultUser storage VaultUserFrom = s.vaultUser[vault][from];


147             STypes.Asset storage Asset = s.asset[asset];


118             uint256 unitRethTWAP = OracleLibrary.estimateTWAP(1 ether, 30 minutes, VAULT.RETH_WETH, VAULT.RETH, C.WETH);


122             uint256 unitWstethTWAP = OracleLibrary.estimateTWAP(1 ether, 30 minutes, VAULT.WSTETH_WETH, VAULT.WSTETH, C.WETH);

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L122:122

File: libraries/LibBytes.sol


11          function readProposalData(address SSTORE2Pointer, uint8 slateLength) internal view returns (MTypes.ProposalData[] memory) {


24                  uint64 CR; // bytes8

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBytes.sol#L24:24

File: libraries/LibOracle.sol


36                          uint80 roundID,
37                          int256 price,
38                          /*uint256 startedAt*/
39                          ,
40                          uint256 timeStamp,


53                          uint80 roundID,
54                          int256 price,
55                          /*uint256 startedAt*/
56                          ,
57                          uint256 timeStamp,

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L53:57

File: libraries/LibOrders.sol


737             STypes.Asset storage Asset = s.asset[asset];


187             uint16 canceledID = orders[asset][C.HEAD].prevId;


891             STypes.Asset storage Asset = s.asset[asset];


909                         STypes.Vault storage Vault = s.vault[vault];


315             uint16 prevHEAD = orders[asset][C.HEAD].prevId;


958             STypes.Vault storage Vault = s.vault[vault];


324             uint16 prevHEAD,


812             STypes.Asset storage Asset = s.asset[asset];


291             uint16 prevHEAD = orders[asset][C.HEAD].prevId;


195                 uint16 prevCanceledID = orders[asset][canceledID].prevId;


670             STypes.Asset storage Asset = s.asset[asset];


672             STypes.Vault storage Vault = s.vault[vault];

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L672:672

File: libraries/LibSRUtil.sol


27              STypes.Asset storage Asset = s.asset[asset];


109             uint256 recoveryCR = LibAsset.recoveryCR(asset);


29              STypes.Vault storage Vault = s.vault[vault];


115                     uint256 assetCR = Asset.dethCollateral.div(oraclePrice.mul(Asset.ercDebt));


112                 STypes.Asset storage Asset = s.asset[asset];


102         function checkRecoveryModeViolation(address asset, uint256 shortRecordCR, uint256 oraclePrice)

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L102:102

</details>

NC021 - Function names should use lowerCamelCase:

According to the Solidity style guide function names should be in mixedCase (lowerCamelCase).

File: facets/RedemptionFacet.sol


31          function validRedemptionSR(STypes.ShortRecord storage shortRecord, address proposer, address shorter, uint256 minShortErc)

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L31:31

File: libraries/LibOrders.sol


35          function convertCR(uint16 cr) internal pure returns (uint256) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L35:35

File: libraries/UniswapOracleLibrary.sol


47          function estimateTWAP(uint128 amountIn, uint32 secondsAgo, address pool, address baseToken, address quoteToken)

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L47:47

NC022 - Consider adding a deny-list:

Doing so will significantly increase centralization, but will help to prevent hackers from using stolen tokens.

<details> <summary>Click to show 4 findings</summary>
File: facets/BridgeRouterFacet.sol


18      contract BridgeRouterFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L18:18

File: facets/PrimaryLiquidationFacet.sol


21      contract PrimaryLiquidationFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L21:21

File: facets/RedemptionFacet.sol


21      contract RedemptionFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L21:21

File: facets/ShortOrdersFacet.sol


18      contract ShortOrdersFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/ShortOrdersFacet.sol#L18:18

</details>

NC023 - Events are missing sender information:

When an action is triggered based on a user's action, not being able to filter based on who triggered the action makes event processing a lot more cumbersome. Including the msg.sender the events of these types of action will make events much more useful to end users. Include msg.sender in the event output.

File: facets/RedemptionFacet.sol


284                     emit Events.DisputeRedemptionAll(d.asset, redeemer);

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L284:284

NC024 - Zero as a function argument should have a descriptive meaning:

Consider using descriptive constants or an enum instead of passing zero directly on function calls, as that might be error-prone, to fully describe the caller's intention.

<details> <summary>Click to show 23 findings</summary>
File: facets/RedemptionFacet.sol


182                 m = uint256(3 ether).div(0.3 ether);


182                 m = uint256(3 ether).div(0.3 ether);


185                 m = uint256(1.5 ether).div(0.2 ether);


185                 m = uint256(1.5 ether).div(0.2 ether);


189                 m = uint256(0.75 ether).div(0.2 ether);


189                 m = uint256(0.75 ether).div(0.2 ether);


193                 m = uint256(0.417 ether).div(0.1 ether);


193                 m = uint256(0.417 ether).div(0.1 ether);


197                 m = uint256(C.ONE_THIRD.div(0.1 ether));


289                 uint256 penaltyPct = LibOrders.min(
290                     LibOrders.max(LibAsset.callerFeePct(d.asset), (currentProposal.CR - disputeCR).div(currentProposal.CR)), 0.33 ether
291                 );


396             newBaseRate = LibOrders.min(newBaseRate, 1 ether); // cap baseRate at a maximum of 100%


401             uint256 redemptionRate = LibOrders.min((Asset.baseRate + 0.005 ether), 1 ether);

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L401:401

File: facets/ShortOrdersFacet.sol


48              incomingShort.shortRecordId = LibShortRecord.createShortRecord(asset, msg.sender, SR.Closed, 0, 0, 0, 0, 0);

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/ShortOrdersFacet.sol#L48:48

File: libraries/LibBridgeRouter.sol


118             uint256 unitRethTWAP = OracleLibrary.estimateTWAP(1 ether, 30 minutes, VAULT.RETH_WETH, VAULT.RETH, C.WETH);


122             uint256 unitWstethTWAP = OracleLibrary.estimateTWAP(1 ether, 30 minutes, VAULT.WSTETH_WETH, VAULT.WSTETH, C.WETH);

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L122:122

File: libraries/LibOracle.sol


87                  try IDiamond(payable(address(this))).estimateWETHInUSDC(C.UNISWAP_WETH_BASE_AMT, 30 minutes) returns (uint256 twapPrice)


133             uint256 twapPrice = IDiamond(payable(address(this))).estimateWETHInUSDC(C.UNISWAP_WETH_BASE_AMT, 30 minutes);

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L133:133

File: libraries/LibOrders.sol


761                     bool startingShortWithinOracleRange = shortPrice <= oraclePrice.mul(1.005 ether) && prevShortPrice >= oraclePrice;


960             bool isDiscounted = savedPrice > price.mul(1.01 ether);


965                 uint256 discountPct = max(0.01 ether, min(((savedPrice - price).div(savedPrice)), 0.04 ether));


965                 uint256 discountPct = max(0.01 ether, min(((savedPrice - price).div(savedPrice)), 0.04 ether));


968                 Vault.dethTitheMod = (C.MAX_TITHE - Vault.dethTithePercent).mulU16(discountPct.div(0.04 ether));

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L968:968

File: libraries/UniswapOracleLibrary.sol


54              uint32[] memory secondsAgos = new uint32[](2);

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L54:54

</details>

NC025 - It is standard for all external and public functions to be override from an interface:

This is to ensure the whole API is extracted in an interface

<details> <summary>Click to show 12 findings</summary>
File: facets/BridgeRouterFacet.sol


40          function getDethTotal(uint256 vault) external view nonReentrantView returns (uint256) {
41              return vault.getDethTotal();
42          }


51          function getBridges(uint256 vault) external view returns (address[] memory) {
52              return s.vaultBridges[vault];
53          }


63          function deposit(address bridge, uint88 amount) external nonReentrant {
64              if (amount < C.MIN_DEPOSIT) revert Errors.UnderMinimumDeposit();
65      
66              (uint256 vault, uint256 bridgePointer) = _getVault(bridge);
67              // @dev amount after deposit might be less, if bridge takes a fee
68              uint88 dethAmount = uint88(IBridge(bridge).deposit(msg.sender, amount)); // @dev(safe-cast)
69      
70              vault.addDeth(bridgePointer, dethAmount);
71              maybeUpdateYield(vault, dethAmount);
72              emit Events.Deposit(bridge, msg.sender, dethAmount);
73          }


82          function depositEth(address bridge) external payable nonReentrant {
83              if (msg.value < C.MIN_DEPOSIT) revert Errors.UnderMinimumDeposit();
84      
85              (uint256 vault, uint256 bridgePointer) = _getVault(bridge);
86      
87              uint88 dethAmount = uint88(IBridge(bridge).depositEth{value: msg.value}()); // Assumes 1 ETH = 1 DETH
88              vault.addDeth(bridgePointer, dethAmount);
89              maybeUpdateYield(vault, dethAmount);
90              emit Events.DepositEth(bridge, msg.sender, dethAmount);
91          }


101         function withdraw(address bridge, uint88 dethAmount) external nonReentrant {
102             if (dethAmount == 0) revert Errors.ParameterIsZero();
103     
104             (uint256 vault, uint256 bridgePointer) = _getVault(bridge);
105     
106             uint88 fee;
107             if (vault == VAULT.ONE) {
108                 uint88 dethAssessable = vault.assessDeth(bridgePointer, dethAmount, rethBridge, stethBridge);
109                 if (dethAssessable > 0) {
110                     uint256 withdrawalFeePct = LibBridgeRouter.withdrawalFeePct(bridgePointer, rethBridge, stethBridge);
111                     if (withdrawalFeePct > 0) {
112                         fee = dethAssessable.mulU88(withdrawalFeePct);
113                         dethAmount -= fee;
114                         s.vaultUser[vault][address(this)].ethEscrowed += fee;
115                     }
116                 }
117             }
118     
119             uint88 ethAmount = _ethConversion(vault, dethAmount);
120             vault.removeDeth(dethAmount, fee);
121             IBridge(bridge).withdraw(msg.sender, ethAmount);
122             emit Events.Withdraw(bridge, msg.sender, dethAmount, fee);
123         }


133         function withdrawTapp(address bridge, uint88 dethAmount) external onlyDAO {
134             if (dethAmount == 0) revert Errors.ParameterIsZero();
135     
136             (uint256 vault,) = _getVault(bridge);
137             uint88 ethAmount = _ethConversion(vault, dethAmount);
138     
139             s.vaultUser[vault][address(this)].ethEscrowed -= dethAmount;
140             s.vault[vault].dethTotal -= dethAmount;
141     
142             IBridge(bridge).withdraw(msg.sender, ethAmount);
143             emit Events.WithdrawTapp(bridge, msg.sender, dethAmount);
144         }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L133:144

File: facets/PrimaryLiquidationFacet.sol


47          function liquidate(address asset, address shorter, uint8 id, uint16[] memory shortHintArray, uint16 shortOrderId)
48              external
49              isNotFrozen(asset)
50              nonReentrant
51              onlyValidShortRecord(asset, shorter, id)
52              returns (uint88, uint88)
53          {
54              if (msg.sender == shorter) revert Errors.CannotLiquidateSelf();
55              // @dev TAPP partially reimburses gas fees, capped at 10 to limit arbitrary high cost
56              if (shortHintArray.length > 10) revert Errors.TooManyHints();
57      
58              // @dev Ensures SR has enough ercDebt/collateral to make caller fee worthwhile
59              LibSRUtil.checkCancelShortOrder({
60                  asset: asset,
61                  initialStatus: s.shortRecords[asset][shorter][id].status,
62                  shortOrderId: shortOrderId,
63                  shortRecordId: id,
64                  shorter: shorter
65              });
66      
67              // @dev liquidate requires more up-to-date oraclePrice
68              LibOrders.updateOracleAndStartingShortViaTimeBidOnly(asset, shortHintArray);
69      
70              MTypes.PrimaryLiquidation memory m = _setLiquidationStruct(asset, shorter, id, shortOrderId);
71      
72              // @dev Can liquidate as long as CR is low enough
73              if (m.cRatio >= LibAsset.liquidationCR(m.asset)) {
74                  // If CR is too high, check for recovery mode and violation to continue liquidation
75                  if (!LibSRUtil.checkRecoveryModeViolation(m.asset, m.cRatio, m.oraclePrice)) revert Errors.SufficientCollateral();
76              }
77      
78              // revert if no asks, or price too high
79              _checklowestSell(m);
80      
81              _performForcedBid(m, shortHintArray);
82      
83              _liquidationFeeHandler(m);
84      
85              _fullorPartialLiquidation(m);
86      
87              emit Events.Liquidate(asset, shorter, id, msg.sender, m.ercDebtMatched);
88      
89              return (m.gasFee, m.ethFilled);
90          }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L47:90

File: facets/RedemptionFacet.sol


56          function proposeRedemption(
57              address asset,
58              MTypes.ProposalInput[] calldata proposalInput,
59              uint88 redemptionAmount,
60              uint88 maxRedemptionFee
61          ) external isNotFrozen(asset) nonReentrant {
62              if (proposalInput.length > type(uint8).max) revert Errors.TooManyProposals();
63              MTypes.ProposeRedemption memory p;
64              p.asset = asset;
65              STypes.AssetUser storage redeemerAssetUser = s.assetUser[p.asset][msg.sender];
66              uint256 minShortErc = LibAsset.minShortErc(p.asset);
67      
68              if (redemptionAmount < minShortErc) revert Errors.RedemptionUnderMinShortErc();
69      
70              if (redeemerAssetUser.ercEscrowed < redemptionAmount) revert Errors.InsufficientERCEscrowed();
71      
72              // @dev redeemerAssetUser.SSTORE2Pointer gets reset to address(0) after actual redemption
73              if (redeemerAssetUser.SSTORE2Pointer != address(0)) revert Errors.ExistingProposedRedemptions();
74      
75              p.oraclePrice = LibOracle.getPrice(p.asset);
76      
77              bytes memory slate;
78              for (uint8 i = 0; i < proposalInput.length; i++) {
79                  p.shorter = proposalInput[i].shorter;
80                  p.shortId = proposalInput[i].shortId;
81                  p.shortOrderId = proposalInput[i].shortOrderId;
82                  // @dev Setting this above _onlyValidShortRecord to allow skipping
83                  STypes.ShortRecord storage currentSR = s.shortRecords[p.asset][p.shorter][p.shortId];
84      
85                  /// Evaluate proposed shortRecord
86      
87                  if (!validRedemptionSR(currentSR, msg.sender, p.shorter, minShortErc)) continue;
88      
89                  currentSR.updateErcDebt(p.asset);
90                  p.currentCR = currentSR.getCollateralRatio(p.oraclePrice);
91      
92                  // @dev Skip if proposal is not sorted correctly or if above redemption threshold
93                  if (p.previousCR > p.currentCR || p.currentCR >= C.MAX_REDEMPTION_CR) continue;
94      
95                  // @dev totalAmountProposed tracks the actual amount that can be redeemed. totalAmountProposed <= redemptionAmount
96                  if (p.totalAmountProposed + currentSR.ercDebt <= redemptionAmount) {
97                      p.amountProposed = currentSR.ercDebt;
98                  } else {
99                      p.amountProposed = redemptionAmount - p.totalAmountProposed;
100                     // @dev Exit when proposal would leave less than minShortErc, proxy for nearing end of slate
101                     if (currentSR.ercDebt - p.amountProposed < minShortErc) break;
102                 }
103     
104                 /// At this point, the shortRecord passes all checks and will be included in the slate
105     
106                 p.previousCR = p.currentCR;
107     
108                 // @dev Cancel attached shortOrder if below minShortErc, regardless of ercDebt in SR
109                 // @dev All verified SR have ercDebt >= minShortErc so CR does not change in cancelShort()
110                 STypes.Order storage shortOrder = s.shorts[asset][p.shortOrderId];
111                 if (currentSR.status == SR.PartialFill && shortOrder.ercAmount < minShortErc) {
112                     if (shortOrder.shortRecordId != p.shortId || shortOrder.addr != p.shorter) revert Errors.InvalidShortOrder();
113                     LibOrders.cancelShort(asset, p.shortOrderId);
114                 }
115     
116                 p.colRedeemed = p.oraclePrice.mulU88(p.amountProposed);
117                 if (p.colRedeemed > currentSR.collateral) {
118                     p.colRedeemed = currentSR.collateral;
119                 }
120     
121                 currentSR.collateral -= p.colRedeemed;
122                 currentSR.ercDebt -= p.amountProposed;
123     
124                 p.totalAmountProposed += p.amountProposed;
125                 p.totalColRedeemed += p.colRedeemed;
126     
127                 // @dev directly write the properties of MTypes.ProposalData into bytes
128                 // instead of usual abi.encode to save on extra zeros being written
129                 slate = bytes.concat(
130                     slate,
131                     bytes20(p.shorter),
132                     bytes1(p.shortId),
133                     bytes8(uint64(p.currentCR)),
134                     bytes11(p.amountProposed),
135                     bytes11(p.colRedeemed)
136                 );
137     
138                 LibSRUtil.disburseCollateral(p.asset, p.shorter, p.colRedeemed, currentSR.dethYieldRate, currentSR.updatedAt);
139                 p.redemptionCounter++;
140                 if (redemptionAmount - p.totalAmountProposed < minShortErc) break;
141             }
142     
143             if (p.totalAmountProposed < minShortErc) revert Errors.RedemptionUnderMinShortErc();
144     
145             // @dev SSTORE2 the entire proposalData after validating proposalInput
146             redeemerAssetUser.SSTORE2Pointer = SSTORE2.write(slate);
147             redeemerAssetUser.slateLength = p.redemptionCounter;
148             redeemerAssetUser.oraclePrice = p.oraclePrice;
149             redeemerAssetUser.ercEscrowed -= p.totalAmountProposed;
150     
151             STypes.Asset storage Asset = s.asset[p.asset];
152             Asset.ercDebt -= p.totalAmountProposed;
153     
154             uint32 protocolTime = LibOrders.getOffsetTime();
155             redeemerAssetUser.timeProposed = protocolTime;
156             // @dev Calculate the dispute period
157             // @dev timeToDispute is immediate for shorts with CR <= 1.1x
158     
159             /*
160             +-------+------------+
161             | CR(X) |  Hours(Y)  |
162             +-------+------------+
163             | 1.1   |     0      |
164             | 1.2   |    .333    |
165             | 1.3   |    .75     |
166             | 1.5   |    1.5     |
167             | 1.7   |     3      |
168             | 2.0   |     6      |
169             +-------+------------+
170     
171             Creating fixed points and interpolating between points on the graph without using exponentials
172             Using simple y = mx + b formula
173             
174             where x = currentCR - previousCR
175             m = (y2-y1)/(x2-x1)
176             b = previous fixed point (Y)
177             */
178     
179             uint256 m;
180     
181             if (p.currentCR > 1.7 ether) {
182                 m = uint256(3 ether).div(0.3 ether);
183                 redeemerAssetUser.timeToDispute = protocolTime + uint32((m.mul(p.currentCR - 1.7 ether) + 3 ether) * 1 hours / 1 ether);
184             } else if (p.currentCR > 1.5 ether) {
185                 m = uint256(1.5 ether).div(0.2 ether);
186                 redeemerAssetUser.timeToDispute =
187                     protocolTime + uint32((m.mul(p.currentCR - 1.5 ether) + 1.5 ether) * 1 hours / 1 ether);
188             } else if (p.currentCR > 1.3 ether) {
189                 m = uint256(0.75 ether).div(0.2 ether);
190                 redeemerAssetUser.timeToDispute =
191                     protocolTime + uint32((m.mul(p.currentCR - 1.3 ether) + 0.75 ether) * 1 hours / 1 ether);
192             } else if (p.currentCR > 1.2 ether) {
193                 m = uint256(0.417 ether).div(0.1 ether);
194                 redeemerAssetUser.timeToDispute =
195                     protocolTime + uint32((m.mul(p.currentCR - 1.2 ether) + C.ONE_THIRD) * 1 hours / 1 ether);
196             } else if (p.currentCR > 1.1 ether) {
197                 m = uint256(C.ONE_THIRD.div(0.1 ether));
198                 redeemerAssetUser.timeToDispute = protocolTime + uint32(m.mul(p.currentCR - 1.1 ether) * 1 hours / 1 ether);
199             }
200     
201             redeemerAssetUser.oraclePrice = p.oraclePrice;
202             redeemerAssetUser.timeProposed = LibOrders.getOffsetTime();
203     
204             uint88 redemptionFee = calculateRedemptionFee(asset, p.totalColRedeemed, p.totalAmountProposed);
205             if (redemptionFee > maxRedemptionFee) revert Errors.RedemptionFeeTooHigh();
206     
207             STypes.VaultUser storage VaultUser = s.vaultUser[Asset.vault][msg.sender];
208             if (VaultUser.ethEscrowed < redemptionFee) revert Errors.InsufficientETHEscrowed();
209             VaultUser.ethEscrowed -= redemptionFee;
210             emit Events.ProposeRedemption(p.asset, msg.sender);
211         }


224         function disputeRedemption(address asset, address redeemer, uint8 incorrectIndex, address disputeShorter, uint8 disputeShortId)
225             external
226             isNotFrozen(asset)
227             nonReentrant
228         {
229             if (redeemer == msg.sender) revert Errors.CannotDisputeYourself();
230             MTypes.DisputeRedemption memory d;
231             d.asset = asset;
232             d.redeemer = redeemer;
233     
234             STypes.AssetUser storage redeemerAssetUser = s.assetUser[d.asset][d.redeemer];
235             if (redeemerAssetUser.SSTORE2Pointer == address(0)) revert Errors.InvalidRedemption();
236     
237             if (LibOrders.getOffsetTime() >= redeemerAssetUser.timeToDispute) revert Errors.TimeToDisputeHasElapsed();
238     
239             MTypes.ProposalData[] memory decodedProposalData =
240                 LibBytes.readProposalData(redeemerAssetUser.SSTORE2Pointer, redeemerAssetUser.slateLength);
241     
242             for (uint256 i = 0; i < decodedProposalData.length; i++) {
243                 if (decodedProposalData[i].shorter == disputeShorter && decodedProposalData[i].shortId == disputeShortId) {
244                     revert Errors.CannotDisputeWithRedeemerProposal();
245                 }
246             }
247     
248             STypes.ShortRecord storage disputeSR = s.shortRecords[d.asset][disputeShorter][disputeShortId];
249             // Match continue (skip) conditions in proposeRedemption()
250             uint256 minShortErc = LibAsset.minShortErc(d.asset);
251             if (!validRedemptionSR(disputeSR, d.redeemer, disputeShorter, minShortErc)) revert Errors.InvalidRedemption();
252     
253             MTypes.ProposalData memory incorrectProposal = decodedProposalData[incorrectIndex];
254             MTypes.ProposalData memory currentProposal;
255             STypes.Asset storage Asset = s.asset[d.asset];
256     
257             uint256 disputeCR = disputeSR.getCollateralRatio(redeemerAssetUser.oraclePrice);
258     
259             if (disputeCR < incorrectProposal.CR && disputeSR.updatedAt + C.DISPUTE_REDEMPTION_BUFFER <= redeemerAssetUser.timeProposed)
260             {
261                 // @dev All proposals from the incorrectIndex onward will be removed
262                 // @dev Thus the proposer can only redeem a portion of their original slate
263                 for (uint256 i = incorrectIndex; i < decodedProposalData.length; i++) {
264                     currentProposal = decodedProposalData[i];
265     
266                     STypes.ShortRecord storage currentSR = s.shortRecords[d.asset][currentProposal.shorter][currentProposal.shortId];
267                     currentSR.collateral += currentProposal.colRedeemed;
268                     currentSR.ercDebt += currentProposal.ercDebtRedeemed;
269     
270                     d.incorrectCollateral += currentProposal.colRedeemed;
271                     d.incorrectErcDebt += currentProposal.ercDebtRedeemed;
272                 }
273     
274                 s.vault[Asset.vault].dethCollateral += d.incorrectCollateral;
275                 Asset.dethCollateral += d.incorrectCollateral;
276                 Asset.ercDebt += d.incorrectErcDebt;
277     
278                 // @dev Update the redeemer's SSTORE2Pointer
279                 if (incorrectIndex > 0) {
280                     redeemerAssetUser.slateLength = incorrectIndex;
281                 } else {
282                     // @dev this implies everything in the redeemer's proposal was incorrect
283                     delete redeemerAssetUser.SSTORE2Pointer;
284                     emit Events.DisputeRedemptionAll(d.asset, redeemer);
285                 }
286     
287                 // @dev Penalty is based on the proposal with highest CR (decodedProposalData is sorted)
288                 // @dev PenaltyPct is bound between CallerFeePct and 33% to prevent exploiting primaryLiquidation fees
289                 uint256 penaltyPct = LibOrders.min(
290                     LibOrders.max(LibAsset.callerFeePct(d.asset), (currentProposal.CR - disputeCR).div(currentProposal.CR)), 0.33 ether
291                 );
292     
293                 uint88 penaltyAmt = d.incorrectErcDebt.mulU88(penaltyPct);
294     
295                 // @dev Give redeemer back ercEscrowed that is no longer used to redeem (penalty applied)
296                 redeemerAssetUser.ercEscrowed += (d.incorrectErcDebt - penaltyAmt);
297                 s.assetUser[d.asset][msg.sender].ercEscrowed += penaltyAmt;
298             } else {
299                 revert Errors.InvalidRedemptionDispute();
300             }
301         }


310         function claimRedemption(address asset) external isNotFrozen(asset) nonReentrant {
311             uint256 vault = s.asset[asset].vault;
312             STypes.AssetUser storage redeemerAssetUser = s.assetUser[asset][msg.sender];
313             STypes.VaultUser storage redeemerVaultUser = s.vaultUser[vault][msg.sender];
314             if (redeemerAssetUser.SSTORE2Pointer == address(0)) revert Errors.InvalidRedemption();
315             if (LibOrders.getOffsetTime() < redeemerAssetUser.timeToDispute) revert Errors.TimeToDisputeHasNotElapsed();
316     
317             MTypes.ProposalData[] memory decodedProposalData =
318                 LibBytes.readProposalData(redeemerAssetUser.SSTORE2Pointer, redeemerAssetUser.slateLength);
319     
320             uint88 totalColRedeemed;
321             for (uint256 i = 0; i < decodedProposalData.length; i++) {
322                 MTypes.ProposalData memory currentProposal = decodedProposalData[i];
323                 totalColRedeemed += currentProposal.colRedeemed;
324                 _claimRemainingCollateral({
325                     asset: asset,
326                     vault: vault,
327                     shorter: currentProposal.shorter,
328                     shortId: currentProposal.shortId
329                 });
330             }
331             redeemerVaultUser.ethEscrowed += totalColRedeemed;
332             delete redeemerAssetUser.SSTORE2Pointer;
333             emit Events.ClaimRedemption(asset, msg.sender);
334         }


347         function claimRemainingCollateral(address asset, address redeemer, uint8 claimIndex, uint8 id)
348             external
349             isNotFrozen(asset)
350             nonReentrant
351         {
352             STypes.AssetUser storage redeemerAssetUser = s.assetUser[asset][redeemer];
353             if (redeemerAssetUser.SSTORE2Pointer == address(0)) revert Errors.InvalidRedemption();
354             if (redeemerAssetUser.timeToDispute > LibOrders.getOffsetTime()) revert Errors.TimeToDisputeHasNotElapsed();
355     
356             // @dev Only need to read up to the position of the SR to be claimed
357             MTypes.ProposalData[] memory decodedProposalData =
358                 LibBytes.readProposalData(redeemerAssetUser.SSTORE2Pointer, claimIndex + 1);
359             MTypes.ProposalData memory claimProposal = decodedProposalData[claimIndex];
360     
361             if (claimProposal.shorter != msg.sender && claimProposal.shortId != id) revert Errors.CanOnlyClaimYourShort();
362     
363             STypes.Asset storage Asset = s.asset[asset];
364             _claimRemainingCollateral({asset: asset, vault: Asset.vault, shorter: msg.sender, shortId: id});
365         }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L347:365

File: facets/ShortOrdersFacet.sol


35          function createLimitShort(
36              address asset,
37              uint80 price,
38              uint88 ercAmount,
39              MTypes.OrderHint[] memory orderHintArray,
40              uint16[] memory shortHintArray,
41              uint16 shortOrderCR
42          ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant {
43              MTypes.CreateLimitShortParam memory p;
44              STypes.Asset storage Asset = s.asset[asset];
45              STypes.Order memory incomingShort;
46      
47              // @dev create "empty" SR. Fail early.
48              incomingShort.shortRecordId = LibShortRecord.createShortRecord(asset, msg.sender, SR.Closed, 0, 0, 0, 0, 0);
49      
50              uint256 cr = LibOrders.convertCR(shortOrderCR);
51              if ((shortOrderCR + C.BID_CR) < Asset.initialCR || cr >= C.CRATIO_MAX_INITIAL) {
52                  revert Errors.InvalidCR();
53              }
54      
55              // @dev minShortErc needs to be modified (bigger) when cr < 1
56              p.minShortErc = cr < 1 ether ? LibAsset.minShortErc(asset).mul(1 ether + cr.inv()) : LibAsset.minShortErc(asset);
57              p.eth = price.mul(ercAmount);
58              p.minAskEth = LibAsset.minAskEth(asset);
59              if (ercAmount < p.minShortErc || p.eth < p.minAskEth) revert Errors.OrderUnderMinimumSize();
60      
61              // For a short, need enough collateral to cover minting ERC (calculated using initialCR)
62              if (s.vaultUser[Asset.vault][msg.sender].ethEscrowed < p.eth.mul(cr)) revert Errors.InsufficientETHEscrowed();
63      
64              incomingShort.addr = msg.sender;
65              incomingShort.price = price;
66              incomingShort.ercAmount = ercAmount;
67              incomingShort.id = Asset.orderIdCounter;
68              incomingShort.orderType = O.LimitShort;
69              incomingShort.creationTime = LibOrders.getOffsetTime();
70              incomingShort.shortOrderCR = shortOrderCR; // 170 -> 1.70x
71      
72              p.startingId = s.bids[asset][C.HEAD].nextId;
73              STypes.Order storage highestBid = s.bids[asset][p.startingId];
74              // @dev if match and match price is gt .5% to saved oracle in either direction, update startingShortId
75              if (highestBid.price >= incomingShort.price && highestBid.orderType == O.LimitBid) {
76                  LibOrders.updateOracleAndStartingShortViaThreshold(asset, LibOracle.getPrice(asset), incomingShort, shortHintArray);
77              }
78      
79              p.oraclePrice = LibOracle.getSavedOrSpotOraclePrice(asset);
80              if (LibSRUtil.checkRecoveryModeViolation(asset, cr, p.oraclePrice)) {
81                  revert Errors.BelowRecoveryModeCR();
82              }
83      
84              // @dev reading spot oracle price
85              if (incomingShort.price < p.oraclePrice) {
86                  LibOrders.addShort(asset, incomingShort, orderHintArray);
87              } else {
88                  LibOrders.sellMatchAlgo(asset, incomingShort, orderHintArray, p.minAskEth);
89              }
90          }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/ShortOrdersFacet.sol#L35:90

</details>

NC026 - Consider adding formal verification proofs:

Consider using formal verification to mathematically prove that your code does what is intended, and does not have any edge cases with unexpected behavior. The solidity compiler itself has this functionality built in based off of SMTChecker.

File: Various Files


None

NC027 - Setters should prevent re-setting of the same value:

This especially problematic when the setter also emits the same value, which may be confusing to offline parsers.

File: facets/PrimaryLiquidationFacet.sol


122         function _setLiquidationStruct(address asset, address shorter, uint8 id, uint16 shortOrderId)
123             private
124             returns (MTypes.PrimaryLiquidation memory)
125         {
126             LibShortRecord.updateErcDebt(asset, shorter, id);
127             {
128                 MTypes.PrimaryLiquidation memory m;
129                 m.asset = asset;
130                 m.short = s.shortRecords[asset][shorter][id];
131                 m.shortOrderId = shortOrderId;
132                 m.vault = s.asset[asset].vault;
133                 m.shorter = shorter;
134                 m.penaltyCR = LibAsset.penaltyCR(asset);
135                 m.oraclePrice = LibOracle.getPrice(asset);
136                 m.cRatio = m.short.getCollateralRatio(m.oraclePrice);
137                 m.forcedBidPriceBuffer = LibAsset.forcedBidPriceBuffer(asset);
138                 m.callerFeePct = LibAsset.callerFeePct(m.asset);
139                 m.tappFeePct = LibAsset.tappFeePct(m.asset);
140                 m.ethDebt = m.short.ercDebt.mul(m.oraclePrice).mul(m.forcedBidPriceBuffer).mul(1 ether + m.tappFeePct + m.callerFeePct); // ethDebt accounts for forcedBidPriceBuffer and potential fees
141                 return m;
142             }
143         }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L122:143

NC028 - Consider splitting long calculations:

The longer a string of operations is, the harder it is to understand it. Consider splitting the full calculation into more steps, with more descriptive temporary variable names, and add extensive comments.

<details> <summary>Click to show 6 findings</summary>
File: facets/RedemptionFacet.sol


186                 redeemerAssetUser.timeToDispute =
187                     protocolTime + uint32((m.mul(p.currentCR - 1.5 ether) + 1.5 ether) * 1 hours / 1 ether);


190                 redeemerAssetUser.timeToDispute =
191                     protocolTime + uint32((m.mul(p.currentCR - 1.3 ether) + 0.75 ether) * 1 hours / 1 ether);


183                 redeemerAssetUser.timeToDispute = protocolTime + uint32((m.mul(p.currentCR - 1.7 ether) + 3 ether) * 1 hours / 1 ether);


194                 redeemerAssetUser.timeToDispute =
195                     protocolTime + uint32((m.mul(p.currentCR - 1.2 ether) + C.ONE_THIRD) * 1 hours / 1 ether);

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L194:195

File: libraries/LibOracle.sol


125             bool invalidFetchData = roundId == 0 || timeStamp == 0 || timeStamp > block.timestamp || chainlinkPrice <= 0
126                 || baseRoundId == 0 || baseTimeStamp == 0 || baseTimeStamp > block.timestamp || baseChainlinkPrice <= 0;


76              bool invalidFetchData = roundId == 0 || timeStamp == 0 || timeStamp > block.timestamp || chainlinkPrice <= 0
77                  || block.timestamp > 2 hours + timeStamp;

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L76:77

</details>

NC029 - High cyclomatic complexity:

Consider breaking down these blocks into more manageable units, by splitting things into utility functions, by reducing nesting, and by using early returns.

<details> <summary>Click to show 9 findings</summary>
File: facets/RedemptionFacet.sol


224         function disputeRedemption(address asset, address redeemer, uint8 incorrectIndex, address disputeShorter, uint8 disputeShortId)
225             external
226             isNotFrozen(asset)
227             nonReentrant
228         {
229             if (redeemer == msg.sender) revert Errors.CannotDisputeYourself();
230             MTypes.DisputeRedemption memory d;
231             d.asset = asset;
232             d.redeemer = redeemer;
233     
234             STypes.AssetUser storage redeemerAssetUser = s.assetUser[d.asset][d.redeemer];
235             if (redeemerAssetUser.SSTORE2Pointer == address(0)) revert Errors.InvalidRedemption();
236     
237             if (LibOrders.getOffsetTime() >= redeemerAssetUser.timeToDispute) revert Errors.TimeToDisputeHasElapsed();
238     
239             MTypes.ProposalData[] memory decodedProposalData =
240                 LibBytes.readProposalData(redeemerAssetUser.SSTORE2Pointer, redeemerAssetUser.slateLength);
241     
242             for (uint256 i = 0; i < decodedProposalData.length; i++) {
243                 if (decodedProposalData[i].shorter == disputeShorter && decodedProposalData[i].shortId == disputeShortId) {
244                     revert Errors.CannotDisputeWithRedeemerProposal();
245                 }
246             }
247     
248             STypes.ShortRecord storage disputeSR = s.shortRecords[d.asset][disputeShorter][disputeShortId];
249             // Match continue (skip) conditions in proposeRedemption()
250             uint256 minShortErc = LibAsset.minShortErc(d.asset);
251             if (!validRedemptionSR(disputeSR, d.redeemer, disputeShorter, minShortErc)) revert Errors.InvalidRedemption();
252     
253             MTypes.ProposalData memory incorrectProposal = decodedProposalData[incorrectIndex];
254             MTypes.ProposalData memory currentProposal;
255             STypes.Asset storage Asset = s.asset[d.asset];
256     
257             uint256 disputeCR = disputeSR.getCollateralRatio(redeemerAssetUser.oraclePrice);
258     
259             if (disputeCR < incorrectProposal.CR && disputeSR.updatedAt + C.DISPUTE_REDEMPTION_BUFFER <= redeemerAssetUser.timeProposed)
260             {
261                 // @dev All proposals from the incorrectIndex onward will be removed
262                 // @dev Thus the proposer can only redeem a portion of their original slate
263                 for (uint256 i = incorrectIndex; i < decodedProposalData.length; i++) {
264                     currentProposal = decodedProposalData[i];
265     
266                     STypes.ShortRecord storage currentSR = s.shortRecords[d.asset][currentProposal.shorter][currentProposal.shortId];
267                     currentSR.collateral += currentProposal.colRedeemed;
268                     currentSR.ercDebt += currentProposal.ercDebtRedeemed;
269     
270                     d.incorrectCollateral += currentProposal.colRedeemed;
271                     d.incorrectErcDebt += currentProposal.ercDebtRedeemed;
272                 }
273     
274                 s.vault[Asset.vault].dethCollateral += d.incorrectCollateral;
275                 Asset.dethCollateral += d.incorrectCollateral;
276                 Asset.ercDebt += d.incorrectErcDebt;
277     
278                 // @dev Update the redeemer's SSTORE2Pointer
279                 if (incorrectIndex > 0) {
280                     redeemerAssetUser.slateLength = incorrectIndex;
281                 } else {
282                     // @dev this implies everything in the redeemer's proposal was incorrect
283                     delete redeemerAssetUser.SSTORE2Pointer;
284                     emit Events.DisputeRedemptionAll(d.asset, redeemer);
285                 }
286     
287                 // @dev Penalty is based on the proposal with highest CR (decodedProposalData is sorted)
288                 // @dev PenaltyPct is bound between CallerFeePct and 33% to prevent exploiting primaryLiquidation fees
289                 uint256 penaltyPct = LibOrders.min(
290                     LibOrders.max(LibAsset.callerFeePct(d.asset), (currentProposal.CR - disputeCR).div(currentProposal.CR)), 0.33 ether
291                 );
292     
293                 uint88 penaltyAmt = d.incorrectErcDebt.mulU88(penaltyPct);
294     
295                 // @dev Give redeemer back ercEscrowed that is no longer used to redeem (penalty applied)
296                 redeemerAssetUser.ercEscrowed += (d.incorrectErcDebt - penaltyAmt);
297                 s.assetUser[d.asset][msg.sender].ercEscrowed += penaltyAmt;
298             } else {
299                 revert Errors.InvalidRedemptionDispute();
300             }
301         }


56          function proposeRedemption(
57              address asset,
58              MTypes.ProposalInput[] calldata proposalInput,
59              uint88 redemptionAmount,
60              uint88 maxRedemptionFee
61          ) external isNotFrozen(asset) nonReentrant {
62              if (proposalInput.length > type(uint8).max) revert Errors.TooManyProposals();
63              MTypes.ProposeRedemption memory p;
64              p.asset = asset;
65              STypes.AssetUser storage redeemerAssetUser = s.assetUser[p.asset][msg.sender];
66              uint256 minShortErc = LibAsset.minShortErc(p.asset);
67      
68              if (redemptionAmount < minShortErc) revert Errors.RedemptionUnderMinShortErc();
69      
70              if (redeemerAssetUser.ercEscrowed < redemptionAmount) revert Errors.InsufficientERCEscrowed();
71      
72              // @dev redeemerAssetUser.SSTORE2Pointer gets reset to address(0) after actual redemption
73              if (redeemerAssetUser.SSTORE2Pointer != address(0)) revert Errors.ExistingProposedRedemptions();
74      
75              p.oraclePrice = LibOracle.getPrice(p.asset);
76      
77              bytes memory slate;
78              for (uint8 i = 0; i < proposalInput.length; i++) {
79                  p.shorter = proposalInput[i].shorter;
80                  p.shortId = proposalInput[i].shortId;
81                  p.shortOrderId = proposalInput[i].shortOrderId;
82                  // @dev Setting this above _onlyValidShortRecord to allow skipping
83                  STypes.ShortRecord storage currentSR = s.shortRecords[p.asset][p.shorter][p.shortId];
84      
85                  /// Evaluate proposed shortRecord
86      
87                  if (!validRedemptionSR(currentSR, msg.sender, p.shorter, minShortErc)) continue;
88      
89                  currentSR.updateErcDebt(p.asset);
90                  p.currentCR = currentSR.getCollateralRatio(p.oraclePrice);
91      
92                  // @dev Skip if proposal is not sorted correctly or if above redemption threshold
93                  if (p.previousCR > p.currentCR || p.currentCR >= C.MAX_REDEMPTION_CR) continue;
94      
95                  // @dev totalAmountProposed tracks the actual amount that can be redeemed. totalAmountProposed <= redemptionAmount
96                  if (p.totalAmountProposed + currentSR.ercDebt <= redemptionAmount) {
97                      p.amountProposed = currentSR.ercDebt;
98                  } else {
99                      p.amountProposed = redemptionAmount - p.totalAmountProposed;
100                     // @dev Exit when proposal would leave less than minShortErc, proxy for nearing end of slate
101                     if (currentSR.ercDebt - p.amountProposed < minShortErc) break;
102                 }
103     
104                 /// At this point, the shortRecord passes all checks and will be included in the slate
105     
106                 p.previousCR = p.currentCR;
107     
108                 // @dev Cancel attached shortOrder if below minShortErc, regardless of ercDebt in SR
109                 // @dev All verified SR have ercDebt >= minShortErc so CR does not change in cancelShort()
110                 STypes.Order storage shortOrder = s.shorts[asset][p.shortOrderId];
111                 if (currentSR.status == SR.PartialFill && shortOrder.ercAmount < minShortErc) {
112                     if (shortOrder.shortRecordId != p.shortId || shortOrder.addr != p.shorter) revert Errors.InvalidShortOrder();
113                     LibOrders.cancelShort(asset, p.shortOrderId);
114                 }
115     
116                 p.colRedeemed = p.oraclePrice.mulU88(p.amountProposed);
117                 if (p.colRedeemed > currentSR.collateral) {
118                     p.colRedeemed = currentSR.collateral;
119                 }
120     
121                 currentSR.collateral -= p.colRedeemed;
122                 currentSR.ercDebt -= p.amountProposed;
123     
124                 p.totalAmountProposed += p.amountProposed;
125                 p.totalColRedeemed += p.colRedeemed;
126     
127                 // @dev directly write the properties of MTypes.ProposalData into bytes
128                 // instead of usual abi.encode to save on extra zeros being written
129                 slate = bytes.concat(
130                     slate,
131                     bytes20(p.shorter),
132                     bytes1(p.shortId),
133                     bytes8(uint64(p.currentCR)),
134                     bytes11(p.amountProposed),
135                     bytes11(p.colRedeemed)
136                 );
137     
138                 LibSRUtil.disburseCollateral(p.asset, p.shorter, p.colRedeemed, currentSR.dethYieldRate, currentSR.updatedAt);
139                 p.redemptionCounter++;
140                 if (redemptionAmount - p.totalAmountProposed < minShortErc) break;
141             }
142     
143             if (p.totalAmountProposed < minShortErc) revert Errors.RedemptionUnderMinShortErc();
144     
145             // @dev SSTORE2 the entire proposalData after validating proposalInput
146             redeemerAssetUser.SSTORE2Pointer = SSTORE2.write(slate);
147             redeemerAssetUser.slateLength = p.redemptionCounter;
148             redeemerAssetUser.oraclePrice = p.oraclePrice;
149             redeemerAssetUser.ercEscrowed -= p.totalAmountProposed;
150     
151             STypes.Asset storage Asset = s.asset[p.asset];
152             Asset.ercDebt -= p.totalAmountProposed;
153     
154             uint32 protocolTime = LibOrders.getOffsetTime();
155             redeemerAssetUser.timeProposed = protocolTime;
156             // @dev Calculate the dispute period
157             // @dev timeToDispute is immediate for shorts with CR <= 1.1x
158     
159             /*
160             +-------+------------+
161             | CR(X) |  Hours(Y)  |
162             +-------+------------+
163             | 1.1   |     0      |
164             | 1.2   |    .333    |
165             | 1.3   |    .75     |
166             | 1.5   |    1.5     |
167             | 1.7   |     3      |
168             | 2.0   |     6      |
169             +-------+------------+
170     
171             Creating fixed points and interpolating between points on the graph without using exponentials
172             Using simple y = mx + b formula
173             
174             where x = currentCR - previousCR
175             m = (y2-y1)/(x2-x1)
176             b = previous fixed point (Y)
177             */
178     
179             uint256 m;
180     
181             if (p.currentCR > 1.7 ether) {
182                 m = uint256(3 ether).div(0.3 ether);
183                 redeemerAssetUser.timeToDispute = protocolTime + uint32((m.mul(p.currentCR - 1.7 ether) + 3 ether) * 1 hours / 1 ether);
184             } else if (p.currentCR > 1.5 ether) {
185                 m = uint256(1.5 ether).div(0.2 ether);
186                 redeemerAssetUser.timeToDispute =
187                     protocolTime + uint32((m.mul(p.currentCR - 1.5 ether) + 1.5 ether) * 1 hours / 1 ether);
188             } else if (p.currentCR > 1.3 ether) {
189                 m = uint256(0.75 ether).div(0.2 ether);
190                 redeemerAssetUser.timeToDispute =
191                     protocolTime + uint32((m.mul(p.currentCR - 1.3 ether) + 0.75 ether) * 1 hours / 1 ether);
192             } else if (p.currentCR > 1.2 ether) {
193                 m = uint256(0.417 ether).div(0.1 ether);
194                 redeemerAssetUser.timeToDispute =
195                     protocolTime + uint32((m.mul(p.currentCR - 1.2 ether) + C.ONE_THIRD) * 1 hours / 1 ether);
196             } else if (p.currentCR > 1.1 ether) {
197                 m = uint256(C.ONE_THIRD.div(0.1 ether));
198                 redeemerAssetUser.timeToDispute = protocolTime + uint32(m.mul(p.currentCR - 1.1 ether) * 1 hours / 1 ether);
199             }
200     
201             redeemerAssetUser.oraclePrice = p.oraclePrice;
202             redeemerAssetUser.timeProposed = LibOrders.getOffsetTime();
203     
204             uint88 redemptionFee = calculateRedemptionFee(asset, p.totalColRedeemed, p.totalAmountProposed);
205             if (redemptionFee > maxRedemptionFee) revert Errors.RedemptionFeeTooHigh();
206     
207             STypes.VaultUser storage VaultUser = s.vaultUser[Asset.vault][msg.sender];
208             if (VaultUser.ethEscrowed < redemptionFee) revert Errors.InsufficientETHEscrowed();
209             VaultUser.ethEscrowed -= redemptionFee;
210             emit Events.ProposeRedemption(p.asset, msg.sender);
211         }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L56:211

File: facets/ShortOrdersFacet.sol


35          function createLimitShort(
36              address asset,
37              uint80 price,
38              uint88 ercAmount,
39              MTypes.OrderHint[] memory orderHintArray,
40              uint16[] memory shortHintArray,
41              uint16 shortOrderCR
42          ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant {
43              MTypes.CreateLimitShortParam memory p;
44              STypes.Asset storage Asset = s.asset[asset];
45              STypes.Order memory incomingShort;
46      
47              // @dev create "empty" SR. Fail early.
48              incomingShort.shortRecordId = LibShortRecord.createShortRecord(asset, msg.sender, SR.Closed, 0, 0, 0, 0, 0);
49      
50              uint256 cr = LibOrders.convertCR(shortOrderCR);
51              if ((shortOrderCR + C.BID_CR) < Asset.initialCR || cr >= C.CRATIO_MAX_INITIAL) {
52                  revert Errors.InvalidCR();
53              }
54      
55              // @dev minShortErc needs to be modified (bigger) when cr < 1
56              p.minShortErc = cr < 1 ether ? LibAsset.minShortErc(asset).mul(1 ether + cr.inv()) : LibAsset.minShortErc(asset);
57              p.eth = price.mul(ercAmount);
58              p.minAskEth = LibAsset.minAskEth(asset);
59              if (ercAmount < p.minShortErc || p.eth < p.minAskEth) revert Errors.OrderUnderMinimumSize();
60      
61              // For a short, need enough collateral to cover minting ERC (calculated using initialCR)
62              if (s.vaultUser[Asset.vault][msg.sender].ethEscrowed < p.eth.mul(cr)) revert Errors.InsufficientETHEscrowed();
63      
64              incomingShort.addr = msg.sender;
65              incomingShort.price = price;
66              incomingShort.ercAmount = ercAmount;
67              incomingShort.id = Asset.orderIdCounter;
68              incomingShort.orderType = O.LimitShort;
69              incomingShort.creationTime = LibOrders.getOffsetTime();
70              incomingShort.shortOrderCR = shortOrderCR; // 170 -> 1.70x
71      
72              p.startingId = s.bids[asset][C.HEAD].nextId;
73              STypes.Order storage highestBid = s.bids[asset][p.startingId];
74              // @dev if match and match price is gt .5% to saved oracle in either direction, update startingShortId
75              if (highestBid.price >= incomingShort.price && highestBid.orderType == O.LimitBid) {
76                  LibOrders.updateOracleAndStartingShortViaThreshold(asset, LibOracle.getPrice(asset), incomingShort, shortHintArray);
77              }
78      
79              p.oraclePrice = LibOracle.getSavedOrSpotOraclePrice(asset);
80              if (LibSRUtil.checkRecoveryModeViolation(asset, cr, p.oraclePrice)) {
81                  revert Errors.BelowRecoveryModeCR();
82              }
83      
84              // @dev reading spot oracle price
85              if (incomingShort.price < p.oraclePrice) {
86                  LibOrders.addShort(asset, incomingShort, orderHintArray);
87              } else {
88                  LibOrders.sellMatchAlgo(asset, incomingShort, orderHintArray, p.minAskEth);
89              }
90          }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/ShortOrdersFacet.sol#L35:90

File: libraries/LibBridgeRouter.sol


39          function assessDeth(uint256 vault, uint256 bridgePointer, uint88 amount, address rethBridge, address stethBridge)
40              internal
41              returns (uint88)
42          {
43              AppStorage storage s = appStorage();
44              STypes.VaultUser storage VaultUser = s.vaultUser[vault][msg.sender];
45      
46              uint88 creditReth;
47              uint88 creditSteth;
48              if (bridgePointer == VAULT.BRIDGE_RETH) {
49                  // Withdraw RETH
50                  creditReth = VaultUser.bridgeCreditReth;
51                  if (creditReth >= amount) {
52                      VaultUser.bridgeCreditReth -= amount;
53                      return 0;
54                  }
55      
56                  VaultUser.bridgeCreditReth = 0;
57                  amount -= creditReth;
58                  creditSteth = VaultUser.bridgeCreditSteth;
59                  if (creditSteth < C.ROUNDING_ZERO) {
60                      // Valid withdraw when no STETH credits
61                      return amount;
62                  } else {
63                      if (IBridge(stethBridge).getDethValue() < C.ROUNDING_ZERO) {
64                          // Can withdraw RETH using STETH credit when STETH bridge is empty
65                          if (creditSteth >= amount) {
66                              VaultUser.bridgeCreditSteth -= amount;
67                              return 0;
68                          } else {
69                              VaultUser.bridgeCreditSteth = 0;
70                              return amount - creditSteth;
71                          }
72                      } else {
73                          // Must use available bridge credits on withdrawal
74                          // @dev Prevents abusing bridge for arbitrage
75                          revert Errors.MustUseExistingBridgeCredit();
76                      }
77                  }
78              } else {
79                  // Withdraw STETH
80                  creditSteth = VaultUser.bridgeCreditSteth;
81                  if (creditSteth >= amount) {
82                      VaultUser.bridgeCreditSteth -= amount;
83                      return 0;
84                  }
85      
86                  VaultUser.bridgeCreditSteth = 0;
87                  amount -= creditSteth;
88                  creditReth = VaultUser.bridgeCreditReth;
89                  if (creditReth < C.ROUNDING_ZERO) {
90                      // Valid withdraw when no RETH credits
91                      return amount;
92                  } else {
93                      if (IBridge(rethBridge).getDethValue() < C.ROUNDING_ZERO) {
94                          // Can withdraw STETH using RETH credit when RETH bridge is empty
95                          if (creditReth >= amount) {
96                              VaultUser.bridgeCreditReth -= amount;
97                              return 0;
98                          } else {
99                              VaultUser.bridgeCreditReth = 0;
100                             return amount - creditReth;
101                         }
102                     } else {
103                         // Must use available bridge credits on withdrawal
104                         // @dev Prevents abusing bridge for arbitrage
105                         revert Errors.MustUseExistingBridgeCredit();
106                     }
107                 }
108             }
109         }


144         function transferBridgeCredit(address asset, address from, address to, uint88 collateral) internal {
145             AppStorage storage s = appStorage();
146     
147             STypes.Asset storage Asset = s.asset[asset];
148             uint256 vault = Asset.vault;
149     
150             if (vault == VAULT.ONE) {
151                 STypes.VaultUser storage VaultUserFrom = s.vaultUser[vault][from];
152                 uint88 creditReth = VaultUserFrom.bridgeCreditReth;
153                 uint88 creditSteth = VaultUserFrom.bridgeCreditSteth;
154                 STypes.VaultUser storage VaultUserTo = s.vaultUser[vault][to];
155     
156                 if (creditReth < C.ROUNDING_ZERO && creditSteth < C.ROUNDING_ZERO) {
157                     // No bridge credits
158                     return;
159                 }
160     
161                 if (creditReth > C.ROUNDING_ZERO && creditSteth < C.ROUNDING_ZERO) {
162                     // Only creditReth
163                     if (creditReth > collateral) {
164                         VaultUserFrom.bridgeCreditReth -= collateral;
165                         VaultUserTo.bridgeCreditReth += collateral;
166                     } else {
167                         VaultUserFrom.bridgeCreditReth = 0;
168                         VaultUserTo.bridgeCreditReth += creditReth;
169                     }
170                 } else if (creditReth < C.ROUNDING_ZERO && creditSteth > C.ROUNDING_ZERO) {
171                     // Only creditSteth
172                     if (creditSteth > collateral) {
173                         VaultUserFrom.bridgeCreditSteth -= collateral;
174                         VaultUserTo.bridgeCreditSteth += collateral;
175                     } else {
176                         VaultUserFrom.bridgeCreditSteth = 0;
177                         VaultUserTo.bridgeCreditSteth += creditSteth;
178                     }
179                 } else {
180                     // Both creditReth and creditSteth
181                     uint88 creditTotal = creditReth + creditSteth;
182                     if (creditTotal > collateral) {
183                         creditReth = creditReth.divU88(creditTotal).mulU88(collateral);
184                         creditSteth = creditSteth.divU88(creditTotal).mulU88(collateral);
185                         VaultUserFrom.bridgeCreditReth -= creditReth;
186                         VaultUserFrom.bridgeCreditSteth -= creditSteth;
187                     } else {
188                         VaultUserFrom.bridgeCreditReth = 0;
189                         VaultUserFrom.bridgeCreditSteth = 0;
190                     }
191                     VaultUserTo.bridgeCreditReth += creditReth;
192                     VaultUserTo.bridgeCreditSteth += creditSteth;
193                 }
194             }
195         }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L144:195

File: libraries/LibOracle.sol


69          function baseOracleCircuitBreaker(
70              uint256 protocolPrice,
71              uint80 roundId,
72              int256 chainlinkPrice,
73              uint256 timeStamp,
74              uint256 chainlinkPriceInEth
75          ) private view returns (uint256 _protocolPrice) {
76              bool invalidFetchData = roundId == 0 || timeStamp == 0 || timeStamp > block.timestamp || chainlinkPrice <= 0
77                  || block.timestamp > 2 hours + timeStamp;
78              uint256 chainlinkDiff =
79                  chainlinkPriceInEth > protocolPrice ? chainlinkPriceInEth - protocolPrice : protocolPrice - chainlinkPriceInEth;
80              bool priceDeviation = protocolPrice > 0 && chainlinkDiff.div(protocolPrice) > 0.5 ether;
81      
82              // @dev if there is issue with chainlink, get twap price. Verify twap and compare with chainlink
83              if (invalidFetchData) {
84                  return twapCircuitBreaker();
85              } else if (priceDeviation) {
86                  // Check valid twap price
87                  try IDiamond(payable(address(this))).estimateWETHInUSDC(C.UNISWAP_WETH_BASE_AMT, 30 minutes) returns (uint256 twapPrice)
88                  {
89                      if (twapPrice == 0) {
90                          return chainlinkPriceInEth;
91                      }
92      
93                      uint256 twapPriceNormalized = twapPrice * (1 ether / C.DECIMAL_USDC);
94                      uint256 twapPriceInEth = twapPriceNormalized.inv();
95                      uint256 twapDiff = twapPriceInEth > protocolPrice ? twapPriceInEth - protocolPrice : protocolPrice - twapPriceInEth;
96      
97                      // Save the price that is closest to saved oracle price
98                      if (chainlinkDiff <= twapDiff) {
99                          return chainlinkPriceInEth;
100                     } else {
101                         // Check valid twap liquidity
102                         IERC20 weth = IERC20(C.WETH);
103                         uint256 wethBal = weth.balanceOf(C.USDC_WETH);
104                         if (wethBal < 100 ether) {
105                             return chainlinkPriceInEth;
106                         }
107                         return twapPriceInEth;
108                     }
109                 } catch {
110                     return chainlinkPriceInEth;
111                 }
112             } else {
113                 return chainlinkPriceInEth;
114             }
115         }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L69:115

File: libraries/LibOrders.sol


556         function sellMatchAlgo(
557             address asset,
558             STypes.Order memory incomingAsk,
559             MTypes.OrderHint[] memory orderHintArray,
560             uint256 minAskEth
561         ) internal {
562             AppStorage storage s = appStorage();
563             uint16 startingId = s.bids[asset][C.HEAD].nextId;
564             if (incomingAsk.price > s.bids[asset][startingId].price) {
565                 if (incomingAsk.ercAmount.mul(incomingAsk.price) >= minAskEth) {
566                     addSellOrder(incomingAsk, asset, orderHintArray);
567                 }
568                 return;
569             }
570             // matching loop starts
571             MTypes.Match memory matchTotal;
572             while (true) {
573                 STypes.Order memory highestBid = s.bids[asset][startingId];
574                 if (incomingAsk.price <= highestBid.price) {
575                     // Consider ask filled if only dust amount left
576                     if (incomingAsk.ercAmount.mul(highestBid.price) == 0) {
577                         updateBidOrdersOnMatch(s.bids, asset, highestBid.id, false);
578                         incomingAsk.ercAmount = 0;
579                         matchIncomingSell(asset, incomingAsk, matchTotal);
580                         return;
581                     }
582                     matchHighestBid(incomingAsk, highestBid, asset, matchTotal);
583                     if (incomingAsk.ercAmount > highestBid.ercAmount) {
584                         incomingAsk.ercAmount -= highestBid.ercAmount;
585                         highestBid.ercAmount = 0;
586                         matchOrder(s.bids, asset, highestBid.id);
587     
588                         // loop
589                         startingId = highestBid.nextId;
590                         if (startingId == C.TAIL) {
591                             matchIncomingSell(asset, incomingAsk, matchTotal);
592     
593                             if (incomingAsk.ercAmount.mul(incomingAsk.price) >= minAskEth) {
594                                 addSellOrder(incomingAsk, asset, orderHintArray);
595                             }
596                             s.bids[asset][C.HEAD].nextId = C.TAIL;
597                             return;
598                         }
599                     } else {
600                         if (incomingAsk.ercAmount == highestBid.ercAmount) {
601                             matchOrder(s.bids, asset, highestBid.id);
602                             updateBidOrdersOnMatch(s.bids, asset, highestBid.id, true);
603                         } else {
604                             highestBid.ercAmount -= incomingAsk.ercAmount;
605                             s.bids[asset][highestBid.id].ercAmount = highestBid.ercAmount;
606                             updateBidOrdersOnMatch(s.bids, asset, highestBid.id, false);
607                             // Check reduced dust threshold for existing limit orders
608                             if (highestBid.ercAmount.mul(highestBid.price) < LibAsset.minBidEth(asset).mul(C.DUST_FACTOR)) {
609                                 cancelBid(asset, highestBid.id);
610                             }
611                         }
612                         incomingAsk.ercAmount = 0;
613                         matchIncomingSell(asset, incomingAsk, matchTotal);
614                         return;
615                     }
616                 } else {
617                     updateBidOrdersOnMatch(s.bids, asset, highestBid.id, false);
618                     matchIncomingSell(asset, incomingAsk, matchTotal);
619     
620                     if (incomingAsk.ercAmount.mul(incomingAsk.price) >= minAskEth) {
621                         addSellOrder(incomingAsk, asset, orderHintArray);
622                     }
623                     return;
624                 }
625             }
626         }


499         function updateSellOrdersOnMatch(address asset, MTypes.BidMatchAlgo memory b) internal {
500             AppStorage storage s = appStorage();
501             if (b.matchedAskId != 0) {
502                 _updateOrders(s.asks, asset, C.HEAD, b.matchedAskId);
503             }
504     
505             if (b.matchedShortId != 0) {
506                 if (!b.isMovingBack && !b.isMovingFwd) {
507                     // @dev Handles only matching one thing
508                     // @dev If does not get fully matched, b.matchedShortId == 0 and therefore not hit this block
509                     _updateOrders(s.shorts, asset, b.prevShortId, b.matchedShortId);
510                 } else if (!b.isMovingBack && b.isMovingFwd) {
511                     // @dev Handles moving forward only
512                     _updateOrders(s.shorts, asset, b.firstShortIdBelowOracle, b.matchedShortId);
513                 } else if (b.isMovingBack && !b.isMovingFwd) {
514                     //@handles moving backwards only.
515                     _updateOrders(s.shorts, asset, b.prevShortId, b.shortHintId);
516                 } else if (b.isMovingBack && b.isMovingFwd) {
517                     uint16 id = b.prevShortId == b.firstShortIdBelowOracle ? b.shortHintId : b.matchedShortId;
518                     // @dev Handle going backward and forward
519                     _updateOrders(s.shorts, asset, b.firstShortIdBelowOracle, id);
520                 }
521             }
522         }


728         function _updateOracleAndStartingShort(address asset, uint16[] memory shortHintArray) private {
729             AppStorage storage s = appStorage();
730             uint256 oraclePrice = LibOracle.getOraclePrice(asset);
731             uint256 savedPrice = asset.getPrice();
732             asset.setPriceAndTime(oraclePrice, getOffsetTime());
733             if (oraclePrice == savedPrice) {
734                 return; // no need to update startingShortId
735             }
736     
737             STypes.Asset storage Asset = s.asset[asset];
738             bool shortOrdersIsEmpty = s.shorts[asset][C.HEAD].nextId == C.TAIL;
739             if (shortOrdersIsEmpty) {
740                 Asset.startingShortId = C.HEAD;
741             } else {
742                 uint16 shortHintId;
743                 for (uint256 i = 0; i < shortHintArray.length;) {
744                     shortHintId = shortHintArray[i];
745                     unchecked {
746                         ++i;
747                     }
748     
749                     STypes.Order storage short = s.shorts[asset][shortHintId];
750                     {
751                         O shortOrderType = short.orderType;
752                         if (shortOrderType == O.Cancelled || shortOrderType == O.Matched || shortOrderType == O.Uninitialized) {
753                             continue;
754                         }
755                     }
756     
757                     uint80 shortPrice = short.price;
758                     uint16 prevId = short.prevId;
759                     uint80 prevShortPrice = s.shorts[asset][prevId].price;
760                     // @dev force hint to be within 0.5% of oraclePrice
761                     bool startingShortWithinOracleRange = shortPrice <= oraclePrice.mul(1.005 ether) && prevShortPrice >= oraclePrice;
762                     bool isExactStartingShort = shortPrice >= oraclePrice && prevShortPrice < oraclePrice;
763                     bool allShortUnderOraclePrice = shortPrice < oraclePrice && short.nextId == C.TAIL;
764     
765                     if (isExactStartingShort) {
766                         Asset.startingShortId = shortHintId;
767                         return;
768                     } else if (startingShortWithinOracleRange) {
769                         // @dev prevShortPrice >= oraclePrice
770                         Asset.startingShortId = prevId;
771                         return;
772                     } else if (allShortUnderOraclePrice) {
773                         Asset.startingShortId = C.HEAD;
774                         return;
775                     }
776                 }
777     
778                 revert Errors.BadShortHint();
779             }
780         }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L728:780

</details>

NC030 - Unused import:

The identifier is imported but never used within the file

<details> <summary>Click to show 11 findings</summary>
File: facets/PrimaryLiquidationFacet.sol


10      import {STypes, MTypes, SR} from "contracts/libraries/DataTypes.sol";


10      import {STypes, MTypes, SR} from "contracts/libraries/DataTypes.sol";

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L10:10

File: facets/RedemptionFacet.sol


9       import {STypes, MTypes, SR} from "contracts/libraries/DataTypes.sol";


9       import {STypes, MTypes, SR} from "contracts/libraries/DataTypes.sol";


19      import {console} from "contracts/libraries/console.sol";

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L19:19

File: facets/ShortOrdersFacet.sol


7       import {STypes, MTypes, O, SR} from "contracts/libraries/DataTypes.sol";


7       import {STypes, MTypes, O, SR} from "contracts/libraries/DataTypes.sol";

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/ShortOrdersFacet.sol#L7:7

File: libraries/LibBridgeRouter.sol


6       import {STypes} from "contracts/libraries/DataTypes.sol";

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L6:6

File: libraries/LibOrders.sol


9       import {STypes, MTypes, O, SR} from "contracts/libraries/DataTypes.sol";


9       import {STypes, MTypes, O, SR} from "contracts/libraries/DataTypes.sol";

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L9:9

File: libraries/LibSRUtil.sol


6       import {STypes, SR} from "contracts/libraries/DataTypes.sol";

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L6:6

</details>

NC031 - Unsafe conversion from unsigned to signed values:

Solidity follows two's complement rules for its integers, meaning that the most significant bit for signed integers is used to denote the sign, and converting between the two requires inverting all of the bits and adding one. Because of this, casting an unsigned integer to a signed one may result in a change of the sign and or magnitude of the value. For example, int8(type(uint8).max) is not equal to type(int8).max, but is equal to -1. type(uint8).max in binary is 11111111, which if cast to a signed value, means the first binary 1 indicates a negative value, and the binary 1s, invert to all zeroes, and when one is added, it becomes one, but negative, and therefore the decimal value of binary 11111111 is -1.

File: libraries/UniswapOracleLibrary.sol


65              if (tickCumulativesDelta < 0 && (tickCumulativesDelta % int32(secondsAgo) != 0)) {


62              int24 tick = int24(tickCumulativesDelta / int32(secondsAgo));

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L62:62

NC032 - Style guide: State and local variables should be named using lowerCamelCase:

The Solidity style guide says to use mixedCase for local and state variable names. Note that while OpenZeppelin may not follow this advice, it still is the recommended way of naming variables.

<details> <summary>Click to show 82 findings</summary>
File: facets/PrimaryLiquidationFacet.sol


165             STypes.VaultUser storage TAPP = s.vaultUser[m.vault][address(this)];


170                 STypes.Asset storage Asset = s.asset[m.asset];


210             STypes.VaultUser storage VaultUser = s.vaultUser[m.vault][msg.sender];


211             STypes.VaultUser storage TAPP = s.vaultUser[m.vault][address(this)];


241             STypes.VaultUser storage TAPP = s.vaultUser[m.vault][address(this)];

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L241:241

File: facets/RedemptionFacet.sol


151             STypes.Asset storage Asset = s.asset[p.asset];


207             STypes.VaultUser storage VaultUser = s.vaultUser[Asset.vault][msg.sender];


255             STypes.Asset storage Asset = s.asset[d.asset];


363             STypes.Asset storage Asset = s.asset[asset];


386             STypes.Asset storage Asset = s.asset[asset];

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L386:386

File: facets/ShortOrdersFacet.sol


44              STypes.Asset storage Asset = s.asset[asset];

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/ShortOrdersFacet.sol#L44:44

File: libraries/LibBridgeRouter.sol


23              STypes.VaultUser storage VaultUser = s.vaultUser[vault][msg.sender];


44              STypes.VaultUser storage VaultUser = s.vaultUser[vault][msg.sender];


147             STypes.Asset storage Asset = s.asset[asset];


151                 STypes.VaultUser storage VaultUserFrom = s.vaultUser[vault][from];


154                 STypes.VaultUser storage VaultUserTo = s.vaultUser[vault][to];

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L154:154

File: libraries/LibBytes.sol


24                  uint64 CR; // bytes8


11          function readProposalData(address SSTORE2Pointer, uint8 slateLength) internal view returns (MTypes.ProposalData[] memory) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBytes.sol#L11:11

File: libraries/LibOracle.sol


11      import {LibOrders} from "contracts/libraries/LibOrders.sol";


11      import {LibOrders} from "contracts/libraries/LibOrders.sol";


11      import {LibOrders} from "contracts/libraries/LibOrders.sol";


11      import {LibOrders} from "contracts/libraries/LibOrders.sol";


11      import {LibOrders} from "contracts/libraries/LibOrders.sol";


11      import {LibOrders} from "contracts/libraries/LibOrders.sol";


11      import {LibOrders} from "contracts/libraries/LibOrders.sol";


11      import {LibOrders} from "contracts/libraries/LibOrders.sol";

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L11:11

File: libraries/LibOrders.sol


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


670             STypes.Asset storage Asset = s.asset[asset];


672             STypes.Vault storage Vault = s.vault[vault];


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


737             STypes.Asset storage Asset = s.asset[asset];


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


812             STypes.Asset storage Asset = s.asset[asset];


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


891             STypes.Asset storage Asset = s.asset[asset];


909                         STypes.Vault storage Vault = s.vault[vault];


11      import {Events} from "contracts/libraries/Events.sol";


958             STypes.Vault storage Vault = s.vault[vault];


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";


11      import {Events} from "contracts/libraries/Events.sol";

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L11:11

File: libraries/LibSRUtil.sol


27              STypes.Asset storage Asset = s.asset[asset];


29              STypes.Vault storage Vault = s.vault[vault];


11      import {LibAsset} from "contracts/libraries/LibAsset.sol";


11      import {LibAsset} from "contracts/libraries/LibAsset.sol";


11      import {LibAsset} from "contracts/libraries/LibAsset.sol";


112                 STypes.Asset storage Asset = s.asset[asset];


11      import {LibAsset} from "contracts/libraries/LibAsset.sol";


11      import {LibAsset} from "contracts/libraries/LibAsset.sol";


11      import {LibAsset} from "contracts/libraries/LibAsset.sol";

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L11:11

File: libraries/UniswapOracleLibrary.sol


11          function observe(uint32[] calldata secondsAgos)


11          function observe(uint32[] calldata secondsAgos)

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L11:11

</details>

NC033 - Function definitions should have NatSpec @dev annotations:

Explain to a developer any extra details

<details> <summary>Click to show 57 findings</summary>
File: facets/BridgeRouterFacet.sol


29          constructor(address _rethBridge, address _stethBridge) {


40          function getDethTotal(uint256 vault) external view nonReentrantView returns (uint256) {


156         function _getVault(address bridge) private view returns (uint256 vault, uint256 bridgePointer) {


169         function _ethConversion(uint256 vault, uint88 amount) private view returns (uint88) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L169:169

File: facets/PrimaryLiquidationFacet.sol


30          constructor(address _dusd) {


122         function _setLiquidationStruct(address asset, address shorter, uint8 id, uint16 shortOrderId)


229         function min88(uint256 a, uint88 b) private pure returns (uint88) {


240         function _fullorPartialLiquidation(MTypes.PrimaryLiquidation memory m) private {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L240:240

File: facets/RedemptionFacet.sol


31          function validRedemptionSR(STypes.ShortRecord storage shortRecord, address proposer, address shorter, uint256 minShortErc)


347         function claimRemainingCollateral(address asset, address redeemer, uint8 claimIndex, uint8 id)


368         function _claimRemainingCollateral(address asset, uint256 vault, address shorter, uint8 shortId) private {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L368:368

File: libraries/LibBridgeRouter.sol


21          function addDeth(uint256 vault, uint256 bridgePointer, uint88 amount) internal {


39          function assessDeth(uint256 vault, uint256 bridgePointer, uint88 amount, address rethBridge, address stethBridge)


198         function removeDeth(uint256 vault, uint88 amount, uint88 fee) internal {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L198:198

File: libraries/LibBytes.sol


11          function readProposalData(address SSTORE2Pointer, uint8 slateLength) internal view returns (MTypes.ProposalData[] memory) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBytes.sol#L11:11

File: libraries/LibOracle.sol


19          function getOraclePrice(address asset) internal view returns (uint256) {


69          function baseOracleCircuitBreaker(


117         function oracleCircuitBreaker(


131         function twapCircuitBreaker() private view returns (uint256 twapPriceInEth) {


149         function setPriceAndTime(address asset, uint256 oraclePrice, uint32 oracleTime) internal {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L149:149

File: libraries/LibOrders.sol


35          function convertCR(uint16 cr) internal pure returns (uint256) {


40          function increaseSharesOnMatch(address asset, STypes.Order memory order, MTypes.Match memory matchTotal, uint88 eth) internal {


55          function currentOrders(mapping(address => mapping(uint16 => STypes.Order)) storage orders, address asset)


78          function isShort(STypes.Order memory order) internal pure returns (bool) {


82          function addBid(address asset, STypes.Order memory order, MTypes.OrderHint[] memory orderHintArray) internal {


103         function addAsk(address asset, STypes.Order memory order, MTypes.OrderHint[] memory orderHintArray) internal {


128         function addShort(address asset, STypes.Order memory order, MTypes.OrderHint[] memory orderHintArray) internal {


153         function addSellOrder(STypes.Order memory incomingOrder, address asset, MTypes.OrderHint[] memory orderHintArray) private {


231         function verifyBidId(address asset, uint16 _prevId, uint256 _newPrice, uint16 _nextId)


260         function verifySellId(


320         function _reuseOrderIds(


362         function findPrevOfIncomingId(


402         function verifyId(


440         function getOrderId(


532         function _updateOrders(


556         function sellMatchAlgo(


628         function matchIncomingSell(address asset, STypes.Order memory incomingOrder, MTypes.Match memory matchTotal) private {


652         function matchIncomingAsk(address asset, STypes.Order memory incomingAsk, MTypes.Match memory matchTotal) private {


668         function matchIncomingShort(address asset, STypes.Order memory incomingShort, MTypes.Match memory matchTotal) private {


728         function _updateOracleAndStartingShort(address asset, uint16[] memory shortHintArray) private {


810         function updateStartingShortIdViaShort(address asset, STypes.Order memory incomingShort) internal {


826         function findOrderHintId(


854         function cancelBid(address asset, uint16 id) internal {


868         function cancelAsk(address asset, uint16 id) internal {


882         function cancelShort(address asset, uint16 id) internal {


955         function handlePriceDiscount(address asset, uint80 price) internal {


985         function min(uint256 a, uint256 b) internal pure returns (uint256) {


989         function max(uint256 a, uint256 b) internal pure returns (uint256) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L989:989

File: libraries/LibSRUtil.sol


22          function disburseCollateral(address asset, address shorter, uint88 collateral, uint256 dethYieldRate, uint32 updatedAt)


49          function checkCancelShortOrder(address asset, SR initialStatus, uint16 shortOrderId, uint8 shortRecordId, address shorter)


72          function checkShortMinErc(address asset, SR initialStatus, uint16 shortOrderId, uint8 shortRecordId, address shorter)


102         function checkRecoveryModeViolation(address asset, uint256 shortRecordCR, uint256 oraclePrice)


124         function transferShortRecord(address from, address to, uint40 tokenId) internal {


151         function updateErcDebt(STypes.ShortRecord storage short, address asset) internal {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L151:151

File: libraries/UniswapOracleLibrary.sol


11          function observe(uint32[] calldata secondsAgos)


28          function getQuoteAtTick(int24 tick, uint128 baseAmount, address baseToken, address quoteToken)


47          function estimateTWAP(uint128 amountIn, uint32 secondsAgo, address pool, address baseToken, address quoteToken)

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L47:47

</details>

NC034 - Function definitions should have NatSpec @notice annotations:

Explain to an end user what this does

<details> <summary>Click to show 55 findings</summary>
File: facets/BridgeRouterFacet.sol


29          constructor(address _rethBridge, address _stethBridge) {


148         function maybeUpdateYield(uint256 vault, uint88 amount) private {


156         function _getVault(address bridge) private view returns (uint256 vault, uint256 bridgePointer) {


169         function _ethConversion(uint256 vault, uint88 amount) private view returns (uint88) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L169:169

File: facets/PrimaryLiquidationFacet.sol


30          constructor(address _dusd) {


96          function _checklowestSell(MTypes.PrimaryLiquidation memory m) private view {


229         function min88(uint256 a, uint88 b) private pure returns (uint88) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L229:229

File: facets/RedemptionFacet.sol


31          function validRedemptionSR(STypes.ShortRecord storage shortRecord, address proposer, address shorter, uint256 minShortErc)


347         function claimRemainingCollateral(address asset, address redeemer, uint8 claimIndex, uint8 id)


368         function _claimRemainingCollateral(address asset, uint256 vault, address shorter, uint8 shortId) private {


382         function calculateRedemptionFee(address asset, uint88 colRedeemed, uint88 ercDebtRedeemed)

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L382:382

File: libraries/LibBridgeRouter.sol


21          function addDeth(uint256 vault, uint256 bridgePointer, uint88 amount) internal {


39          function assessDeth(uint256 vault, uint256 bridgePointer, uint88 amount, address rethBridge, address stethBridge)


113         function withdrawalFeePct(uint256 bridgePointer, address rethBridge, address stethBridge) internal view returns (uint256 fee) {


144         function transferBridgeCredit(address asset, address from, address to, uint88 collateral) internal {


198         function removeDeth(uint256 vault, uint88 amount, uint88 fee) internal {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L198:198

File: libraries/LibBytes.sol


11          function readProposalData(address SSTORE2Pointer, uint8 slateLength) internal view returns (MTypes.ProposalData[] memory) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBytes.sol#L11:11

File: libraries/LibOracle.sol


19          function getOraclePrice(address asset) internal view returns (uint256) {


69          function baseOracleCircuitBreaker(


117         function oracleCircuitBreaker(


131         function twapCircuitBreaker() private view returns (uint256 twapPriceInEth) {


149         function setPriceAndTime(address asset, uint256 oraclePrice, uint32 oracleTime) internal {


156         function getTime(address asset) internal view returns (uint256 creationTime) {


162         function getPrice(address asset) internal view returns (uint80 oraclePrice) {


168         function getSavedOrSpotOraclePrice(address asset) internal view returns (uint256) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L168:168

File: libraries/LibOrders.sol


30          function getOffsetTime() internal view returns (uint32 timeInSeconds) {


35          function convertCR(uint16 cr) internal pure returns (uint256) {


40          function increaseSharesOnMatch(address asset, STypes.Order memory order, MTypes.Match memory matchTotal, uint88 eth) internal {


55          function currentOrders(mapping(address => mapping(uint16 => STypes.Order)) storage orders, address asset)


78          function isShort(STypes.Order memory order) internal pure returns (bool) {


82          function addBid(address asset, STypes.Order memory order, MTypes.OrderHint[] memory orderHintArray) internal {


103         function addAsk(address asset, STypes.Order memory order, MTypes.OrderHint[] memory orderHintArray) internal {


173         function addOrder(


320         function _reuseOrderIds(


420         function normalizeOrderType(O o) private pure returns (O newO) {


628         function matchIncomingSell(address asset, STypes.Order memory incomingOrder, MTypes.Match memory matchTotal) private {


728         function _updateOracleAndStartingShort(address asset, uint16[] memory shortHintArray) private {


783         function updateOracleAndStartingShortViaThreshold(


803         function updateOracleAndStartingShortViaTimeBidOnly(address asset, uint16[] memory shortHintArray) internal {


810         function updateStartingShortIdViaShort(address asset, STypes.Order memory incomingShort) internal {


826         function findOrderHintId(


854         function cancelBid(address asset, uint16 id) internal {


868         function cancelAsk(address asset, uint16 id) internal {


882         function cancelShort(address asset, uint16 id) internal {


955         function handlePriceDiscount(address asset, uint80 price) internal {


985         function min(uint256 a, uint256 b) internal pure returns (uint256) {


989         function max(uint256 a, uint256 b) internal pure returns (uint256) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L989:989

File: libraries/LibSRUtil.sol


22          function disburseCollateral(address asset, address shorter, uint88 collateral, uint256 dethYieldRate, uint32 updatedAt)


49          function checkCancelShortOrder(address asset, SR initialStatus, uint16 shortOrderId, uint8 shortRecordId, address shorter)


72          function checkShortMinErc(address asset, SR initialStatus, uint16 shortOrderId, uint8 shortRecordId, address shorter)


102         function checkRecoveryModeViolation(address asset, uint256 shortRecordCR, uint256 oraclePrice)


124         function transferShortRecord(address from, address to, uint40 tokenId) internal {


151         function updateErcDebt(STypes.ShortRecord storage short, address asset) internal {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L151:151

File: libraries/UniswapOracleLibrary.sol


11          function observe(uint32[] calldata secondsAgos)


47          function estimateTWAP(uint128 amountIn, uint32 secondsAgo, address pool, address baseToken, address quoteToken)

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L47:47

</details>

NC035 - Interface declarations should have NatSpec @title annotations:

A title that should describe the contract/interface

File: libraries/UniswapOracleLibrary.sol


10      interface IUniswapV3Pool {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L10:10

NC036 - Interface declarations should have NatSpec @author annotations:

The name of the author

File: libraries/UniswapOracleLibrary.sol


10      interface IUniswapV3Pool {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L10:10

NC037 - Interface declarations should have NatSpec @notice annotations:

Explain to an end user what this does

File: libraries/UniswapOracleLibrary.sol


10      interface IUniswapV3Pool {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L10:10

NC038 - Interface declarations should have NatSpec @dev annotations:

Explain to a developer any extra details

File: libraries/UniswapOracleLibrary.sol


10      interface IUniswapV3Pool {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L10:10

NC039 - Library declarations should have Natspec @title annotations:

A title that should describe the contract/interface

<details> <summary>Click to show 5 findings</summary>
File: libraries/LibBridgeRouter.sol


16      library LibBridgeRouter {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L16:16

File: libraries/LibBytes.sol


9       library LibBytes {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBytes.sol#L9:9

File: libraries/LibOracle.sol


16      library LibOracle {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L16:16

File: libraries/LibOrders.sol


20      library LibOrders {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L20:20

File: libraries/LibSRUtil.sol


18      library LibSRUtil {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L18:18

</details>

NC040 - Library declarations should have Natspec @author annotations:

The name of the author

<details> <summary>Click to show 6 findings</summary>
File: libraries/LibBridgeRouter.sol


16      library LibBridgeRouter {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L16:16

File: libraries/LibBytes.sol


9       library LibBytes {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBytes.sol#L9:9

File: libraries/LibOracle.sol


16      library LibOracle {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L16:16

File: libraries/LibOrders.sol


20      library LibOrders {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L20:20

File: libraries/LibSRUtil.sol


18      library LibSRUtil {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L18:18

File: libraries/UniswapOracleLibrary.sol


21      library OracleLibrary {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L21:21

</details>

NC041 - Library declarations should have Natspec @notice annotations:

Explain to an end user what this does

<details> <summary>Click to show 5 findings</summary>
File: libraries/LibBridgeRouter.sol


16      library LibBridgeRouter {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L16:16

File: libraries/LibBytes.sol


9       library LibBytes {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBytes.sol#L9:9

File: libraries/LibOracle.sol


16      library LibOracle {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L16:16

File: libraries/LibOrders.sol


20      library LibOrders {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L20:20

File: libraries/LibSRUtil.sol


18      library LibSRUtil {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L18:18

</details>

NC042 - Library declarations should have Natspec @dev annotations:

Explain to a developer any extra details

<details> <summary>Click to show 6 findings</summary>
File: libraries/LibBridgeRouter.sol


16      library LibBridgeRouter {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L16:16

File: libraries/LibBytes.sol


9       library LibBytes {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBytes.sol#L9:9

File: libraries/LibOracle.sol


16      library LibOracle {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L16:16

File: libraries/LibOrders.sol


20      library LibOrders {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L20:20

File: libraries/LibSRUtil.sol


18      library LibSRUtil {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L18:18

File: libraries/UniswapOracleLibrary.sol


21      library OracleLibrary {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L21:21

</details>

NC043 - Contract definitions should have Natspec @title annotations:

title that should describe the contract

<details> <summary>Click to show 4 findings</summary>
File: facets/BridgeRouterFacet.sol


18      contract BridgeRouterFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L18:18

File: facets/PrimaryLiquidationFacet.sol


21      contract PrimaryLiquidationFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L21:21

File: facets/RedemptionFacet.sol


21      contract RedemptionFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L21:21

File: facets/ShortOrdersFacet.sol


18      contract ShortOrdersFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/ShortOrdersFacet.sol#L18:18

</details>

NC044 - Contract definitions should have Natspec @author annotations:

The name of the author

<details> <summary>Click to show 4 findings</summary>
File: facets/BridgeRouterFacet.sol


18      contract BridgeRouterFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L18:18

File: facets/PrimaryLiquidationFacet.sol


21      contract PrimaryLiquidationFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L21:21

File: facets/RedemptionFacet.sol


21      contract RedemptionFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L21:21

File: facets/ShortOrdersFacet.sol


18      contract ShortOrdersFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/ShortOrdersFacet.sol#L18:18

</details>

NC045 - Contract definitions should have Natspec @notice annotations:

Explain to an end user what this does

<details> <summary>Click to show 4 findings</summary>
File: facets/BridgeRouterFacet.sol


18      contract BridgeRouterFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L18:18

File: facets/PrimaryLiquidationFacet.sol


21      contract PrimaryLiquidationFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L21:21

File: facets/RedemptionFacet.sol


21      contract RedemptionFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L21:21

File: facets/ShortOrdersFacet.sol


18      contract ShortOrdersFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/ShortOrdersFacet.sol#L18:18

</details>

NC046 - Contract definitions should have Natspec @dev annotations:

Explain to a developer any extra details

<details> <summary>Click to show 4 findings</summary>
File: facets/BridgeRouterFacet.sol


18      contract BridgeRouterFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L18:18

File: facets/PrimaryLiquidationFacet.sol


21      contract PrimaryLiquidationFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L21:21

File: facets/RedemptionFacet.sol


21      contract RedemptionFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L21:21

File: facets/ShortOrdersFacet.sol


18      contract ShortOrdersFacet is Modifiers {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/ShortOrdersFacet.sol#L18:18

</details>

NC047 - State variable declarations should have Natspec @notice annotations:

Explain to an end user what this does

File: facets/BridgeRouterFacet.sol


26          address private immutable rethBridge;


27          address private immutable stethBridge;

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L27:27

File: facets/PrimaryLiquidationFacet.sol


28          address private immutable dusd;

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L28:28

NC048 - State variable declarations should have Natspec @dev annotations:

Explain to a developer any extra details

File: facets/BridgeRouterFacet.sol


26          address private immutable rethBridge;


27          address private immutable stethBridge;

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L27:27

File: facets/PrimaryLiquidationFacet.sol


28          address private immutable dusd;

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L28:28

NC049 - Functions should have Natspec @return annotations:

Documents the return variables of a contract’s function

<details> <summary>Click to show 31 findings</summary>
File: facets/BridgeRouterFacet.sol


40          function getDethTotal(uint256 vault) external view nonReentrantView returns (uint256) {


51          function getBridges(uint256 vault) external view returns (address[] memory) {


156         function _getVault(address bridge) private view returns (uint256 vault, uint256 bridgePointer) {


169         function _ethConversion(uint256 vault, uint88 amount) private view returns (uint88) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L169:169

File: facets/PrimaryLiquidationFacet.sol


229         function min88(uint256 a, uint88 b) private pure returns (uint88) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L229:229

File: facets/RedemptionFacet.sol


31          function validRedemptionSR(STypes.ShortRecord storage shortRecord, address proposer, address shorter, uint256 minShortErc)


382         function calculateRedemptionFee(address asset, uint88 colRedeemed, uint88 ercDebtRedeemed)

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L382:382

File: libraries/LibBridgeRouter.sol


39          function assessDeth(uint256 vault, uint256 bridgePointer, uint88 amount, address rethBridge, address stethBridge)


113         function withdrawalFeePct(uint256 bridgePointer, address rethBridge, address stethBridge) internal view returns (uint256 fee) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L113:113

File: libraries/LibBytes.sol


11          function readProposalData(address SSTORE2Pointer, uint8 slateLength) internal view returns (MTypes.ProposalData[] memory) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBytes.sol#L11:11

File: libraries/LibOracle.sol


19          function getOraclePrice(address asset) internal view returns (uint256) {


69          function baseOracleCircuitBreaker(


131         function twapCircuitBreaker() private view returns (uint256 twapPriceInEth) {


156         function getTime(address asset) internal view returns (uint256 creationTime) {


162         function getPrice(address asset) internal view returns (uint80 oraclePrice) {


168         function getSavedOrSpotOraclePrice(address asset) internal view returns (uint256) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L168:168

File: libraries/LibOrders.sol


30          function getOffsetTime() internal view returns (uint32 timeInSeconds) {


35          function convertCR(uint16 cr) internal pure returns (uint256) {


55          function currentOrders(mapping(address => mapping(uint16 => STypes.Order)) storage orders, address asset)


78          function isShort(STypes.Order memory order) internal pure returns (bool) {


362         function findPrevOfIncomingId(


420         function normalizeOrderType(O o) private pure returns (O newO) {


440         function getOrderId(


826         function findOrderHintId(


985         function min(uint256 a, uint256 b) internal pure returns (uint256) {


989         function max(uint256 a, uint256 b) internal pure returns (uint256) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L989:989

File: libraries/LibSRUtil.sol


49          function checkCancelShortOrder(address asset, SR initialStatus, uint16 shortOrderId, uint8 shortRecordId, address shorter)


72          function checkShortMinErc(address asset, SR initialStatus, uint16 shortOrderId, uint8 shortRecordId, address shorter)


102         function checkRecoveryModeViolation(address asset, uint256 shortRecordCR, uint256 oraclePrice)

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L102:102

File: libraries/UniswapOracleLibrary.sol


11          function observe(uint32[] calldata secondsAgos)


47          function estimateTWAP(uint128 amountIn, uint32 secondsAgo, address pool, address baseToken, address quoteToken)

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L47:47

</details>

NC050 - Functions should have Natspec @param annotations:

Documents a parameter just like in Doxygen (must be followed by parameter name)

<details> <summary>Click to show 54 findings</summary>
File: facets/BridgeRouterFacet.sol


29          constructor(address _rethBridge, address _stethBridge) {


148         function maybeUpdateYield(uint256 vault, uint88 amount) private {


156         function _getVault(address bridge) private view returns (uint256 vault, uint256 bridgePointer) {


169         function _ethConversion(uint256 vault, uint88 amount) private view returns (uint88) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BridgeRouterFacet.sol#L169:169

File: facets/PrimaryLiquidationFacet.sol


30          constructor(address _dusd) {


96          function _checklowestSell(MTypes.PrimaryLiquidation memory m) private view {


229         function min88(uint256 a, uint88 b) private pure returns (uint88) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L229:229

File: facets/RedemptionFacet.sol


31          function validRedemptionSR(STypes.ShortRecord storage shortRecord, address proposer, address shorter, uint256 minShortErc)


347         function claimRemainingCollateral(address asset, address redeemer, uint8 claimIndex, uint8 id)


368         function _claimRemainingCollateral(address asset, uint256 vault, address shorter, uint8 shortId) private {


382         function calculateRedemptionFee(address asset, uint88 colRedeemed, uint88 ercDebtRedeemed)

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L382:382

File: libraries/LibBridgeRouter.sol


21          function addDeth(uint256 vault, uint256 bridgePointer, uint88 amount) internal {


39          function assessDeth(uint256 vault, uint256 bridgePointer, uint88 amount, address rethBridge, address stethBridge)


113         function withdrawalFeePct(uint256 bridgePointer, address rethBridge, address stethBridge) internal view returns (uint256 fee) {


144         function transferBridgeCredit(address asset, address from, address to, uint88 collateral) internal {


198         function removeDeth(uint256 vault, uint88 amount, uint88 fee) internal {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L198:198

File: libraries/LibBytes.sol


11          function readProposalData(address SSTORE2Pointer, uint8 slateLength) internal view returns (MTypes.ProposalData[] memory) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBytes.sol#L11:11

File: libraries/LibOracle.sol


19          function getOraclePrice(address asset) internal view returns (uint256) {


69          function baseOracleCircuitBreaker(


117         function oracleCircuitBreaker(


131         function twapCircuitBreaker() private view returns (uint256 twapPriceInEth) {


149         function setPriceAndTime(address asset, uint256 oraclePrice, uint32 oracleTime) internal {


156         function getTime(address asset) internal view returns (uint256 creationTime) {


162         function getPrice(address asset) internal view returns (uint80 oraclePrice) {


168         function getSavedOrSpotOraclePrice(address asset) internal view returns (uint256) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L168:168

File: libraries/LibOrders.sol


30          function getOffsetTime() internal view returns (uint32 timeInSeconds) {


35          function convertCR(uint16 cr) internal pure returns (uint256) {


40          function increaseSharesOnMatch(address asset, STypes.Order memory order, MTypes.Match memory matchTotal, uint88 eth) internal {


55          function currentOrders(mapping(address => mapping(uint16 => STypes.Order)) storage orders, address asset)


78          function isShort(STypes.Order memory order) internal pure returns (bool) {


82          function addBid(address asset, STypes.Order memory order, MTypes.OrderHint[] memory orderHintArray) internal {


103         function addAsk(address asset, STypes.Order memory order, MTypes.OrderHint[] memory orderHintArray) internal {


173         function addOrder(


320         function _reuseOrderIds(


420         function normalizeOrderType(O o) private pure returns (O newO) {


628         function matchIncomingSell(address asset, STypes.Order memory incomingOrder, MTypes.Match memory matchTotal) private {


728         function _updateOracleAndStartingShort(address asset, uint16[] memory shortHintArray) private {


783         function updateOracleAndStartingShortViaThreshold(


803         function updateOracleAndStartingShortViaTimeBidOnly(address asset, uint16[] memory shortHintArray) internal {


810         function updateStartingShortIdViaShort(address asset, STypes.Order memory incomingShort) internal {


826         function findOrderHintId(


854         function cancelBid(address asset, uint16 id) internal {


868         function cancelAsk(address asset, uint16 id) internal {


882         function cancelShort(address asset, uint16 id) internal {


955         function handlePriceDiscount(address asset, uint80 price) internal {


985         function min(uint256 a, uint256 b) internal pure returns (uint256) {


989         function max(uint256 a, uint256 b) internal pure returns (uint256) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L989:989

File: libraries/LibSRUtil.sol


22          function disburseCollateral(address asset, address shorter, uint88 collateral, uint256 dethYieldRate, uint32 updatedAt)


49          function checkCancelShortOrder(address asset, SR initialStatus, uint16 shortOrderId, uint8 shortRecordId, address shorter)


72          function checkShortMinErc(address asset, SR initialStatus, uint16 shortOrderId, uint8 shortRecordId, address shorter)


102         function checkRecoveryModeViolation(address asset, uint256 shortRecordCR, uint256 oraclePrice)


124         function transferShortRecord(address from, address to, uint40 tokenId) internal {


151         function updateErcDebt(STypes.ShortRecord storage short, address asset) internal {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L151:151

File: libraries/UniswapOracleLibrary.sol


47          function estimateTWAP(uint128 amountIn, uint32 secondsAgo, address pool, address baseToken, address quoteToken)

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L47:47

</details>

NC051 - Missing events in sensitive functions:

Sensitive setter functions in smart contracts often alter critical state variables. Without events emitted in these functions, external observers or dApps cannot easily track or react to these state changes. Missing events can obscure contract activity, hampering transparency and making integration more challenging. To resolve this, incorporate appropriate event emissions within these functions. Events offer an efficient way to log crucial changes, aiding in real-time tracking and post-transaction verification..

File: facets/PrimaryLiquidationFacet.sol


122         function _setLiquidationStruct(address asset, address shorter, uint8 id, uint16 shortOrderId)
123             private
124             returns (MTypes.PrimaryLiquidation memory)
125         {
126             LibShortRecord.updateErcDebt(asset, shorter, id);
127             {
128                 MTypes.PrimaryLiquidation memory m;
129                 m.asset = asset;
130                 m.short = s.shortRecords[asset][shorter][id];
131                 m.shortOrderId = shortOrderId;
132                 m.vault = s.asset[asset].vault;
133                 m.shorter = shorter;
134                 m.penaltyCR = LibAsset.penaltyCR(asset);
135                 m.oraclePrice = LibOracle.getPrice(asset);
136                 m.cRatio = m.short.getCollateralRatio(m.oraclePrice);
137                 m.forcedBidPriceBuffer = LibAsset.forcedBidPriceBuffer(asset);
138                 m.callerFeePct = LibAsset.callerFeePct(m.asset);
139                 m.tappFeePct = LibAsset.tappFeePct(m.asset);
140                 m.ethDebt = m.short.ercDebt.mul(m.oraclePrice).mul(m.forcedBidPriceBuffer).mul(1 ether + m.tappFeePct + m.callerFeePct); // ethDebt accounts for forcedBidPriceBuffer and potential fees
141                 return m;
142             }
143         }

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L122:143

NC052 - Unused file:

The file is never imported by any other source file. If the file is needed for tests, it should be moved to a test directory

<details> <summary>Click to show 6 findings</summary>
File: libraries/LibBridgeRouter.sol


/// @auditbase `gitio/libraries/LibBridgeRouter.sol` not used in any contracts

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L1:1

File: libraries/LibBytes.sol


/// @auditbase `gitio/libraries/LibBytes.sol` not used in any contracts

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBytes.sol#L1:1

File: libraries/LibOracle.sol


/// @auditbase `gitio/libraries/LibOracle.sol` not used in any contracts

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L1:1

File: libraries/LibOrders.sol


/// @auditbase `gitio/libraries/LibOrders.sol` not used in any contracts

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L1:1

File: libraries/LibSRUtil.sol


/// @auditbase `gitio/libraries/LibSRUtil.sol` not used in any contracts

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L1:1

File: libraries/UniswapOracleLibrary.sol


/// @auditbase `gitio/libraries/UniswapOracleLibrary.sol` not used in any contracts

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L1:1

</details>

NC053 - Consider only defining one library/interface/contract per sol file:

Combining multiple libraries, interfaces, or contracts in a single file can lead to clutter, reduced readability, and versioning issues. Resolution: Adopt the best practice of defining only one library, interface, or contract per Solidity file. This modular approach enhances clarity, simplifies unit testing, and streamlines code review. Furthermore, segregating components makes version management easier, as updates to one component won't necessitate changes to a file housing multiple unrelated components. Structured file management can further assist in avoiding naming collisions and ensure smoother integration into larger systems or DApps.

File: libraries/UniswapOracleLibrary.sol


10      interface IUniswapV3Pool {
11          function observe(uint32[] calldata secondsAgos)
12              external
13              view
14              returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s);
15      }
16      
17      /* solhint-disable */
18      
19      /// @title Oracle library
20      /// @notice Provides functions to integrate with V3 pool oracle
21      library OracleLibrary {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L10:21

NC054 - Use a struct to encapsulate multiple function parameters:

Using a struct to encapsulate multiple parameters in Solidity functions can significantly enhance code readability and maintainability. Instead of passing a long list of arguments, which can be error-prone and hard to manage, a struct allows grouping related data into a single, coherent entity. This approach simplifies function signatures and makes the code more organized. It also enhances code clarity, as developers can easily understand the relationship between the parameters. Moreover, it aids in future code modifications and expansions, as adding or modifying a parameter only requires changes in the struct definition, rather than in every function that uses these parameters.

<details> <summary>Click to show 14 findings</summary>
File: facets/PrimaryLiquidationFacet.sol


47          function liquidate(address asset, address shorter, uint8 id, uint16[] memory shortHintArray, uint16 shortOrderId)
48              external
49              isNotFrozen(asset)
50              nonReentrant
51              onlyValidShortRecord(asset, shorter, id)
52              returns (uint88, uint88)
53          {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/PrimaryLiquidationFacet.sol#L47:53

File: facets/RedemptionFacet.sol


224         function disputeRedemption(address asset, address redeemer, uint8 incorrectIndex, address disputeShorter, uint8 disputeShortId)
225             external
226             isNotFrozen(asset)
227             nonReentrant
228         {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/RedemptionFacet.sol#L224:228

File: facets/ShortOrdersFacet.sol


35          function createLimitShort(
36              address asset,
37              uint80 price,
38              uint88 ercAmount,
39              MTypes.OrderHint[] memory orderHintArray,
40              uint16[] memory shortHintArray,
41              uint16 shortOrderCR
42          ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/ShortOrdersFacet.sol#L35:42

File: libraries/LibBridgeRouter.sol


39          function assessDeth(uint256 vault, uint256 bridgePointer, uint88 amount, address rethBridge, address stethBridge)
40              internal
41              returns (uint88)
42          {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibBridgeRouter.sol#L39:42

File: libraries/LibOracle.sol


69          function baseOracleCircuitBreaker(
70              uint256 protocolPrice,
71              uint80 roundId,
72              int256 chainlinkPrice,
73              uint256 timeStamp,
74              uint256 chainlinkPriceInEth
75          ) private view returns (uint256 _protocolPrice) {


117         function oracleCircuitBreaker(
118             uint80 roundId,
119             uint80 baseRoundId,
120             int256 chainlinkPrice,
121             int256 baseChainlinkPrice,
122             uint256 timeStamp,
123             uint256 baseTimeStamp
124         ) private view {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOracle.sol#L117:124

File: libraries/LibOrders.sol


260         function verifySellId(
261             mapping(address => mapping(uint16 => STypes.Order)) storage orders,
262             address asset,
263             uint16 _prevId,
264             uint256 _newPrice,
265             uint16 _nextId
266         ) private view returns (int256 direction) {


320         function _reuseOrderIds(
321             mapping(address => mapping(uint16 => STypes.Order)) storage orders,
322             address asset,
323             uint16 id,
324             uint16 prevHEAD,
325             O cancelledOrMatched
326         ) private {


402         function verifyId(
403             mapping(address => mapping(uint16 => STypes.Order)) storage orders,
404             address asset,
405             uint16 prevId,
406             uint256 newPrice,
407             uint16 nextId,
408             O orderType
409         ) internal view returns (int256 direction) {


440         function getOrderId(
441             mapping(address => mapping(uint16 => STypes.Order)) storage orders,
442             address asset,
443             int256 direction,
444             uint16 hintId,
445             uint256 _newPrice,
446             O orderType
447         ) internal view returns (uint16 _hintId) {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibOrders.sol#L440:447

File: libraries/LibSRUtil.sol


22          function disburseCollateral(address asset, address shorter, uint88 collateral, uint256 dethYieldRate, uint32 updatedAt)
23              internal
24          {


49          function checkCancelShortOrder(address asset, SR initialStatus, uint16 shortOrderId, uint8 shortRecordId, address shorter)
50              internal
51              returns (bool isCancelled)
52          {


72          function checkShortMinErc(address asset, SR initialStatus, uint16 shortOrderId, uint8 shortRecordId, address shorter)
73              internal
74              returns (bool isCancelled)
75          {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L72:75

File: libraries/UniswapOracleLibrary.sol


47          function estimateTWAP(uint128 amountIn, uint32 secondsAgo, address pool, address baseToken, address quoteToken)
48              internal
49              view
50              returns (uint256 amountOut)
51          {

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/UniswapOracleLibrary.sol#L47:51

</details>

#0 - c4-pre-sort

2024-04-08T02:42:59Z

raymondfam marked the issue as sufficient quality report

#1 - raymondfam

2024-04-08T02:49:30Z

This bot report should be deemed high quality but there could only be a winner for sponsor's review.

#2 - c4-judge

2024-04-17T07:56:32Z

hansfriese marked the issue as grade-a

Findings Information

Labels

analysis-advanced
grade-a
sufficient quality report
A-14

Awards

236.6726 USDC - $236.67

External Links

Introduction to Ditto Protocol

Overview: The Ditto protocol emerges as a pioneering solution within the Ethereum ecosystem, introducing a decentralized stable asset protocol poised to redefine the landscape of decentralized finance (DeFi). By leveraging overcollateralized liquid staked ETH (rETH, stETH), Ditto facilitates the creation of stablecoins through a meticulously crafted gas-optimized orderbook. At its core, Ditto aims to provide users with a seamless experience in generating and managing stable assets, starting with the introduction of a USD stablecoin, dUSD.

Foundations: Built upon the foundational principles of cryptocurrencies, DittoETH prioritizes core values such as censorship resistance, neutrality, and permissionless transactions. Drawing inspiration from Bitshares' innovative polymorphic digital assets (PMDA) concept, DittoETH employs an orderbook model, ensuring transparent and efficient asset issuance and trading mechanisms.

Pegged Asset Issuance: Central to DittoETH's functionality is the issuance of pegged assets, ERC-20 tokens designed to track the price of targeted assets. Users can mint these DittoAssets by collateralizing staked ETH, providing them with exposure to various financial instruments while preserving the benefits of decentralization.

Market Collateralization: DittoETH's market collateralization framework relies on staked ETH derivatives and ETH itself to ensure the stability and solvency of the protocol. Shorters play a pivotal role in minting pegged assets, earning rewards and additional collateral from matched long traders, thereby enhancing liquidity and market resilience.

Refinements and Innovations: Drawing from the experiences of predecessors like MakerDAO, Synthetix, and Liquity, DittoETH introduces refinements and innovations to address existing limitations. By exclusively using staked ETH as collateral and implementing an orderbook model, DittoETH offers a more resilient and scalable solution for stable asset creation and management.

Key Features and Mechanisms: DittoETH incorporates several key features and mechanisms to ensure stability and sustainability within the protocol. This includes an incentive structure for shorters, robust liquidation parameters, and the establishment of the Treasury Asset Protection Pool (TAPP) to safeguard against market volatility.

Integration of Oracles: To maintain the stability and peg of DittoAssets, the protocol integrates oracles from Chainlink, providing reliable price feeds essential for accurate asset valuation and management.

Conclusion: In summary, the Ditto protocol represents a significant leap forward in the realm of decentralized finance, offering a robust and efficient solution for creating and managing stable assets. With its innovative approach, commitment to decentralization, and emphasis on security, DittoETH sets a new standard for stability and reliability in the DeFi landscape, promising a future of decentralized financial empowerment.

Approach Taken in Evaluating the Codebase

my approach to evaluating the Ditto Protocol's codebase involved a thorough analysis of its contracts and facets. assessing its functionality and key features for efficient trading. We also analyzed the architecture, identified components, and evaluated code quality, highlighting security concerns and centralization risks. Additionally, we reviewed the bid matching algorithm and oracle integration, conducting a risk analysis to identify potential vulnerabilities. This comprehensive approach provided stakeholders with valuable insights to make informed decisions about deploying and operating the protocol.

How the Scope contract work:

contracts

facets

BidOrdersFacet.sol

Explanation of the contract:

BidOrdersFacet. It encompasses functionality related to the creation and processing of bid orders within a market. The contract contains methods for creating bid orders, matching bids with existing sell orders (asks or shorts), and settling matched bids. The contract is structured to handle various scenarios related to bid order creation, matching, and settlement within the context of a market environment.

Functionality of the contract:

  1. Bid Order Creation: The contract facilitates the creation of bid orders in a market. These bid orders can be either limit orders or market orders. The parameters required for creating a bid order include the market impacted, unit price in Ether for the ERC token being traded, the amount of ERC token to buy, and additional parameters related to gas-optimized sorted placement and matching.

  2. Bid Matching Algorithm: The contract implements a bid matching algorithm (bidMatchAlgo) that determines how incoming bid orders are matched with existing sell orders on the market. It handles scenarios where bid orders match with existing sell orders, considering factors such as price, order type, and availability of matching sell orders.

  3. Settlement of Matched Bids: Once a bid order is matched with existing sell orders, the contract settles the matched bids by transferring assets (Ether and ERC tokens) between the involved parties. It also updates relevant data structures and variables to reflect the execution of the matched bids.

Key Features:

  1. Bid Order Creation: The contract provides functions (createBid and createForcedBid) for creating bid orders in the market. These functions ensure that bid orders are created with necessary parameters and adhere to certain conditions specified within the contract.

  2. Bid Matching Algorithm: The contract implements a sophisticated bid matching algorithm (bidMatchAlgo) that determines how bid orders are matched with existing sell orders on the market. This algorithm considers factors such as price, order type, and availability of matching sell orders, ensuring efficient and fair matching of bids.

  3. Settlement of Matched Bids: Once bid orders are successfully matched with existing sell orders, the contract settles the matched bids by transferring assets between the involved parties. It also updates relevant data structures and variables to reflect the execution of the matched bids, ensuring accurate tracking of market activity.

Overall, the contract provides comprehensive functionality for managing bid orders within a market environment, including creation, matching, and settlement of bids, thereby facilitating efficient trading activities on the platform.

ShortOrdersFacet.sol

Explanation of the contract: ShortOrdersFacet. It is part of a larger system, likely a decentralized finance (DeFi) protocol, and specifically focuses on managing short orders within a market system. The contract facilitates the creation of limit short orders, allowing users to sell ERC tokens short in the market.

Functionality of the contract:

  1. Creation of Limit Short Orders: The primary functionality of the contract is to create limit short orders within the market system. Users can specify the asset, unit price in ETH for the ERC tokens sold short, the amount of ERC tokens to be sold short, and additional parameters such as order hints and the initial collateral ratio for the short order.

  2. Validation and Processing: Upon receiving the parameters for creating a limit short order, the contract validates various conditions such as the collateral ratio, minimum order size, and user escrowed ETH balance to ensure the integrity of the system. It then processes the order by interacting with other components of the system, such as the asset storage, order book, and oracle.

  3. Oracle Interaction: The contract interacts with an oracle to obtain the current price of the asset being traded. This price is used for validation and decision-making during the creation of limit short orders.

  4. Matching Algorithms: The contract includes algorithms for matching short orders with existing bids in the order book. It considers factors such as price thresholds and recovery mode violations to determine the appropriate action for processing the short order.

Key Features:

  1. Limit Short Order Creation: Users can create limit short orders, specifying the price and quantity of ERC tokens to be sold short. This feature allows traders to profit from price declines in the asset's value.

  2. Validation Checks: The contract performs various validation checks to ensure that limit short orders meet specific criteria, such as minimum order size and collateral requirements. This helps maintain the integrity and stability of the market system.

  3. Oracle Integration: By interacting with an oracle, the contract obtains real-time price information, which is crucial for making informed decisions during the creation and processing of short orders. This enhances the accuracy and efficiency of the trading system.

  4. Dynamic Matching Algorithms: The contract includes dynamic matching algorithms that consider factors such as current market conditions and recovery mode violations when processing short orders. This adaptive approach ensures optimal execution of orders and mitigates risks associated with market volatility.

Overall, the contract plays a vital role in facilitating short selling activities within the market system, offering users a mechanism to profit from downward price movements while maintaining the stability and integrity of the overall protocol.

PrimaryLiquidationFacet.sol

Explanation of the contract: PrimaryLiquidationFacet. It is part of a larger system, likely a decentralized finance (DeFi) protocol, and specifically focuses on the primary method of liquidating short positions within this system. The contract facilitates the liquidation of short positions by forcing the shorter to place a bid on the market, effectively closing their position.

Functionality of the contract:

  1. Liquidation of Short Positions: The primary functionality of the contract is to liquidate short positions within the market system. When triggered, this contract forces the shorter to place a bid on the market to cover their short position. This process involves interacting with various components of the system, such as short records, asset storage, and the order book.

  2. Validation and Processing: Upon receiving a request for liquidation, the contract performs various validation checks to ensure the integrity and validity of the process. It checks factors such as the collateral ratio of the short position, the availability of eligible sell orders in the market, and the ability of the shorter to cover the bid cost.

  3. Forced Bid Execution: Once the necessary validations are complete, the contract executes the forced bid process. This involves setting up and executing a bid on behalf of the shorter to cover their short position. The shorter bears the cost of this forced bid on the market.

  4. Fee Handling: The contract handles the distribution of fees associated with the liquidation process. These fees include caller fees, TAPP (Teller Auction Pricing Protocol) fees, and gas fees. The contract ensures that fees are distributed appropriately between the TAPP and the caller, considering factors such as available collateral and the outcome of the liquidation.

  5. Accounting for Liquidation Results: Depending on whether the liquidation is successful or partial, the contract adjusts the relevant accounting entries accordingly. It updates the short records, disburse collateral, and handles the transfer of assets between parties involved in the liquidation process.

Key Features:

  1. Forced Bid Liquidation: The contract implements a forced bid mechanism to liquidate short positions in the market system. This mechanism ensures that shorters fulfill their obligations by placing bids to cover their short positions, even if they are unwilling or unable to do so voluntarily.

  2. Validation Checks: The contract performs thorough validation checks to ensure the integrity and validity of the liquidation process. It verifies factors such as collateral ratios, market conditions, and the availability of sell orders before proceeding with the liquidation.

  3. Fee Distribution: The contract handles the distribution of fees associated with the liquidation process, ensuring fair and transparent allocation of fees between the TAPP and the caller. This feature enhances the efficiency and reliability of the liquidation mechanism.

  4. Accounting for Results: Depending on the outcome of the liquidation process, the contract adjusts relevant accounting entries to reflect the results accurately. It handles full and partial liquidations differently, updating short records and collateral disbursements accordingly.

Overall, the contract plays a crucial role in maintaining the stability and integrity of the market system by facilitating the efficient and effective liquidation of short positions. It provides a robust mechanism for enforcing obligations and managing risk within the decentralized finance ecosystem.

BridgeRouterFacet.sol

Explanation of the contract: BridgeRouterFacet. It serves as a router for interacting with bridges in a decentralized finance (DeFi) protocol. The contract facilitates depositing and withdrawing assets (referred to as LST - Liquidity Staking Tokens) into and out of the protocol via various bridges. Bridges are smart contracts responsible for bridging assets between different blockchain networks.

Functionality of the contract:

  1. Bridge Management: The contract manages interactions with different bridges by facilitating depositing and withdrawing assets through these bridges. Each bridge corresponds to a specific asset or token, allowing users to interact with different assets supported by the protocol.

  2. Deposit Functionality: Users can deposit LST tokens into the protocol via specific bridges. The contract handles the deposit process, ensuring that the deposited tokens are converted into an equivalent amount of a protocol-specific asset called dETH (deposited ETH). The deposited assets are credited to the user's account within the protocol.

  3. Withdrawal Functionality: Users can withdraw dETH from the protocol, converting it back to LST tokens via the specified bridge. The contract manages the withdrawal process, ensuring that users receive the correct amount of LST tokens equivalent to their dETH holdings. Withdrawal credits may be applicable in certain scenarios, particularly for vaults holding multiple unique LST tokens.

  4. Bridge Management Functions: The contract includes functions for retrieving information about bridges associated with a specific vault, such as getting the present value of all bridges within a vault and fetching an array of bridge addresses for a given vault.

  5. Yield Rate Updates: The contract automatically updates the yield rate for a vault when bridge deposits are sufficiently large. This feature aims to deter attempts to take advantage of long delays between updates to the yield rate by creating large temporary positions.

Key Features:

  1. Bridge Interaction: The contract enables seamless interaction with bridges, allowing users to deposit and withdraw assets across different blockchain networks. This feature enhances interoperability and liquidity within the protocol by supporting various assets.

  2. Deposit and Withdrawal Management: The contract manages the deposit and withdrawal processes, ensuring accurate conversion between LST tokens and dETH assets. It also handles withdrawal credits for vaults holding multiple unique LST tokens, enhancing user experience and security.

  3. Automatic Yield Rate Updates: The contract includes a mechanism for automatically updating the yield rate for a vault based on the size of bridge deposits. This feature enhances the protocol's efficiency and responsiveness to market dynamics, ensuring optimal yield generation for users.

Overall, the contract serves as a critical component of the protocol's infrastructure, facilitating efficient asset bridging, deposit, and withdrawal operations while optimizing yield generation for users.

ExitShortFacet.sol

Explanation of the contract: ExitShortFacet, which is likely part of a larger DeFi protocol or system. Its primary purpose is to facilitate the exit of short positions within the system. The contract provides several methods for shorters to exit their positions based on their preferences and available assets.

Functionality of the contract:

  1. Exiting Short Positions:

    • The contract offers three main functions for exiting short positions:
      • exitShortWallet: Allows shorters to exit using ERC tokens from their wallet (e.g., MetaMask).
      • exitShortErcEscrowed: Enables shorters to exit using ERC tokens from their balance (ErcEscrowed).
      • exitShort: Facilitates exiting shorts by placing bids on the market.
  2. Partial Exit Support:

    • All exit functions allow for partial exits, where shorters can specify the amount of ERC tokens they want to buy back as part of the exit process.
  3. Validation and Processing:

    • The contract performs various validation checks before processing exit requests, ensuring that the exit conditions are met and the process is valid.
    • Checks include verifying the availability of sufficient ERC tokens for buyback, ensuring short positions are not frozen, and validating the integrity of short records.
  4. Bid Placement for Exit:

    • In the exitShort function, shorters can choose to exit their positions by placing bids on the market. This involves specifying a price at which they want to place the bid and providing hints for matching against shorts.
  5. Collateral Refund:

    • Upon successful exits, the contract handles the refund of collateral to shorters. If the ERC debt is fully paid back, the remaining collateral is refunded to the shorters, effectively closing their positions.

Key Features:

  1. Multiple Exit Methods:

    • Shorters have the flexibility to choose from multiple exit methods, allowing them to exit their positions based on their preferences and available assets.
  2. Partial Exit Support:

    • The contract supports partial exits, enabling shorters to manage their positions effectively and adjust their exposure to the market as needed.
  3. Validation Checks:

    • Thorough validation checks ensure the integrity and validity of the exit process, preventing erroneous or unauthorized exits and maintaining the stability of the system.
  4. Collateral Refund Mechanism:

    • The contract includes a mechanism for refunding collateral to shorters after successful exits, ensuring that shorters receive their assets in a timely and efficient manner.
  5. Bid Placement for Market Exit:

    • Shorters can utilize the bid placement functionality to exit their positions through market transactions, enhancing liquidity and market efficiency within the system.
RedemptionFacet.sol

Explanation of the contract: It appears to be a part of a decentralized application (DApp) or protocol related to financial markets or assets. The contract is specifically named "RedemptionFacet" suggesting its functionality is related to redemption processes within the protocol.

Functionality of the contract: The contract contains functions related to proposing, disputing, and claiming redemptions within the protocol. Users can propose redemptions for certain assets, which involves submitting a list of Short Records (SRs) as candidates for redemption along with other parameters. These proposals are subject to a dispute period. During the dispute period, users can challenge proposed redemptions if they believe there are inaccuracies or inconsistencies. After the dispute period, the redeemer can claim the redemption, and any leftover collateral from fully redeemed SRs can be claimed by the shorter.

Key Features:

  1. Propose Redemption: Users can propose redemptions by submitting a list of SRs as candidates for redemption along with other parameters such as redemption amount and maximum redemption fee. Collateral and debt are immediately removed from the SR candidates, and a redemption fee is calculated based on the amount redeemed.
  2. Dispute Redemption: During the dispute period, users can challenge proposed redemptions if they believe there are inaccuracies or inconsistencies. A penalty may be applied to incorrect proposals, and disputed redemptions may result in collateral and debt being returned to the shorter.
  3. Claim Redemption: After the dispute period has passed, the redeemer can claim the redemption. Any leftover collateral from fully redeemed SRs can be claimed by the shorter after the dispute period has elapsed.

Overall, the contract facilitates a redemption process within the protocol, allowing users to propose, dispute, and claim redemptions for assets, with mechanisms in place to address inaccuracies and ensure fairness in the redemption process.

libraries

/LibBridgeRouter.sol

Explanation of the library: The LibBridgeRouter library facilitates interactions related to the bridge mechanism within a decentralized finance (DeFi) protocol. It includes functions to handle adding and removing dETH (deposited ETH) from user accounts, assessing the amount of dETH not covered by bridge credits during withdrawal, calculating withdrawal fees, and transferring bridge credits between users.

Functionality of the library:

  1. Adding dETH (addDeth): This function credits a user's account with dETH and adjusts the bridge credit if applicable. It updates the total dETH held within the specified vault.

  2. Assessing dETH (assessDeth): This function determines how much dETH is not covered by bridge credits during withdrawal. It handles cases where users withdraw either RETH (redeemable ETH) or stETH (staked ETH) and calculates the remaining dETH that needs to be withdrawn from the vault.

  3. Calculating withdrawal fee percentage (withdrawalFeePct): This function calculates the withdrawal fee percentage based on the premium or discount differential between the market prices of RETH and stETH. The fee is charged to prevent free arbitrage and is applicable only to VAULT.ONE, which has mixed LST (liquidity staking tokens).

  4. Transferring bridge credit (transferBridgeCredit): This function transfers bridge credits from one user to another when an NFT (non-fungible token) short record (SR) is being transferred. It ensures that bridge credits are not abused and prevents workarounds to the bridge credit system.

  5. Removing dETH (removeDeth): This function updates the user's account upon dETH withdrawal, deducting the withdrawn amount along with any associated withdrawal fee from the user's ethEscrowed balance. It also updates the total dETH held within the vault.

Key Features:

  • Efficient Management of dETH: The library provides efficient functions for managing dETH within user accounts, including adding, assessing, and removing dETH based on various conditions.
  • Bridge Credit Handling: It includes functionality to handle bridge credits, ensuring they are used appropriately during withdrawals and transferred securely between users.
  • Withdrawal Fee Calculation: The library calculates withdrawal fees based on market premiums or discounts between RETH and stETH, contributing to maintaining the stability of the protocol and preventing arbitrage opportunities.
/LibBytes.sol

Explanation of the library: The LibBytes library contains a custom decoding function readProposalData designed to decode proposal data stored in a specific format within a byte array. This library is utilized to extract proposal data, which includes various parameters such as addresses, IDs, and numerical values, from a given byte array.

Functionality of the library:

  1. readProposalData Function: This function decodes proposal data stored in a byte array. It takes two parameters: the pointer to the SSTORE2 contract and the length of the slate (number of proposals). The function iterates over the byte array, extracting proposal data for each proposal and storing it in an array of MTypes.ProposalData structs.

    Inside the loop, it performs bitwise operations and assembly code to extract specific fields from the byte array and store them in the ProposalData struct. Each ProposalData struct contains fields such as shorter (an address), shortId (a single byte), CR (an unsigned 64-bit integer), ercDebtRedeemed (an unsigned 88-bit integer), and colRedeemed (an unsigned 88-bit integer).

Key Features:

  • Custom Decoding: The library provides a custom decoding mechanism tailored to the specific format of proposal data stored in the byte array. It allows efficient extraction of proposal parameters without relying on standard encoding formats like ABI encoding.
  • Efficient Assembly Code: The use of assembly code within the function contributes to optimized gas usage and efficient data extraction from the byte array.
  • Error Handling: The function includes error handling logic to ensure the integrity of the proposal data by verifying the length of the byte array and throwing an error if it does not match the expected length.
  • Flexibility: The library can be reused across contracts within the project to decode proposal data stored in the specified format, providing a modular and flexible solution for working with proposal data.
/LibOracle.sol

Explanation of the library:

The LibOracle library provides functionalities related to fetching and managing oracle prices for assets in a decentralized finance (DeFi) system. It integrates with Chainlink oracles and calculates prices based on various conditions, including circuit breakers to handle potential issues with oracle data.

Functionality of the library:

  1. getOraclePrice Function: This function retrieves the price of an asset from a specified oracle. It calculates the price based on the oracle's latest round data and adjusts it according to specific conditions. The function includes circuit breaker mechanisms to handle scenarios where oracle data might be unavailable or unreliable.

  2. baseOracleCircuitBreaker Function: This function serves as a circuit breaker for the base oracle. It evaluates whether the fetched data from the base oracle is valid and adjusts the price calculation accordingly. If there are issues with the base oracle, it falls back to using a time-weighted average price (TWAP) or another reliable source.

  3. oracleCircuitBreaker Function: Similar to baseOracleCircuitBreaker, this function acts as a circuit breaker for non-base oracles. It ensures that the fetched data from the oracle is valid and rejects it if there are any issues.

  4. twapCircuitBreaker Function: This function calculates the TWAP of an asset's price if there are issues with the oracle data. It verifies the TWAP price and liquidity before returning it as the final price.

  5. setPriceAndTime Function: This function sets the oracle price and time for an asset. It updates the oracle price and the time of the last update in the storage.

  6. getTime Function: Retrieves the time of the last oracle price update for a specific asset.

  7. getPrice Function: Retrieves the last recorded oracle price for a specific asset.

  8. getSavedOrSpotOraclePrice Function: This function allows callers to retrieve the saved oracle price if the last update occurred within a certain timeframe. If the last update is too old, it fetches the oracle price using the getOraclePrice function.

Key Features:

  • Integration with Chainlink Oracles: The library integrates with Chainlink oracles to fetch asset prices, ensuring reliable and accurate data for decentralized applications.
  • Circuit Breaker Mechanisms: The library includes circuit breakers to handle scenarios where oracle data might be unreliable or unavailable. It falls back to TWAP prices or other sources if issues arise with the primary oracle.
  • Price and Time Management: Functions are provided to set and retrieve the oracle price and time of the last update for each asset, facilitating transparency and accountability in price management.
  • Gas Optimization: The getSavedOrSpotOraclePrice function allows callers to save gas by retrieving the saved oracle price if it was recently updated, reducing the need for frequent oracle queries.
/LibOrders.sol

Explanation of the Library: LibOrders is a Solidity library designed to manage various aspects of trading orders within a decentralized trading system. It encompasses a range of functionalities crucial for maintaining an efficient and reliable trading environment on the blockchain. Let's break down its key features and functionalities:

  1. Order Management: LibOrders provides comprehensive support for managing different types of orders, including limit bids, asks, and shorts. It offers functions for adding new orders, canceling existing ones, and executing matches between compatible orders.

  2. Price and Time Handling: The library interacts with external oracles, such as LibOracle, to fetch real-time asset prices. It also utilizes timestamps for managing order durations and enforcing time-based conditions for order execution.

  3. Shares and Rewards Calculation: LibOrders calculates shares and rewards associated with order matches, considering various factors such as order duration, executed price, and market conditions. This ensures fair distribution of rewards among participants.

  4. Order Matching Logic: It contains sophisticated logic for matching incoming orders with existing ones in the order book. This logic ensures that trades are executed according to predefined market rules and order preferences, maintaining market integrity.

  5. Order Book Maintenance: LibOrders is responsible for maintaining the order book, ensuring that orders are correctly added, updated, or removed based on trading activities. This includes handling complexities such as order matching, price adjustments, and order lifecycle events.

Key Features:

  • Advanced Order Types: Supports advanced order types beyond basic buy and sell orders, including short selling. It incorporates specific logic tailored to handle the complexities of different order types within the trading system.

  • Decentralized Price Feeds: Utilizes decentralized oracles to fetch accurate and tamper-proof price information from external sources. This ensures that the trading system operates with reliable market data, crucial for fair and transparent trading.

  • Efficient State Management: Employs efficient state management techniques, such as the use of storage mappings and structs, to handle the substantial amount of data associated with orders, trades, and assets. This helps optimize gas usage and reduce transaction costs.

  • Event Emission: Emits events for significant actions and state changes within the library, providing transparency and enabling off-chain applications to react to on-chain activities effectively.

  • Gas Optimization: Implements strategies for gas optimization, such as reusing order IDs and optimizing storage patterns. These optimizations are essential for minimizing transaction costs and improving the overall efficiency of the trading system.

Overall, LibOrders plays a pivotal role in facilitating smooth and secure trading activities on the blockchain by providing essential functionalities for order management, price handling, and order book maintenance. Its comprehensive feature set ensures that the trading system operates efficiently and transparently, enhancing the overall trading experience for participants.

/LibSRUtil.sol

Explanation of the library:

The provided Solidity library, named LibSRUtil, serves as an extension to the existing functionality related to managing short positions within a decentralized trading system. It introduces additional helper functions to facilitate various operations involved in managing short positions and associated records.

Functionality of the library:

  1. Disbursement of Collateral: The disburseCollateral function facilitates the disbursement of collateral associated with short positions. It adjusts the collateral balances of both the vault and the asset while distributing any yield earned from the collateral to the appropriate parties.

  2. Cancellation and Verification of Short Orders: The checkCancelShortOrder function checks whether a short order can be canceled based on specific conditions. It verifies the status of the short order and the associated short record, allowing for cancellation under certain circumstances.

  3. Verification of Minimum ERC Debt: The checkShortMinErc function ensures that the ERC debt associated with a short position meets the minimum threshold required for the asset. It validates the remaining ERC debt against the minimum ERC threshold and cancels the short order if necessary.

  4. Recovery Mode Violation Check: The checkRecoveryModeViolation function checks for violations of recovery mode conditions. It examines the collateralization ratio of a short record against the recovery collateralization ratio for the asset, determining whether a violation has occurred.

  5. Transfer of Short Records: The transferShortRecord function facilitates the transfer of short records between different addresses. It handles the cancellation of associated short orders, updates record statuses, and transfers collateral balances accordingly.

  6. Update of ERC Debt: The updateErcDebt function updates the ERC debt associated with a short record based on the current ERC debt rate for the asset.

Key Features:

  • Enhanced Short Position Management: The library enhances the management of short positions within the trading system by providing additional functionalities for collateral disbursement, order cancellation, verification of minimum ERC debt, and recovery mode violation checks.

  • Improved Security and Reliability: By introducing verification mechanisms and checks for various conditions, the library helps ensure the integrity and security of short position management operations, reducing the risk of errors or malicious activities.

  • Streamlined Short Record Transfer: The transferShortRecord function streamlines the process of transferring short records between addresses, handling all necessary steps such as order cancellation and collateral transfer, thereby simplifying asset management operations for users.

  • Compatibility and Integration: The library is designed to seamlessly integrate with existing components and libraries within the trading system, providing an extension of functionality while maintaining compatibility with the broader ecosystem.

  • Efficient Resource Utilization: Through the use of optimized data structures and algorithms, the library aims to optimize gas usage and minimize transaction costs, ensuring efficient resource utilization within the decentralized trading platform.

/UniswapOracleLibrary.sol

Explanation of the library:

The provided Solidity library, named OracleLibrary, serves as a utility for interacting with Uniswap V3 pool oracles. It enables smart contracts to fetch price quotes and estimate Time-Weighted Average Prices (TWAPs) for token pairs traded on Uniswap V3 pools.

Functionality of the library:

  1. Quote Calculation: The getQuoteAtTick function calculates the amount of quote token received in exchange for a given amount of base token at a specified tick value. It leverages Uniswap V3 pool tick math to compute the quote amount with high precision, considering the square root ratio of the tick.

  2. TWAP Estimation: The estimateTWAP function estimates the Time-Weighted Average Price (TWAP) for a token pair within a specified time window. It retrieves tick cumulatives from the Uniswap V3 pool oracle, calculates the tick change over the time window, and derives the corresponding tick value. Then, it uses the tick value to compute the price quote for the given input amount of base token.

Key Features:

  • Integration with Uniswap V3 Pool Oracles: The library seamlessly integrates with Uniswap V3 pool oracles, allowing smart contracts to interact with these oracles to fetch price data and TWAP estimates.

  • Accurate Price Calculation: By utilizing tick math and tick cumulatives from Uniswap V3 pools, the library ensures accurate and precise calculation of token exchange rates and TWAPs, enhancing the reliability of price data for decentralized applications.

  • Support for Various Token Pairs: The library supports arbitrary token pairs traded on Uniswap V3 pools, enabling developers to fetch price quotes and estimate TWAPs for diverse sets of tokens within the ecosystem.

  • Robust Error Handling: The library includes error handling mechanisms to ensure that inputs are valid and operations are performed securely. It reverts transactions with invalid inputs or unexpected conditions, promoting robustness and reliability in smart contract interactions.

  • Efficiency and Gas Optimization: Through optimized algorithms and precise mathematical calculations, the library aims to minimize gas consumption and optimize resource utilization, making it suitable for use in gas-sensitive environments such as Ethereum smart contracts.

Architecture Recommendation

Architecture Description:

The Ditto Protocol's advanced architecture is intricately woven through its contract system, which forms the backbone of its decentralized stable asset ecosystem within Ethereum. These contracts are meticulously designed to handle various aspects of asset creation, trading, collateralization, liquidation, redemption, and bridge interactions. Each contract plays a vital role in ensuring the stability, efficiency, and security of the protocol.

Key Components of the Contract System:

  1. BidOrdersFacet:

    • Manages the creation, matching, and settlement of bid orders within the market environment.
    • Implements a sophisticated bid matching algorithm to ensure efficient and fair matching of bids with existing sell orders.
    • Provides comprehensive functionality for bid order management, including creation, matching, and settlement, thereby facilitating efficient trading activities.
  2. ShortOrdersFacet:

    • Focuses on managing short orders within the market system, allowing users to sell ERC tokens short.
    • Enables the creation of limit short orders and performs validation checks to ensure the integrity of the system.
    • Interacts with an oracle to obtain real-time price information, crucial for making informed decisions during short order creation and processing.
  3. PrimaryLiquidationFacet:

    • Facilitates the primary method of liquidating short positions within the system.
    • Forces shorters to place bids on the market to cover their short positions, ensuring obligations are met even if they are unwilling or unable to do so voluntarily.
    • Handles fee distribution associated with the liquidation process and adjusts accounting entries based on liquidation results.
  4. BridgeRouterFacet:

    • Serves as a router for interacting with bridges in the protocol, facilitating asset deposit and withdrawal operations.
    • Manages interactions with different bridges, allowing users to deposit and withdraw assets across different blockchain networks.
    • Optimizes yield generation for users by automatically updating the yield rate for a vault based on bridge deposits.
  5. ExitShortFacet:

    • Facilitates the exit of short positions within the system through multiple methods, including wallet-based exits, ERC token escrowed exits, and market-based exits.
    • Supports partial exits, allowing shorters to manage their positions effectively based on available assets and preferences.
    • Implements validation checks to ensure the integrity of the exit process and handles collateral refund upon successful exits.
  6. RedemptionFacet:

    • Facilitates proposing, disputing, and claiming redemptions within the protocol.
    • Users can propose redemptions for certain assets, subject to a dispute period, during which challenges to proposed redemptions can be made.
    • Implements mechanisms for collateral and debt adjustments based on redemption outcomes, ensuring fairness and integrity in the redemption process.

Architecture Recommendation:

Given the critical role of contracts in the Ditto Protocol, it's essential to adhere to the following architectural principles:

  1. Optimized Gas-Efficiency Enhancements:

    • Explore advanced gas-efficient design patterns such as batch processing and gas abstraction layers to optimize gas consumption across facets, minimizing transaction costs and enhancing protocol scalability.
  2. Enhanced Cryptographic Security Measures:

    • Implement advanced cryptographic techniques such as zero-knowledge proofs and multi-party computation to enhance protocol security, protecting sensitive user data and mitigating risks associated with potential security vulnerabilities.
  3. Intelligent Machine Learning Integration:

    • Explore the integration of intelligent machine learning algorithms to analyze market data trends, optimize bid matching algorithms, and enhance protocol decision-making processes, fostering adaptive and data-driven market operations.
  4. Dynamic Scalability Solutions:

    • Investigate dynamic scalability solutions such as state channel networks and optimistic rollups to enhance protocol scalability, enabling efficient processing of high-volume transactions and accommodating future growth in user demand.
  5. Advanced User Experience Optimization:

    • Utilize advanced user experience optimization techniques such as human-centered design principles and user behavior analytics to iteratively enhance user interfaces and streamline user interactions, fostering intuitive and engaging user experiences.
  6. Real-Time Market Surveillance Mechanisms:

    • Develop real-time market surveillance mechanisms leveraging advanced data analytics and anomaly detection algorithms to monitor market activities, detect irregularities, and mitigate potential risks such as market manipulation and insider trading.

Codebase Quality Analysis

In this table:

  • "High" indicates contracts with excellent code quality across multiple aspects.
  • "Moderate" indicates contracts with satisfactory code quality but with areas for improvement.
  • "Low" indicates contracts with notable deficiencies or lacking in certain aspects of code quality.
ContractCode MaintainabilityCode CommentsDocumentationError HandlingImports
BidOrdersFacetHighModerateLowHighModerate
BridgeRouterFacetHighHighModerateHighModerate
ExitShortFacetHighModerateModerateHighModerate
PrimaryLiquidationFacetHighModerateModerateHighModerate
RedemptionFacetModerateHighLowHighModerate
ShortOrdersFacetHighHighLowHighHigh
<details> <summary>Click Here For details Codebase Quality Analysis </summary>
ContractCode MaintainabilityCode CommentsDocumentationError HandlingImports
BidOrdersFacetThe code appears well-structured and modular, with separate functions for different tasks. Comments exist to describe the purpose of functions, parameters, and important logic. However, some functions lack detailed comments, such as _shortDirectionHandler, which could benefit from more descriptive explanations. Overall, the codebase seems maintainable.Comments aid in understanding the code, providing insights into the purpose of functions, parameters, and key logic. While most functions have sufficient comments, some areas could enhance clarity, especially regarding complex algorithms or decision-making processes.The code lacks formal documentation such as a README file or inline documentation using tools like NatSpec. Formal documentation could provide an overview of the contract, its functions, and usage instructions, making it easier for developers to understand and integrate the contract into their projects.Error handling mechanisms are implemented using Solidity's revert statement to revert transactions in case of errors. Error messages are thrown using custom error codes defined in the Errors library, enhancing reliability by ensuring transactions fail gracefully when encountering exceptional conditions.The contract imports various libraries and interfaces from other contracts/modules, essential for accessing shared functionality and ensuring modularity. However, it's important to verify the integrity and security of the imported code to mitigate potential risks such as dependency vulnerabilities.
BridgeRouterFacetThe codebase appears well-maintained and reliable, following Solidity best practices, such as using libraries for common functionality and employing modifiers for access control. The use of using directives indicates a thoughtful approach to code organization and readability.Code comments provide explanations for various functions, parameters, and implementation details, enhancing code comprehension and maintainability. Comments follow Solidity's documentation style, improving readability and developer experience.Documentation within the code is sufficient for understanding the purpose and functionality of different functions and modifiers. However, additional high-level documentation describing the overall architecture and design decisions would be beneficial for onboarding new developers and maintaining the codebase in the long term.Error handling is implemented effectively throughout the codebase, with functions reverting with specific error messages when encountering invalid inputs or exceptional conditions, ensuring robustness and user experience.The code imports external Solidity files using relative paths, maintaining clarity and ensuring modularity. This approach simplifies dependency management and facilitates code reuse. However, ensuring that imported contracts are up-to-date and secure is essential for mitigating potential vulnerabilities.
ExitShortFacetThe codebase demonstrates a good level of maintainability and reliability, adhering to Solidity best practices by using libraries for common functionality, employing modifiers for access control, and utilizing descriptive function names. However, some functions could benefit from further modularization and abstraction to reduce complexity and enhance readability.Comments exist to explain the purpose, behavior, and parameters of various functions, aiding code comprehension and maintenance. However, some complex or critical sections could benefit from more detailed explanations to clarify intricate logic or edge cases.The codebase provides documentation within comments to describe the intent and usage of different functions. While these comments enhance code comprehension, additional high-level documentation outlining the overall architecture, design decisions, and interaction flow would improve developers' understanding of the system's structure and operation.Error handling mechanisms are implemented effectively throughout the codebase, with functions reverting with specific error messages when encountering invalid inputs or exceptional conditions, ensuring robustness and user-friendly error messages. Further validation checks and defensive programming could be applied to anticipate and prevent potential edge cases or vulnerabilities.The code imports external Solidity files using relative paths, promoting clarity and modularity. However, ensuring that imported contracts are up-to-date and secure is crucial for mitigating potential vulnerabilities and maintaining compatibility with future Solidity versions. Additionally, organizing imports alphabetically can enhance readability and maintainability.
PrimaryLiquidationFacetThe codebase demonstrates a good level of maintainability and reliability, adhering to Solidity best practices by utilizing libraries for common functionality, employing modifiers for access control, and utilizing descriptive function names. However, some functions could benefit from further modularization and abstraction to reduce complexity and enhance readability.Code comments explain the purpose, behavior, and parameters of various functions, enhancing code comprehension and facilitating maintenance tasks. Comments clarify the logic behind liquidation procedures, error handling, and interaction with external contracts. However, additional comments could be provided to elucidate complex algorithms or critical sections further.The codebase provides documentation within comments to describe the intent and usage of different functions. High-level comments outline the purpose of primary liquidation and its key components, such as forced bids, fee handling, and collateral accounting. However, supplementing these comments with detailed explanations of data structures and algorithmic processes would further aid developers' understanding.Error handling mechanisms are implemented effectively throughout the codebase, with functions reverting with specific error messages (e.g., Errors.CannotLiquidateSelf(), Errors.NoSells()) when encountering invalid inputs, exceptional conditions, or contract violations. This ensures robustness and provides users with clear feedback on transaction failures or unauthorized operations.The code imports external Solidity files using relative paths, promoting clarity and modularity. However, ensuring that imported contracts are up-to-date and secure is crucial for mitigating potential vulnerabilities and maintaining compatibility with future Solidity versions.
RedemptionFacetThe codebase demonstrates a moderate level of maintainability and reliability, with a consistent coding style and structure. Usage of libraries for common functionalities enhances reliability. However, some areas may benefit from additional comments or code documentation to improve maintainability, especially for complex logic or algorithm implementations. Overall, the code appears stable and reliable, with no evident issues or bugs.Comments are present throughout the codebase, providing explanations for functions, variables, and complex logic. They help improve code readability and understanding, aiding future maintenance and collaboration efforts. The comments follow best practices, offering insights into the purpose, behavior, and usage of various components. Enhancing comments with additional details or examples could further improve clarity.While the code includes inline comments, comprehensive external documentation describing the architecture, design decisions, and usage guidelines is not provided. External documentation, such as README files or developer guides, can enhance understanding and facilitate onboarding for new contributors or users. Including information on dependencies, deployment procedures, and API usage would be beneficial for users and developers alike.Error handling mechanisms are implemented effectively throughout the codebase, with various functions including error checks and revert statements to ensure proper execution and prevent unexpected behavior. Error messages are informative, helping users identify and address issues effectively. The codebase handles common error scenarios gracefully, promoting robustness and reliability.The codebase imports necessary external dependencies and libraries efficiently, utilizing import statements to include external contracts and libraries, ensuring modularity and reusability. Grouping related imports together enhances readability and organization. However, organizing imports alphabetically or categorically could further improve clarity and maintainability.
ShortOrdersFacetThe codebase exhibits a high level of maintainability and reliability, following best practices for code organization and readability. The use of modular design patterns and descriptive function names enhances maintainability by facilitating code navigation and comprehension. Furthermore, the codebase employs error handling mechanisms to handle unexpected scenarios gracefully, contributing to its overall reliability. Overall, the code is well-structured and easy to maintain.Comprehensive inline comments are provided throughout the codebase, offering insights into the purpose, behavior, and usage of various components. These comments enhance code readability and understanding, aiding developers in navigating and modifying the code. Additionally, the comments adhere to best practices, providing clarity and context for complex logic and algorithm implementations. Overall, the codebase is well-documented, facilitating future maintenance and collaboration efforts.While the code includes detailed inline comments, comprehensive external documentation describing the architecture, design decisions, and usage guidelines is not provided. External documentation, such as README files or developer guides, can enhance understanding and facilitate onboarding for new contributors or users. Including information on dependencies, deployment procedures, and API usage would be beneficial for users and developers alike.The codebase employs robust error handling mechanisms to handle various scenarios effectively. Revert statements are used strategically to revert transactions in case of invalid inputs or unexpected conditions, preventing erroneous state changes. Error messages are informative and descriptive, helping users identify and address issues efficiently. Additionally, error checks are performed diligently, reducing the likelihood of runtime errors and enhancing overall reliability.The codebase efficiently imports necessary external dependencies and libraries. Import statements are organized logically, enhancing readability and maintainability. Grouping related imports together promotes clarity and reduces cognitive overhead. However, organizing imports alphabetically or categorically could further improve consistency and ease of navigation.

This table provides a detailed breakdown of the quality analysis for each contract, covering code maintainability, code comments, documentation, error handling, and imports.

</details>

Security Concerns

contracts

facets

BidOrdersFacet.sol

Security Concerns:

  1. Input Validation:

    • The contract should enforce strict input validation to prevent unexpected behavior or vulnerabilities. Ensure that all function parameters are validated to avoid issues such as integer overflow/underflow, invalid addresses, or incorrect asset amounts. For instance, the createBid function should validate parameters like price and ercAmount to ensure they are within acceptable ranges and adhere to the contract's requirements.
  2. Reentrancy:

    • Reentrancy vulnerabilities can arise if the contract interacts with external contracts or sends Ether within its functions. Consider implementing the nonReentrant modifier or similar patterns to prevent reentrancy attacks. Pay special attention to functions like matchIncomingBid, matchlowestSell, and _createBid, as they involve state modifications and external calls.
  3. Oracle Manipulation:

    • Contracts relying on external oracles, such as LibOracle.getPrice, should implement measures to mitigate oracle manipulation attacks. Ensure that the oracle data is securely sourced and validated to prevent malicious actors from influencing market prices or exploiting inaccuracies in the oracle's data feed.
  4. Access Control:

    • Review the access control mechanisms to ensure that only authorized users or contracts can execute sensitive functions. Functions like createForcedBid should be restricted to specific contracts or addresses to prevent unauthorized access and potential abuse of privileged functionalities.
  5. Gas Limitations:

    • Gas limitations should be carefully considered, especially in loops or complex algorithms like bidMatchAlgo. Ensure that the contract remains usable within the Ethereum gas limits to prevent out-of-gas errors and ensure smooth execution under various network conditions.
  6. Library and Dependency Security:

    • Validate the security of imported libraries and dependencies, such as LibOrders, LibOracle, and LibAsset, to mitigate the risk of potential vulnerabilities inherited from external code. Conduct thorough code reviews and consider auditing the dependencies for security vulnerabilities and best practices adherence.
  7. Front-Running:

    • Implement measures to prevent front-running attacks, particularly in functions involving price-sensitive transactions like bidMatchAlgo. Consider techniques such as using commit-reveal schemes or adjusting the order of operations to minimize the impact of front-running strategies and ensure fair execution of orders.
  8. Consistency and Atomicity:

    • Ensure that critical operations are executed atomically or in a consistent state to prevent race conditions and maintain the integrity of the contract's state. Functions like matchIncomingBid and matchlowestSell should handle state updates and interactions with external contracts in a manner that guarantees consistency and atomicity.

By addressing these security concerns and conducting thorough testing and auditing, developers can enhance the robustness and security posture of the BidOrdersFacet contract, mitigating potential risks and vulnerabilities in its implementation.

BridgeRouterFacet.sol

Security Concerns:

The provided Solidity code exhibits several security considerations:

  1. Input Validation: The contract validates inputs such as deposit amounts to prevent underflows or malicious behavior. For instance, the deposit and depositEth functions verify that the deposited amount meets a minimum threshold (C.MIN_DEPOSIT) before proceeding.

  2. Reentrancy Protection: The nonReentrant modifier is used to prevent reentrancy attacks, ensuring that functions cannot be called recursively and protecting against potential exploits involving repeated interactions with the contract's state.

  3. Access Control: Certain functions, such as withdrawTapp, are restricted to specific roles (onlyDAO), limiting their execution to authorized entities and preventing unauthorized access to critical functionalities.

  4. Safe Math Operations: The code employs safe arithmetic operations (mul, div) to prevent overflows and underflows, reducing the risk of integer vulnerabilities.

  5. External Contract Interaction: Interactions with external contracts (IBridge) are conducted cautiously, with appropriate error handling and checks to ensure the integrity of cross-contract calls.

  6. Constant Gas Usage: Functions are designed to consume predictable and reasonable amounts of gas, mitigating the risk of DoS attacks by ensuring that contract execution costs remain within acceptable bounds.

Despite these measures, security is an ongoing concern in decentralized applications. Regular audits, peer reviews, and adherence to best practices can further strengthen the security posture of the contract. Additionally, staying informed about emerging vulnerabilities and promptly addressing any identified issues are crucial for maintaining a secure and robust smart contract ecosystem.

ExitShortFacet.sol

Security Concerns:

The provided Solidity code exhibits several security considerations:

  1. Input Validation: The contract validates inputs, such as buybackAmount, to prevent underflows, overflows, and unexpected behavior. Functions revert with specific error messages (Errors.InvalidBuyback(), Errors.InsufficientCollateral(), etc.) when encountering invalid or unauthorized operations.

  2. Reentrancy Protection: The nonReentrant modifier is utilized to prevent reentrancy attacks, ensuring that functions cannot be called recursively and protecting against potential exploits involving repeated interactions with the contract's state.

  3. Access Control: Certain functions are restricted to authorized entities, such as valid short record owners or non-frozen assets, using modifiers (onlyValidShortRecord, isNotFrozen, etc.), enhancing security by limiting access to critical functionalities.

  4. Safe Math Operations: The code employs safe arithmetic operations (mul, div) to prevent overflows and underflows, reducing the risk of integer vulnerabilities and ensuring the correctness of financial calculations.

  5. External Contract Interaction: Interactions with external contracts (IDiamond) are conducted with caution, ensuring proper error handling and validation checks to maintain the integrity of cross-contract calls and prevent potential exploits or unauthorized operations.

  6. Collateral Ratio Checks: The contract includes checks to ensure that collateral ratios remain within acceptable bounds (getCollateralRatioNonPrice), mitigating the risk of under-collateralization and protecting users' assets from liquidation or loss.

  7. Atomic Operations: To prevent race conditions and ensure atomicity, certain operations are executed atomically within the same function call, reducing the risk of concurrency-related vulnerabilities.

Despite these measures, security is an ongoing concern in decentralized applications. Regular audits, thorough testing, and adherence to best practices are essential for maintaining a secure and robust smart contract ecosystem. Additionally, staying informed about emerging vulnerabilities and promptly addressing any identified issues are crucial for safeguarding users' funds and maintaining trust in the protocol.

PrimaryLiquidationFacet.sol

Security Concerns:

  1. Input Validation: The contract validates inputs, such as shortHintArray length, to prevent potential exploits or unexpected behavior. It ensures that parameters meet specified criteria and reverts transactions with specific error messages (Errors.TooManyHints()) if validation fails, mitigating the risk of malicious inputs.

  2. Reentrancy Protection: The nonReentrant modifier is utilized to prevent reentrancy attacks, ensuring that functions cannot be called recursively and protecting against potential exploits involving repeated interactions with the contract's state. This helps safeguard against reentrancy vulnerabilities and unauthorized reentrant calls.

  3. Access Control: Certain functions are restricted to authorized entities, such as valid short record owners or non-frozen assets, using modifiers (onlyValidShortRecord, isNotFrozen), enhancing security by limiting access to critical functionalities. Access control mechanisms prevent unauthorized users from executing privileged operations, reducing the risk of unauthorized asset manipulation.

  4. Safe Math Operations: The code employs safe arithmetic operations (mulU80, mulU88) to prevent overflows and underflows, reducing the risk of integer vulnerabilities and ensuring the correctness of financial calculations. Safe math operations mitigate the risk of arithmetic exceptions and ensure accurate computation of liquidation fees, bid amounts, and collateral adjustments.

  5. External Contract Interaction: Interactions with external contracts (IDiamond) are conducted with caution, ensuring proper error handling and validation checks to maintain the integrity of cross-contract calls and prevent potential exploits or unauthorized operations. Contract interactions are carefully managed to prevent unauthorized access or manipulation of external contract state, enhancing security and reliability.

Centralization Risks

BidOrdersFacet.sol

Centralization Risks:

  • Diamond Standard Dependency: The contract's reliance on the IDiamond interface introduces centralization risks if the referenced Diamond Standard implementation holds significant control over critical functions or resources. This dependency could lead to centralization of control, making the system vulnerable to manipulation by the owner or administrator of the Diamond Standard contract.

  • Admin Functions: The presence of functions like createForcedBid accessible only to specific contracts hints at potential centralization of authority or privileges. If these admin functions grant excessive control to specific entities, it may centralize power, leading to potential misuse or exploitation.

BridgeRouterFacet.sol

Centralization Risks:

  • Bridge Centralization: The contract's interaction with bridge contracts (IBridge) for asset deposits and withdrawals introduces centralization risks if these bridges are controlled by a single entity. Dependency on centralized bridges could lead to manipulation or disruptions in asset transfers.

  • Admin Privileges: Privileged functions such as withdrawTapp, limited to specific entities like a DAO, pose centralization risks if governance becomes centralized over time. Misuse of these privileges could lead to unfair advantages or protocol manipulation.

ExitShortFacet.sol

Centralization Risks:

  • Market Control: Control over markets and short positions by a few entities may lead to manipulation or exploitation of vulnerabilities. Concentrated influence could disrupt market dynamics or unfairly advantage certain participants.

  • Admin Privileges: Functions like exitShortWallet and exitShortErcEscrowed, accessible under specific conditions, pose centralization risks if abused. Misuse of these privileges could result in protocol manipulation or disruptions in short position exits.

PrimaryLiquidationFacet.sol

Centralization Risks:

  • Market Control: The contract's role in facilitating short position liquidations may pose risks if control over liquidations is centralized. This concentration of control could enable market manipulation or unfair treatment of participants.

  • Admin Privileges: Certain functions restricted to specific entities may centralize authority. If not decentralized, these privileges could lead to biased outcomes or disruptions in the liquidation process.

RedemptionFacet.sol

Centralization Risks:

  • Admin Controls: Privileged admin functions could centralize control over critical functionalities, potentially leading to biased outcomes or exploitation.

  • Oracle Dependency: Reliance on a single oracle or a small set of oracles introduces centralization risks. Manipulation or failure of oracles could disrupt the system's operations or compromise data integrity.

  • Vault Management: Centralized management of vaults or collateral assets may pose risks to system stability and security. Decentralized control mechanisms should be implemented to mitigate these risks.

  • Governance Structure: Lack of decentralized governance mechanisms or concentration of governance power may lead to biased decision-making or resistance to beneficial changes.

ShortOrdersFacet.sol

Centralization Risks:

  • Restricted Functions: The presence of functions restricted to specific conditions or entities may centralize control. This restriction could lead to biased outcomes or disruptions if not adequately decentralized.

This report outlines the centralization risks inherent in each contract, emphasizing potential vulnerabilities arising from dependencies, privileged functions, and governance structures. Addressing these risks is crucial to ensuring the resilience and fairness of the system.

Mechanism Review

BidOrdersFacet.sol

Order Matching Algorithm: The implemented bid matching algorithm (bidMatchAlgo) requires meticulous scrutiny to ensure it effectively matches bid orders with appropriate sell orders. It is imperative to verify that the algorithm operates efficiently and accurately while safeguarding against vulnerabilities such as front-running or order manipulation.

Oracle Integration: The contract's interaction with an oracle for price information necessitates a comprehensive evaluation of the integration's reliability, accuracy, and security. Additionally, measures should be in place to gracefully handle oracle failures or manipulation to maintain the integrity of price data.

BridgeRouterFacet.sol

Bridge Interaction Mechanism: Functions facilitating asset deposits and withdrawals via bridge contracts (IBridge) demand a robust review to ascertain seamless asset transfers while mitigating risks such as front-running, reentrancy attacks, or unexpected behavior during deposit or withdrawal operations.

Yield Rate Update Mechanism: The inclusion of a mechanism (maybeUpdateYield) for automatically updating the vault yield rate based on bridge deposits necessitates assessment regarding its effectiveness and fairness in maintaining a balanced yield rate. It is crucial to deter attempts to manipulate the yield through large temporary positions.

ExitShortFacet.sol

Short Position Exit Mechanisms: Various mechanisms for exiting short positions, including the use of ERC tokens from wallets or held in escrow, require evaluation to ensure effectiveness, security, and fairness. It is imperative to prevent unauthorized access and minimize the risk of asset loss while facilitating partial or full exits.

Bid Placement Mechanism: The mechanism for placing bids on markets to exit short positions warrants scrutiny to ensure accurate reflection of market conditions, prevention of front-running or manipulation, and facilitation of fair and efficient short position exits.

PrimaryLiquidationFacet.sol

Liquidation Mechanism: The mechanism for liquidating short positions by compelling the shorter to place bids on the market necessitates assessment regarding its effectiveness, fairness, and security. It is crucial to facilitate timely and efficient short position exits while minimizing the risk of market manipulation or abuse.

Fee Handling Mechanism: Mechanisms for handling liquidation fees and distributing them among different parties involved in the process require scrutiny to ensure transparency, fairness, and efficiency in fee distribution. Preventing abuse or disruptions in the fee allocation process is essential.

RedemptionFacet.sol

Redemption Proposal Submission: The mechanism allowing users to submit proposals for redeeming assets necessitates validation to ensure accurate proposal evaluation and fee calculations.

Proposal Evaluation: Evaluation of proposed redemption candidates based on criteria such as collateral ratio and thresholds requires scrutiny to ensure accurate evaluation and inclusion criteria.

Dispute Resolution: Handling of disputes triggered by users challenging proposed redemptions warrants assessment to ensure proper dispute resolution and penalties for incorrect proposals.

Redemption Claim: Verification of proper processing of claims for redeemed assets along with any remaining collateral is necessary to ensure system integrity.

Redemption Fee Calculation: The calculation of redemption fees based on various factors requires thorough review to ensure accurate fee calculation considering all relevant factors.

ShortOrdersFacet.sol

Operational Flow and Logic: Functions such as createLimitShort necessitate review to ensure correct handling of edge cases and prevention of vulnerabilities such as reentrancy attacks or unexpected state changes. Thorough scrutiny of the operational flow and logic is essential to identify potential inefficiencies or vulnerabilities.

This report provides a structured analysis of each contract's mechanisms, highlighting areas that require careful review to ensure the system operates as intended while mitigating potential risks.

Risk Analysis

This report provides an in-depth analysis of various risks associated with the contracts and facets within the provided codebase. The risks are categorized into four main categories: Systemic Risks, Admin Abuse Risks, Technical Risks, and Integration Risks.

BidOrdersFacet.sol

Systemic Risks:

  • Order Book Management: The management of the order book could pose systemic risks if vulnerabilities exist in order prioritization, matching algorithms, or order manipulation, potentially affecting the entire trading system.

  • Vault Operations: Flaws in vault operations, such as inadequate collateralization or inefficient liquidation mechanisms, could introduce systemic risks impacting the stability and integrity of the protocol.

Admin Abuse Risks:

  • Forced Bid Creation: The presence of admin functions like createForcedBid may lead to admin abuse if misused to manipulate market conditions or exploit users unfairly.

  • Escrowed Ether Handling: Improper management of escrowed ether balances could result in admin abuse if unauthorized parties gain access to or manipulate these balances.

Technical Risks:

  • Reentrancy Vulnerabilities: Complex order matching and settlement logic may introduce reentrancy vulnerabilities, potentially leading to exploits or unexpected behavior during order execution.

  • Gas Optimization: Inefficient gas usage or storage operations could increase transaction costs or lead to contract execution failures.

Integration Risks:

  • External Contract Dependencies: Integration with external contracts could introduce compatibility issues or vulnerabilities that impact protocol functionality or security.

  • Oracle Dependency: Reliance on external oracles for price data may introduce risks related to oracle reliability, accuracy, and security.

BridgeRouterFacet.sol

Systemic Risks:

  • Vault Management System: Flaws in vault management or bridge interactions could pose systemic risks impacting asset transfers and yield rate adjustments.

  • Bridge Dependency: Dependency on external bridges could introduce systemic risks related to bridge failures, network congestion, or vulnerabilities in bridge contracts.

Admin Abuse Risks:

  • Privileged Functions: Admin functions like withdrawTapp may lead to admin abuse if misused by DAO governance to unfairly benefit specific entities or groups.

  • Eth Escrow Handling: Mishandling of eth escrow balances may result in admin abuse if unauthorized parties access or manipulate these balances.

Technical Risks:

  • Reentrancy Vulnerabilities: Complex deposit and withdrawal logic may introduce reentrancy vulnerabilities, leading to asset loss or protocol exploits.

  • Gas Optimization: Inefficient gas usage could increase transaction costs or lead to contract execution failures.

Integration Risks:

  • Bridge Contract Dependencies: Integration with external bridge contracts may introduce compatibility issues or vulnerabilities impacting asset transfers or protocol functionality.

ExitShortFacet.sol

Systemic Risks:

  • Market Dynamics: Inadequacies in market mechanisms or short position management could introduce systemic risks impacting market stability and protocol integrity.

  • Protocol Integrity: Flaws in protocol design or governance may lead to systemic failures affecting all participants and assets within the system.

Admin Abuse Risks:

  • Privileged Functions: Admin functions restricted to specific entities may result in admin abuse if misused to manipulate markets or exert excessive control over protocol operations.

  • Asset Management: Mismanagement of assets or short positions by entities with administrative privileges may lead to asset loss or unfair treatment of users.

Technical Risks:

  • Smart Contract Vulnerabilities: Complex logic for exiting short positions may introduce vulnerabilities such as reentrancy attacks or unexpected state changes.

  • External Contract Dependencies: Reliance on external contracts for market interactions may introduce vulnerabilities impacting protocol functionality or security.

Integration Risks:

  • External Contract Interactions: Interactions with external contracts may introduce compatibility issues or vulnerabilities impacting protocol operations or security.

  • Protocol Upgrades: Upgrades or modifications to external protocols may introduce compatibility issues or disruptions in existing functionalities.

PrimaryLiquidationFacet.sol

Systemic Risks:

  • Market Stability: Flaws in the liquidation process or collateral management could introduce systemic risks impacting market stability and protocol integrity.

  • Protocol Integrity: Design flaws or governance issues may lead to systemic failures affecting all users and assets relying on the protocol.

Admin Abuse Risks:

  • Privileged Functions: Admin functions restricted to specific entities may result in admin abuse if misused to manipulate markets or exert control over the liquidation process.

  • Asset Management: Mismanagement of assets or collateral by entities with administrative privileges may lead to asset loss or unfair treatment of users.

Technical Risks:

  • Smart Contract Vulnerabilities: Vulnerabilities in the liquidation process may introduce exploits or disruptions impacting asset values or protocol integrity.

  • External Contract Dependencies: Integration with external contracts may introduce vulnerabilities impacting protocol functionality or security.

Integration Risks:

  • External Contract Interactions: Interactions with external contracts may introduce compatibility issues or vulnerabilities impacting protocol operations or security.

  • Protocol Upgrades: Upgrades to external protocols may introduce compatibility issues or disruptions in existing functionalities.

ShortOrdersFacet.sol

Systemic Risks:

  • Systemic Risks: Vulnerabilities in dependencies, flaws in the contract architecture, or Ethereum platform vulnerabilities could compromise the entire system's integrity or functionality.

Admin Abuse Risks:

  • Admin Abuse Risks: Certain functions restricted to specific roles or addresses may concentrate power in the hands of contract administrators,

leading to potential misuse or exploitation.

Technical Risks:

  • Technical Risks: Vulnerabilities in Solidity code, incorrect usage of external libraries, or gas optimization issues could compromise the smart contract's security, reliability, or performance.

Integration Risks:

  • Integration Risks: Integration with external systems or protocols may introduce compatibility issues, data mismatches, or communication failures, impacting protocol functionality or security.

This comprehensive risk analysis provides valuable insights into the potential vulnerabilities and threats associated with each contract and facet in the provided codebase. Mitigating these risks requires a holistic approach encompassing rigorous testing, adherence to best practices, and continuous monitoring and improvement of the system's security, reliability, and performance.

Time spent:

25 hours

#0 - c4-pre-sort

2024-04-07T20:27:37Z

raymondfam marked the issue as sufficient quality report

#1 - c4-judge

2024-04-17T07:16:36Z

hansfriese marked the issue as grade-b

#2 - albahaca0000

2024-04-17T13:29:42Z

Hi @hansfriese thanks for judging


I wish to express my gratitude for your consideration of my analysis. I firmly believe that it adheres closely to the code4rena judging criteria, offering a comprehensive examination of the risks associated with each contract. These encompass systemic risks, technical risks, integration risks, and concerns regarding centralization or admin abuse.

Highlighted areas of focus: - Comprehensive depiction of the project’s risk framework: - Risks related to admin abuse - Systemic risks - Technical risks - Integration risks

Within this assessment, I've strived to provide a robust security analysis of each contract, addressing systemic risk, integration risk, technical risk, and centralization risk or admin abuse risk. This evaluation is grounded in thorough data analysis.

I kindly request that you review other sections of the report, such as the Codebase Quality Analysis and Security Concerns, as well as the 'How the Contract Works' section. These sections provide further insights into the meticulous examination conducted and contribute to the holistic understanding of the project's intricacies, Please take another look at my report to see the considerable effort put into its completion.

Thank you for your judging.

#3 - hansfriese

2024-04-21T07:57:13Z

After checking again, I agree it deserves a grade-a.

#4 - c4-judge

2024-04-21T07:57:22Z

hansfriese marked the issue as grade-a

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter