Open Dollar - 0x11singh99's results

A floating $1.00 pegged stablecoin backed by Liquid Staking Tokens with NFT controlled vaults.

General Information

Platform: Code4rena

Start Date: 18/10/2023

Pot Size: $36,500 USDC

Total HM: 17

Participants: 77

Period: 7 days

Judge: MiloTruck

Total Solo HM: 5

Id: 297

League: ETH

Open Dollar

Findings Distribution

Researcher Performance

Rank: 69/77

Findings: 1

Award: $12.14

Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: 0xVolcano

Also found by: 0x11singh99, 0xhacksmithh, JCK, dharma09, nailkhalimov, naman1778, unique

Labels

bug
G (Gas Optimization)
grade-b
sufficient quality report
G-02

Awards

12.1398 USDC - $12.14

External Links

Gas Optimizations

NumberIssueInstances
[G-01]State variables only set in the constructor should be declared immutable7
[G-02]State variables can be cached instead of re-reading them from storage19
[G-03]Structs can be packed into fewer storage slots4
[G-04]State variables can be packed into fewer storage slots2
[G-05]Missing zero-address check in constructor1
[G-06]Custom errors cost less than require/assert1
[G-07]Revert Transaction as soon as possible3
[G-08]Cache calculated value instead of recalculate7
[G-09]Remove unused import variable5
[G-10]Remove unused function parameter to save gas1
[G-11]Use calldata instead of memory for function arguments that do not get mutated1
[G-12]Don’t cache value if it is only used once6

[G-01] State variables only set in the constructor should be declared immutable

Accessing state variables within a function involves an SLOAD operation, but immutable variables can be accessed directly without the need of it, thus reducing gas costs. As these state variables are assigned only in the constructor, consider declaring them immutable. Avoids a Gsset(** 20000 gas**) in the constructor, and replaces the first access in each transaction(Gcoldsload - ** 2100 gas **) and each access thereafter(Gwarmacces - ** 100 gas ) with aPUSH32( 3 gas **).

Note: These instances missed by bot-report

7 instance in 2 files

File : /src/contracts/oracles/UniV3Relayer.sol

23:  address public baseToken;

25:  address public quoteToken;

30:  string public symbol;

33:  uint128 public baseAmount;

35:  uint256 public multiplier;

37:  uint32 public quotePeriod;

23-37

File : src/contracts/proxies/ODSafeManager.sol

26: address public safeEngine;

26

[G-02] State variables can be cached instead of re-reading them from storage

Caching of a state variable replaces each Gwarmaccess (100 gas) with a much cheaper stack read.

Note: These instances missed by bot-report

19 Instances in 2 Files

<details> <summary>see instances</summary>

File : contracts/proxies/Vault721.sol

  function mint(address _proxy, uint256 _safeId) external {
    require(msg.sender == address(safeManager), 'V721: only safeManager');
    require(_proxyRegistry[_proxy] != address(0), 'V721: non-native proxy');
    address _user = _proxyRegistry[_proxy];
    _safeMint(_user, _safeId);
  }

94-99

File : contracts/proxies/Vault721.sol

   function mint(address _proxy, uint256 _safeId) external {
      require(msg.sender == address(safeManager), 'V721: only safeManager');
+     address _user = _proxyRegistry[_proxy];
-     require(_proxyRegistry[_proxy] != address(0), 'V721: non-native proxy');
-     address _user = _proxyRegistry[_proxy];
+     require(_user != address(0), 'V721: non-native proxy');
      _safeMint(_user, _safeId);
   }

File : contracts/proxies/Vault721.sol

   function _isNotProxy(address _user) internal view returns (bool) {
       return _userRegistry[_user] == address(0) || ODProxy(_userRegistry[_user]).OWNER() != _user;
   }

154-156

File : contracts/proxies/Vault721.sol

   function _isNotProxy(address _user) internal view returns (bool) {
+      address userRegistry = _userRegistry[_user];
-       return _userRegistry[_user] == address(0) || ODProxy(_userRegistry[_user]).OWNER() != _user;
+       return userRegistry == address(0) || ODProxy(userRegistry).OWNER() != _user;
   }

