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
Rank: 69/77
Findings: 1
Award: $12.14
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xVolcano
Also found by: 0x11singh99, 0xhacksmithh, JCK, dharma09, nailkhalimov, naman1778, unique
12.1398 USDC - $12.14
Number | Issue | Instances |
---|---|---|
[G-01] | State variables only set in the constructor should be declared immutable | 7 |
[G-02] | State variables can be cached instead of re-reading them from storage | 19 |
[G-03] | Structs can be packed into fewer storage slots | 4 |
[G-04] | State variables can be packed into fewer storage slots | 2 |
[G-05] | Missing zero-address check in constructor | 1 |
[G-06] | Custom error s cost less than require /assert | 1 |
[G-07] | Revert Transaction as soon as possible | 3 |
[G-08] | Cache calculated value instead of recalculate | 7 |
[G-09] | Remove unused import variable | 5 |
[G-10] | Remove unused function parameter to save gas | 1 |
[G-11] | Use calldata instead of memory for function arguments that do not get mutated | 1 |
[G-12] | Don’t cache value if it is only used once | 6 |
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;
File : src/contracts/proxies/ODSafeManager.sol 26: address public safeEngine;
State
variables can be cached instead of re-reading them from storageCaching 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); }
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; }
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))); }
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); }
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); } }
</details>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); } }
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
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.sol
by truncating the size of AccountingEngineParams
struct variables.File : src/contracts/AccountingEngine.sol 58: AccountingEngineParams public _params;
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; }
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; }
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
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;
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;
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
_vault721
for address 0File : src/contracts/proxies/ODSafeManager.sol 64: constructor(address _safeEngine, address _vault721) { 65: safeEngine = _safeEngine.assertNonNull(); 66: vault721 = IVault721(_vault721); 67: vault721.initializeManager(); 68: }
error
s 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
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); }
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); }
calculated
value instead of recalculate7 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; } }
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); }
</details>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); }
import
variable5 Instances in 1 File
<details> <summary>see instances</summary></details>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
parameter
to save gas1 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)); } }
calldata
instead of memory
for function arguments that do not get mutatedWhen 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) {
cache
value if it is only used onceIf 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); }
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) ); }
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); }
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); }
</details>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); }
#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