DittoETH - pfapostol'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: 40/43

Findings: 1

Award: $17.99

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

Awards

17.9877 USDC - $17.99

Labels

bug
grade-b
QA (Quality Assurance)
sponsor acknowledged
sufficient quality report
Q-07

External Links

The content section shows only 10 examples, subsequent cases are listed as links.

Summary

Low Risk Issues

IssueInstances
[L-01]Chainlink oracle will return the wrong price if the aggregator hits minAnswer3
[L-02]Array lengths not checked3
[L-03]Code does not follow the best practice of check-effects-interaction23
[L-04]Consider bounding input array length3
[L-05]Privileged functions can create points of failure3

NonCritical Risk Issues

IssueInstances
[N-01]Functions which are either private or internal should have a preceding _ in their name65
[N-02]Private and internal state variables should have a preceding _ in their name unless they are constants4
[N-03]Emits without msg.sender parameter1
[N-04]A function which defines named returns in it's declaration doesn't need to use return22
[N-05]Use multiple require() and if statements instead of &&29
[N-06]Constants in comparisons should appear on the left side98
[N-07]Variable names that consist of all capital letters should be reserved for constant/immutable variables4
[N-08]Consider using delete rather than assigning zero/false to clear values17
[N-09]Large numeric literals should use underscores for readability12
[N-10]Unused contract variables13
[N-11]else-block not required38
[N-12]Convert simple if-statements to ternary expressions28
[N-13]Remaining eth may not be refunded to users1
[N-14]External calls in an un-bounded for-loop may result in a DOS4
[N-15]constructor missing zero address check3
[N-16]Variable initialization with default value6
[N-17]Large multiples of ten should use scientific notation24
[N-18]Complex math should be split into multiple steps4
[N-19]Duplicated require/if statements should be refactored54
[N-20]Variable names don't follow the Solidity naming convention37
[N-21]Contract functions should use an interface17
[N-22]State variables should include comments4
[N-23]Consider implementing two-step procedure for updating protocol addresses6
[N-24]Constant redefined elsewhere2
[N-25]Function names should use lowerCamelCase3
[N-26]Overflows in unchecked blocks1
[N-27]Events may be emitted out of order due to reentrancy10
[N-28]Function can return unassigned variable2
[N-29]Simplify complex require statements1
[N-30]Events should be emitted before external calls12
[N-31]Contract name does not follow the Solidity Style Guide1
[N-32]Variable names for immutables should use UPPER_CASE_WITH_UNDERSCORES4
[N-33]Contracts should have full test coverage1
[N-34]Large or complicated code bases should implement invariant tests1
[N-35]Codebase should implement formal verification testing1
[N-36]Unsafe uint to int conversion2
[N-37]State variables not capped at reasonable values7
[N-38]Consider adding a block/deny-list1
[N-39]Typos2
[N-40]Alternative Solady library can be used instead of OpenZeppelin to save gas1
[N-41]Setting the constructor to payable3
[N-42]++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops8
[N-43]Internal functions only called once can be inlined to save gas14
[N-44]Usage of uint/int smaller than 32 bytes (256 bits) incurs overhead191
[N-45]>= costs less gas than >26
[N-46]Using storage instead of memory for structs/arrays saves gas1
[N-47]Stack variable used as a cheaper cache for a state variable is only used once47
[N-48]Newer versions of solidity are more gas efficient12
[N-49]Consider activating via-ir for deploying1
[N-50]Function calls should be cached instead of re-calling the function12
[N-51]Consider pre-calculating the address of address(this)18
[N-52]Optimize Zero Checks Using Assembly51
[N-53]Consider Caching Multiple Accesses to Mappings/Arrays51
[N-54]Optimize Gas by Using Do-While Loops12
[N-55]Use Assembly for Efficient Event Emission15
[N-56]Optimize External Calls with Assembly for Memory Efficiency22
[N-57]Consider Using Solady's Gas Optimized Lib for Math25
[N-58]Trade-offs Between Modifiers and Internal Functions32
[N-59]Optimize Unsigned Integer Comparison With Zero11
[N-60]Delete Unused State Variables13
[N-61]Optimize Gas by Using Only Named Returns17
[N-62]Optimize Address Storage Value Management with assembly4
[N-63]Gas savings can be achieved by changing the model for assigning value to the structure1
[N-64]Use Array.unsafeAccess to avoid repeated array length checks125
[N-65]Consider using OpenZeppelin's EnumerateSet instead of nested mappings12
[N-66]Counting down in for statements is more gas efficient8

Low Risk Issues

[L-01]<a name="l-01"></a> Chainlink oracle will return the wrong price if the aggregator hits minAnswer

Chainlink aggregators have a built-in circuit breaker if the price of an asset goes outside of a predetermined price band.

The result is that if an asset experiences a huge drop in value (i.e. LUNA crash) the price of the oracle will continue to return the minPrice instead of the actual price of the asset.

This would allow users to continue borrowing with the asset but at the wrong price. This is exactly what happened to Venus on BSC when LUNA crashed.

There are 3 instance(s) of this issue:


- contracts/libraries/LibOracle.sol
42	                ) = oracle.latestRoundData();
...
59	                ) = oracle.latestRoundData();
...
27	        try baseOracle.latestRoundData() returns (uint80 baseRoundID, int256 basePrice, uint256, uint256 baseTimeStamp, uint80) {

GitHub : 42, 59, 27

[L-02]<a name="l-02"></a> 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

There are 3 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
40	    function createBid(
41	        address asset,
42	        uint80 price,
43	        uint88 ercAmount,
44	        bool isMarketOrder,
45	        MTypes.OrderHint[] calldata orderHintArray,
46	        uint16[] calldata shortHintArray
47	    ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
77	    function _createBid(
78	        address sender,
79	        address asset,
80	        uint80 price,
81	        uint88 ercAmount,
82	        bool isMarketOrder,
83	        MTypes.OrderHint[] memory orderHintArray,
84	        uint16[] memory shortHintArray
85	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {

GitHub : 40..47, 77..85


- contracts/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 {

GitHub : 35..42

[L-03]<a name="l-03"></a> Code does not follow the best practice of check-effects-interaction

Code should follow the best-practice of check-effects-interaction, where state variables are updated before any external calls are made. Doing so prevents a large class of reentrancy bugs.

There are 23 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
// @audit Some statements does not follow CEI
215	    function matchlowestSell(
216	        address asset,
217	        STypes.Order memory lowestSell,
218	        STypes.Order memory incomingBid,
219	        MTypes.Match memory matchTotal
220	    ) private {
...
// @audit Statement out of CEI order
261	        matchTotal.lastMatchPrice = lowestSell.price;

GitHub : 261


- contracts/facets/PrimaryLiquidationFacet.sol
// @audit Some statements does not follow CEI
96	    function _checklowestSell(MTypes.PrimaryLiquidation memory m) private view {
...
// @audit Statement out of CEI order
109	            revert Errors.NoSells();
...
// @audit Some statements does not follow CEI
122	    function _setLiquidationStruct(address asset, address shorter, uint8 id, uint16 shortOrderId)
123	        private
124	        returns (MTypes.PrimaryLiquidation memory)
125	    {
...
// @audit Statement out of CEI order
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

GitHub : 109, 140


- contracts/facets/BridgeRouterFacet.sol
// @audit Some statements does not follow CEI
156	    function _getVault(address bridge) private view returns (uint256 vault, uint256 bridgePointer) {
...
// @audit Statement out of CEI order
164	            if (vault == 0) revert Errors.InvalidBridge();

GitHub : 164


- contracts/facets/RedemptionFacet.sol
// @audit Some statements does not follow CEI
56	    function proposeRedemption(
57	        address asset,
58	        MTypes.ProposalInput[] calldata proposalInput,
59	        uint88 redemptionAmount,
60	        uint88 maxRedemptionFee
61	    ) external isNotFrozen(asset) nonReentrant {
...
// @audit Statement out of CEI order
209	        VaultUser.ethEscrowed -= redemptionFee;

GitHub : 209, 299, 331, 401


- contracts/libraries/LibBridgeRouter.sol

GitHub : 105


- contracts/libraries/LibBytes.sol

GitHub : 45..51


- contracts/libraries/LibOracle.sol

GitHub : 27..66, 87..111, 128


- contracts/libraries/LibOrders.sol

GitHub : 100, 118, 143, 488, 693, 725, 778, 850


- contracts/libraries/LibSRUtil.sol

GitHub : 98, 148

[L-04]<a name="l-04"></a> 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.

There are 3 instance(s) of this issue:


- contracts/facets/RedemptionFacet.sol
// @audit array parameter `proposalInput` is used as loop condition and length is not cheched in function
58	        MTypes.ProposalInput[] calldata proposalInput,
...
// @audit loop uses unbounded parameter
78	        for (uint8 i = 0; i < proposalInput.length; i++) {

GitHub : 78


- contracts/libraries/LibOrders.sol
// @audit array parameter `shortHintArray` is used as loop condition and length is not cheched in function
728	    function _updateOracleAndStartingShort(address asset, uint16[] memory shortHintArray) private {
...
// @audit loop uses unbounded parameter
743	            for (uint256 i = 0; i < shortHintArray.length;) {

GitHub : 743


- contracts/libraries/LibOrders.sol
// @audit array parameter `orderHintArray` is used as loop condition and length is not cheched in function
829	        MTypes.OrderHint[] memory orderHintArray
...
// @audit loop uses unbounded parameter
832	        for (uint256 i; i < orderHintArray.length; i++) {

GitHub : 832

[L-05]<a name="l-05"></a> Privileged functions can create points of failure

Ensure such accounts are protected and consider implementing multi sig to prevent a single point of failure

There are 3 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
// @audit Modifiers that checks `msg.sender`: `onlyDiamond`, `onlyOwner`, `onlyRole`
65	    function createForcedBid(address sender, address asset, uint80 price, uint88 ercAmount, uint16[] calldata shortHintArray)
66	        external
67	        onlyDiamond
68	        returns (uint88 ethFilled, uint88 ercAmountLeft)
69	    {

GitHub : 65..69


- contracts/facets/PrimaryLiquidationFacet.sol
// @audit `msg.sender` is checked in function body
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	    {
...
// @note `msg.sender` is checked here
54	        if (msg.sender == shorter) revert Errors.CannotLiquidateSelf();

GitHub : 47..53


- contracts/facets/RedemptionFacet.sol
// @audit `msg.sender` is checked in function body
224	    function disputeRedemption(address asset, address redeemer, uint8 incorrectIndex, address disputeShorter, uint8 disputeShortId)
225	        external
226	        isNotFrozen(asset)
227	        nonReentrant
228	    {
...
// @note `msg.sender` is checked here
229	        if (redeemer == msg.sender) revert Errors.CannotDisputeYourself();

GitHub : 224..228

NonCritical Risk Issues

[N-01]<a name="n-01"></a> Functions which are either private or internal should have a preceding _ in their name

Add a preceding underscore to the function name, take care to refactor where there functions are called

There are 65 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
130	    function bidMatchAlgo(
131	        address asset,
132	        STypes.Order memory incomingBid,
133	        MTypes.OrderHint[] memory orderHintArray,
134	        MTypes.BidMatchAlgo memory b
135	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
215	    function matchlowestSell(
216	        address asset,
217	        STypes.Order memory lowestSell,
218	        STypes.Order memory incomingBid,
219	        MTypes.Match memory matchTotal
220	    ) private {
...
275	    function matchIncomingBid(
276	        address asset,
277	        STypes.Order memory incomingBid,
278	        MTypes.Match memory matchTotal,
279	        MTypes.BidMatchAlgo memory b
280	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {

GitHub : 130..135, 215..220, 275..280


- contracts/facets/PrimaryLiquidationFacet.sol
229	    function min88(uint256 a, uint88 b) private pure returns (uint88) {

GitHub : 229


- contracts/facets/BridgeRouterFacet.sol
148	    function maybeUpdateYield(uint256 vault, uint88 amount) private {

GitHub : 148


- contracts/facets/ExitShortFacet.sol
213	    function getCollateralRatioNonPrice(STypes.ShortRecord storage short) internal view returns (uint256 cRatio) {

GitHub : 213


- contracts/facets/RedemptionFacet.sol
31	    function validRedemptionSR(STypes.ShortRecord storage shortRecord, address proposer, address shorter, uint256 minShortErc)
32	        internal
33	        view
34	        returns (bool)
35	    {
...
382	    function calculateRedemptionFee(address asset, uint88 colRedeemed, uint88 ercDebtRedeemed)
383	        internal
384	        returns (uint88 redemptionFee)
385	    {

GitHub : 31..35, 382..385


- contracts/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)
40	        internal
41	        returns (uint88)
42	    {

GitHub : 21, 39..42, 113, 144, 198


- contracts/libraries/LibBytes.sol

GitHub : 11


- contracts/libraries/LibOracle.sol

GitHub : 19, 69..75, 117..124, 131, 149, 156, 162, 168


- contracts/libraries/LibOrders.sol

GitHub : 30, 35, 40, 55..59, 78, 82, 103, 128, 153, 173..178, 231..235, 260..266, 289, 314, 362..367, 402..409, 420, 440..447, 474..479, 499, 556..561, 628, 652, 668, 705..710, 783..788, 803, 810, 826..830, 854, 868, 882, 955, 985, 989


- contracts/libraries/LibSRUtil.sol

GitHub : 22..24, 49..52, 72..75, 102..106, 124, 151


- contracts/libraries/UniswapOracleLibrary.sol

GitHub : 28..32, 47..51

[N-02]<a name="n-02"></a> Private and internal state variables should have a preceding _ in their name unless they are constants

Add a preceding underscore to the state variable name, take care to refactor where there variables are read/wrote

There are 4 instance(s) of this issue:


- contracts/facets/PrimaryLiquidationFacet.sol
28	    address private immutable dusd;

GitHub : 28


- contracts/facets/BridgeRouterFacet.sol
26	    address private immutable rethBridge;
...
27	    address private immutable stethBridge;

GitHub : 26, 27


- contracts/facets/ExitShortFacet.sol
26	    address private immutable dusd;

GitHub : 26

[N-03]<a name="n-03"></a> Emits without msg.sender parameter

If msg.sender play a part in the functionality of a function, any emits of this function should include msg.sender to ensure transparency with users

There are 1 instance(s) of this issue:


- contracts/facets/RedemptionFacet.sol
// @audit Current function use `msg.sender`, but do not emit it
284	                emit Events.DisputeRedemptionAll(d.asset, redeemer);

GitHub : 284

[N-04]<a name="n-04"></a> A function which defines named returns in it's declaration doesn't need to use return

Once the return variable has been assigned (or has its default value), there is no need to explicitly return it at the end of the function, since it's returned automatically. Remove the return statement once ensuring it is safe to do so

There are 22 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
40	    function createBid(
41	        address asset,
42	        uint80 price,
43	        uint88 ercAmount,
44	        bool isMarketOrder,
45	        MTypes.OrderHint[] calldata orderHintArray,
46	        uint16[] calldata shortHintArray
47	    ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
65	    function createForcedBid(address sender, address asset, uint80 price, uint88 ercAmount, uint16[] calldata shortHintArray)
66	        external
67	        onlyDiamond
68	        returns (uint88 ethFilled, uint88 ercAmountLeft)
69	    {
...
77	    function _createBid(
78	        address sender,
79	        address asset,
80	        uint80 price,
81	        uint88 ercAmount,
82	        bool isMarketOrder,
83	        MTypes.OrderHint[] memory orderHintArray,
84	        uint16[] memory shortHintArray
85	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
130	    function bidMatchAlgo(
131	        address asset,
132	        STypes.Order memory incomingBid,
133	        MTypes.OrderHint[] memory orderHintArray,
134	        MTypes.BidMatchAlgo memory b
135	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
275	    function matchIncomingBid(
276	        address asset,
277	        STypes.Order memory incomingBid,
278	        MTypes.Match memory matchTotal,
279	        MTypes.BidMatchAlgo memory b
280	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
340	    function _getLowestSell(address asset, MTypes.BidMatchAlgo memory b) private view returns (STypes.Order memory lowestSell) {

GitHub : 40..47, 65..69, 77..85, 130..135, 275..280, 340


- contracts/facets/ExitShortFacet.sol
213	    function getCollateralRatioNonPrice(STypes.ShortRecord storage short) internal view returns (uint256 cRatio) {

GitHub : 213


- contracts/facets/RedemptionFacet.sol
382	    function calculateRedemptionFee(address asset, uint88 colRedeemed, uint88 ercDebtRedeemed)
383	        internal
384	        returns (uint88 redemptionFee)
385	    {

GitHub : 382..385


- contracts/libraries/LibBridgeRouter.sol
113	    function withdrawalFeePct(uint256 bridgePointer, address rethBridge, address stethBridge) internal view returns (uint256 fee) {

GitHub : 113


- contracts/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) {

GitHub : 69..75, 131, 156, 162


- contracts/libraries/LibOrders.sol

GitHub : 30, 231..235, 260..266, 402..409, 420, 440..447, 826..830


- contracts/libraries/LibSRUtil.sol

GitHub : 49..52, 102..106

[N-05]<a name="n-05"></a> Use multiple require() and if statements instead of &&

Using multiple require() and if improves code readability and makes it easier to debug.

There are 29 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
106	        if (incomingBid.price >= lowestSell.price && (lowestSell.orderType == O.LimitAsk || lowestSell.orderType == O.LimitShort)) {
107	            // @dev if match and match price is gt .5% to saved oracle in either direction, update startingShortId
108	            LibOrders.updateOracleAndStartingShortViaThreshold(asset, LibOracle.getPrice(asset), incomingBid, shortHintArray);
109	            b.shortHintId = b.shortId = Asset.startingShortId;
110	            b.oraclePrice = LibOracle.getPrice(asset);
111	            return bidMatchAlgo(asset, incomingBid, orderHintArray, b);
112	        } else {
113	            // @dev no match, add to market if limit order
114	            LibOrders.addBid(asset, incomingBid, orderHintArray);
115	            return (0, ercAmount);
116	        }
...
141	            if (b.askId == C.TAIL && b.shortId == C.TAIL) {
142	                if (incomingBid.ercAmount.mul(incomingBid.price) >= minBidEth) {
143	                    LibOrders.addBid(asset, incomingBid, orderHintArray);
144	                }
145	                return matchIncomingBid(asset, incomingBid, matchTotal, b);
146	            }
...
315	            if (shortOrderType != O.Cancelled && shortOrderType != O.Matched) {
316	                Asset.startingShortId = b.shortId;
317	            } else if (prevShortOrderType != O.Cancelled && prevShortOrderType != O.Matched && prevShort.price >= b.oraclePrice) {
318	                Asset.startingShortId = b.prevShortId;
319	            } else {
320	                if (b.isMovingFwd) {
321	                    Asset.startingShortId = currentShort.nextId;
322	                } else {
323	                    Asset.startingShortId = s.shorts[asset][b.shortHintId].nextId;
324	                }
325	            }
...
317	            } else if (prevShortOrderType != O.Cancelled && prevShortOrderType != O.Matched && prevShort.price >= b.oraclePrice) {
318	                Asset.startingShortId = b.prevShortId;
319	            } else {
320	                if (b.isMovingFwd) {
321	                    Asset.startingShortId = currentShort.nextId;
322	                } else {
323	                    Asset.startingShortId = s.shorts[asset][b.shortHintId].nextId;
324	                }
325	            }
...
392	        if (prevPrice >= b.oraclePrice && !b.isMovingFwd) {
393	            // @dev shortHintId should always be the first thing matched
394	            b.isMovingBack = true;
395	            b.shortId = b.prevShortId;
396	        } else if (prevPrice < b.oraclePrice && !b.isMovingFwd) {
397	            b.firstShortIdBelowOracle = b.prevShortId;
398	            b.shortId = s.shorts[asset][b.shortHintId].nextId;
399	
400	            STypes.Order storage nextShort = s.shorts[asset][lowestSell.nextId];
401	            // @dev Only set to true if actually moving forward
402	            if (b.shortId != C.HEAD && nextShort.price <= incomingBid.price) {
403	                b.isMovingFwd = true;
404	            }
405	        } else if (b.isMovingFwd) {
406	            b.shortId = lowestSell.nextId;
407	        }
...
396	        } else if (prevPrice < b.oraclePrice && !b.isMovingFwd) {
397	            b.firstShortIdBelowOracle = b.prevShortId;
398	            b.shortId = s.shorts[asset][b.shortHintId].nextId;
399	
400	            STypes.Order storage nextShort = s.shorts[asset][lowestSell.nextId];
401	            // @dev Only set to true if actually moving forward
402	            if (b.shortId != C.HEAD && nextShort.price <= incomingBid.price) {
403	                b.isMovingFwd = true;
404	            }
405	        } else if (b.isMovingFwd) {
406	            b.shortId = lowestSell.nextId;
407	        }
...
402	            if (b.shortId != C.HEAD && nextShort.price <= incomingBid.price) {
403	                b.isMovingFwd = true;
404	            }

GitHub : 106..116, 141..146, 315..325, 317..325, 392..407, 396..407, 402..404


- contracts/facets/ShortOrdersFacet.sol
75	        if (highestBid.price >= incomingShort.price && highestBid.orderType == O.LimitBid) {
76	            LibOrders.updateOracleAndStartingShortViaThreshold(asset, LibOracle.getPrice(asset), incomingShort, shortHintArray);
77	        }

GitHub : 75..77


- contracts/facets/PrimaryLiquidationFacet.sol
100	        if (
101	            // Checks for no eligible asks
102	            (lowestAskKey == C.TAIL || s.asks[m.asset][lowestAskKey].price > bufferPrice)
103	            // Checks for no eligible shorts
104	            && (
105	                startingShortId == C.HEAD // means no short >= oracleprice
106	                    || s.shorts[m.asset][startingShortId].price > bufferPrice
107	            )
108	        ) {
109	            revert Errors.NoSells();
110	        }
...
263	            if (m.loseCollateral && m.shorter != address(this)) {
264	                // Delete partially liquidated short
265	                LibShortRecord.deleteShortRecord(m.asset, m.shorter, m.short.id);
266	                // Absorb leftovers into TAPP short
267	                LibShortRecord.fillShortRecord(
268	                    m.asset,
269	                    address(this),
270	                    C.SHORT_STARTING_ID,
271	                    SR.FullyFilled,
272	                    m.short.collateral,
273	                    m.short.ercDebt,
274	                    s.asset[m.asset].ercDebtRate,
275	                    m.short.dethYieldRate
276	                );
277	            }

GitHub : 100..110, 263..277


- contracts/facets/BridgeRouterFacet.sol

GitHub : 150..152


- contracts/facets/RedemptionFacet.sol

GitHub : 111..114, 243..245, 259..300, 361, 371..378


- contracts/libraries/LibBridgeRouter.sol

GitHub : 156..159, 161..193, 170..193


- contracts/libraries/LibOrders.sol

GitHub : 241..247, 272..278, 506..520, 510..520, 513..520, 516..520, 821..823, 840..842


- contracts/libraries/LibSRUtil.sol

GitHub : 97..99


- contracts/libraries/UniswapOracleLibrary.sol

GitHub : 65..67

[N-06]<a name="n-06"></a> Constants in comparisons should appear on the left side

Doing so will prevent typo bugs

There are 98 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
152	                if (incomingBid.ercAmount.mul(lowestSell.price) == 0) {
...
281	        if (matchTotal.fillEth == 0) {
...
292	        if (b.dustAskId > 0) {
...
294	        } else if (b.dustShortId > 0) {
...
299	        if (matchTotal.shortFillEth > 0) {

GitHub : 152, 281, 292, 294, 299


- contracts/facets/ShortOrdersFacet.sol
56	        p.minShortErc = cr < 1 ether ? LibAsset.minShortErc(asset).mul(1 ether + cr.inv()) : LibAsset.minShortErc(asset);

GitHub : 56


- contracts/facets/PrimaryLiquidationFacet.sol
56	        if (shortHintArray.length > 10) revert Errors.TooManyHints();

GitHub : 56


- contracts/facets/BridgeRouterFacet.sol
102	        if (dethAmount == 0) revert Errors.ParameterIsZero();
...
109	            if (dethAssessable > 0) {
...
111	                if (withdrawalFeePct > 0) {

GitHub : 102, 109, 111, 134, 164


- contracts/facets/ExitShortFacet.sol

GitHub : 53, 99, 174, 188


- contracts/facets/RedemptionFacet.sol

GitHub : 73, 181, 184, 188, 192, 196, 198, 198, 198, 195, 195, 195, 191, 191, 191, 191, 187, 187, 187, 187, 183, 183, 183, 183, 235, 279, 314, 353, 358, 371, 388, 397, 401


- contracts/libraries/LibBridgeRouter.sol

GitHub : 135, 129


- contracts/libraries/LibBytes.sol

GitHub : 14, 14, 20, 20


- contracts/libraries/LibOracle.sol

GitHub : 25, 30, 60, 60, 76, 76, 76, 80, 80, 89, 104, 125, 125, 125, 126, 126, 126, 134, 139, 169


- contracts/libraries/LibOrders.sol

GitHub : 36, 47, 371, 371, 501, 505, 576, 677, 794, 792, 805


- contracts/libraries/LibSRUtil.sol

GitHub : 35, 113, 131, 158


- contracts/libraries/UniswapOracleLibrary.sol

GitHub : 41, 43, 43, 39, 39, 52, 65, 65

[N-07]<a name="n-07"></a> 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 (e.g. like this).

There are 4 instance(s) of this issue:


- contracts/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)];

GitHub : 165, 211, 241


- contracts/libraries/LibBytes.sol
24	            uint64 CR; // bytes8

GitHub : 24

[N-08]<a name="n-08"></a> Consider using delete rather than assigning zero/false 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

There are 17 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
191	                            b.dustShortId = b.dustAskId = 0;
...
194	                    incomingBid.ercAmount = 0;
...
158	                    lowestSell.ercAmount = 0;

GitHub : 191, 194, 158


- contracts/libraries/LibBridgeRouter.sol
86	            VaultUser.bridgeCreditSteth = 0;
...
99	                        VaultUser.bridgeCreditReth = 0;
...
56	            VaultUser.bridgeCreditReth = 0;
...
69	                        VaultUser.bridgeCreditSteth = 0;
...
188	                    VaultUserFrom.bridgeCreditReth = 0;
...
189	                    VaultUserFrom.bridgeCreditSteth = 0;
...
176	                    VaultUserFrom.bridgeCreditSteth = 0;

GitHub : 86, 99, 56, 69, 188, 189, 176, 167


- contracts/libraries/LibOrders.sol

GitHub : 578, 612, 585


- contracts/libraries/LibSRUtil.sol

GitHub : 138, 148


- contracts/libraries/UniswapOracleLibrary.sol

GitHub : 56

[N-09]<a name="n-09"></a> Large numeric literals should use underscores for readability

There are 12 instance(s) of this issue:


- contracts/facets/RedemptionFacet.sol
193	            m = uint256(0.417 ether).div(0.1 ether);
...
189	            m = uint256(0.75 ether).div(0.2 ether);
...
191	                protocolTime + uint32((m.mul(p.currentCR - 1.3 ether) + 0.75 ether) * 1 hours / 1 ether);
...
290	                LibOrders.max(LibAsset.callerFeePct(d.asset), (currentProposal.CR - disputeCR).div(currentProposal.CR)), 0.33 ether
...
401	        uint256 redemptionRate = LibOrders.min((Asset.baseRate + 0.005 ether), 1 ether);

GitHub : 193, 189, 191, 290, 401


- contracts/libraries/LibOrders.sol
761	                bool startingShortWithinOracleRange = shortPrice <= oraclePrice.mul(1.005 ether) && prevShortPrice >= oraclePrice;
...
794	            orderPriceGtThreshold = (savedPrice - incomingOrder.price).div(savedPrice) > 0.005 ether;
...
792	            orderPriceGtThreshold = (incomingOrder.price - savedPrice).div(savedPrice) > 0.005 ether;
...
960	        bool isDiscounted = savedPrice > price.mul(1.01 ether);
...
965	            uint256 discountPct = max(0.01 ether, min(((savedPrice - price).div(savedPrice)), 0.04 ether));

GitHub : 761, 794, 792, 960, 965, 965, 968

[N-10]<a name="n-10"></a> Unused contract variables

Note that there may be cases where a variable appears to be used, but this is only because there are multiple definitions of the varible in different files. In such cases, the variable definition should be moved into a separate file. The instances below are the unused variables.

There are 13 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
47	    ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
47	    ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
68	        returns (uint88 ethFilled, uint88 ercAmountLeft)
...
68	        returns (uint88 ethFilled, uint88 ercAmountLeft)
...
85	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
85	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
135	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
135	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
280	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
280	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {

GitHub : 47, 47, 68, 68, 85, 85, 135, 135, 280, 280, 340


- contracts/facets/ExitShortFacet.sol

GitHub : 213


- contracts/facets/RedemptionFacet.sol

GitHub : 384

[N-11]<a name="n-11"></a> else-block not required

One level of nesting can be removed by not having an else block when the if-block returns, and if (foo) { return 1; } else { return 2; } becomes if (foo) { return 1; } return 2;

There are 38 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
106	        if (incomingBid.price >= lowestSell.price && (lowestSell.orderType == O.LimitAsk || lowestSell.orderType == O.LimitShort)) {
107	            // @dev if match and match price is gt .5% to saved oracle in either direction, update startingShortId
108	            LibOrders.updateOracleAndStartingShortViaThreshold(asset, LibOracle.getPrice(asset), incomingBid, shortHintArray);
109	            b.shortHintId = b.shortId = Asset.startingShortId;
110	            b.oraclePrice = LibOracle.getPrice(asset);
111	            return bidMatchAlgo(asset, incomingBid, orderHintArray, b);
112	        } else {
113	            // @dev no match, add to market if limit order
114	            LibOrders.addBid(asset, incomingBid, orderHintArray);
115	            return (0, ercAmount);
116	        }
...
150	            if (incomingBid.price >= lowestSell.price) {
151	                // Consider bid filled if only dust amount left
152	                if (incomingBid.ercAmount.mul(lowestSell.price) == 0) {
153	                    return matchIncomingBid(asset, incomingBid, matchTotal, b);
154	                }
155	                matchlowestSell(asset, lowestSell, incomingBid, matchTotal);
156	                if (incomingBid.ercAmount > lowestSell.ercAmount) {
157	                    incomingBid.ercAmount -= lowestSell.ercAmount;
158	                    lowestSell.ercAmount = 0;
159	                    if (lowestSell.isShort()) {
160	                        b.matchedShortId = lowestSell.id;
161	                        b.prevShortId = lowestSell.prevId;
162	                        LibOrders.matchOrder(s.shorts, asset, lowestSell.id);
163	                        _shortDirectionHandler(asset, lowestSell, incomingBid, b);
164	                    } else {
165	                        b.matchedAskId = lowestSell.id;
166	                        LibOrders.matchOrder(s.asks, asset, lowestSell.id);
167	                        b.askId = lowestSell.nextId;
168	                    }
169	                } else {
170	                    if (incomingBid.ercAmount == lowestSell.ercAmount) {
171	                        if (lowestSell.isShort()) {
172	                            b.matchedShortId = lowestSell.id;
173	                            b.prevShortId = lowestSell.prevId;
174	                            LibOrders.matchOrder(s.shorts, asset, lowestSell.id);
175	                        } else {
176	                            b.matchedAskId = lowestSell.id;
177	                            LibOrders.matchOrder(s.asks, asset, lowestSell.id);
178	                        }
179	                    } else {
180	                        lowestSell.ercAmount -= incomingBid.ercAmount;
181	                        if (lowestSell.isShort()) {
182	                            b.dustShortId = lowestSell.id;
183	                            STypes.Order storage lowestShort = s.shorts[asset][lowestSell.id];
184	                            lowestShort.ercAmount = lowestSell.ercAmount;
185	                        } else {
186	                            b.dustAskId = lowestSell.id;
187	                            s.asks[asset][lowestSell.id].ercAmount = lowestSell.ercAmount;
188	                        }
189	                        // Check reduced dust threshold for existing limit orders
190	                        if (lowestSell.ercAmount.mul(lowestSell.price) >= LibAsset.minAskEth(asset).mul(C.DUST_FACTOR)) {
191	                            b.dustShortId = b.dustAskId = 0;
192	                        }
193	                    }
194	                    incomingBid.ercAmount = 0;
195	                    return matchIncomingBid(asset, incomingBid, matchTotal, b);
196	                }
197	            } else {
198	                if (incomingBid.ercAmount.mul(incomingBid.price) >= minBidEth) {
199	                    LibOrders.addBid(asset, incomingBid, orderHintArray);
200	                }
201	                return matchIncomingBid(asset, incomingBid, matchTotal, b);
202	            }
...
156	                if (incomingBid.ercAmount > lowestSell.ercAmount) {
157	                    incomingBid.ercAmount -= lowestSell.ercAmount;
158	                    lowestSell.ercAmount = 0;
159	                    if (lowestSell.isShort()) {
160	                        b.matchedShortId = lowestSell.id;
161	                        b.prevShortId = lowestSell.prevId;
162	                        LibOrders.matchOrder(s.shorts, asset, lowestSell.id);
163	                        _shortDirectionHandler(asset, lowestSell, incomingBid, b);
164	                    } else {
165	                        b.matchedAskId = lowestSell.id;
166	                        LibOrders.matchOrder(s.asks, asset, lowestSell.id);
167	                        b.askId = lowestSell.nextId;
168	                    }
169	                } else {
170	                    if (incomingBid.ercAmount == lowestSell.ercAmount) {
171	                        if (lowestSell.isShort()) {
172	                            b.matchedShortId = lowestSell.id;
173	                            b.prevShortId = lowestSell.prevId;
174	                            LibOrders.matchOrder(s.shorts, asset, lowestSell.id);
175	                        } else {
176	                            b.matchedAskId = lowestSell.id;
177	                            LibOrders.matchOrder(s.asks, asset, lowestSell.id);
178	                        }
179	                    } else {
180	                        lowestSell.ercAmount -= incomingBid.ercAmount;
181	                        if (lowestSell.isShort()) {
182	                            b.dustShortId = lowestSell.id;
183	                            STypes.Order storage lowestShort = s.shorts[asset][lowestSell.id];
184	                            lowestShort.ercAmount = lowestSell.ercAmount;
185	                        } else {
186	                            b.dustAskId = lowestSell.id;
187	                            s.asks[asset][lowestSell.id].ercAmount = lowestSell.ercAmount;
188	                        }
189	                        // Check reduced dust threshold for existing limit orders
190	                        if (lowestSell.ercAmount.mul(lowestSell.price) >= LibAsset.minAskEth(asset).mul(C.DUST_FACTOR)) {
191	                            b.dustShortId = b.dustAskId = 0;
192	                        }
193	                    }
194	                    incomingBid.ercAmount = 0;
195	                    return matchIncomingBid(asset, incomingBid, matchTotal, b);
196	                }
...
341	        if (b.shortId != C.HEAD) {
342	            STypes.Order storage lowestShort = s.shorts[asset][b.shortId];
343	            STypes.Order storage lowestAsk = s.asks[asset][b.askId];
344	            // @dev Setting lowestSell after comparing short and ask prices
345	            bool noAsks = b.askId == C.TAIL;
346	            bool shortPriceLessThanAskPrice = lowestShort.price < lowestAsk.price;
347	            if (noAsks || shortPriceLessThanAskPrice) {
348	                return lowestShort;
349	            } else {
350	                return lowestAsk;
351	            }
352	        } else if (b.askId != C.TAIL) {
353	            // @dev Handles scenario when there are no shorts
354	            return s.asks[asset][b.askId];
355	        }
...
347	            if (noAsks || shortPriceLessThanAskPrice) {
348	                return lowestShort;
349	            } else {
350	                return lowestAsk;
351	            }

GitHub : 106..116, 150..202, 156..196, 341..355, 347..351


- contracts/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	        }

GitHub : 173..180


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

GitHub : 38..42


- contracts/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	        }
...
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	            }
...
95	                    if (creditReth >= amount) {
96	                        VaultUser.bridgeCreditReth -= amount;
97	                        return 0;
98	                    } else {
99	                        VaultUser.bridgeCreditReth = 0;
100	                        return amount - creditReth;
101	                    }

GitHub : 48..108, 89..107, 95..101, 59..77, 65..71, 125..140, 131..140


- contracts/libraries/LibOracle.sol

GitHub : 28..46, 48..65, 83..114, 85..114, 98..108, 169..173


- contracts/libraries/LibOrders.sol

GitHub : 241..247, 243..247, 272..278, 274..278, 381..388, 383..388, 412..416, 421..427, 423..427, 574..624, 583..615, 739..779, 765..775, 768..775, 836..842, 963..979, 973..978


- contracts/libraries/LibSRUtil.sol

GitHub : 59..68

[N-12]<a name="n-12"></a> Convert simple if-statements to ternary expressions

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)

There are 28 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
170	                    if (incomingBid.ercAmount == lowestSell.ercAmount) {
171	                        if (lowestSell.isShort()) {
172	                            b.matchedShortId = lowestSell.id;
173	                            b.prevShortId = lowestSell.prevId;
174	                            LibOrders.matchOrder(s.shorts, asset, lowestSell.id);
175	                        } else {
176	                            b.matchedAskId = lowestSell.id;
177	                            LibOrders.matchOrder(s.asks, asset, lowestSell.id);
178	                        }
179	                    } else {
180	                        lowestSell.ercAmount -= incomingBid.ercAmount;
181	                        if (lowestSell.isShort()) {
182	                            b.dustShortId = lowestSell.id;
183	                            STypes.Order storage lowestShort = s.shorts[asset][lowestSell.id];
184	                            lowestShort.ercAmount = lowestSell.ercAmount;
185	                        } else {
186	                            b.dustAskId = lowestSell.id;
187	                            s.asks[asset][lowestSell.id].ercAmount = lowestSell.ercAmount;
188	                        }
189	                        // Check reduced dust threshold for existing limit orders
190	                        if (lowestSell.ercAmount.mul(lowestSell.price) >= LibAsset.minAskEth(asset).mul(C.DUST_FACTOR)) {
191	                            b.dustShortId = b.dustAskId = 0;
192	                        }
193	                    }
...
171	                        if (lowestSell.isShort()) {
172	                            b.matchedShortId = lowestSell.id;
173	                            b.prevShortId = lowestSell.prevId;
174	                            LibOrders.matchOrder(s.shorts, asset, lowestSell.id);
175	                        } else {
176	                            b.matchedAskId = lowestSell.id;
177	                            LibOrders.matchOrder(s.asks, asset, lowestSell.id);
178	                        }
...
159	                    if (lowestSell.isShort()) {
160	                        b.matchedShortId = lowestSell.id;
161	                        b.prevShortId = lowestSell.prevId;
162	                        LibOrders.matchOrder(s.shorts, asset, lowestSell.id);
163	                        _shortDirectionHandler(asset, lowestSell, incomingBid, b);
164	                    } else {
165	                        b.matchedAskId = lowestSell.id;
166	                        LibOrders.matchOrder(s.asks, asset, lowestSell.id);
167	                        b.askId = lowestSell.nextId;
168	                    }
...
224	        if (lowestSell.orderType == O.LimitShort) {
225	            // Match short
226	            uint88 colUsed = fillEth.mulU88(LibOrders.convertCR(lowestSell.shortOrderCR));
227	            LibOrders.increaseSharesOnMatch(asset, lowestSell, matchTotal, colUsed);
228	            uint88 shortFillEth = fillEth + colUsed;
229	            matchTotal.shortFillEth += shortFillEth;
230	            // Saves gas when multiple shorts are matched
231	            if (!matchTotal.ratesQueried) {
232	                STypes.Asset storage Asset = s.asset[asset];
233	                matchTotal.ratesQueried = true;
234	                matchTotal.ercDebtRate = Asset.ercDebtRate;
235	                matchTotal.dethYieldRate = s.vault[Asset.vault].dethYieldRate;
236	            }
237	            // Default enum is PartialFill
238	            SR status;
239	            if (incomingBid.ercAmount >= lowestSell.ercAmount) {
240	                status = SR.FullyFilled;
241	            }
242	
243	            LibShortRecord.fillShortRecord(
244	                asset,
245	                lowestSell.addr,
246	                lowestSell.shortRecordId,
247	                status,
248	                shortFillEth,
249	                fillErc,
250	                matchTotal.ercDebtRate,
251	                matchTotal.dethYieldRate
252	            );
253	        } else {
254	            // Match ask
255	            s.vaultUser[s.asset[asset].vault][lowestSell.addr].ethEscrowed += fillEth;
256	            matchTotal.askFillErc += fillErc;
257	        }
...
315	            if (shortOrderType != O.Cancelled && shortOrderType != O.Matched) {
316	                Asset.startingShortId = b.shortId;
317	            } else if (prevShortOrderType != O.Cancelled && prevShortOrderType != O.Matched && prevShort.price >= b.oraclePrice) {
318	                Asset.startingShortId = b.prevShortId;
319	            } else {
320	                if (b.isMovingFwd) {
321	                    Asset.startingShortId = currentShort.nextId;
322	                } else {
323	                    Asset.startingShortId = s.shorts[asset][b.shortHintId].nextId;
324	                }
325	            }
...
317	            } else if (prevShortOrderType != O.Cancelled && prevShortOrderType != O.Matched && prevShort.price >= b.oraclePrice) {
318	                Asset.startingShortId = b.prevShortId;
319	            } else {
320	                if (b.isMovingFwd) {
321	                    Asset.startingShortId = currentShort.nextId;
322	                } else {
323	                    Asset.startingShortId = s.shorts[asset][b.shortHintId].nextId;
324	                }
325	            }
...
320	                if (b.isMovingFwd) {
321	                    Asset.startingShortId = currentShort.nextId;
322	                } else {
323	                    Asset.startingShortId = s.shorts[asset][b.shortHintId].nextId;
324	                }
...
392	        if (prevPrice >= b.oraclePrice && !b.isMovingFwd) {
393	            // @dev shortHintId should always be the first thing matched
394	            b.isMovingBack = true;
395	            b.shortId = b.prevShortId;
396	        } else if (prevPrice < b.oraclePrice && !b.isMovingFwd) {
397	            b.firstShortIdBelowOracle = b.prevShortId;
398	            b.shortId = s.shorts[asset][b.shortHintId].nextId;
399	
400	            STypes.Order storage nextShort = s.shorts[asset][lowestSell.nextId];
401	            // @dev Only set to true if actually moving forward
402	            if (b.shortId != C.HEAD && nextShort.price <= incomingBid.price) {
403	                b.isMovingFwd = true;
404	            }
405	        } else if (b.isMovingFwd) {
406	            b.shortId = lowestSell.nextId;
407	        }
...
396	        } else if (prevPrice < b.oraclePrice && !b.isMovingFwd) {
397	            b.firstShortIdBelowOracle = b.prevShortId;
398	            b.shortId = s.shorts[asset][b.shortHintId].nextId;
399	
400	            STypes.Order storage nextShort = s.shorts[asset][lowestSell.nextId];
401	            // @dev Only set to true if actually moving forward
402	            if (b.shortId != C.HEAD && nextShort.price <= incomingBid.price) {
403	                b.isMovingFwd = true;
404	            }
405	        } else if (b.isMovingFwd) {
406	            b.shortId = lowestSell.nextId;
407	        }

GitHub : 170..193, 171..178, 159..168, 224..257, 315..325, 317..325, 320..324, 392..407, 396..407


- contracts/facets/BridgeRouterFacet.sol
157	        if (bridge == rethBridge) {
158	            vault = VAULT.ONE;
159	        } else if (bridge == stethBridge) {
160	            vault = VAULT.ONE;
161	            bridgePointer = VAULT.BRIDGE_STETH;
162	        } else {
163	            vault = s.bridge[bridge].vault;
164	            if (vault == 0) revert Errors.InvalidBridge();
165	        }

GitHub : 157..165


- contracts/facets/RedemptionFacet.sol

GitHub : 96..102


- contracts/libraries/LibBridgeRouter.sol

GitHub : 27..31, 95..101, 65..71


- contracts/libraries/LibOrders.sol

GitHub : 90..94, 111..115, 132..136, 196..202, 339..348, 455..460, 765..775, 768..775, 791..795, 817..823, 944..948, 963..979


- contracts/libraries/LibSRUtil.sol

GitHub : 41..45


- contracts/libraries/UniswapOracleLibrary.sol

GitHub : 36..44

[N-13]<a name="n-13"></a> Remaining eth may not be refunded to users

When a contract function accepts Ethereum and executes a .call() or similar function that also forwards Ethereum value, it's important to check for and refund any remaining balance. This is because some of the supplied value may not be used during the call execution due to gas constraints, a revert in the called contract, or simply because not all the value was needed.

If you do not account for this remaining balance, it can become "locked" in the contract. It's crucial to either return the remaining balance to the sender or handle it in a way that ensures it is not permanently stuck. Neglecting to do so can lead to loss of funds and degradation of the contract's reliability. Furthermore, it's good practice to ensure fairness and trust with your users by returning unused funds.

There are 1 instance(s) of this issue:


- contracts/facets/BridgeRouterFacet.sol
82	    function depositEth(address bridge) external payable nonReentrant {

GitHub : 82

[N-14]<a name="n-14"></a> External calls in an un-bounded for-loop may result in a DOS

Consider limiting the number of iterations in for-loops that make external calls

There are 4 instance(s) of this issue:


- contracts/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	        }
...
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	        }

GitHub : 78..141, 242..246, 321..330


- contracts/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	            }

GitHub : 743..776

[N-15]<a name="n-15"></a> constructor missing zero address check

It is important to ensure that the constructor does not allow zero address to be set. This is a common mistake that can lead to loss of funds or redeployment of the contract.

There are 3 instance(s) of this issue:


- contracts/facets/PrimaryLiquidationFacet.sol
30	    constructor(address _dusd) {

GitHub : 30


- contracts/facets/BridgeRouterFacet.sol
29	    constructor(address _rethBridge, address _stethBridge) {

GitHub : 29


- contracts/facets/ExitShortFacet.sol
28	    constructor(address _dusd) {

GitHub : 28

[N-16]<a name="n-16"></a> Variable initialization with default value

It's not necessary to initialize a variable with its default value, and it's actually worse in gas terms as it adds an overhead.

There are 6 instance(s) of this issue:


- contracts/facets/RedemptionFacet.sol
78	        for (uint8 i = 0; i < proposalInput.length; i++) {
...
242	        for (uint256 i = 0; i < decodedProposalData.length; i++) {
...
321	        for (uint256 i = 0; i < decodedProposalData.length; i++) {

GitHub : 78, 242, 321


- contracts/libraries/LibBytes.sol
18	        for (uint256 i = 0; i < slateLength; i++) {

GitHub : 18


- contracts/libraries/LibOrders.sol
71	        for (uint256 i = 0; i < size; i++) {
...
743	            for (uint256 i = 0; i < shortHintArray.length;) {

GitHub : 71, 743

[N-17]<a name="n-17"></a> Large multiples of ten should use scientific notation

Use a scientific notation rather than decimal literals (e.g. 1e6 instead of 1000000), for better code readability. The same for exponentiation (e.g. 1e18 instead of 10**18): although the compiler is capable of optimizing it, it is considered good coding practice to utilize idioms that don't rely on compiler optimization, whenever possible.

There are 24 instance(s) of this issue:


- contracts/facets/ShortOrdersFacet.sol
56	        p.minShortErc = cr < 1 ether ? LibAsset.minShortErc(asset).mul(1 ether + cr.inv()) : LibAsset.minShortErc(asset);
...
56	        p.minShortErc = cr < 1 ether ? LibAsset.minShortErc(asset).mul(1 ether + cr.inv()) : LibAsset.minShortErc(asset);

GitHub : 56, 56


- contracts/facets/PrimaryLiquidationFacet.sol
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
...
180	            m.short.ercDebt = uint88(m.ethDebt.div(_bidPrice.mul(1 ether + m.callerFeePct + m.tappFeePct))); // @dev(safe-cast)

GitHub : 140, 180


- contracts/facets/RedemptionFacet.sol
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);
...
193	            m = uint256(0.417 ether).div(0.1 ether);
...
195	                protocolTime + uint32((m.mul(p.currentCR - 1.2 ether) + C.ONE_THIRD) * 1 hours / 1 ether);
...
191	                protocolTime + uint32((m.mul(p.currentCR - 1.3 ether) + 0.75 ether) * 1 hours / 1 ether);
...
187	                protocolTime + uint32((m.mul(p.currentCR - 1.5 ether) + 1.5 ether) * 1 hours / 1 ether);

GitHub : 197, 198, 193, 195, 191, 187, 183, 388, 396, 401


- contracts/libraries/LibBridgeRouter.sol

GitHub : 118, 122, 135, 129


- contracts/libraries/LibOracle.sol

GitHub : 93, 104, 139, 141


- contracts/libraries/LibOrders.sol

GitHub : 36, 965

[N-18]<a name="n-18"></a> Complex math should be split into multiple steps

Consider splitting long arithmetic calculations into multiple steps to improve the code readability.

There are 4 instance(s) of this issue:


- contracts/facets/RedemptionFacet.sol
195	                protocolTime + uint32((m.mul(p.currentCR - 1.2 ether) + C.ONE_THIRD) * 1 hours / 1 ether);
...
191	                protocolTime + uint32((m.mul(p.currentCR - 1.3 ether) + 0.75 ether) * 1 hours / 1 ether);
...
187	                protocolTime + uint32((m.mul(p.currentCR - 1.5 ether) + 1.5 ether) * 1 hours / 1 ether);
...
183	            redeemerAssetUser.timeToDispute = protocolTime + uint32((m.mul(p.currentCR - 1.7 ether) + 3 ether) * 1 hours / 1 ether);

GitHub : 195, 191, 187, 183

[N-19]<a name="n-19"></a> Duplicated require/if statements should be refactored

These statements should be refactored to a separate function, as there are multiple parts of the codebase that use the same logic, to improve the code readability and reduce code duplication.

There are 54 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
320	                if (b.isMovingFwd) {
...
405	        } else if (b.isMovingFwd) {
...
142	                if (incomingBid.ercAmount.mul(incomingBid.price) >= minBidEth) {
...
159	                    if (lowestSell.isShort()) {
...
171	                        if (lowestSell.isShort()) {
...
181	                        if (lowestSell.isShort()) {
...
198	                if (incomingBid.ercAmount.mul(incomingBid.price) >= minBidEth) {

GitHub : 320, 405, 142, 159, 171, 181, 198


- contracts/facets/BridgeRouterFacet.sol
102	        if (dethAmount == 0) revert Errors.ParameterIsZero();
...
134	        if (dethAmount == 0) revert Errors.ParameterIsZero();

GitHub : 102, 134


- contracts/facets/RedemptionFacet.sol
235	        if (redeemerAssetUser.SSTORE2Pointer == address(0)) revert Errors.InvalidRedemption();

GitHub : 235, 251, 314, 353, 87


- contracts/libraries/LibBridgeRouter.sol

GitHub : 27, 48, 51, 63, 65, 81, 93, 95, 127, 150, 25


- contracts/libraries/LibOracle.sol

GitHub : 28, 48, 83, 89, 104, 128, 134, 139


- contracts/libraries/LibOrders.sol

GitHub : 272, 274, 276, 425, 565, 593, 620, 635, 637, 859, 873, 887, 155, 157, 241, 243, 245


- contracts/libraries/LibSRUtil.sol

GitHub : 54, 57, 81, 84

[N-20]<a name="n-20"></a> Variable names don't follow the Solidity naming convention

Use mixedCase for local and state variables that are not constants, and add a trailing underscore for internal variables. Documentation

There are 37 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
89	        STypes.Asset storage Asset = s.asset[asset];
...
232	                STypes.Asset storage Asset = s.asset[asset];
...
285	        STypes.Asset storage Asset = s.asset[asset];
...
300	            STypes.Vault storage Vault = s.vault[vault];

GitHub : 89, 232, 285, 300


- contracts/facets/ShortOrdersFacet.sol
44	        STypes.Asset storage Asset = s.asset[asset];

GitHub : 44


- contracts/facets/PrimaryLiquidationFacet.sol
159	        uint80 _bidPrice = m.oraclePrice.mulU80(m.forcedBidPriceBuffer);
...
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)];

GitHub : 159, 165, 170, 210, 211, 241


- contracts/facets/ExitShortFacet.sol

GitHub : 47, 93, 102, 154, 182


- contracts/facets/RedemptionFacet.sol

GitHub : 151, 207, 255, 363, 386


- contracts/libraries/LibBridgeRouter.sol

GitHub : 23, 44, 147, 151, 154


- contracts/libraries/LibBytes.sol

GitHub : 24


- contracts/libraries/LibOrders.sol

GitHub : 670, 672, 737, 812, 891, 909, 958


- contracts/libraries/LibSRUtil.sol

GitHub : 27, 29, 112

[N-21]<a name="n-21"></a> Contract functions should use an interface

All external/public functions should extend an interface. This is useful to make sure that the whole API is extracted.

There are 17 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
40	    function createBid(
41	        address asset,
42	        uint80 price,
43	        uint88 ercAmount,
44	        bool isMarketOrder,
45	        MTypes.OrderHint[] calldata orderHintArray,
46	        uint16[] calldata shortHintArray
47	    ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
65	    function createForcedBid(address sender, address asset, uint80 price, uint88 ercAmount, uint16[] calldata shortHintArray)
66	        external
67	        onlyDiamond
68	        returns (uint88 ethFilled, uint88 ercAmountLeft)
69	    {

GitHub : 40..47, 65..69


- contracts/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 {

GitHub : 35..42


- contracts/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	    {

GitHub : 47..53


- contracts/facets/BridgeRouterFacet.sol
40	    function getDethTotal(uint256 vault) external view nonReentrantView returns (uint256) {
...
51	    function getBridges(uint256 vault) external view returns (address[] memory) {
...
63	    function deposit(address bridge, uint88 amount) external nonReentrant {
...
82	    function depositEth(address bridge) external payable nonReentrant {
...
101	    function withdraw(address bridge, uint88 dethAmount) external nonReentrant {
...
133	    function withdrawTapp(address bridge, uint88 dethAmount) external onlyDAO {

GitHub : 40, 51, 63, 82, 101, 133


- contracts/facets/ExitShortFacet.sol

GitHub : 41..46, 87..92, 142..149


- contracts/facets/RedemptionFacet.sol

GitHub : 56..61, 224..228, 310, 347..351

[N-22]<a name="n-22"></a> State variables should include comments

Consider adding some comments on critical state variables to explain what they are supposed to do: this will help for future code reviews.

There are 4 instance(s) of this issue:


- contracts/facets/PrimaryLiquidationFacet.sol
28	    address private immutable dusd;

GitHub : 28


- contracts/facets/BridgeRouterFacet.sol
26	    address private immutable rethBridge;
...
27	    address private immutable stethBridge;

GitHub : 26, 27


- contracts/facets/ExitShortFacet.sol
26	    address private immutable dusd;

GitHub : 26

[N-23]<a name="n-23"></a> Consider implementing two-step procedure for updating protocol addresses

A copy-paste error or a typo may end up bricking protocol functionality, or sending tokens to an address with no known private key. Consider implementing a two-step procedure for updating protocol addresses, where the recipient is set as pending, and must 'accept' the assignment by making an affirmative call. A straight forward way of doing this would be to have the target contracts implement EIP-165, and to have the 'set' functions ensure that the recipient is of the right interface type.

There are 6 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
77	    function _createBid(
78	        address sender,
79	        address asset,
80	        uint80 price,
81	        uint88 ercAmount,
82	        bool isMarketOrder,
83	        MTypes.OrderHint[] memory orderHintArray,
84	        uint16[] memory shortHintArray
85	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {

GitHub : 77..85


- contracts/facets/PrimaryLiquidationFacet.sol
122	    function _setLiquidationStruct(address asset, address shorter, uint8 id, uint16 shortOrderId)
123	        private
124	        returns (MTypes.PrimaryLiquidation memory)
125	    {

GitHub : 122..125


- contracts/facets/ExitShortFacet.sol
142	    function exitShort(
143	        address asset,
144	        uint8 id,
145	        uint88 buybackAmount,
146	        uint80 price,
147	        uint16[] memory shortHintArray,
148	        uint16 shortOrderId
149	    ) external isNotFrozen(asset) nonReentrant onlyValidShortRecord(asset, msg.sender, id) {

GitHub : 142..149


- contracts/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 {
...
224	    function disputeRedemption(address asset, address redeemer, uint8 incorrectIndex, address disputeShorter, uint8 disputeShortId)
225	        external
226	        isNotFrozen(asset)
227	        nonReentrant
228	    {

GitHub : 56..61, 224..228


- contracts/libraries/LibSRUtil.sol
124	    function transferShortRecord(address from, address to, uint40 tokenId) internal {

GitHub : 124

[N-24]<a name="n-24"></a> Constant redefined elsewhere

Consider defining in only one contract so that values cannot become out of sync when only one location is updated. A cheap way to store constants in a single location is to create an internal constant in a library. If the variable is a local cache of another contract's value, consider making the cache variable internal or private, which will require external users to query the contract with the source of truth, so that callers don't get out of sync.

There are 2 instance(s) of this issue:


- contracts/facets/ExitShortFacet.sol
// @audit Variable redefined in: file `contracts/facets/ExitShortFacet.sol`: `dusd`, file `contracts/facets/PrimaryLiquidationFacet.sol`: `dusd`
26	    address private immutable dusd;

GitHub : 26


- contracts/facets/PrimaryLiquidationFacet.sol
// @audit Variable redefined in: file `contracts/facets/ExitShortFacet.sol`: `dusd`, file `contracts/facets/PrimaryLiquidationFacet.sol`: `dusd`
28	    address private immutable dusd;

GitHub : 28

[N-25]<a name="n-25"></a> Function names should use lowerCamelCase

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

There are 3 instance(s) of this issue:


- contracts/facets/RedemptionFacet.sol
31	    function validRedemptionSR(STypes.ShortRecord storage shortRecord, address proposer, address shorter, uint256 minShortErc)
32	        internal
33	        view
34	        returns (bool)
35	    {

GitHub : 31..35


- contracts/libraries/LibOrders.sol
35	    function convertCR(uint16 cr) internal pure returns (uint256) {

GitHub : 35


- contracts/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	    {

GitHub : 47..51

[N-26]<a name="n-26"></a> Overflows in unchecked blocks

While integers with a large number of bits are unlikely to overflow on human time scales, it is not strictly correct to use an unchecked block around them, because eventually they will overflow, and unchecked blocks are meant for cases where it's mathematically impossible for an operation to trigger an overflow (e.g. a prior require() statement prevents the overflow case)

There are 1 instance(s) of this issue:


- contracts/libraries/LibOrders.sol
745	                unchecked {
746	                    ++i;
747	                }

GitHub : 745..747

[N-27]<a name="n-27"></a> Events may be emitted out of order due to reentrancy

Ensure that events follow the best practice of check-effects-interaction, and are emitted before external calls

There are 10 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
275	    function matchIncomingBid(
276	        address asset,
277	        STypes.Order memory incomingBid,
278	        MTypes.Match memory matchTotal,
279	        MTypes.BidMatchAlgo memory b
280	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {

GitHub : 275..280


- contracts/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	    {

GitHub : 47..53


- contracts/facets/BridgeRouterFacet.sol
63	    function deposit(address bridge, uint88 amount) external nonReentrant {
...
82	    function depositEth(address bridge) external payable nonReentrant {
...
101	    function withdraw(address bridge, uint88 dethAmount) external nonReentrant {
...
133	    function withdrawTapp(address bridge, uint88 dethAmount) external onlyDAO {

GitHub : 63, 82, 101, 133


- contracts/facets/ExitShortFacet.sol
41	    function exitShortWallet(address asset, uint8 id, uint88 buybackAmount, uint16 shortOrderId)
42	        external
43	        isNotFrozen(asset)
44	        nonReentrant
45	        onlyValidShortRecord(asset, msg.sender, id)
46	    {
...
87	    function exitShortErcEscrowed(address asset, uint8 id, uint88 buybackAmount, uint16 shortOrderId)
88	        external
89	        isNotFrozen(asset)
90	        nonReentrant
91	        onlyValidShortRecord(asset, msg.sender, id)
92	    {
...
142	    function exitShort(
143	        address asset,
144	        uint8 id,
145	        uint88 buybackAmount,
146	        uint80 price,
147	        uint16[] memory shortHintArray,
148	        uint16 shortOrderId
149	    ) external isNotFrozen(asset) nonReentrant onlyValidShortRecord(asset, msg.sender, id) {

GitHub : 41..46, 87..92, 142..149


- contracts/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 {

GitHub : 56..61

[N-28]<a name="n-28"></a> Function can return unassigned variable

Make sure that functions with a return value always return a valid and assigned value. Even if the default value is as expected, it should be assigned with the default value for code clarity and to reduce confusion.

There are 2 instance(s) of this issue:


- contracts/libraries/LibOracle.sol
//@audit Variable `priceInEth` defined here, but never assigned
43	                uint256 priceInEth = uint256(price).div(uint256(basePrice));
...
//@audit Variable `priceInEth` defined here, but never assigned
63	                uint256 priceInEth = uint256(price * C.BASE_ORACLE_DECIMALS).mul(twapInv);
...
//@audit Variable returned here uninitialized
45	                return priceInEth;
...
//@audit Variable returned here uninitialized
64	                return priceInEth;

GitHub : 45, 64

[N-29]<a name="n-29"></a> Simplify complex require statements

Simplifying complex require statements with local variables and if(or revert) statements can improve readability, make debugging easier, and promote modularity and reusability in the code.

There are 1 instance(s) of this issue:


- contracts/libraries/LibBytes.sol
14	        require(slate.length % 51 == 0, "Invalid data length");

GitHub : 14

[N-30]<a name="n-30"></a> Events should be emitted before external calls

Ensure that events follow the best practice of check-effects-interaction, and are emitted before external calls

There are 12 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
// @audit functions: `updateSellOrdersOnMatch`, `_cancelShort`, `_cancelAsk` called before this event
332	        emit Events.MatchOrder(asset, bidder, incomingBid.orderType, incomingBid.id, matchTotal.fillEth, matchTotal.fillErc);

GitHub : 332


- contracts/facets/PrimaryLiquidationFacet.sol
// @audit functions: `CannotLiquidateSelf`, `TooManyHints`, `checkCancelShortOrder`, `updateOracleAndStartingShortViaTimeBidOnly`, `liquidationCR`, `checkRecoveryModeViolation`, `SufficientCollateral` called before this event
87	        emit Events.Liquidate(asset, shorter, id, msg.sender, m.ercDebtMatched);

GitHub : 87


- contracts/facets/BridgeRouterFacet.sol
// @audit functions: `UnderMinimumDeposit`, `deposit`, `addDeth` called before this event
72	        emit Events.Deposit(bridge, msg.sender, dethAmount);
...
// @audit functions: `UnderMinimumDeposit`, `addDeth` called before this event
90	        emit Events.DepositEth(bridge, msg.sender, dethAmount);
...
// @audit functions: `ParameterIsZero`, `assessDeth`, `withdrawalFeePct`, `mulU88`, `removeDeth`, `withdraw` called before this event
122	        emit Events.Withdraw(bridge, msg.sender, dethAmount, fee);
...
// @audit functions: `ParameterIsZero`, `withdraw` called before this event
143	        emit Events.WithdrawTapp(bridge, msg.sender, dethAmount);

GitHub : 72, 90, 122, 143


- contracts/facets/ExitShortFacet.sol
// @audit functions: `updateErcDebt`, `InvalidBuyback`, `burnMsgSenderDebt`, `disburseCollateral`, `deleteShortRecord`, `checkShortMinErc` called before this event
75	        emit Events.ExitShortWallet(asset, msg.sender, id, buybackAmount);
...
// @audit functions: `updateErcDebt`, `InvalidBuyback`, `InsufficientERCEscrowed`, `disburseCollateral`, `deleteShortRecord`, `checkShortMinErc` called before this event
128	        emit Events.ExitShortErcEscrowed(asset, msg.sender, id, buybackAmount);
...
// @audit functions: `updateOracleAndStartingShortViaTimeBidOnly`, `checkCancelShortOrder`, `updateErcDebt`, `InvalidBuyback`, `mul`, `InsufficientCollateral`, `createForcedBid`, `ExitShortPriceTooLow`, `minShortErc`, `CannotLeaveDustAmount`, `PostExitCRLtPreExitCR`, `disburseCollateral`, `disburseCollateral`, `deleteShortRecord` called before this event
210	        emit Events.ExitShort(asset, msg.sender, id, e.ercFilled);

GitHub : 75, 128, 210


- contracts/facets/RedemptionFacet.sol
// @audit functions: `TooManyProposals`, `minShortErc`, `RedemptionUnderMinShortErc`, `InsufficientERCEscrowed`, `ExistingProposedRedemptions`, `getPrice`, `updateErcDebt`, `getCollateralRatio`, `InvalidShortOrder`, `cancelShort`, `mulU88`, `concat`, `disburseCollateral`, `RedemptionUnderMinShortErc`, `write`, `getOffsetTime`, `div`, `mul`, `div`, `mul`, `div`, `mul`, `div`, `mul`, `div`, `mul`, `getOffsetTime`, `RedemptionFeeTooHigh`, `InsufficientETHEscrowed` called before this event
210	        emit Events.ProposeRedemption(p.asset, msg.sender);

GitHub : 210, 284, 333

[N-31]<a name="n-31"></a> Contract name does not follow the Solidity Style Guide

According to the Solidity Style Guide, contracts and libraries should be named using the CapWords style.

There are 1 instance(s) of this issue:


- contracts/libraries/LibSRUtil.sol
18	library LibSRUtil {
19	    using U88 for uint88;

GitHub : 18..19

[N-32]<a name="n-32"></a> Variable names for immutables should use UPPER_CASE_WITH_UNDERSCORES

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

There are 4 instance(s) of this issue:


- contracts/facets/PrimaryLiquidationFacet.sol
28	    address private immutable dusd;

GitHub : 28


- contracts/facets/BridgeRouterFacet.sol
26	    address private immutable rethBridge;
...
27	    address private immutable stethBridge;

GitHub : 26, 27


- contracts/facets/ExitShortFacet.sol
26	    address private immutable dusd;

GitHub : 26

[N-33]<a name="n-33"></a> Contracts should have full test coverage

A 100% test coverage is not foolproof, but it helps immensely in reducing the amount of bugs that may occur.

There are 1 instance(s) of this issue:

GitHub : project\2024-03-dittoeth

[N-34]<a name="n-34"></a> Large or complicated code bases should implement invariant tests

This includes: large code bases, or code with lots of inline-assembly, complicated math, or complicated interactions between multiple contracts.

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 may help significantly.

There are 1 instance(s) of this issue:

GitHub : project\2024-03-dittoeth

[N-35]<a name="n-35"></a> Codebase should implement formal verification testing

Formal verification is the act of proving or disproving the correctness of intended algorithms underlying a system with respect to a certain formal specification/property/invariant, using formal methods of mathematics.

Some tools that are currently available to perform these tests on smart contracts are SMTChecker and Certora Prover.

There are 1 instance(s) of this issue:

GitHub : project\2024-03-dittoeth

[N-36]<a name="n-36"></a> Unsafe uint to int conversion

The int type in Solidity uses the two's complement system, so it is possible to accidentally overflow a very large uint to an int, even if they share the same number of bytes (e.g. a uint256 number > type(uint128).max will overflow a int256 cast).

Consider using the SafeCast library to prevent any overflows.

There are 2 instance(s) of this issue:


- contracts/libraries/UniswapOracleLibrary.sol
62	        int24 tick = int24(tickCumulativesDelta / int32(secondsAgo));
...
65	        if (tickCumulativesDelta < 0 && (tickCumulativesDelta % int32(secondsAgo) != 0)) {

GitHub : 62, 65

[N-37]<a name="n-37"></a> State variables not capped at reasonable values

Consider adding minimum/maximum value checks to ensure that the state variables below can never be used to excessively harm users, including via griefing

There are 7 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
// @audit: parameter `price` is not limited in function body
80	        uint80 price,

GitHub : 80


- contracts/facets/ShortOrdersFacet.sol
// @audit: parameter `price` is not limited in function body
37	        uint80 price,

GitHub : 37


- contracts/facets/BridgeRouterFacet.sol
// @audit: parameter `amount` is not limited in function body
148	    function maybeUpdateYield(uint256 vault, uint88 amount) private {
...
// @audit: parameter `amount` is not limited in function body
169	    function _ethConversion(uint256 vault, uint88 amount) private view returns (uint88) {

GitHub : 148, 169


- contracts/facets/ExitShortFacet.sol
// @audit: parameter `price` is not limited in function body
146	        uint80 price,

GitHub : 146


- contracts/libraries/LibBridgeRouter.sol
// @audit: parameter `amount` is not limited in function body
39	    function assessDeth(uint256 vault, uint256 bridgePointer, uint88 amount, address rethBridge, address stethBridge)

GitHub : 39


- contracts/libraries/UniswapOracleLibrary.sol
// @audit: parameter `amountIn` is not limited in function body
47	    function estimateTWAP(uint128 amountIn, uint32 secondsAgo, address pool, address baseToken, address quoteToken)

GitHub : 47

[N-38]<a name="n-38"></a> Consider adding a block/deny-list

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

There are 1 instance(s) of this issue:


- contracts/facets/BridgeRouterFacet.sol
18	contract BridgeRouterFacet is Modifiers {
19	    using U256 for uint256;

GitHub : 18..19

[N-39]<a name="n-39"></a> Typos

There are 2 instance(s) of this issue:

error: `forumula` should be `formula`
  --> project\2024-03-dittoeth\contracts\facets\RedemptionFacet.sol:393:34
    |
393 |         // @dev Derived via this forumula: baseRateNew = baseRateOld + redeemedLUSD / (2 * totalLUSD)
    |                                  ^^^^^^^^
    |

GitHub : 1

error: `mulitply` should be `multiply`
  --> project\2024-03-dittoeth\contracts\libraries\LibBytes.sol:19:44
   |
19 |             // 32 offset for array length, mulitply by each ProposalData
   |                                            ^^^^^^^^
   |

GitHub : 1

[N-40]<a name="n-40"></a> Alternative Solady library can be used instead of OpenZeppelin to save gas

The following OpenZeppelin imports have a Solady equivalent, as such they can be used to save GAS as Solady modules have been specifically designed to be as GAS efficient as possible.

There are 1 instance(s) of this issue:


- contracts/libraries/LibOracle.sol
 // @audit `IERC20` has Solady alternative
7	import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

GitHub : 7

[N-41]<a name="n-41"></a> Setting the constructor to payable

Saves ~13 gas per instance

There are 3 instance(s) of this issue:


- contracts/facets/PrimaryLiquidationFacet.sol
30	    constructor(address _dusd) {

GitHub : 30


- contracts/facets/BridgeRouterFacet.sol
29	    constructor(address _rethBridge, address _stethBridge) {

GitHub : 29


- contracts/facets/ExitShortFacet.sol
28	    constructor(address _dusd) {

GitHub : 28

[N-42]<a name="n-42"></a> ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops

The unchecked keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas per loop

There are 8 instance(s) of this issue:


- contracts/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	        }
...
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	            }
...
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	        }

GitHub : 78..141, 242..246, 263..272, 321..330


- contracts/libraries/LibBytes.sol
18	        for (uint256 i = 0; i < slateLength; i++) {
19	            // 32 offset for array length, mulitply by each ProposalData
20	            uint256 offset = i * 51 + 32;
21	
22	            address shorter; // bytes20
23	            uint8 shortId; // bytes1
24	            uint64 CR; // bytes8
25	            uint88 ercDebtRedeemed; // bytes11
26	            uint88 colRedeemed; // bytes11
27	
28	            assembly {
29	                // mload works 32 bytes at a time
30	                let fullWord := mload(add(slate, offset))
31	                // read 20 bytes
32	                shorter := shr(96, fullWord) // 0x60 = 96 (256-160)
33	                // read 8 bytes
34	                shortId := and(0xff, shr(88, fullWord)) // 0x58 = 88 (96-8), mask of bytes1 = 0xff * 1
35	                // read 64 bytes
36	                CR := and(0xffffffffffffffff, shr(24, fullWord)) // 0x18 = 24 (88-64), mask of bytes8 = 0xff * 8
37	
38	                fullWord := mload(add(slate, add(offset, 29))) // (29 offset)
39	                // read 88 bytes
40	                ercDebtRedeemed := shr(168, fullWord) // (256-88 = 168)
41	                // read 88 bytes
42	                colRedeemed := add(0xffffffffffffffffffffff, shr(80, fullWord)) // (256-88-88 = 80), mask of bytes11 = 0xff * 11
43	            }
44	
45	            data[i] = MTypes.ProposalData({
46	                shorter: shorter,
47	                shortId: shortId,
48	                CR: CR,
49	                ercDebtRedeemed: ercDebtRedeemed,
50	                colRedeemed: colRedeemed
51	            });
52	        }

GitHub : 18..52


- contracts/libraries/LibOrders.sol
63	        while (currentId != C.TAIL) {
64	            size++;
65	            currentId = orders[asset][currentId].nextId;
66	        }
...
71	        for (uint256 i = 0; i < size; i++) {
72	            list[i] = orders[asset][currentId];
73	            currentId = orders[asset][currentId].nextId;
74	        }
...
832	        for (uint256 i; i < orderHintArray.length; i++) {
833	            MTypes.OrderHint memory orderHint = orderHintArray[i];
834	            STypes.Order storage order = orders[asset][orderHint.hintId];
835	            O hintOrderType = order.orderType;
836	            if (hintOrderType == O.Cancelled || hintOrderType == O.Matched) {
837	                continue;
838	            } else if (order.creationTime == orderHint.creationTime) {
839	                return orderHint.hintId;
840	            } else if (!anyOrderHintPrevMatched && order.prevOrderType == O.Matched) {
841	                anyOrderHintPrevMatched = true;
842	            }
843	        }

GitHub : 63..66, 71..74, 832..843

[N-43]<a name="n-43"></a> Internal functions only called once can be inlined to save gas

There are 14 instance(s) of this issue:


- contracts/facets/RedemptionFacet.sol
382	    function calculateRedemptionFee(address asset, uint88 colRedeemed, uint88 ercDebtRedeemed)
383	        internal
384	        returns (uint88 redemptionFee)
385	    {

GitHub : 382..385


- contracts/libraries/LibOracle.sol
19	    function getOraclePrice(address asset) internal view returns (uint256) {
...
156	    function getTime(address asset) internal view returns (uint256 creationTime) {

GitHub : 19, 156


- contracts/libraries/LibOrders.sol
40	    function increaseSharesOnMatch(address asset, STypes.Order memory order, MTypes.Match memory matchTotal, uint88 eth) 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 {
...
231	    function verifyBidId(address asset, uint16 _prevId, uint256 _newPrice, uint16 _nextId)
232	        internal
233	        view
234	        returns (int256 direction)
235	    {
...
362	    function findPrevOfIncomingId(
363	        mapping(address => mapping(uint16 => STypes.Order)) storage orders,
364	        address asset,
365	        STypes.Order memory incomingOrder,
366	        uint16 hintId
367	    ) internal view returns (uint16) {
...
705	    function matchHighestBid(
706	        STypes.Order memory incomingSell,
707	        STypes.Order memory highestBid,
708	        address asset,
709	        MTypes.Match memory matchTotal
710	    ) internal {
...
810	    function updateStartingShortIdViaShort(address asset, STypes.Order memory incomingShort) internal {

GitHub : 40, 103, 128, 231..235, 362..367, 705..710, 810, 854, 985, 989


- contracts/libraries/UniswapOracleLibrary.sol

GitHub : 28..32

[N-44]<a name="n-44"></a> Usage of uint/int smaller than 32 bytes (256 bits) incurs overhead

When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.

Each operation involving a uint8 costs an extra 22-28 gas (depending on whether the other operand is also a variable of type uint8) as compared to ones involving uint256, due to the compiler having to clear the higher bits of the memory word before operating on the uint8, as well as the associated stack operations of doing so. Use a larger size then downcast where needed.

https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html

Use a larger size then downcast where needed.

There are 191 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
42	        uint80 price,
...
43	        uint88 ercAmount,
...
47	    ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
47	    ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
65	    function createForcedBid(address sender, address asset, uint80 price, uint88 ercAmount, uint16[] calldata shortHintArray)
...
65	    function createForcedBid(address sender, address asset, uint80 price, uint88 ercAmount, uint16[] calldata shortHintArray)
...
68	        returns (uint88 ethFilled, uint88 ercAmountLeft)
...
68	        returns (uint88 ethFilled, uint88 ercAmountLeft)
...
80	        uint80 price,
...
81	        uint88 ercAmount,

GitHub : 42, 43, 47, 47, 65, 65, 68, 68, 80, 81, 85, 85, 135, 135, 221, 222, 226, 228, 280, 280, 391


- contracts/facets/ShortOrdersFacet.sol

GitHub : 37, 38, 41


- contracts/facets/PrimaryLiquidationFacet.sol

GitHub : 47, 47, 52, 52, 97, 98, 122, 122, 156, 159, 171, 181, 213, 214, 229, 229, 242


- contracts/facets/BridgeRouterFacet.sol

GitHub : 68, 63, 87, 106, 108, 119, 101, 137, 133, 149, 148, 171, 169, 169


- contracts/facets/ExitShortFacet.sol

GitHub : 59, 41, 41, 41, 111, 87, 87, 87, 144, 145, 146, 148


- contracts/facets/RedemptionFacet.sol

GitHub : 78, 154, 204, 59, 60, 293, 224, 224, 320, 347, 347, 373, 368, 387, 392, 382, 382, 384


- contracts/libraries/LibBridgeRouter.sol

GitHub : 21, 46, 47, 39, 41, 152, 153, 181, 144, 198, 198


- contracts/libraries/LibBytes.sol

GitHub : 23, 24, 25, 26, 11


- contracts/libraries/LibOracle.sol

GitHub : 36, 27, 27, 53, 71, 118, 119, 149, 162


- contracts/libraries/LibOrders.sol

GitHub : 30, 35, 44, 47, 40, 60, 84, 89, 99, 105, 110, 130, 131, 142, 182, 183, 186, 187, 195, 177, 231, 231, 263, 265, 291, 289, 315, 314, 323, 324, 368, 386, 366, 367, 405, 407, 449, 456, 444, 447, 477, 517, 540, 535, 536, 563, 712, 713, 742, 757, 758, 759, 830, 862, 854, 868, 889, 894, 903, 907, 911, 882, 955


- contracts/libraries/LibSRUtil.sol

GitHub : 34, 22, 22, 49, 49, 72, 72, 142, 124, 155, 156


- contracts/libraries/UniswapOracleLibrary.sol

GitHub : 33, 28, 28, 61, 62, 47, 47

[N-45]<a name="n-45"></a> >= costs less gas than >

The compiler uses opcodes GT and ISZERO for solidity code that uses >, but only requires LT for >=, which saves 3 gas. Similarly for <=.

There are 26 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
292	        if (b.dustAskId > 0) {
...
294	        } else if (b.dustShortId > 0) {
...
299	        if (matchTotal.shortFillEth > 0) {

GitHub : 292, 294, 299


- contracts/facets/ShortOrdersFacet.sol
56	        p.minShortErc = cr < 1 ether ? LibAsset.minShortErc(asset).mul(1 ether + cr.inv()) : LibAsset.minShortErc(asset);

GitHub : 56


- contracts/facets/PrimaryLiquidationFacet.sol
56	        if (shortHintArray.length > 10) revert Errors.TooManyHints();

GitHub : 56


- contracts/facets/BridgeRouterFacet.sol
109	            if (dethAssessable > 0) {
...
111	                if (withdrawalFeePct > 0) {

GitHub : 109, 111


- contracts/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) {

GitHub : 181, 184, 188, 192, 196, 279, 397


- contracts/libraries/LibOracle.sol

GitHub : 30, 80, 80, 104, 139, 169


- contracts/libraries/LibOrders.sol

GitHub : 794, 792


- contracts/libraries/LibSRUtil.sol

GitHub : 35, 113, 158


- contracts/libraries/UniswapOracleLibrary.sol

GitHub : 65

[N-46]<a name="n-46"></a> Using storage instead of memory for structs/arrays saves gas

When fetching data from a storage location, assigning the data to a memory variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD rather than a cheap stack read. Instead of declearing the variable with the memory keyword, declaring the variable with the storage keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory, or if the array/struct is being read from another memory array/struct

There are 1 instance(s) of this issue:


- contracts/libraries/LibOrders.sol
573	            STypes.Order memory highestBid = s.bids[asset][startingId];

GitHub : 573

[N-47]<a name="n-47"></a> Stack variable used as a cheaper cache for a state variable is only used once

If the variable is only accessed once, it's cheaper to use the state variable directly that one time, and save the 3 gas the extra stack assignment would spend

There are 47 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
65	    function createForcedBid(address sender, address asset, uint80 price, uint88 ercAmount, uint16[] calldata shortHintArray)
66	        external
67	        onlyDiamond
68	        returns (uint88 ethFilled, uint88 ercAmountLeft)
69	    {
...
130	    function bidMatchAlgo(
131	        address asset,
132	        STypes.Order memory incomingBid,
133	        MTypes.OrderHint[] memory orderHintArray,
134	        MTypes.BidMatchAlgo memory b
135	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
340	    function _getLowestSell(address asset, MTypes.BidMatchAlgo memory b) private view returns (STypes.Order memory lowestSell) {
...
358	    function _shortDirectionHandler(
359	        address asset,
360	        STypes.Order memory lowestSell,
361	        STypes.Order memory incomingBid,
362	        MTypes.BidMatchAlgo memory b
363	    ) private view {

GitHub : 65..69, 130..135, 340, 358..363


- contracts/facets/PrimaryLiquidationFacet.sol
154	    function _performForcedBid(MTypes.PrimaryLiquidation memory m, uint16[] memory shortHintArray) private {

GitHub : 154


- contracts/facets/BridgeRouterFacet.sol
63	    function deposit(address bridge, uint88 amount) external nonReentrant {
...
82	    function depositEth(address bridge) external payable nonReentrant {
...
101	    function withdraw(address bridge, uint88 dethAmount) external nonReentrant {
...
133	    function withdrawTapp(address bridge, uint88 dethAmount) external onlyDAO {

GitHub : 63, 82, 101, 133


- contracts/facets/ExitShortFacet.sol
41	    function exitShortWallet(address asset, uint8 id, uint88 buybackAmount, uint16 shortOrderId)
42	        external
43	        isNotFrozen(asset)
44	        nonReentrant
45	        onlyValidShortRecord(asset, msg.sender, id)
46	    {

GitHub : 41..46, 87..92, 142..149


- contracts/facets/RedemptionFacet.sol

GitHub : 224..228, 310, 347..351, 382..385


- contracts/libraries/LibBridgeRouter.sol

GitHub : 39..42, 113, 144


- contracts/libraries/LibBytes.sol

GitHub : 11


- contracts/libraries/LibOracle.sol

GitHub : 19, 69..75, 117..124, 131, 156, 162


- contracts/libraries/LibOrders.sol

GitHub : 40, 82, 128, 173..178, 289, 314, 362..367, 440..447, 499, 668, 705..710, 728, 803, 810, 854, 882, 955


- contracts/libraries/LibSRUtil.sol

GitHub : 22..24, 102..106, 124, 151

[N-48]<a name="n-48"></a> Newer versions of solidity are more gas efficient

The solidity language continues to pursue more efficient gas optimization schemes. Adopting a newer version of solidity can be more gas efficient.

solc-version = "0.8.21"

proof: https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/foundry.toml#L2

There are 12 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
2	pragma solidity 0.8.21;

GitHub : 2


- contracts/facets/ShortOrdersFacet.sol
2	pragma solidity 0.8.21;

GitHub : 2


- contracts/facets/PrimaryLiquidationFacet.sol
2	pragma solidity 0.8.21;

GitHub : 2


- contracts/facets/BridgeRouterFacet.sol
2	pragma solidity 0.8.21;

GitHub : 2


- contracts/facets/ExitShortFacet.sol
2	pragma solidity 0.8.21;

GitHub : 2


- contracts/facets/RedemptionFacet.sol
2	pragma solidity 0.8.21;

GitHub : 2


- contracts/libraries/LibBridgeRouter.sol
2	pragma solidity 0.8.21;

GitHub : 2


- contracts/libraries/LibBytes.sol
2	pragma solidity 0.8.21;

GitHub : 2


- contracts/libraries/LibOracle.sol
2	pragma solidity 0.8.21;

GitHub : 2


- contracts/libraries/LibOrders.sol
2	pragma solidity 0.8.21;

GitHub : 2


- contracts/libraries/LibSRUtil.sol

GitHub : 2


- contracts/libraries/UniswapOracleLibrary.sol

GitHub : 2

[N-49]<a name="n-49"></a> Consider activating via-ir for deploying

The IR-based code generator was developed to make code generation more performant by enabling optimization passes that can be applied across functions.

It is possible to activate the IR-based code generator through the command line by using the flag --via-ir or by including the option {"viaIR": true}.

Keep in mind that compiling with this option may take longer. However, you can simply test it before deploying your code. If you find that it provides better performance, you can add the --via-ir flag to your deploy command.

There are 1 instance(s) of this issue:

GitHub : project\2024-03-dittoeth

[N-50]<a name="n-50"></a> Function calls should be cached instead of re-calling the function

Consider caching the result instead of re-calling the function when possible. Note: this also includes casts, which cost between 42-46 gas, depending on the type.

There are 12 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
77	    function _createBid(
78	        address sender,
79	        address asset,
80	        uint80 price,
81	        uint88 ercAmount,
82	        bool isMarketOrder,
83	        MTypes.OrderHint[] memory orderHintArray,
84	        uint16[] memory shortHintArray
85	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
130	    function bidMatchAlgo(
131	        address asset,
132	        STypes.Order memory incomingBid,
133	        MTypes.OrderHint[] memory orderHintArray,
134	        MTypes.BidMatchAlgo memory b
135	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {

GitHub : 77..85, 130..135


- contracts/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 {

GitHub : 35..42


- contracts/facets/PrimaryLiquidationFacet.sol
240	    function _fullorPartialLiquidation(MTypes.PrimaryLiquidation memory m) private {

GitHub : 240


- contracts/facets/ExitShortFacet.sol
142	    function exitShort(
143	        address asset,
144	        uint8 id,
145	        uint88 buybackAmount,
146	        uint80 price,
147	        uint16[] memory shortHintArray,
148	        uint16 shortOrderId
149	    ) external isNotFrozen(asset) nonReentrant onlyValidShortRecord(asset, msg.sender, id) {

GitHub : 142..149


- contracts/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 {

GitHub : 56..61


- contracts/libraries/LibBridgeRouter.sol
144	    function transferBridgeCredit(address asset, address from, address to, uint88 collateral) internal {

GitHub : 144


- contracts/libraries/LibOracle.sol
19	    function getOraclePrice(address asset) internal view returns (uint256) {

GitHub : 19


- contracts/libraries/LibOrders.sol
556	    function sellMatchAlgo(
557	        address asset,
558	        STypes.Order memory incomingAsk,
559	        MTypes.OrderHint[] memory orderHintArray,
560	        uint256 minAskEth
561	    ) internal {
...
783	    function updateOracleAndStartingShortViaThreshold(
784	        address asset,
785	        uint256 savedPrice,
786	        STypes.Order memory incomingOrder,
787	        uint16[] memory shortHintArray
788	    ) internal {

GitHub : 556..561, 783..788, 882


- contracts/libraries/LibSRUtil.sol

GitHub : 49..52

[N-51]<a name="n-51"></a> Consider pre-calculating the address of address(this)

It can be more gas-efficient to use a hardcoded address instead of the address(this) expression, especially if you need to use the same address multiple times in your contract.

The reason for this, is that using address(this) requires an additional EXTCODESIZE operation to retrieve the contract’s address from its bytecode, which can increase the gas cost of your contract. By pre-calculating and using a hardcoded address, you can avoid this additional operation and reduce the overall gas cost of your contract.

Save 100 - 2600 Gas

There are 18 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
295	            IDiamond(payable(address(this)))._cancelShort(asset, b.dustShortId);
...
293	            IDiamond(payable(address(this)))._cancelAsk(asset, b.dustAskId);

GitHub : 295, 293


- contracts/facets/PrimaryLiquidationFacet.sol
165	        STypes.VaultUser storage TAPP = s.vaultUser[m.vault][address(this)];
...
188	            IDiamond(payable(address(this))).createForcedBid(address(this), m.asset, _bidPrice, m.short.ercDebt, shortHintArray);
...
188	            IDiamond(payable(address(this))).createForcedBid(address(this), m.asset, _bidPrice, m.short.ercDebt, shortHintArray);
...
193	        s.assetUser[m.asset][address(this)].ercEscrowed -= m.ercDebtMatched;
...
211	        STypes.VaultUser storage TAPP = s.vaultUser[m.vault][address(this)];
...
241	        STypes.VaultUser storage TAPP = s.vaultUser[m.vault][address(this)];
...
263	            if (m.loseCollateral && m.shorter != address(this)) {
...
269	                    address(this),

GitHub : 165, 188, 188, 193, 211, 241, 263, 269, 280


- contracts/facets/BridgeRouterFacet.sol

GitHub : 114, 139


- contracts/facets/ExitShortFacet.sol

GitHub : 187


- contracts/libraries/LibOracle.sol

GitHub : 87, 133


- contracts/libraries/LibOrders.sol

GitHub : 982


- contracts/libraries/LibSRUtil.sol

GitHub : 44

[N-52]<a name="n-52"></a> Optimize Zero Checks Using Assembly

The usage of inline assembly to check if variable is the zero can save gas compared to traditional require or if statement checks.

The assembly check uses the extcodesize operation which is generally cheaper in terms of gas.

More information can be found here.

There are 51 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
152	                if (incomingBid.ercAmount.mul(lowestSell.price) == 0) {
...
281	        if (matchTotal.fillEth == 0) {
...
292	        if (b.dustAskId > 0) {
...
294	        } else if (b.dustShortId > 0) {
...
299	        if (matchTotal.shortFillEth > 0) {

GitHub : 152, 281, 292, 294, 299


- contracts/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();

GitHub : 102, 109, 111, 134, 164


- contracts/facets/ExitShortFacet.sol

GitHub : 53, 99, 174, 188


- contracts/facets/RedemptionFacet.sol

GitHub : 73, 235, 279, 314, 353, 371, 397


- contracts/libraries/LibBytes.sol

GitHub : 14


- contracts/libraries/LibOracle.sol

GitHub : 25, 30, 60, 60, 76, 76, 76, 80, 89, 125, 125, 125, 126, 126, 126, 134


- contracts/libraries/LibOrders.sol

GitHub : 371, 371, 501, 505, 576, 677


- contracts/libraries/LibSRUtil.sol

GitHub : 35, 113, 131, 158


- contracts/libraries/UniswapOracleLibrary.sol

GitHub : 52, 65, 65

[N-53]<a name="n-53"></a> Consider Caching Multiple Accesses to Mappings/Arrays

Leveraging a local variable to cache these values when accessed more than once can yield a gas saving of approximately 42 units per access. This reduction is attributed to eliminating the need for recalculating the key's keccak256 hash (which costs Gkeccak256 - 30 gas) and the associated stack operations. For arrays, this also prevents the overhead of re-computing offsets in memory or calldata.

There are 51 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
77	    function _createBid(
78	        address sender,
79	        address asset,
80	        uint80 price,
81	        uint88 ercAmount,
82	        bool isMarketOrder,
83	        MTypes.OrderHint[] memory orderHintArray,
84	        uint16[] memory shortHintArray
85	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
130	    function bidMatchAlgo(
131	        address asset,
132	        STypes.Order memory incomingBid,
133	        MTypes.OrderHint[] memory orderHintArray,
134	        MTypes.BidMatchAlgo memory b
135	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
215	    function matchlowestSell(
216	        address asset,
217	        STypes.Order memory lowestSell,
218	        STypes.Order memory incomingBid,
219	        MTypes.Match memory matchTotal
220	    ) private {
...
275	    function matchIncomingBid(
276	        address asset,
277	        STypes.Order memory incomingBid,
278	        MTypes.Match memory matchTotal,
279	        MTypes.BidMatchAlgo memory b
280	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
340	    function _getLowestSell(address asset, MTypes.BidMatchAlgo memory b) private view returns (STypes.Order memory lowestSell) {
...
358	    function _shortDirectionHandler(
359	        address asset,
360	        STypes.Order memory lowestSell,
361	        STypes.Order memory incomingBid,
362	        MTypes.BidMatchAlgo memory b
363	    ) private view {

GitHub : 77..85, 130..135, 215..220, 275..280, 340, 358..363


- contracts/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 {

GitHub : 35..42


- contracts/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	    {
...
96	    function _checklowestSell(MTypes.PrimaryLiquidation memory m) private view {
...
122	    function _setLiquidationStruct(address asset, address shorter, uint8 id, uint16 shortOrderId)
123	        private
124	        returns (MTypes.PrimaryLiquidation memory)
125	    {

GitHub : 47..53, 96, 122..125, 154, 209, 240


- contracts/facets/ExitShortFacet.sol

GitHub : 41..46, 87..92, 142..149


- contracts/facets/RedemptionFacet.sol

GitHub : 56..61, 224..228, 310, 347..351, 368


- contracts/libraries/LibBridgeRouter.sol

GitHub : 144


- contracts/libraries/LibBytes.sol

GitHub : 11


- contracts/libraries/LibOracle.sol

GitHub : 149


- contracts/libraries/LibOrders.sol

GitHub : 55..59, 82, 103, 128, 173..178, 231..235, 260..266, 289, 314, 320..326, 362..367, 440..447, 474..479, 499, 532..537, 556..561, 652, 728, 810, 854, 868, 882


- contracts/libraries/LibSRUtil.sol

GitHub : 22..24, 49..52, 72..75, 124


- contracts/libraries/UniswapOracleLibrary.sol

GitHub : 47..51

[N-54]<a name="n-54"></a> Optimize Gas by Using Do-While Loops

Using do-while loops instead of for loops can be more gas-efficient. Even if you add an if condition to account for the case where the loop doesn't execute at all, a do-while loop can still be cheaper in terms of gas.

There are 12 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
139	        while (true) {
140	            // @dev Handles scenario when no sells left after partial fill
141	            if (b.askId == C.TAIL && b.shortId == C.TAIL) {
142	                if (incomingBid.ercAmount.mul(incomingBid.price) >= minBidEth) {
143	                    LibOrders.addBid(asset, incomingBid, orderHintArray);
144	                }
145	                return matchIncomingBid(asset, incomingBid, matchTotal, b);
146	            }
147	
148	            STypes.Order memory lowestSell = _getLowestSell(asset, b);
149	
150	            if (incomingBid.price >= lowestSell.price) {
151	                // Consider bid filled if only dust amount left
152	                if (incomingBid.ercAmount.mul(lowestSell.price) == 0) {
153	                    return matchIncomingBid(asset, incomingBid, matchTotal, b);
154	                }
155	                matchlowestSell(asset, lowestSell, incomingBid, matchTotal);
156	                if (incomingBid.ercAmount > lowestSell.ercAmount) {
157	                    incomingBid.ercAmount -= lowestSell.ercAmount;
158	                    lowestSell.ercAmount = 0;
159	                    if (lowestSell.isShort()) {
160	                        b.matchedShortId = lowestSell.id;
161	                        b.prevShortId = lowestSell.prevId;
162	                        LibOrders.matchOrder(s.shorts, asset, lowestSell.id);
163	                        _shortDirectionHandler(asset, lowestSell, incomingBid, b);
164	                    } else {
165	                        b.matchedAskId = lowestSell.id;
166	                        LibOrders.matchOrder(s.asks, asset, lowestSell.id);
167	                        b.askId = lowestSell.nextId;
168	                    }
169	                } else {
170	                    if (incomingBid.ercAmount == lowestSell.ercAmount) {
171	                        if (lowestSell.isShort()) {
172	                            b.matchedShortId = lowestSell.id;
173	                            b.prevShortId = lowestSell.prevId;
174	                            LibOrders.matchOrder(s.shorts, asset, lowestSell.id);
175	                        } else {
176	                            b.matchedAskId = lowestSell.id;
177	                            LibOrders.matchOrder(s.asks, asset, lowestSell.id);
178	                        }
179	                    } else {
180	                        lowestSell.ercAmount -= incomingBid.ercAmount;
181	                        if (lowestSell.isShort()) {
182	                            b.dustShortId = lowestSell.id;
183	                            STypes.Order storage lowestShort = s.shorts[asset][lowestSell.id];
184	                            lowestShort.ercAmount = lowestSell.ercAmount;
185	                        } else {
186	                            b.dustAskId = lowestSell.id;
187	                            s.asks[asset][lowestSell.id].ercAmount = lowestSell.ercAmount;
188	                        }
189	                        // Check reduced dust threshold for existing limit orders
190	                        if (lowestSell.ercAmount.mul(lowestSell.price) >= LibAsset.minAskEth(asset).mul(C.DUST_FACTOR)) {
191	                            b.dustShortId = b.dustAskId = 0;
192	                        }
193	                    }
194	                    incomingBid.ercAmount = 0;
195	                    return matchIncomingBid(asset, incomingBid, matchTotal, b);
196	                }
197	            } else {
198	                if (incomingBid.ercAmount.mul(incomingBid.price) >= minBidEth) {
199	                    LibOrders.addBid(asset, incomingBid, orderHintArray);
200	                }
201	                return matchIncomingBid(asset, incomingBid, matchTotal, b);
202	            }
203	        }

GitHub : 139..203


- contracts/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	        }
...
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	            }
...
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	        }

GitHub : 78..141, 242..246, 263..272, 321..330


- contracts/libraries/LibBytes.sol
18	        for (uint256 i = 0; i < slateLength; i++) {
19	            // 32 offset for array length, mulitply by each ProposalData
20	            uint256 offset = i * 51 + 32;
21	
22	            address shorter; // bytes20
23	            uint8 shortId; // bytes1
24	            uint64 CR; // bytes8
25	            uint88 ercDebtRedeemed; // bytes11
26	            uint88 colRedeemed; // bytes11
27	
28	            assembly {
29	                // mload works 32 bytes at a time
30	                let fullWord := mload(add(slate, offset))
31	                // read 20 bytes
32	                shorter := shr(96, fullWord) // 0x60 = 96 (256-160)
33	                // read 8 bytes
34	                shortId := and(0xff, shr(88, fullWord)) // 0x58 = 88 (96-8), mask of bytes1 = 0xff * 1
35	                // read 64 bytes
36	                CR := and(0xffffffffffffffff, shr(24, fullWord)) // 0x18 = 24 (88-64), mask of bytes8 = 0xff * 8
37	
38	                fullWord := mload(add(slate, add(offset, 29))) // (29 offset)
39	                // read 88 bytes
40	                ercDebtRedeemed := shr(168, fullWord) // (256-88 = 168)
41	                // read 88 bytes
42	                colRedeemed := add(0xffffffffffffffffffffff, shr(80, fullWord)) // (256-88-88 = 80), mask of bytes11 = 0xff * 11
43	            }
44	
45	            data[i] = MTypes.ProposalData({
46	                shorter: shorter,
47	                shortId: shortId,
48	                CR: CR,
49	                ercDebtRedeemed: ercDebtRedeemed,
50	                colRedeemed: colRedeemed
51	            });
52	        }

GitHub : 18..52


- contracts/libraries/LibOrders.sol
63	        while (currentId != C.TAIL) {
64	            size++;
65	            currentId = orders[asset][currentId].nextId;
66	        }
...
71	        for (uint256 i = 0; i < size; i++) {
72	            list[i] = orders[asset][currentId];
73	            currentId = orders[asset][currentId].nextId;
74	        }
...
448	        while (true) {
449	            uint16 nextId = orders[asset][hintId].nextId;
450	
451	            if (verifyId(orders, asset, hintId, _newPrice, nextId, orderType) == C.EXACT) {
452	                return hintId;
453	            }
454	
455	            if (direction == C.PREV) {
456	                uint16 prevId = orders[asset][hintId].prevId;
457	                hintId = prevId;
458	            } else {
459	                hintId = nextId;
460	            }
461	        }
...
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	        }

GitHub : 63..66, 71..74, 448..461, 572..625, 743..776, 832..843

[N-55]<a name="n-55"></a> Use Assembly for Efficient Event Emission

To efficiently emit events, consider utilizing assembly by making use of scratch space and the free memory pointer. This approach can potentially avoid the costs associated with memory expansion.

However, it's crucial to cache and restore the free memory pointer for safe optimization. Good examples of such practices can be found in well-optimized Solady's codebases. Please review your code and consider the potential gas savings of this approach.

There are 15 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
332	        emit Events.MatchOrder(asset, bidder, incomingBid.orderType, incomingBid.id, matchTotal.fillEth, matchTotal.fillErc);

GitHub : 332


- contracts/facets/PrimaryLiquidationFacet.sol
87	        emit Events.Liquidate(asset, shorter, id, msg.sender, m.ercDebtMatched);

GitHub : 87


- contracts/facets/BridgeRouterFacet.sol
72	        emit Events.Deposit(bridge, msg.sender, dethAmount);
...
90	        emit Events.DepositEth(bridge, msg.sender, dethAmount);
...
122	        emit Events.Withdraw(bridge, msg.sender, dethAmount, fee);
...
143	        emit Events.WithdrawTapp(bridge, msg.sender, dethAmount);

GitHub : 72, 90, 122, 143


- contracts/facets/ExitShortFacet.sol
75	        emit Events.ExitShortWallet(asset, msg.sender, id, buybackAmount);
...
128	        emit Events.ExitShortErcEscrowed(asset, msg.sender, id, buybackAmount);
...
210	        emit Events.ExitShort(asset, msg.sender, id, e.ercFilled);

GitHub : 75, 128, 210


- contracts/facets/RedemptionFacet.sol
210	        emit Events.ProposeRedemption(p.asset, msg.sender);

GitHub : 210, 284, 333


- contracts/libraries/LibOrders.sol

GitHub : 218, 301, 631..633

[N-56]<a name="n-56"></a> Optimize External Calls with Assembly for Memory Efficiency

Using interfaces to make external contract calls in Solidity is convenient but can be inefficient in terms of memory utilization. Each such call involves creating a new memory location to store the data being passed, thus incurring memory expansion costs.

Inline assembly allows for optimized memory usage by re-using already allocated memory spaces or using the scratch space for smaller datasets. This can result in notable gas savings, especially for contracts that make frequent external calls.

Additionally, using inline assembly enables important safety checks like verifying if the target address has code deployed to it using extcodesize(addr) before making the call, mitigating risks associated with contract interactions.

There are 22 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
295	            IDiamond(payable(address(this)))._cancelShort(asset, b.dustShortId);
...
293	            IDiamond(payable(address(this)))._cancelAsk(asset, b.dustAskId);

GitHub : 295, 293


- contracts/facets/PrimaryLiquidationFacet.sol
188	            IDiamond(payable(address(this))).createForcedBid(address(this), m.asset, _bidPrice, m.short.ercDebt, shortHintArray);

GitHub : 188


- contracts/facets/BridgeRouterFacet.sol
68	        uint88 dethAmount = uint88(IBridge(bridge).deposit(msg.sender, amount)); // @dev(safe-cast)
...
87	        uint88 dethAmount = uint88(IBridge(bridge).depositEth{value: msg.value}()); // Assumes 1 ETH = 1 DETH
...
87	        uint88 dethAmount = uint88(IBridge(bridge).depositEth{value: msg.value}()); // Assumes 1 ETH = 1 DETH
...
121	        IBridge(bridge).withdraw(msg.sender, ethAmount);
...
142	        IBridge(bridge).withdraw(msg.sender, ethAmount);

GitHub : 68, 87, 87, 121, 142


- contracts/facets/ExitShortFacet.sol
187	            IDiamond(payable(address(this))).createForcedBid(msg.sender, e.asset, price, e.buybackAmount, shortHintArray);

GitHub : 187


- contracts/libraries/LibBridgeRouter.sol
93	                if (IBridge(rethBridge).getDethValue() < C.ROUNDING_ZERO) {

GitHub : 93, 63, 119, 123


- contracts/libraries/LibOracle.sol

GitHub : 42, 59, 27, 103, 87, 133, 138


- contracts/libraries/LibOrders.sol

GitHub : 982


- contracts/libraries/UniswapOracleLibrary.sol

GitHub : 59

[N-57]<a name="n-57"></a> Consider Using Solady's Gas Optimized Lib for Math

Utilizing gas-optimized math functions from libraries like Solady can lead to more efficient smart contracts. This is particularly beneficial in contracts where these operations are frequently used.

There are 25 instance(s) of this issue:


- contracts/facets/PrimaryLiquidationFacet.sol
199	        m.gasFee = uint88(gasUsed * block.basefee); // @dev(safe-cast)

GitHub : 199


- contracts/facets/RedemptionFacet.sol
198	            redeemerAssetUser.timeToDispute = protocolTime + uint32(m.mul(p.currentCR - 1.1 ether) * 1 hours / 1 ether);
...
198	            redeemerAssetUser.timeToDispute = protocolTime + uint32(m.mul(p.currentCR - 1.1 ether) * 1 hours / 1 ether);
...
195	                protocolTime + uint32((m.mul(p.currentCR - 1.2 ether) + C.ONE_THIRD) * 1 hours / 1 ether);
...
195	                protocolTime + uint32((m.mul(p.currentCR - 1.2 ether) + C.ONE_THIRD) * 1 hours / 1 ether);
...
191	                protocolTime + uint32((m.mul(p.currentCR - 1.3 ether) + 0.75 ether) * 1 hours / 1 ether);
...
191	                protocolTime + uint32((m.mul(p.currentCR - 1.3 ether) + 0.75 ether) * 1 hours / 1 ether);
...
187	                protocolTime + uint32((m.mul(p.currentCR - 1.5 ether) + 1.5 ether) * 1 hours / 1 ether);
...
187	                protocolTime + uint32((m.mul(p.currentCR - 1.5 ether) + 1.5 ether) * 1 hours / 1 ether);
...
183	            redeemerAssetUser.timeToDispute = protocolTime + uint32((m.mul(p.currentCR - 1.7 ether) + 3 ether) * 1 hours / 1 ether);

GitHub : 198, 198, 195, 195, 191, 191, 187, 187, 183, 183, 388


- contracts/libraries/LibBytes.sol

GitHub : 20


- contracts/libraries/LibOracle.sol

GitHub : 30, 63, 93, 93, 141, 141


- contracts/libraries/LibOrders.sol

GitHub : 36, 36, 47, 47


- contracts/libraries/UniswapOracleLibrary.sol

GitHub : 37, 62

[N-58]<a name="n-58"></a> Trade-offs Between Modifiers and Internal Functions

In Solidity, both internal functions and modifiers are used to refactor and manage code, but they come with their own trade-offs, especially in terms of gas cost and flexibility.

Modifiers:
  • Less runtime gas cost (saves around 24 gas per function call).
  • Increases deployment gas cost due to repetitive code.
  • Can only be executed at the start or end of a function.
Internal Functions:
  • Lower deployment cost.
  • Can be executed at any point in a function.
  • Slightly higher runtime gas cost (24 gas) due to the need to jump to the function's location in bytecode.
Recommendations:
  • Use modifiers for high-frequency functions where runtime gas cost matters the most.
  • Use internal functions where the priority is reducing deployment gas cost or when you need more flexibility in the function's logic.

Example analysis shows that using modifiers can increase deployment costs by over 35k gas but save 24 gas per function call during runtime. Choose wisely based on your specific use case.

There are 32 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
47	    ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
47	    ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
47	    ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
67	        onlyDiamond

GitHub : 47, 47, 47, 67


- contracts/facets/ShortOrdersFacet.sol
42	    ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant {
...
42	    ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant {
...
42	    ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant {

GitHub : 42, 42, 42


- contracts/facets/PrimaryLiquidationFacet.sol
49	        isNotFrozen(asset)
...
50	        nonReentrant
...
51	        onlyValidShortRecord(asset, shorter, id)

GitHub : 49, 50, 51


- contracts/facets/BridgeRouterFacet.sol

GitHub : 40, 63, 82, 101, 133


- contracts/facets/ExitShortFacet.sol

GitHub : 43, 44, 45, 89, 90, 91, 149, 149, 149


- contracts/facets/RedemptionFacet.sol

GitHub : 61, 61, 226, 227, 310, 310, 349, 350

[N-59]<a name="n-59"></a> Optimize Unsigned Integer Comparison With Zero

For unsigned integers, checking whether the integer is not equal to zero (!= 0) is less gas-intensive than checking whether it is greater than zero (> 0).

This is because the Ethereum Virtual Machine (EVM) can perform a simple bitwise operation to check if any bit is set (which directly translates to != 0), while checking for > 0 requires additional logic.

As such, when dealing with unsigned integers in Solidity, it is recommended to use the != 0 comparison for gas optimization.

There are 11 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
292	        if (b.dustAskId > 0) {
...
294	        } else if (b.dustShortId > 0) {
...
299	        if (matchTotal.shortFillEth > 0) {

GitHub : 292, 294, 299


- contracts/facets/BridgeRouterFacet.sol
109	            if (dethAssessable > 0) {
...
111	                if (withdrawalFeePct > 0) {

GitHub : 109, 111


- contracts/facets/RedemptionFacet.sol
279	            if (incorrectIndex > 0) {
...
397	        assert(newBaseRate > 0); // Base rate is always non-zero after redemption

GitHub : 279, 397


- contracts/libraries/LibOracle.sol
80	        bool priceDeviation = protocolPrice > 0 && chainlinkDiff.div(protocolPrice) > 0.5 ether;

GitHub : 80


- contracts/libraries/LibSRUtil.sol
35	        if (yield > 0) {
...
113	            if (Asset.ercDebt > 0) {

GitHub : 35, 113, 158

[N-60]<a name="n-60"></a> Delete Unused State Variables

State variables that aren't used in the contract not only clutter the codebase but also consume unnecessary gas during deployment. Specifically, setting non-zero initial values for state variables costs significant gas. By removing these unused state variables, you can save on both deployment gas and potential future storage gas costs. This optimization not only reduces gas expenditures but also enhances code clarity and maintainability. Always ensure a thorough review to confirm that these variables are indeed redundant before removal.

There are 13 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
47	    ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
47	    ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
68	        returns (uint88 ethFilled, uint88 ercAmountLeft)
...
68	        returns (uint88 ethFilled, uint88 ercAmountLeft)
...
85	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
85	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
135	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
135	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
280	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
280	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {

GitHub : 47, 47, 68, 68, 85, 85, 135, 135, 280, 280, 340


- contracts/facets/ExitShortFacet.sol

GitHub : 213


- contracts/facets/RedemptionFacet.sol

GitHub : 384

[N-61]<a name="n-61"></a> Optimize Gas by Using Only Named Returns

The Solidity compiler can generate more efficient bytecode when using named returns. It's recommended to replace anonymous returns with named returns for potential gas savings.

Example:

/// 985 gas cost
function add(uint256 x, uint256 y) public pure returns (uint256) {
  return x + y;
}
/// 941 gas cost
function addNamed(uint256 x, uint256 y) public pure returns (uint256 res) {
  res = x + y;
}

There are 17 instance(s) of this issue:


- contracts/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	    {
...
122	    function _setLiquidationStruct(address asset, address shorter, uint8 id, uint16 shortOrderId)
123	        private
124	        returns (MTypes.PrimaryLiquidation memory)
125	    {
...
229	    function min88(uint256 a, uint88 b) private pure returns (uint88) {

GitHub : 47..53, 122..125, 229


- contracts/facets/BridgeRouterFacet.sol
40	    function getDethTotal(uint256 vault) external view nonReentrantView returns (uint256) {
...
51	    function getBridges(uint256 vault) external view returns (address[] memory) {
...
169	    function _ethConversion(uint256 vault, uint88 amount) private view returns (uint88) {

GitHub : 40, 51, 169


- contracts/facets/RedemptionFacet.sol
31	    function validRedemptionSR(STypes.ShortRecord storage shortRecord, address proposer, address shorter, uint256 minShortErc)
32	        internal
33	        view
34	        returns (bool)
35	    {

GitHub : 31..35


- contracts/libraries/LibBridgeRouter.sol
39	    function assessDeth(uint256 vault, uint256 bridgePointer, uint88 amount, address rethBridge, address stethBridge)
40	        internal
41	        returns (uint88)
42	    {

GitHub : 39..42


- contracts/libraries/LibBytes.sol
11	    function readProposalData(address SSTORE2Pointer, uint8 slateLength) internal view returns (MTypes.ProposalData[] memory) {

GitHub : 11


- contracts/libraries/LibOracle.sol
19	    function getOraclePrice(address asset) internal view returns (uint256) {

GitHub : 19, 168


- contracts/libraries/LibOrders.sol

GitHub : 35, 55..59, 78, 362..367, 985, 989

[N-62]<a name="n-62"></a> Optimize Address Storage Value Management with assembly

There are 4 instance(s) of this issue:


- contracts/facets/PrimaryLiquidationFacet.sol
31	        dusd = _dusd;

GitHub : 31


- contracts/facets/BridgeRouterFacet.sol
30	        rethBridge = _rethBridge;
...
31	        stethBridge = _stethBridge;

GitHub : 30, 31


- contracts/facets/ExitShortFacet.sol
29	        dusd = _dusd;

GitHub : 29

[N-63]<a name="n-63"></a> Gas savings can be achieved by changing the model for assigning value to the structure

Change this structName a = structName({item1: val1,item2: val2}); ==> structName a; a.item1 = val1; a.item2 = val2;

There are 1 instance(s) of this issue:


- contracts/libraries/LibBytes.sol
45	            data[i] = MTypes.ProposalData({
46	                shorter: shorter,
47	                shortId: shortId,
48	                CR: CR,
49	                ercDebtRedeemed: ercDebtRedeemed,
50	                colRedeemed: colRedeemed
51	            });

GitHub : 45..51

[N-64]<a name="n-64"></a> Use Array.unsafeAccess to avoid repeated array length checks

When using storage arrays, solidity adds an internal lookup of the array's length (a Gcoldsload 2100 gas) to ensure it doesn't read past the array's end.

It's possible to avoid this lookup by using Array.unsafeAccess in cases where the length has already been checked.

There are 125 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
215	    function matchlowestSell(
216	        address asset,
217	        STypes.Order memory lowestSell,
218	        STypes.Order memory incomingBid,
219	        MTypes.Match memory matchTotal
220	    ) private {
...
// @audit: array `asset[asset]` is accesed 2 times
232	                STypes.Asset storage Asset = s.asset[asset];
...
// @audit: array `asset[asset]` is accesed 2 times
255	            s.vaultUser[s.asset[asset].vault][lowestSell.addr].ethEscrowed += fillEth;
...
275	    function matchIncomingBid(
276	        address asset,
277	        STypes.Order memory incomingBid,
278	        MTypes.Match memory matchTotal,
279	        MTypes.BidMatchAlgo memory b
280	    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
...
// @audit: array `shorts[asset]` is accesed 3 times
310	            STypes.Order storage currentShort = s.shorts[asset][b.shortId];
...
// @audit: array `shorts[asset]` is accesed 3 times
312	            STypes.Order storage prevShort = s.shorts[asset][b.prevShortId];
...
// @audit: array `shorts[asset]` is accesed 3 times
323	                    Asset.startingShortId = s.shorts[asset][b.shortHintId].nextId;
...
340	    function _getLowestSell(address asset, MTypes.BidMatchAlgo memory b) private view returns (STypes.Order memory lowestSell) {
...
// @audit: array `asks[asset]` is accesed 2 times
343	            STypes.Order storage lowestAsk = s.asks[asset][b.askId];
...
// @audit: array `asks[asset]` is accesed 2 times
354	            return s.asks[asset][b.askId];

GitHub : 232, 255, 310, 312, 323, 343, 354, 343, 354, 391, 398, 400


- contracts/facets/ShortOrdersFacet.sol

GitHub : 72, 73


- contracts/facets/PrimaryLiquidationFacet.sol

GitHub : 97, 102, 170, 194, 210, 211, 241, 250


- contracts/facets/RedemptionFacet.sol

GitHub : 79, 80, 81, 243, 243, 264, 234, 297, 248, 266


- contracts/libraries/LibBridgeRouter.sol

GitHub : 151, 154


- contracts/libraries/LibOracle.sol

GitHub : 151, 152, 151, 152


- contracts/libraries/LibOrders.sol

GitHub : 60, 65, 69, 72, 73, 60, 69, 65, 72, 73, 89, 90, 110, 111, 131, 132, 183, 187, 192, 195, 197, 201, 212, 214, 217, 187, 197, 201, 192, 195, 183, 217, 238, 239, 268, 270, 291, 297, 297, 297, 298, 298, 298, 301, 297, 297, 298, 298, 301, 333, 334, 340, 347, 333, 340, 347, 368, 386, 368, 386, 449, 456, 449, 456, 487, 488, 540, 542, 544, 563, 564, 573, 596, 605, 563, 596, 564, 573, 738, 749, 759, 814, 815, 884, 943


- contracts/libraries/LibSRUtil.sol

GitHub : 42, 44

[N-65]<a name="n-65"></a> Consider using OpenZeppelin's EnumerateSet instead of nested mappings

Nested mappings and multi-dimensional arrays in Solidity operate through a process of double hashing, wherein the original storage slot and the first key are concatenated and hashed, and then this hash is again concatenated with the second key and hashed. This process can be quite gas expensive due to the double-hashing operation and subsequent storage operation (sstore).

OpenZeppelin's EnumerableSet provides a potential solution to this problem. It creates a data structure that combines the benefits of set operations with the ability to enumerate stored elements, which is not natively available in Solidity. EnumerableSet handles the element uniqueness internally and can therefore provide a more gas-efficient and collision-resistant alternative to nested mappings or multi-dimensional arrays in certain scenarios.

There are 12 instance(s) of this issue:


- contracts/libraries/LibOrders.sol
55	    function currentOrders(mapping(address => mapping(uint16 => STypes.Order)) storage orders, address asset)
...
174	        mapping(address => mapping(uint16 => STypes.Order)) storage orders,
...
261	        mapping(address => mapping(uint16 => STypes.Order)) storage orders,
...
289	    function cancelOrder(mapping(address => mapping(uint16 => STypes.Order)) storage orders, address asset, uint16 id) internal {
...
314	    function matchOrder(mapping(address => mapping(uint16 => STypes.Order)) storage orders, address asset, uint16 id) internal {
...
321	        mapping(address => mapping(uint16 => STypes.Order)) storage orders,
...
363	        mapping(address => mapping(uint16 => STypes.Order)) storage orders,
...
403	        mapping(address => mapping(uint16 => STypes.Order)) storage orders,
...
441	        mapping(address => mapping(uint16 => STypes.Order)) storage orders,
...
475	        mapping(address => mapping(uint16 => STypes.Order)) storage orders,

GitHub : 55, 174, 261, 289, 314, 321, 363, 403, 441, 475, 533, 827

[N-66]<a name="n-66"></a> Counting down in for statements is more gas efficient

Counting down is more gas efficient than counting up because neither we are making zero variable to non-zero variable and also we will get gas refund in the last transaction when making non-zero to zero variable. More info

There are 8 instance(s) of this issue:


- contracts/facets/RedemptionFacet.sol
// @audit `i` is counter for the loop
78	        for (uint8 i = 0; i < proposalInput.length; i++) {
...
 // @audit increment is used for counter
78	        for (uint8 i = 0; i < proposalInput.length; i++) {
...
// @audit `i` is counter for the loop
242	        for (uint256 i = 0; i < decodedProposalData.length; i++) {
...
 // @audit increment is used for counter
242	        for (uint256 i = 0; i < decodedProposalData.length; i++) {
...
// @audit `i` is counter for the loop
263	            for (uint256 i = incorrectIndex; i < decodedProposalData.length; i++) {
...
 // @audit increment is used for counter
263	            for (uint256 i = incorrectIndex; i < decodedProposalData.length; i++) {
...
// @audit `i` is counter for the loop
321	        for (uint256 i = 0; i < decodedProposalData.length; i++) {
...
 // @audit increment is used for counter
321	        for (uint256 i = 0; i < decodedProposalData.length; i++) {

GitHub : 78, 242, 263, 321


- contracts/libraries/LibBytes.sol
// @audit `i` is counter for the loop
18	        for (uint256 i = 0; i < slateLength; i++) {
...
 // @audit increment is used for counter
18	        for (uint256 i = 0; i < slateLength; i++) {

GitHub : 18


- contracts/libraries/LibOrders.sol

GitHub : 71, 746, 832

The rule itself is correct. But here are the cases in which SemVer allows you to use version up to 0.8.20

There are 12 instance(s) of this issue:


- contracts/facets/BidOrdersFacet.sol
2	pragma solidity 0.8.21;

GitHub : 2


- contracts/facets/ShortOrdersFacet.sol
2	pragma solidity 0.8.21;

GitHub : 2


- contracts/facets/PrimaryLiquidationFacet.sol
2	pragma solidity 0.8.21;

GitHub : 2


- contracts/facets/BridgeRouterFacet.sol
2	pragma solidity 0.8.21;

GitHub : 2


- contracts/facets/ExitShortFacet.sol
2	pragma solidity 0.8.21;

GitHub : 2


- contracts/facets/RedemptionFacet.sol
2	pragma solidity 0.8.21;

GitHub : 2


- contracts/libraries/LibBridgeRouter.sol
2	pragma solidity 0.8.21;

GitHub : 2


- contracts/libraries/LibBytes.sol
2	pragma solidity 0.8.21;

GitHub : 2


- contracts/libraries/LibOracle.sol
2	pragma solidity 0.8.21;

GitHub : 2


- contracts/libraries/LibOrders.sol
2	pragma solidity 0.8.21;

GitHub : 2


- contracts/libraries/LibSRUtil.sol

GitHub : 2


- contracts/libraries/UniswapOracleLibrary.sol

GitHub : 2

#0 - c4-pre-sort

2024-04-08T02:43:11Z

raymondfam marked the issue as sufficient quality report

#1 - raymondfam

2024-04-08T02:51:55Z

This bot report should be deemed high quality but there could only be a winner for sponsor's review.

#2 - c4-sponsor

2024-04-09T22:10:11Z

ditto-eth (sponsor) disputed

#3 - ditto-eth

2024-04-09T22:10:48Z

these bot submissions add more review overhead and are mostly just spam

#4 - c4-sponsor

2024-04-09T22:12:54Z

ditto-eth (sponsor) acknowledged

#5 - c4-judge

2024-04-17T07:29:48Z

hansfriese marked the issue as grade-b

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