File : contracts/AccountingEngine.sol

  function cancelAuctionedDebtWithSurplus(uint256 _rad) external {
    if (_rad > totalOnAuctionDebt) revert AccEng_InsufficientDebt();

    uint256 _coinBalance = safeEngine.coinBalance(address(this));

    if (_rad > _coinBalance) revert AccEng_InsufficientSurplus();

    safeEngine.settleDebt(_rad);
    totalOnAuctionDebt -= _rad;

    emit CancelDebt(_rad, _coinBalance - _rad, safeEngine.debtBalance(address(this)));
  }

159-170

File : contracts/AccountingEngine.sol

    function cancelAuctionedDebtWithSurplus(uint256 _rad) external {
+       uint256 _totalOnAuctionDebt = totalOnAuctionDebt;
-        if (_rad > totalOnAuctionDebt) revert AccEng_InsufficientDebt();
+        if (_rad > _totalOnAuctionDebt) revert AccEng_InsufficientDebt();

        uint256 _coinBalance = safeEngine.coinBalance(address(this));

        if (_rad > _coinBalance) revert AccEng_InsufficientSurplus();

        safeEngine.settleDebt(_rad);
-        totalOnAuctionDebt -= _rad;
+        totalOnAuctionDebt = _totalOnAuctionDebt -_rad;

    emit CancelDebt(_rad, _coinBalance - _rad, safeEngine.debtBalance(address(this)));
  }

File : contracts/AccountingEngine.sol

  function auctionDebt() external returns (uint256 _id) {
    if (_params.debtAuctionBidSize == 0) revert AccEng_DebtAuctionDisabled();

    uint256 _coinBalance = safeEngine.coinBalance(address(this));
    uint256 _debtBalance = safeEngine.debtBalance(address(this));

    if (_params.debtAuctionBidSize > _unqueuedUnauctionedDebt(_debtBalance)) revert AccEng_InsufficientDebt();

    (_coinBalance, _debtBalance) = _settleDebt(_coinBalance, _debtBalance, _coinBalance);
    totalOnAuctionDebt += _params.debtAuctionBidSize;

    _id = debtAuctionHouse.startAuction({
      _incomeReceiver: address(this),
      _amountToSell: _params.debtAuctionMintedTokens,
      _initialBid: _params.debtAuctionBidSize
    });

    emit AuctionDebt(_id, _params.debtAuctionMintedTokens, _params.debtAuctionBidSize);
  }

175-193

File : contracts/AccountingEngine.sol

  function auctionDebt() external returns (uint256 _id) {
+
+    uint256 _debtAuctionBidSize = _params.debtAuctionBidSize;
-    if (_params.debtAuctionBidSize == 0) revert AccEng_DebtAuctionDisabled();
+    if (_debtAuctionBidSize == 0) revert AccEng_DebtAuctionDisabled();

    uint256 _coinBalance = safeEngine.coinBalance(address(this));
    uint256 _debtBalance = safeEngine.debtBalance(address(this));

-    if (_params.debtAuctionBidSize > _unqueuedUnauctionedDebt(_debtBalance)) revert AccEng_InsufficientDebt();
+    if (_debtAuctionBidSize > _unqueuedUnauctionedDebt(_debtBalance)) revert AccEng_InsufficientDebt();

    (_coinBalance, _debtBalance) = _settleDebt(_coinBalance, _debtBalance, _coinBalance);
-    totalOnAuctionDebt += _params.debtAuctionBidSize;
+    totalOnAuctionDebt += _debtAuctionBidSize;

+    uint256 _debtAuctionMintedTokens = _params.debtAuctionMintedTokens;
    _id = debtAuctionHouse.startAuction({
      _incomeReceiver: address(this),
-      _amountToSell: _params.debtAuctionMintedTokens,
+      _amountToSell: _debtAuctionMintedTokens,
-      _initialBid: _params.debtAuctionBidSize
+      _initialBid: _debtAuctionBidSize
    });

-    emit AuctionDebt(_id, _params.debtAuctionMintedTokens, _params.debtAuctionBidSize);
+    emit AuctionDebt(_id, _debtAuctionMintedTokens, _debtAuctionBidSize);
  }

File : contracts/AccountingEngine.sol

 function transferPostSettlementSurplus() external whenDisabled {
    if (address(postSettlementSurplusDrain) == address(0)) revert AccEng_NullSurplusReceiver();
    if (block.timestamp < disableTimestamp + _params.disableCooldown) revert AccEng_PostSettlementCooldown();

    uint256 _coinBalance = safeEngine.coinBalance(address(this));
    uint256 _debtBalance = safeEngine.debtBalance(address(this));
    uint256 _debtToSettle = Math.min(_coinBalance, _debtBalance);
    (_coinBalance,) = _settleDebt(_coinBalance, _debtBalance, _debtToSettle);

    if (_coinBalance > 0) {
      safeEngine.transferInternalCoins({
        _source: address(this),
        _destination: postSettlementSurplusDrain,
        _rad: _coinBalance
      });

      emit TransferSurplus(postSettlementSurplusDrain, _coinBalance);
    }
  }

260-278

File : contracts/AccountingEngine.sol

 function transferPostSettlementSurplus() external whenDisabled {
    if (address(postSettlementSurplusDrain) == address(0)) revert AccEng_NullSurplusReceiver();
    if (block.timestamp < disableTimestamp + _params.disableCooldown) revert AccEng_PostSettlementCooldown();

    uint256 _coinBalance = safeEngine.coinBalance(address(this));
    uint256 _debtBalance = safeEngine.debtBalance(address(this));
    uint256 _debtToSettle = Math.min(_coinBalance, _debtBalance);
    (_coinBalance,) = _settleDebt(_coinBalance, _debtBalance, _debtToSettle);

    if (_coinBalance > 0) {
+     address _postSettlementSurplusDrain = postSettlementSurplusDrain;
      safeEngine.transferInternalCoins({
        _source: address(this),
-        _destination: postSettlementSurplusDrain,
+        _destination: _postSettlementSurplusDrain,
        _rad: _coinBalance
      });

-      emit TransferSurplus(postSettlementSurplusDrain, _coinBalance);
+      emit TransferSurplus(_postSettlementSurplusDrain, _coinBalance);
    }
  }
</details>

[G-03] Structs can be packed into fewer storage slots

The EVM works with 32 byte words. Variables less than 32 bytes can be declared next to each other in storage and this will pack the values together into a single 32 byte storage slot (if values combined are <= 32 bytes). If the variables packed together are retrieved together in functions (more likely with structs), we will effectively save ~2000 gas with every subsequent SLOAD for that storage slot. This is due to us incurring a Gwarmaccess (100 gas) versus a Gcoldsload (2100 gas).

4 Instances in 1 File

Note : AccountingEngineParams struct defined in interface IAccountingEngine.sol which is not in scope but it is meant to used here taking space in scope contract AccountingEngine.sol storage slots so we will considerAccountingEngineParams struct definition in scope, reduce the size of this to save space in scoped contract AccountingEngine.solby truncating the size of AccountingEngineParams struct variables.

File : src/contracts/AccountingEngine.sol

58: AccountingEngineParams public _params;

58

Reduce uint type for surplusTransferPercentage, surplusDelay, popDebtDelay and disableCooldown from uint256 to uint48 to save 3 SLOT (~6000 Gas)

surplusDelay, popDebtDelay and disableCooldown are holding time in seconds so uint48 is more than sufficient to hold it. surplusTransferPercentage is holding % from 1 to 100 so uint48 will also be enough to hold it even including precision if applied. By doing this we can save 3 storage slots.

File : src/interfaces/IAccountingEngine.sol

struct AccountingEngineParams {
    // percent of the Surplus the system transfers instead of auctioning [0/100]
    uint256 surplusTransferPercentage;
    // Delay between surplus actions
    uint256 surplusDelay;
    // Delay after which debt can be popped from debtQueue
    uint256 popDebtDelay;
    // Time to wait (post settlement) until any remaining surplus can be transferred to the settlement auctioneer
    uint256 disableCooldown;
    // Amount of surplus stability fees transferred or sold in one surplus auction
    uint256 surplusAmount;
    // Amount of stability fees that need to accrue in this contract before any surplus auction can start
    uint256 surplusBuffer;
    // Amount of protocol tokens to be minted post-auction
    uint256 debtAuctionMintedTokens;
    // Amount of debt sold in one debt auction (initial coin bid for debtAuctionMintedTokens protocol tokens)
    uint256 debtAuctionBidSize;
  }

95-112

File : src/interfaces/IAccountingEngine.sol

struct AccountingEngineParams {
-    // percent of the Surplus the system transfers instead of auctioning [0/100]
-    uint256 surplusTransferPercentage;
-    // Delay between surplus actions
-    uint256 surplusDelay;
-    // Delay after which debt can be popped from debtQueue
-    uint256 popDebtDelay;
-    // Time to wait (post settlement) until any remaining surplus can be transferred to the settlement auctioneer
-    uint256 disableCooldown;
+    // percent of the Surplus the system transfers instead of auctioning [0/100]
+    uint48 surplusTransferPercentage;
+    // Delay between surplus actions
+    uint48 surplusDelay;
+    // Delay after which debt can be popped from debtQueue
+    uint48 popDebtDelay;
+    // Time to wait (post settlement) until any remaining surplus can be transferred to the settlement auctioneer
+    uint48 disableCooldown;
    // Amount of surplus stability fees transferred or sold in one surplus auction
    uint256 surplusAmount;
    // Amount of stability fees that need to accrue in this contract before any surplus auction can start
    uint256 surplusBuffer;
    // Amount of protocol tokens to be minted post-auction
    uint256 debtAuctionMintedTokens;
    // Amount of debt sold in one debt auction (initial coin bid for debtAuctionMintedTokens protocol tokens)
    uint256 debtAuctionBidSize;
  }

[G-04] State variables can be packed into fewer storage slots.

The EVM works with 32 byte words. Variables less than 32 bytes can be declared next to eachother in storage and this will pack the values together into a single 32 byte storage slot (if the values combined are <= 32 bytes). If the variables packed together are retrieved together in functions, we will effectively save ~2000 gas with every subsequent SLOAD for that storage slot. This is due to us incurring a Gwarmaccess (100 gas) versus a Gcoldsload (2100 gas).

If variables occupying the same slot are both written the same function or by the constructor, avoids a separate Gsset (20000 gas). Reads of the variables can also be cheaper

2 Instances in 1 File

Note : In bot-report only lastSurplusTime and disableTimestamp is truncated and packed into 1 SLOT which saves only 1 SLOT. But by further re-arranging them and pack both of them with address public extraSurplusReceiver which can save another storage SLOT. This re-arrangement of these variable missed by bot report. So I am including this in my report by showing another extra SLOT saving

Reduce uint type for lastSurplusTime and disableTimestamp by truncating timestamp from uint256 to uint48 and pack with address public extraSurplusReceiver to save 2 SLOT.

File : src/contracts/AccountingEngine.sol

50:  address public postSettlementSurplusDrain;

52:  address public extraSurplusReceiver;

  ...

74:  uint256 public lastSurplusTime;

76:  uint256 public disableTimestamp;

50-76

File : src/contracts/AccountingEngine.sol

  address public postSettlementSurplusDrain;
  /// @inheritdoc IAccountingEngine
  address public extraSurplusReceiver;
+ uint48 public lastSurplusTime;
+ uint48 public disableTimestamp;

  // --- Params ---

  /// @inheritdoc IAccountingEngine
  // solhint-disable-next-line private-vars-leading-underscore
  AccountingEngineParams public _params;

  /// @inheritdoc IAccountingEngine
  function params() external view returns (AccountingEngineParams memory _accEngineParams) {
    return _params;
  }

  // --- Data ---

  /// @inheritdoc IAccountingEngine
  mapping(uint256 _timestamp => uint256 _rad) public debtQueue;
  /// @inheritdoc IAccountingEngine
  uint256 public /* RAD */ totalOnAuctionDebt;
  /// @inheritdoc IAccountingEngine
  uint256 public /* RAD */ totalQueuedDebt;
  /// @inheritdoc IAccountingEngine
- uint256 public lastSurplusTime;
  /// @inheritdoc IAccountingEngine
- uint256 public disableTimestamp;

[G-05] Missing zero-address check in constructor

Missing checks for zero-addresses may lead to infunctional protocol, if the variable addresses are updated incorrectly. It also waste gas as it requires the redeployment of the contract.

1 Instances in 1 File

Check _vault721 for address 0

File : src/contracts/proxies/ODSafeManager.sol

64:  constructor(address _safeEngine, address _vault721) {
65:   safeEngine = _safeEngine.assertNonNull();
66:   vault721 = IVault721(_vault721);
67:   vault721.initializeManager();
68:   }

64-68

[G-06] Custom errors cost less than require/assert

Consider the use of a custom error, as it leads to a cheaper deploy cost and run time cost. The run time cost is only relevant when the revert condition is met.

Note: These instances missed by bot-report

1 Instance in 1 File


File : contracts/proxies/Vault721.sol

   require(_safeManager != address(0));//@audit use custom error

111

[G-07] Revert Transaction as soon as possible

Always try reverting transactions as early as possible when using require/if statements. In case a transaction revert occurs, the user will pay the gas up until the revert was executed

3 Instances in 2 Files

File : contracts/proxies/ODSafeManager.sol

 function transferSAFEOwnership(uint256 _safe, address _dst) external {
+   if (_dst == address(0)) revert ZeroAddress();
    require(msg.sender == address(vault721), 'SafeMngr: Only Vault721');

-   if (_dst == address(0)) revert ZeroAddress();
    SAFEData memory _sData = _safeData[_safe];
    if (_dst == _sData.owner) revert AlreadySafeOwner();

    _usrSafes[_sData.owner].remove(_safe);
    _usrSafesPerCollat[_sData.owner][_sData.collateralType].remove(_safe);

    _usrSafes[_dst].add(_safe);
    _usrSafesPerCollat[_dst][_sData.collateralType].add(_safe);

    _safeData[_safe].owner = _dst;

    emit TransferSAFEOwnership(msg.sender, _safe, _dst);
  }

136-152

File : contracts/AccountingEngine.sol

 function auctionDebt() external returns (uint256 _id) {
    if (_params.debtAuctionBidSize == 0) revert AccEng_DebtAuctionDisabled();
+
+    uint256 _debtBalance = safeEngine.debtBalance(address(this));
+
+    if (_params.debtAuctionBidSize > _unqueuedUnauctionedDebt(_debtBalance)) revert AccEng_InsufficientDebt();
+
     uint256 _coinBalance = safeEngine.coinBalance(address(this));
-    uint256 _debtBalance = safeEngine.debtBalance(address(this));

-    if (_params.debtAuctionBidSize > _unqueuedUnauctionedDebt(_debtBalance)) revert AccEng_InsufficientDebt();

    (_coinBalance, _debtBalance) = _settleDebt(_coinBalance, _debtBalance, _coinBalance);
    totalOnAuctionDebt += _params.debtAuctionBidSize;

    _id = debtAuctionHouse.startAuction({
      _incomeReceiver: address(this),
      _amountToSell: _params.debtAuctionMintedTokens,
      _initialBid: _params.debtAuctionBidSize
    });

    emit AuctionDebt(_id, _params.debtAuctionMintedTokens, _params.debtAuctionBidSize);
  }

175-193

[G-08] Cache calculated value instead of recalculate

7 Instances in 1 File

<details> <summary>see instances</summary>

File : contracts/proxies/actions/BasicActions.sol

    // If there was already enough COIN in the safeEngine balance, just exits it without adding more debt
    if (_coinAmount < _deltaWad * RAY) {
      // Calculates the needed deltaDebt so together with the existing coins in the safeEngine is enough to exit wad amount of COIN tokens
      _deltaDebt = ((_deltaWad * RAY - _coinAmount) / _rate).toInt();
      // This is neeeded due lack of precision. It might need to sum an extra deltaDebt wei (for the given COIN wad amount)
      _deltaDebt = uint256(_deltaDebt) * _rate < _deltaWad * RAY ? _deltaDebt + 1 : _deltaDebt;
    }
  }

39-47

File : contracts/proxies/actions/BasicActions.sol

+    uint256 _delta = _deltaWad * RAY;
    // If there was already enough COIN in the safeEngine balance, just exits it without adding more debt
-    if (_coinAmount < _deltaWad * RAY) {
+    if (_coinAmount < _delta) {
      // Calculates the needed deltaDebt so together with the existing coins in the safeEngine is enough to exit wad amount of COIN tokens
-      _deltaDebt = ((_deltaWad * RAY - _coinAmount) / _rate).toInt();
+      _deltaDebt = ((_delta - _coinAmount) / _rate).toInt();
      // This is neeeded due lack of precision. It might need to sum an extra deltaDebt wei (for the given COIN wad amount)
-      _deltaDebt = uint256(_deltaDebt) * _rate < _deltaWad * RAY ? _deltaDebt + 1 : _deltaDebt;
+      _deltaDebt = uint256(_deltaDebt) * _rate < _delta ? _deltaDebt + 1 : _deltaDebt;
    }
  }

File : contracts/proxies/actions/BasicActions.sol

   function _collectAndExitCoins(address _manager, address _coinJoin, uint256 _safeId, uint256 _deltaWad) internal {
      // Moves the COIN amount to proxy's address
      _transferInternalCoins(_manager, _safeId, address(this), _deltaWad * RAY);
      // Exits the COIN amount to the user's address
      _exitSystemCoins(_coinJoin, _deltaWad * RAY);
  }

201-206

File : contracts/proxies/actions/BasicActions.sol

   function _collectAndExitCoins(address _manager, address _coinJoin, uint256 _safeId, uint256 _deltaWad) internal {
+      uint256 _delta =  _deltaWad * RAY;
      // Moves the COIN amount to proxy's address
-      _transferInternalCoins(_manager, _safeId, address(this), _deltaWad * RAY);
+      _transferInternalCoins(_manager, _safeId, address(this), _delta);
      // Exits the COIN amount to the user's address
-      _exitSystemCoins(_coinJoin, _deltaWad * RAY);
+      _exitSystemCoins(_coinJoin, _delta);
  }
</details>

[G-09] Remove unused import variable

5 Instances in 1 File

<details> <summary>see instances</summary>

File : contracts/proxies/actions/BasicActions.sol

05:     import {ODProxy} from '@contracts/proxies/ODProxy.sol';//@audit remove ODProxy

        import {ISAFEEngine} from '@interfaces/ISAFEEngine.sol';
08:     import {ICoinJoin} from '@interfaces/utils/ICoinJoin.sol';//@audit remove ICoinJoin
        import {ITaxCollector} from '@interfaces/ITaxCollector.sol';
10:     import {ICollateralJoin} from '@interfaces/utils/ICollateralJoin.sol';//@audit remove ICollateralJoin
11:     import {IERC20Metadata} from '@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol';//@audit remove IERC20Metadata
        import {IBasicActions} from '@interfaces/proxies/actions/IBasicActions.sol';

14:     import {Math, WAD, RAY, RAD} from '@libraries/Math.sol';//@audit remove RAD

05-14

</details>

[G-10] Remove unused function parameter to save gas

1 Instance in 1 File


File : contracts/proxies/Vault721.sol

//@audit remove `batchSize`
  function _afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal override {
      require(to != address(0), 'V721: no burn');
      if (from != address(0)) {
       address payable proxy;

       if (_isNotProxy(to)) {
        proxy = _build(to);
      } else {
        proxy = payable(_userRegistry[to]);
      }
      IODSafeManager(safeManager).transferSAFEOwnership(firstTokenId, address(proxy));
     }
   }

187-199

[G-11] Use calldata instead of memory for function arguments that do not get mutated

When you specify a data location as memory, that value will be copied into memory. When you specify the location as calldata, the value will stay static within calldata. If the value is a large, complex type, using memory may result in extra memory expansion costs.

1 Instance in 1 File

File : contracts/proxies/ODProxy.sol

-    function execute(address _target, bytes memory _data) external payable onlyOwner returns (bytes memory _response) {
+    function execute(address _target, bytes calldata _data) external payable onlyOwner returns (bytes memory _response) {

26

[G-12] Don’t cache value if it is only used once

If a value is only intended to be used once then it should not be cached. Caching the value will result in unnecessary stack manipulation.

6 Instances in 2 Files

<details> <summary>see instances</summary>

File : contracts/proxies/actions/BasicActions.sol

    address _safeEngine = ODSafeManager(_manager).safeEngine();//@audit don't cache _safeEngine
    ODSafeManager.SAFEData memory _safeInfo = ODSafeManager(_manager).safeData(_safeId);
    ITaxCollector(_taxCollector).taxSingle(_safeInfo.collateralType);

    // Generates debt in the SAFE
    _modifySAFECollateralization(
      _manager,
      _safeId,
      0,
      _getGeneratedDeltaDebt(_safeEngine, _safeInfo.collateralType, _safeInfo.safeHandler, _deltaWad)
    );

    // Moves the COIN amount to user's address
    _collectAndExitCoins(_manager, _coinJoin, _safeId, _deltaWad);
  }

101-115


File : contracts/proxies/actions/BasicActions.sol

 address _safeEngine = ODSafeManager(_manager).safeEngine();//@audit don't cache _safeEngine
    ODSafeManager.SAFEData memory _safeInfo = ODSafeManager(_manager).safeData(_safeId);
    ITaxCollector(_taxCollector).taxSingle(_safeInfo.collateralType);

    // Joins COIN amount into the safeEngine
    _joinSystemCoins(_coinJoin, _safeInfo.safeHandler, _deltaWad);

    // Paybacks debt to the SAFE
    _modifySAFECollateralization(
      _manager, _safeId, 0, _getRepaidDeltaDebt(_safeEngine, _safeInfo.collateralType, _safeInfo.safeHandler)
    );
  }

128-139


File : contracts/proxies/actions/BasicActions.sol

 address _safeEngine = ODSafeManager(_manager).safeEngine();//@audit don't cache _safeEngine
    ODSafeManager.SAFEData memory _safeInfo = ODSafeManager(_manager).safeData(_safeId);
    ITaxCollector(_taxCollector).taxSingle(_safeInfo.collateralType);

    // Takes token amount from user's wallet and joins into the safeEngine
    _joinCollateral(_collateralJoin, _safeInfo.safeHandler, _collateralAmount);

    // Locks token amount into the SAFE and generates debt
    _modifySAFECollateralization(
      _manager,
      _safeId,
      _collateralAmount.toInt(),
      _getGeneratedDeltaDebt(_safeEngine, _safeInfo.collateralType, _safeInfo.safeHandler, _deltaWad)
    );

    // Exits and transfers COIN amount to the user's address
    _collectAndExitCoins(_manager, _coinJoin, _safeId, _deltaWad);
  }

179-196


File : contracts/proxies/actions/BasicActions.sol

    address _safeEngine = ODSafeManager(_manager).safeEngine();//@audit don't cache _safeEngine
    ODSafeManager.SAFEData memory _safeInfo = ODSafeManager(_manager).safeData(_safeId);
    ITaxCollector(_taxCollector).taxSingle(_safeInfo.collateralType);

    // Joins COIN amount into the safeEngine
    _joinSystemCoins(_coinJoin, _safeInfo.safeHandler, _debtWad);

    // Paybacks debt to the SAFE and unlocks token amount from it
    _modifySAFECollateralization(
      _manager,
      _safeId,
      -_collateralWad.toInt(),
      _getRepaidDeltaDebt(_safeEngine, _safeInfo.collateralType, _safeInfo.safeHandler)
    );

    // Transfers token amount to the user's address
    _collectAndExitCollateral(_manager, _collateralJoin, _safeId, _collateralWad);
  }

354-371


File : contracts/proxies/ODSafeManager.sol

    int256 _deltaCollateral = _safeInfo.lockedCollateral.toInt();//@audit don't cache _deltaCollateral
    int256 _deltaDebt = _safeInfo.generatedDebt.toInt();//@audit don't cache _deltaDebt
    ISAFEEngine(safeEngine).transferSAFECollateralAndDebt(
      _srcData.collateralType, _srcData.safeHandler, _dstData.safeHandler, _deltaCollateral, _deltaDebt
    );

    // Remove safe from owner's list (notice it doesn't erase safe ownership)
    _usrSafes[_srcData.owner].remove(_safeSrc);
    _usrSafesPerCollat[_srcData.owner][_srcData.collateralType].remove(_safeSrc);
    emit MoveSAFE(msg.sender, _safeSrc, _safeDst);
  }

222-232

</details>

#0 - c4-pre-sort

2023-10-27T01:56:58Z

raymondfam marked the issue as sufficient quality report

#1 - c4-judge

2023-11-03T17:41:31Z

MiloTruck marked the issue as grade-c

#2 - c4-judge

2023-11-03T17:41:50Z

MiloTruck 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