| Number | Details | Instances |
|---|---|---|
| [Medium-1] | High level access functions can create points of failure (Not captured by 4nalyzer) | 1 |
| [Medium-2] | Comparisons against msg.sender and address(this) can easily be bypassed | 1 |
| [Medium-3] | Privileged functions can create points of failure | 17 |
| [Medium-4] | Public or external initialize functions should be protected with the initializer modifier | 1 |
| [Medium-5] | Off-by-one timestamp error | 2 |
| Number | Details | Instances |
|---|---|---|
| [Low-1] | Potential division by zero should have zero checks in place | 11 |
| [Low-2] | Gas grief possible on unsafe external calls | 4 |
| [Low-3] | Storage Write Removal Bug On Conditional Early Termination | 5 |
| [Low-4] | Subtraction may underflow if multiplication is too large | 1 |
| [Low-5] | Missing checks for address(0x0) when updating address state variables | 4 |
| [Low-6] | Code does not follow the best practice of check-effects-interaction | 9 |
| [Low-7] | Function returns true/false for empty array | 2 |
| [Low-8] | Some tokens may revert when zero value transfers are made | 3 |
| [Low-9] | Contracts are vulnerable to fee-on-transfer accounting-related issues | 4 |
| [Low-10] | Withdraw on tokens which are payable but have no withdraw functionality | 1 |
| [Low-11] | Contract contains payable functions but no withdraw/sweep function | 1 |
| [Low-12] | Solidity version 0.8.20 won't work on all chains due to PUSH0 | 2 |
| [Low-13] | Require should be used instead of assert as assert in some solidity versions does not refund remaining gas | 2 |
| [Low-14] | Low level calls in solidity versions preceding 0.8.14 can result in an optimiser bug | 13 |
| [Low-15] | Ownable2Step should be used in place of Ownable | 1 |
| [Low-16] | Using > when declaring solidity version without specifying an upperbound can cause future vulnerabilities | 1 |
| [Low-17] | No limits when setting min/max amounts | 1 |
| [Low-18] | The function decimals() is not part of the ERC20 standard | 2 |
| [Low-19] | Minting to the zero address should be avoided | 2 |
| [Low-20] | Loss of precision | 13 |
| [Low-21] | Missing zero address check in constructor | 2 |
| [Low-22] | Use of onlyOwner functions can be lost | 1 |
| [Low-23] | Using zero as a parameter | 4 |
| [Low-24] | Address from parameter can cause issues | 1 |
| [Low-25] | Constant decimal values | 6 |
| [Low-26] | Arrays can grow in size without a way to shrink them | 1 |
| [Low-27] | Contract existence is not checked before low level call | 1 |
| [Low-28] | Missing zero address check in initializer | 1 |
| [Low-29] | Critical functions should have a timelock | 10 |
| [Low-30] | Consider implementing two-step procedure for updating protocol addresses | 2 |
| [Low-31] | Low Level Calls to Custom Addresses | 1 |
| [Low-32] | Constructors missing validation | 3 |
| [Low-33] | Inconsistent use of _msgSender() and msg.sender in contract | 1 |
| [Low-34] | Division in comparison | 2 |
| [Low-35] | increaseAllowance/decreaseAllowance are now deprecated | 2 |
| [Low-36] | Return values not checked for OZ EnumerableSet add/remove functions | 1 |
| [Low-37] | Solidity file is susceptible to .selector-related optimizer bug due to the version used | 4 |
| [Low-38] | Consider the case where totalSupply is zero |
2 |
| [Low-39] | Large transfers may not work with some ERC20 tokens | 7 |
| [Low-40] | Functions calling contracts/addresses with transfer hooks are missing reentrancy guards | 6 |
| [Low-41] | Consider a uptime feed on L2 deployments to prevent issues caused by downtime | 41 |
| Number | Details | Instances |
|---|---|---|
| [NonCritical-1] | Unnecessary struct attribute prefix | 1 |
| [NonCritical-2] | Getting a bool return value does not confirm the existence of a function in an external call | 1 |
| [NonCritical-3] | Default address(0) can be returned | 23 |
| [NonCritical-4] | Large approvals such as type(uint256).max may not work with some tokens | 1 |
| [NonCritical-5] | Revert on Transfer to the Zero Address | 9 |
| [NonCritical-6] | The function symbol() is not part of the ERC20 standard | 1 |
| [NonCritical-7] | The function name() is not part of the ERC20 standard | 1 |
| [NonCritical-8] | Addition/multiplication in unchecked block is unsafe | 9 |
| [NonCritical-9] | Code injection via token name | 3 |
| [NonCritical-10] | Token burning is centralized | 1 |
| [NonCritical-11] | Floating pragma should be avoided | 2 |
| [NonCritical-12] | Interfaces should be declared in a separate file | 1 |
| [NonCritical-13] | Events regarding state variable changes should emit the previous state variable value | 19 |
| [NonCritical-14] | In functions which accept an address as a parameter, there should be a zero address check to prevent bugs | 162 |
| [NonCritical-15] | Enum values should be used in place of constant array indexes | 1 |
| [NonCritical-16] | Default int values are manually set | 1 |
| [NonCritical-17] | Revert statements within external and public functions can be used to perform DOS attacks | 33 |
| [NonCritical-18] | Private and internal state variables should have a preceding _ in their name unless they are constants | 21 |
| [NonCritical-19] | Contract lines should not be longer than 120 characters for readability | 34 |
| [NonCritical-20] | Avoid updating storage when the value hasn't changed | 6 |
| [NonCritical-21] | Specific imports should be used where possible so only used code is imported | 21 |
| [NonCritical-22] | Old Solidity version | 2 |
| [NonCritical-23] | Not all event definitions are utilizing indexed variables. | 26 |
| [NonCritical-24] | Explicitly define visibility of state variables to prevent misconceptions on what can access the variable | 2 |
| [NonCritical-25] | Explicitly define visibility of functions to prevent misconceptions on what can access the function | 24 |
| [NonCritical-26] | Contracts should have all public/external functions exposed by interfaces | 51 |
| [NonCritical-27] | Functions within contracts are not ordered according to the solidity style guide | 7 |
| [NonCritical-28] | Double type casts create complexity within the code | 1 |
| [NonCritical-29] | Emits without msg.sender parameter | 2 |
| [NonCritical-30] | Functions with array parameters should have length checks in place | 1 |
| [NonCritical-31] | A function which defines named returns in it's declaration doesn't need to use return | 6 |
| [NonCritical-32] | Unused state variables present | 1 |
| [NonCritical-33] | Constants should be on the left side of the comparison | 14 |
| [NonCritical-34] | Defined named returns not used within function | 10 |
| [NonCritical-35] | Initialize functions do not emit an event | 1 |
| [NonCritical-36] | Both immutable and constant state variables should be CONSTANT_CASE | 24 |
| [NonCritical-37] | Overly complicated arithmetic | 10 |
| [NonCritical-38] | Use of non-named numeric constants | 12 |
| [NonCritical-39] | Redundant else statement | 2 |
| [NonCritical-40] | Employ Explicit Casting to Bytes or Bytes32 for Enhanced Code Clarity and Meaning | 4 |
| [NonCritical-41] | Event emit should emit a parameter | 16 |
| [NonCritical-42] | Empty bytes check is missing | 3 |
| [NonCritical-43] | No equate comparison checks between to and from address parameters | 10 |
| [NonCritical-44] | Return bool not explicit | 2 |
| [NonCritical-45] | Do not use underscore at the end of variable name | 10 |
| [NonCritical-46] | Use OpenZeppelin's ReentrancyGuard/ReentrancyGuardUpgradeable rather than writing your own | 4 |
| [NonCritical-47] | call bypasses function existence check, type checking and argument packing | 6 |
| [NonCritical-48] | Cyclomatic complexity in functions | 5 |
| [NonCritical-49] | Contract does not follow the Solidity style guide's suggested layout ordering | 1 |
| [NonCritical-50] | Unused import | 1 |
| [NonCritical-51] | Events may be emitted out of order due to code not follow the best practice of check-effects-interaction | 2 |
| [NonCritical-52] | Missing events in sensitive functions | 19 |
| [NonCritical-53] | Ensure block.timestamp is only used in long time intervals | 2 |
| [NonCritical-54] | Avoid mutating function parameters | 3 |
| [NonCritical-55] | Don't only depend on Solidity's arithmetic ordering. | 10 |
| [NonCritical-56] | A event should be emitted if a non immutable state variable is set in a constructor | 3 |
| [NonCritical-57] | No limit when setting fees in initializer | 1 |
| [NonCritical-58] | Non constant/immutable state variables are missing a setter post deployment | 1 |
| [NonCritical-59] | The I name prefix should only be used for interfaces | 2 |
| [NonCritical-60] | Long numbers should include underscores to improve readability and prevent typos | 4 |
| [NonCritical-61] | Floating pragma defined inconsistently | 2 |
| [NonCritical-62] | Use 'using' keyword when using specific imports rather than calling the specific import directly | 35 |
| [NonCritical-63] | Inconsistent checks of address params against address(0) | 6 |
| [NonCritical-64] | Avoid declaring variables with the names of defined functions within the project | 36 |
| [NonCritical-65] | Constructors should emit an event | 10 |
| [NonCritical-66] | Contract and Abstract files should have a fixed compiler version | 41 |
| [NonCritical-67] | Constructor with array/string/bytes parameters has no empty array checks | 3 |
| [NonCritical-68] | Events should have parameters | 2 |
| [NonCritical-69] | Errors should have parameters | 129 |
| [NonCritical-70] | Consider using OpenZeppelins SafeCall library when making calls to arbitrary contracts | 3 |
| [NonCritical-71] | Avoid using 'owner' or '_owner' as a parameter name | 18 |
| [NonCritical-72] | Avoid arithmetic directly within array indices | 1 |
| [NonCritical-73] | Memory-safe annotation missing | 3 |
| [NonCritical-74] | Pure function accesses storage | 1 |
| [NonCritical-75] | Redundant function returns constant | 1 |
| [NonCritical-76] | ERC777 tokens can introduce reentrancy risks | 7 |
| Number | Details | Instances | Gas |
|---|---|---|---|
| [Gas-1] | The result of a function call should be cached rather than re-calling the function | 5 | 3000 |
| [Gas-2] | Avoid updating storage when the value hasn't changed | 5 | 24000 |
| [Gas-3] | Using named returns for pure and view functions is cheaper than using regular returns | 195 | 988650 |
| [Gas-4] | Avoid caching immutable state variables | 1 | 0.0 |
| [Gas-5] | Public functions not used internally can be marked as external to save gas | 5 | 0.0 |
| [Gas-6] | Usage of smaller uint/int types causes overhead | 132 | 958320 |
| [Gas-7] | Mappings are cheaper than arrays for state variable iteration | 2 | 4000 |
| [Gas-8] | Use != 0 instead of > 0 | 14 | 588 |
| [Gas-9] | Integer increments by one can be unchecked to save on gas fees | 1 | 120 |
| [Gas-10] | Default bool values are manually reset | 3 | 0.0 |
| [Gas-11] | Default int values are manually reset | 3 | 0.0 |
| [Gas-12] | Function calls within for loops | 10 | 0.0 |
| [Gas-13] | Use assembly to check for the zero address | 22 | 0.0 |
| [Gas-14] | Can transfer 0 | 3 | 0.0 |
| [Gas-15] | Structs can be packed into fewer storage slots | 2 | 10000 |
| [Gas-16] | Don't use _msgSender() if not supporting EIP-2771 | 9 | 1296 |
| [Gas-17] | Superfluous event fields | 3 | 306 |
| [Gas-18] | Private functions used once can be inlined | 9 | 0.0 |
| [Gas-19] | Use bitmap to save gas | 10 | 7000 |
| [Gas-20] | Use assembly to emit events | 43 | 70262 |
| [Gas-21] | Use solady library where possible to save gas | 7 | 49000 |
| [Gas-22] | Use assembly in place of abi.decode to extract calldata values more efficiently | 5 | 0.0 |
| [Gas-23] | Using private rather than public for constants and immutables, saves gas | 8 | 0.0 |
| [Gas-24] | Modulus operations that could be unchecked | 1 | 85 |
| [Gas-25] | Identical Deployments Should be Replaced with Clone | 1 | 0.0 |
| [Gas-26] | Mark Functions That Revert For Normal Users As payable | 17 | 7225 |
| [Gas-27] | Lack of unchecked in loops | 13 | 10920 |
| [Gas-28] | Avoid indexing dynamic types | 1 | 0.0 |
| [Gas-29] | Where a value is casted more than once, consider caching the result to save gas | 2 | 0.0 |
| [Gas-30] | Assembly let var only used on once | 5 | 0.0 |
| [Gas-31] | Use assembly to validate msg.sender | 5 | 0.0 |
| [Gas-32] | Unnecessary casting as variable is already of the same type | 1 | 22 |
| [Gas-33] | Simple checks for zero uint can be done using assembly to save gas | 8 | 384 |
| [Gas-34] | Using nested if to save gas | 36 | 7776 |
| [Gas-35] | Stack variable cost less than state variables while used in emiting event | 2 | 36 |
| [Gas-36] | Low level call can be optimized with assembly | 10 | 24800 |
| [Gas-37] | Using constants directly, rather than caching the value, saves gas | 1 | 0.0 |
| [Gas-38] | Use s.x = s.x + y instead of s.x += y for memory structs (same for -= etc) | 3 | 900 |
| [Gas-39] | Solidity versions 0.8.19 and above are more gas efficient | 2 | 4000 |
| [Gas-40] | Variable declared within iteration | 3 | 0.0 |
| [Gas-41] | Calling .length in a for loop wastes gas | 8 | 6208 |
| [Gas-42] | Internal functions only used once can be inlined to save gas | 24 | 17280 |
| [Gas-43] | Constructors can be marked as payable to save deployment gas | 16 | 0.0 |
| [Gas-44] | Assigning to structs can be more efficient | 4 | 2080 |
| [Gas-45] | Only emit event in setter function if the state variable was changed | 11 | 0.0 |
| [Gas-46] | It is a waste of GAS to emit variable literals | 6 | 288 |
| [Gas-47] | Short circuit before external calls | 4 | 0.0 |
| [Gas-48] | Use OZ Array.unsafeAccess() to avoid repeated array length checks | 5 | 52500 |
| [Gas-49] | State variable read in a loop | 4 | 92704 |
| [Gas-50] | Use uint256(1)/uint256(2) instead of true/false to save gas for changes | 10 | 1700000 |
| [Gas-51] | Write direct outcome, instead of performing mathematical operations for constant state variables | 3 | 0.0 |
| [Gas-52] | Consider pre-calculating the address of address(this) to save gas | 22 | 0.0 |
| [Gas-53] | Use 'storage' instead of 'memory' for struct/array state variables | 6 | 75600 |
| [Gas-54] | Public functions not called internally | 5 | 0.0 |
| [Gas-55] | Empty blocks should be removed or emit something | 70 | 0.0 |
| [Gas-56] | Use constants instead of type(uint).max | 17 | 0.0 |
| [Gas-57] | Using type(uint).min is redundant as it's always 0 | 1 | 0.0 |
| [Gas-58] | Function can be defined as pure | 69 | 0.0 |
High-level access functions in software, especially those with administrative privileges, can lead to points of failure due to their potential for misuse and unauthorized access. These functions, if compromised, can allow attackers to escalate privileges, leading to unauthorized control or access to sensitive data and functionalities. Insecure direct object references, where user inputs aren't properly validated, can exacerbate this risk, allowing attackers to manipulate the system. Ensuring robust access control and validation mechanisms is crucial to mitigate these vulnerabilities
Num of instances: 1
Click to show findings
['76']
76: function burn(address burnFrom, uint256 amount) external nonReentrant {
77: address sender = _msgSender();
78: MinterData memory minterCache = minters[sender];
79:
80: if (amount == 0) {
81: return;
82: }
83:
84:
85:
86: if (burnFrom != sender && !(burnFrom == address(this) && sender == owner())) { // <= FOUND
87: _spendAllowance(burnFrom, sender, amount);
88: }
89:
90:
91: unchecked {
92:
93: minterCache.minted = minterCache.minted > amount ? minterCache.minted - uint128(amount) : 0;
94: }
95: minters[sender] = minterCache;
96:
97: _burn(burnFrom, amount);
98: }Comparisons using msg.sender and address(this) can be exploited when contracts call their own functions. If an attacker influences the calldata during such a self-call, they can bypass checks expecting msg.sender to be an external address. Instead, msg.sender becomes the contract's own address. This can circumvent security checks designed for external calls or disrupt logic that uses msg.sender for accounting or differentiation. To counter this, contracts should not solely rely on msg.sender checks and should be wary of potential recursive calls. It's crucial to implement additional validation mechanisms and conduct thorough tests to prevent unintended call behaviors.
Num of instances: 1
Click to show findings
['105']
105: if (msg.sender != address(this)) revert E_Unauthorized(); // <= FOUNDEnsure such accounts are protected and consider implementing multi sig to prevent a single point of failure
Num of instances: 17
Click to show findings
['42']
42: function setCapacity(address minter, uint128 capacity) external onlyOwner // <= FOUND['104']
104: function allocate(address vault, uint256 amount) external onlyOwner // <= FOUND['116']
116: function deallocate(address vault, uint256 amount) external onlyOwner // <= FOUND['134']
134: function addIgnoredForTotalSupply(address account) external onlyOwner returns (bool success) // <= FOUND['141']
141: function removeIgnoredForTotalSupply(address account) external onlyOwner returns (bool success) // <= FOUND['179']
179: function governorAdmin() public view virtual override useView(MODULE_GOVERNANCE) returns (address) // <= FOUND['222']
222: function setGovernorAdmin(address newGovernorAdmin) public virtual override use(MODULE_GOVERNANCE) // <= FOUND['164']
164: function setUpgradeAdmin(address newUpgradeAdmin) external nonReentrant adminOnly // <= FOUND['99']
99: function governorAdmin() public view virtual reentrantOK returns (address) // <= FOUND['252']
252: function setGovernorAdmin(address newGovernorAdmin) public virtual nonReentrant governorOnly // <= FOUND['137']
137: function setAdmin(address newAdmin) external onlyAdmin // <= FOUND['148']
148: function setFeeReceiver(address newReceiver) external onlyAdmin // <= FOUND['159']
159: function setProtocolFeeShare(uint16 newProtocolFeeShare) external onlyAdmin // <= FOUND['171']
171: function setInterestFeeRange(uint16 minInterestFee_, uint16 maxInterestFee_) external onlyAdmin // <= FOUND['186']
186: function setVaultInterestFeeRange(address vault, bool exists_, uint16 minInterestFee_, uint16 maxInterestFee_)
187: external
188: onlyAdmin // <= FOUND
189: ['205']
205: function setVaultFeeConfig(address vault, bool exists_, address feeReceiver_, uint16 protocolFeeShare_)
206: external
207: onlyAdmin // <= FOUND
208: ['76']
76: function burn(address burnFrom, uint256 amount) external nonReentrant {
77: address sender = _msgSender();
78: MinterData memory minterCache = minters[sender];
79:
80: if (amount == 0) {
81: return;
82: }
83:
84:
85:
86: if (burnFrom != sender && !(burnFrom == address(this) && sender == owner())) { // <= FOUND
87: _spendAllowance(burnFrom, sender, amount);
88: }
89:
90:
91: unchecked {
92:
93: minterCache.minted = minterCache.minted > amount ? minterCache.minted - uint128(amount) : 0;
94: }
95: minters[sender] = minterCache;
96:
97: _burn(burnFrom, amount);
98: }[Medium-4] Public or external initialize functions should be protected with the initializer modifier
The initializer modifiers ensures two key aspects. A) Only an authorised account can initialize B) initialization can only be done once. Consider using such a modifier and in instances where initialization can be done more than once, the function name should be changed to reflect that.
Num of instances: 1
Click to show findings
['29']
29: function initialize(address proxyCreator) public virtual reentrantOK { // <= FOUND
30: if (initialized) revert E_Initialized();
31: initialized = true;
32:
33:
34:
35:
36: if (msg.data.length != 4 + 32 + PROXY_METADATA_LENGTH) revert E_ProxyMetadata();
37: (IERC20 asset,,) = ProxyUtils.metadata();
38:
39: AddressUtils.checkContract(address(asset));
40:
41:
42:
43:
44: address dToken = address(new DToken());
45:
46:
47:
48: vaultStorage.lastInterestAccumulatorUpdate = uint48(block.timestamp);
49: vaultStorage.interestAccumulator = INITIAL_INTEREST_ACCUMULATOR;
50: vaultStorage.interestFee = DEFAULT_INTEREST_FEE.toConfigAmount();
51: vaultStorage.creator = vaultStorage.governorAdmin = proxyCreator;
52:
53: {
54: string memory underlyingSymbol = getTokenSymbol(address(asset));
55: uint256 seqId = sequenceRegistry.reserveSeqId(underlyingSymbol);
56:
57: vaultStorage.symbol = string(abi.encodePacked("e", underlyingSymbol, "-", uintToString(seqId)));
58: vaultStorage.name = string(abi.encodePacked("EVK Vault ", vaultStorage.symbol));
59: }
60:
61: snapshot.reset();
62:
63:
64:
65: emit EVaultCreated(proxyCreator, address(asset), dToken);
66: logVaultStatus(loadVault(), 0);
67: }In Solidity, using >= or <= to compare against block.timestamp (alias now) may introduce off-by-one errors due to the fact that block.timestamp is only updated once per block and its value remains constant throughout the block's execution. If an operation happens at the exact second when block.timestamp changes, it could result in unexpected behavior. To avoid this, it's safer to use strict inequality operators (> or <). For instance, if a condition should only be met after a certain time, use block.timestamp > time rather than block.timestamp >= time. This way, potential off-by-one errors due to the exact timing of block mining are mitigated, leading to safer, more predictable contract behavior.
Num of instances: 2
Click to show findings
['237']
237: function interestAccruedFromCache(ESRSlot memory esrSlotCache) internal view returns (uint256) { // <= FOUND
238:
239: if (block.timestamp >= esrSlotCache.interestSmearEnd) {
240: return esrSlotCache.interestLeft;
241: }
242:
243:
244: if (esrSlotCache.lastInterestUpdate == block.timestamp) {
245: return 0;
246: }
247:
248:
249: uint256 totalDuration = esrSlotCache.interestSmearEnd - esrSlotCache.lastInterestUpdate;
250: uint256 timePassed = block.timestamp - esrSlotCache.lastInterestUpdate;
251:
252: return esrSlotCache.interestLeft * timePassed / totalDuration;
253: }['37']
37: function getLTV(LTVConfig memory self, bool liquidation) internal view returns (ConfigAmount) { // <= FOUND
38: if (!liquidation) {
39: return self.borrowLTV;
40: } // <= FOUND
41:
42: if (block.timestamp >= self.targetTimestamp || self.liquidationLTV >= self.initialLiquidationLTV) {
43: return self.liquidationLTV;
44: } // <= FOUND
45:
46: uint256 currentLiquidationLTV = self.initialLiquidationLTV.toUint16();
47:
48: unchecked {
49: uint256 targetLiquidationLTV = self.liquidationLTV.toUint16();
50: uint256 timeRemaining = self.targetTimestamp - block.timestamp;
51:
52:
53: currentLiquidationLTV = targetLiquidationLTV
54: + (currentLiquidationLTV - targetLiquidationLTV) * timeRemaining / self.rampDuration;
55: } // <= FOUND
56:
57:
58: return ConfigAmount.wrap(uint16(currentLiquidationLTV));
59: } // <= FOUNDImplement a zero address check for found instances
Num of instances: 11
Click to show findings
['27']
27: function toSharesDownUint(Assets amount, VaultCache memory vaultCache) internal pure returns (uint256) { // <= FOUND
28: (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache);
29: unchecked {
30: return amount.toUint() * totalShares / totalAssets; // <= FOUND
31: }
32: }['34']
34: function toSharesUp(Assets amount, VaultCache memory vaultCache) internal pure returns (Shares) { // <= FOUND
35: (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache);
36: unchecked {
37:
38: return TypesLib.toShares((amount.toUint() * totalShares + (totalAssets - 1)) / totalAssets); // <= FOUND
39: }
40: }['237']
237: function interestAccruedFromCache(ESRSlot memory esrSlotCache) internal view returns (uint256) { // <= FOUND
238:
239: if (block.timestamp >= esrSlotCache.interestSmearEnd) {
240: return esrSlotCache.interestLeft;
241: }
242:
243:
244: if (esrSlotCache.lastInterestUpdate == block.timestamp) {
245: return 0;
246: }
247:
248:
249: uint256 totalDuration = esrSlotCache.interestSmearEnd - esrSlotCache.lastInterestUpdate;
250: uint256 timePassed = block.timestamp - esrSlotCache.lastInterestUpdate;
251:
252: return esrSlotCache.interestLeft * timePassed / totalDuration; // <= FOUND
253: }['37']
37: function getLTV(LTVConfig memory self, bool liquidation) internal view returns (ConfigAmount) { // <= FOUND
38: if (!liquidation) {
39: return self.borrowLTV;
40: }
41:
42: if (block.timestamp >= self.targetTimestamp || self.liquidationLTV >= self.initialLiquidationLTV) {
43: return self.liquidationLTV;
44: }
45:
46: uint256 currentLiquidationLTV = self.initialLiquidationLTV.toUint16();
47:
48: unchecked {
49: uint256 targetLiquidationLTV = self.liquidationLTV.toUint16();
50: uint256 timeRemaining = self.targetTimestamp - block.timestamp;
51:
52:
53: currentLiquidationLTV = targetLiquidationLTV
54: + (currentLiquidationLTV - targetLiquidationLTV) * timeRemaining / self.rampDuration;
55: }
56:
57:
58: return ConfigAmount.wrap(uint16(currentLiquidationLTV));
59: }['40']
40: function mulDiv(Owed self, uint256 multiplier, uint256 divisor) internal pure returns (Owed) { // <= FOUND
41: return TypesLib.toOwed(uint256(Owed.unwrap(self)) * multiplier / divisor); // <= FOUND
42: }['139']
139: function quoteToUnderlyingGivenOut(uint256 amountOut) public view returns (uint256) { // <= FOUND
140: return amountOut * BPS_SCALE * PRICE_SCALE / (BPS_SCALE - TO_UNDERLYING_FEE) / conversionPrice; // <= FOUND
141: }['146']
146: function quoteToSynthGivenIn(uint256 amountIn) public view returns (uint256) { // <= FOUND
147: return amountIn * (BPS_SCALE - TO_SYNTH_FEE) * PRICE_SCALE / BPS_SCALE / conversionPrice; // <= FOUND
148: }['153']
153: function quoteToSynthGivenOut(uint256 amountOut) public view returns (uint256) { // <= FOUND
154: return amountOut * BPS_SCALE * conversionPrice / (BPS_SCALE - TO_SYNTH_FEE) / PRICE_SCALE; // <= FOUND
155: }['22']
22: function toAssetsDown(Shares amount, VaultCache memory vaultCache) internal pure returns (Assets) { // <= FOUND
23: (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache);
24: unchecked {
25: return TypesLib.toAssets(amount.toUint() * totalAssets / totalShares); // <= FOUND
26: }
27: }['29']
29: function toAssetsUp(Shares amount, VaultCache memory vaultCache) internal pure returns (Assets) { // <= FOUND
30: (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache);
31: unchecked {
32:
33: return TypesLib.toAssets((amount.toUint() * totalAssets + (totalShares - 1)) / totalShares); // <= FOUND
34: }
35: }['37']
37: function mulDiv(Shares self, uint256 multiplier, uint256 divisor) internal pure returns (Shares) { // <= FOUND
38: return TypesLib.toShares(uint256(Shares.unwrap(self)) * multiplier / divisor); // <= FOUND
39: }In Solidity, the use of low-level call methods can expose contracts to gas griefing attacks. The potential problem arises when the callee contract returns a large amount of data. This data is allocated in the memory of the calling contract, which pays for the gas costs. If the callee contract intentionally returns an enormous amount of data, the gas costs can skyrocket, causing the transaction to fail due to an Out of Gas error. Therefore, it's advisable to limit the use of call when interacting with untrusted contracts, or ensure that the callee's returned data size is capped or known in advance to prevent unexpected high gas costs.
Num of instances: 4
Click to show findings
['127']
127: function invokeHookTarget(address caller) private { // <= FOUND
128: address hookTarget = vaultStorage.hookTarget;
129:
130: if (hookTarget == address(0)) revert E_OperationDisabled();
131:
132: (bool success, bytes memory data) = hookTarget.call(abi.encodePacked(msg.data, caller)); // <= FOUND
133:
134: if (!success) RevertBytes.revertBytes(data);
135: }['107']
107: function computeInterestRate(VaultCache memory vaultCache) internal virtual returns (uint256) { // <= FOUND
108:
109: address irm = vaultStorage.interestRateModel;
110: uint256 newInterestRate = vaultStorage.interestRate;
111:
112: if (irm != address(0)) { // <= FOUND
113: (bool success, bytes memory data) = irm.call(
114: abi.encodeCall(
115: IIRM.computeInterestRate,
116: (address(this), vaultCache.cash.toUint(), vaultCache.totalBorrows.toAssetsUp().toUint())
117: )
118: );
119:
120: if (success && data.length >= 32) {
121: newInterestRate = abi.decode(data, (uint256));
122: if (newInterestRate > MAX_ALLOWED_INTEREST_RATE) newInterestRate = MAX_ALLOWED_INTEREST_RATE;
123: vaultStorage.interestRate = uint72(newInterestRate);
124: }
125: }
126:
127: return newInterestRate;
128: }['19']
19: function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) // <= FOUND
20: internal
21: returns (bool, bytes memory)
22: {
23: (bool success, bytes memory data) = address(token).call(abi.encodeCall(IERC20.transferFrom, (from, to, value)));
24:
25: return isEmptyOrTrueReturn(success, data) ? (true, bytes("")) : (false, data);
26: }['45']
45: function safeTransfer(IERC20 token, address to, uint256 value) internal { // <= FOUND
46: (bool success, bytes memory data) = address(token).call(abi.encodeCall(IERC20.transfer, (to, value)));
47: if (!isEmptyOrTrueReturn(success, data)) RevertBytes.revertBytes(data);
48: }In September 2022, a bug in Solidity’s Yul optimizer was identified through differential fuzzing. This bug, introduced in version 0.8.13 and fixed by version 0.8.17, can be activated easier with optimized via-IR code generation but can also potentially occur in optimized legacy code generation. This bug is of medium/high severity, which requires contracts that use large inline assembly blocks containing user-defined assembly functions involving return(...) or stop() instructions to be reviewed.
The bug is associated with the Unused Store Eliminator in Yul optimizer, which removes redundant storage writes, and is triggered when function calls are performed. If the function call conditionally continues after the call to a function and terminates using return(...) or stop(), the optimizer may incorrectly remove storage writes before calls to the function.
To evaluate if a contract is affected, you need to check contracts that include inline assembly block with return(...) or stop() statements. If early termination happens conditionally within a function, the optimizer might remove storage writes before function calls.
To avoid this, contracts should not allow for conditional early termination within a function. It is also recommended to use a version of Solidity where this bug has been fixed (version 0.8.17 or later).
Num of instances: 5
Click to show findings
['59']
59: assembly {
60:
61: mstore(0, IMPLEMENTATION_SELECTOR)
62:
63: let result := staticcall(gas(), beacon_, 0, 4, 0, 32)
64: let implementation := mload(0)
65:
66:
67: calldatacopy(0, 0, calldatasize())
68: mstore(calldatasize(), metadata0_)
69: mstore(add(32, calldatasize()), metadata1_)
70: mstore(add(64, calldatasize()), metadata2_)
71: mstore(add(96, calldatasize()), metadata3_)
72: result := delegatecall(gas(), implementation, 0, add(metadataLength_, calldatasize()), 0, 0)
73: returndatacopy(0, 0, returndatasize())
74:
75: switch result
76: case 0 { revert(0, returndatasize()) }
77: default { return(0, returndatasize()) } // <= FOUND
78: }['107']
107: assembly {
108: let size := sub(calldatasize(), 36)
109: calldatacopy(0, 36, size)
110: let result := delegatecall(gas(), calldataload(4), 0, size, 0, 0)
111: returndatacopy(0, 0, returndatasize())
112: switch result
113: case 0 { revert(0, returndatasize()) }
114: default { return(0, returndatasize()) } // <= FOUND
115: }['119']
119: assembly {
120: calldatacopy(0, 0, calldatasize())
121: let result := delegatecall(gas(), module, 0, calldatasize(), 0, 0)
122: returndatacopy(0, 0, returndatasize())
123: switch result
124: case 0 { revert(0, returndatasize()) }
125: default { return(0, returndatasize()) } // <= FOUND
126: }['130']
130: assembly {
131:
132:
133:
134: mstore(0, 0x1fe8b95300000000000000000000000000000000000000000000000000000000)
135: let strippedCalldataSize := sub(calldatasize(), PROXY_METADATA_LENGTH)
136:
137:
138:
139: mstore(add(24, strippedCalldataSize), caller())
140: mstore(4, module)
141: calldatacopy(36, 0, strippedCalldataSize)
142:
143: let result := staticcall(gas(), address(), 0, add(strippedCalldataSize, 56), 0, 0)
144: returndatacopy(0, 0, returndatasize())
145: switch result
146: case 0 { revert(0, returndatasize()) }
147: default { return(0, returndatasize()) } // <= FOUND
148: }['153']
153: assembly {
154: let mainCalldataLength := sub(calldatasize(), PROXY_METADATA_LENGTH)
155:
156: mstore(0, 0x1f8b521500000000000000000000000000000000000000000000000000000000)
157: mstore(4, address())
158: mstore(36, caller())
159: mstore(68, callvalue())
160: mstore(100, 128)
161: mstore(132, mainCalldataLength)
162: calldatacopy(164, 0, mainCalldataLength)
163:
164:
165:
166: mstore(add(164, mainCalldataLength), 0)
167: let result := call(gas(), _evc, callvalue(), 0, add(164, and(add(mainCalldataLength, 31), not(31))), 0, 0)
168:
169: returndatacopy(0, 0, returndatasize())
170: switch result
171: case 0 { revert(0, returndatasize()) }
172: default { return(64, sub(returndatasize(), 64)) } // <= FOUND
173: }In arithmetic operations involving subtraction and multiplication, an underflow may occur if a subtraction result is negative, or if a multiplication result exceeds the maximum value representable in the data type. For instance, if a large multiplication precedes a subtraction, it may create a value too large to subtract from, causing an underflow. This could lead to unexpected and incorrect results in the calculation.
Num of instances: 1
Click to show findings
['114']
114: function calculateMaxLiquidation(LiquidationCache memory liqCache, VaultCache memory vaultCache)
115: private
116: view
117: returns (LiquidationCache memory)
118: {
119:
120:
121: (uint256 collateralAdjustedValue, uint256 liabilityValue) =
122: calculateLiquidity(vaultCache, liqCache.violator, liqCache.collaterals, true);
123:
124:
125: if (collateralAdjustedValue > liabilityValue) return liqCache;
126:
127:
128:
129:
130: uint256 discountFactor = collateralAdjustedValue * 1e18 / liabilityValue;
131: {
132: uint256 minDiscountFactor;
133: unchecked {
134:
135: minDiscountFactor = 1e18 - uint256(1e18) * vaultStorage.maxLiquidationDiscount.toUint16() / CONFIG_SCALE; // <= FOUND
136: }
137: if (discountFactor < minDiscountFactor) discountFactor = minDiscountFactor;
138: }
139:
140:
141:
142: uint256 collateralBalance = IERC20(liqCache.collateral).balanceOf(liqCache.violator);
143: uint256 collateralValue =
144: vaultCache.oracle.getQuote(collateralBalance, liqCache.collateral, vaultCache.unitOfAccount);
145:
146: if (collateralValue == 0) {
147:
148:
149:
150:
151:
152:
153: liqCache.yieldBalance = collateralBalance;
154: return liqCache;
155: }
156:
157: uint256 maxRepayValue = liabilityValue;
158: uint256 maxYieldValue = maxRepayValue * 1e18 / discountFactor;
159:
160:
161:
162:
163: if (collateralValue < maxYieldValue) {
164: maxRepayValue = collateralValue * discountFactor / 1e18;
165: maxYieldValue = collateralValue;
166: }
167:
168: liqCache.repay = (maxRepayValue * liqCache.liability.toUint() / liabilityValue).toAssets();
169: liqCache.yieldBalance = maxYieldValue * collateralBalance / collateralValue;
170:
171: return liqCache;
172: }Num of instances: 4
Click to show findings
['42']
42: function setCapacity(address minter, uint128 capacity) external onlyOwner {
43: minters[minter].capacity = capacity;
44: emit MinterCapacitySet(minter, capacity);
45: }['222']
222: function setGovernorAdmin(address newGovernorAdmin) public virtual override use(MODULE_GOVERNANCE) {}['164']
164: function setUpgradeAdmin(address newUpgradeAdmin) external nonReentrant adminOnly {
165: upgradeAdmin = newUpgradeAdmin;
166: emit SetUpgradeAdmin(newUpgradeAdmin);
167: }['252']
252: function setGovernorAdmin(address newGovernorAdmin) public virtual nonReentrant governorOnly {
253: vaultStorage.governorAdmin = newGovernorAdmin;
254: emit GovSetGovernorAdmin(newGovernorAdmin);
255: }The "check-effects-interaction" pattern is a best practice in smart contract development, emphasizing the order of operations in functions to prevent reentrancy attacks. Violations arise when a function interacts with external contracts before settling internal state changes or checks. This misordering can expose the contract to potential threats. To adhere to this pattern, first ensure all conditions or checks are satisfied, then update any internal states, and only after these steps, interact with external contracts or addresses. Rearranging operations to this recommended sequence bolsters contract security and aligns with established best practices in the Ethereum community.
Num of instances: 9
Click to show findings
['107']
107: function computeInterestRate(VaultCache memory vaultCache) internal virtual returns (uint256) { // <= FOUND
108:
109: address irm = vaultStorage.interestRateModel;
110: uint256 newInterestRate = vaultStorage.interestRate;
111:
112: if (irm != address(0)) {
113: (bool success, bytes memory data) = irm.call( // <= FOUND
114: abi.encodeCall(
115: IIRM.computeInterestRate,
116: (address(this), vaultCache.cash.toUint(), vaultCache.totalBorrows.toAssetsUp().toUint())
117: )
118: );
119:
120: if (success && data.length >= 32) {
121: newInterestRate = abi.decode(data, (uint256));
122: if (newInterestRate > MAX_ALLOWED_INTEREST_RATE) newInterestRate = MAX_ALLOWED_INTEREST_RATE;
123: vaultStorage.interestRate = uint72(newInterestRate);
124: }
125: }
126:
127: return newInterestRate;
128: }['130']
130: function computeInterestRateView(VaultCache memory vaultCache) internal view virtual returns (uint256) { // <= FOUND
131:
132: address irm = vaultStorage.interestRateModel;
133: uint256 newInterestRate = vaultStorage.interestRate;
134:
135: if (irm != address(0) && isVaultStatusCheckDeferred()) {
136: (bool success, bytes memory data) = irm.staticcall( // <= FOUND
137: abi.encodeCall(
138: IIRM.computeInterestRateView,
139: (address(this), vaultCache.cash.toUint(), vaultCache.totalBorrows.toAssetsUp().toUint())
140: )
141: );
142:
143: if (success && data.length >= 32) {
144: newInterestRate = abi.decode(data, (uint256));
145: if (newInterestRate > MAX_ALLOWED_INTEREST_RATE) newInterestRate = MAX_ALLOWED_INTEREST_RATE;
146: }
147: }
148:
149: return newInterestRate;
150: }['124']
124: function deposit(uint256 amount, address receiver) public virtual nonReentrant returns (uint256) { // <= FOUND
125: (VaultCache memory vaultCache, address account) = initOperation(OP_DEPOSIT, CHECKACCOUNT_NONE);
126:
127: Assets assets = amount == type(uint256).max ? vaultCache.asset.balanceOf(account).toAssets() : amount.toAssets(); // <= FOUND
128: if (assets.isZero()) return 0;
129:
130: Shares shares = assets.toSharesDown(vaultCache);
131: if (shares.isZero()) revert E_ZeroShares();
132:
133: finalizeDeposit(vaultCache, assets, shares, account, receiver);
134:
135: return shares.toUint();
136: }['81']
81: function repay(uint256 amount, address receiver) public virtual nonReentrant returns (uint256) { // <= FOUND
82: (VaultCache memory vaultCache, address account) = initOperation(OP_REPAY, CHECKACCOUNT_NONE);
83:
84: uint256 owed = getCurrentOwed(vaultCache, receiver).toAssetsUp().toUint(); // <= FOUND
85:
86: Assets assets = (amount == type(uint256).max ? owed : amount).toAssets();
87: if (assets.isZero()) return 0;
88:
89: pullAssets(vaultCache, account, assets);
90:
91: decreaseBorrow(vaultCache, receiver, assets);
92:
93: return assets.toUint();
94: }['97']
97: function repayWithShares(uint256 amount, address receiver) public virtual nonReentrant returns (uint256, uint256) { // <= FOUND
98: (VaultCache memory vaultCache, address account) = initOperation(OP_REPAY_WITH_SHARES, CHECKACCOUNT_CALLER);
99:
100: Assets owed = getCurrentOwed(vaultCache, receiver).toAssetsUp(); // <= FOUND
101: if (owed.isZero()) return (0, 0);
102:
103: Assets assets;
104: Shares shares;
105:
106: if (amount == type(uint256).max) {
107: shares = vaultStorage.users[account].getBalance();
108: assets = shares.toAssetsDown(vaultCache);
109: } else {
110: assets = amount.toAssets();
111: shares = assets.toSharesUp(vaultCache);
112: }
113:
114: if (assets.isZero()) return (0, 0);
115:
116: if (assets > owed) {
117: assets = owed;
118: shares = assets.toSharesUp(vaultCache);
119: }
120:
121:
122: decreaseBalance(vaultCache, account, account, account, shares, assets);
123:
124:
125: decreaseBorrow(vaultCache, receiver, assets);
126:
127: return (shares.toUint(), assets.toUint());
128: }['131']
131: function pullDebt(uint256 amount, address from) public virtual nonReentrant returns (uint256) { // <= FOUND
132: (VaultCache memory vaultCache, address account) = initOperation(OP_PULL_DEBT, CHECKACCOUNT_CALLER);
133:
134: if (from == account) revert E_SelfTransfer();
135:
136: Assets assets = amount == type(uint256).max ? getCurrentOwed(vaultCache, from).toAssetsUp() : amount.toAssets(); // <= FOUND
137:
138: if (assets.isZero()) return 0;
139: transferBorrow(vaultCache, from, account, assets);
140:
141: emit PullDebt(from, account, assets.toUint());
142:
143: return assets.toUint();
144: }['349']
349: function setHookConfig(address newHookTarget, uint32 newHookedOps) public virtual nonReentrant governorOnly { // <= FOUND
350: if ( // <= FOUND
351: newHookTarget != address(0)
352: && IHookTarget(newHookTarget).isHookTarget() != IHookTarget.isHookTarget.selector
353: ) revert E_NotHookTarget();
354:
355: if (newHookedOps >= OP_MAX_VALUE) revert E_NotSupported(); // <= FOUND
356:
357: vaultStorage.hookTarget = newHookTarget;
358: vaultStorage.hookedOps = Flags.wrap(newHookedOps);
359: emit GovSetHookConfig(newHookTarget, newHookedOps);
360: }['59']
59: function calculateLiquidation(
60: VaultCache memory vaultCache,
61: address liquidator,
62: address violator,
63: address collateral,
64: uint256 desiredRepay
65: ) private view returns (LiquidationCache memory liqCache) {
66:
67:
68: liqCache.liquidator = liquidator;
69: liqCache.violator = violator;
70: liqCache.collateral = collateral;
71:
72: liqCache.repay = Assets.wrap(0);
73: liqCache.yieldBalance = 0;
74: liqCache.liability = getCurrentOwed(vaultCache, violator).toAssetsUp(); // <= FOUND
75: liqCache.collaterals = getCollaterals(violator);
76:
77:
78:
79:
80: if (liqCache.violator == liqCache.liquidator) revert E_SelfLiquidation();
81:
82: if (!isRecognizedCollateral(liqCache.collateral)) revert E_BadCollateral();
83:
84: validateController(liqCache.violator);
85:
86: if (!isCollateralEnabled(liqCache.violator, liqCache.collateral)) revert E_CollateralDisabled();
87:
88:
89: if (isAccountStatusCheckDeferred(violator)) revert E_ViolatorLiquidityDeferred();
90:
91:
92: if (isInLiquidationCoolOff(violator)) revert E_LiquidationCoolOff();
93:
94:
95: if (liqCache.liability.isZero()) return liqCache;
96:
97:
98:
99: liqCache = calculateMaxLiquidation(liqCache, vaultCache);
100:
101:
102:
103: if (desiredRepay != type(uint256).max) {
104: uint256 maxRepay = liqCache.repay.toUint();
105: if (desiredRepay > maxRepay) revert E_ExcessiveRepayAmount();
106:
107: if (maxRepay > 0) {
108: liqCache.yieldBalance = desiredRepay * liqCache.yieldBalance / maxRepay;
109: liqCache.repay = desiredRepay.toAssets();
110: }
111: }
112: }['97']
97: function getCollateralValue(VaultCache memory vaultCache, address account, address collateral, bool liquidation)
98: internal
99: view
100: virtual
101: returns (uint256 value)
102: {
103: ConfigAmount ltv = getLTV(collateral, liquidation);
104: if (ltv.isZero()) return 0;
105:
106: uint256 balance = IERC20(collateral).balanceOf(account); // <= FOUND
107: if (balance == 0) return 0;
108:
109: uint256 currentCollateralValue;
110:
111: if (liquidation) {
112:
113: currentCollateralValue = vaultCache.oracle.getQuote(balance, collateral, vaultCache.unitOfAccount);
114: } else {
115:
116: (currentCollateralValue,) = vaultCache.oracle.getQuotes(balance, collateral, vaultCache.unitOfAccount);
117: }
118:
119: return currentCollateralValue * ltv.toUint16() / CONFIG_SCALE;
120: }The following functions do not take into account emtpy arrays, this can result in the function returning true/false unintentionally which may introduce a unique bypass to certain checks.
Num of instances: 2
Click to show findings
['62']
62: for (uint256 i; i < collaterals.length; ++i) { // <= FOUND
63: address collateral = collaterals[i];
64:
65: if (!isRecognizedCollateral(collateral)) continue;
66:
67: if (IERC20(collateral).balanceOf(account) > 0) return false; // <= FOUND
68: }['62']
62: for (uint256 i; i < collaterals.length; ++i) { // <= FOUND
63: address collateral = collaterals[i];
64:
65: if (!isRecognizedCollateral(collateral)) continue;
66:
67: if (IERC20(collateral).balanceOf(account) > 0) return false; // <= FOUND
68: }Reason: In Solidity, ERC20 token transfers of value 0 can sometimes lead to unexpected issues. This is particularly relevant when dealing with fractional token amounts that round to 0 when less than 1 of the smallest unit is transferred, leading to an effective transfer of nothing while still consuming gas. Furthermore, some ERC20 token implementations may revert on attempts to transfer a value of 0. However, note that this issue doesn't generally apply to wrapper native tokens like WETH.
Resolution: It's advisable to include a condition before any transfer operation to bypass the transaction if the transfer amount is 0. This saves unnecessary gas expenditure and prevents potential function reverts. For handling fractions, ensure token decimals are appropriately assigned and contemplate setting a minimum transfer threshold to avoid rounding down to 0. When dealing with wrapped tokens like WETH, special consideration should be given to their unique characteristics.
Num of instances: 3
Click to show findings
['18']
18: function pullAssets(VaultCache memory vaultCache, address from, Assets amount) internal virtual { // <= FOUND
19: vaultCache.asset.safeTransferFrom(from, address(this), amount.toUint(), permit2); // <= FOUND
20: vaultStorage.cash = vaultCache.cash = vaultCache.cash + amount;
21: }['32']
32: function pushAssets(VaultCache memory vaultCache, address to, Assets amount) internal virtual { // <= FOUND
33: if (
34: to == address(0)
35: || (vaultCache.configFlags.isNotSet(CFG_EVC_COMPATIBLE_ASSET) && isKnownNonOwnerAccount(to))
36: ) {
37: revert E_BadAssetReceiver();
38: }
39:
40: vaultStorage.cash = vaultCache.cash = vaultCache.cash - amount;
41: vaultCache.asset.safeTransfer(to, amount.toUint()); // <= FOUND
42: }['147']
147: function flashLoan(uint256 amount, bytes calldata data) public virtual nonReentrant { // <= FOUND
148: address account = EVCAuthenticate();
149: callHook(vaultStorage.hookedOps, OP_FLASHLOAN, account);
150:
151: (IERC20 asset,,) = ProxyUtils.metadata();
152:
153: uint256 origBalance = asset.balanceOf(address(this));
154:
155: asset.safeTransfer(account, amount); // <= FOUND
156:
157: IFlashLoan(account).onFlashLoan(data);
158:
159: if (asset.balanceOf(address(this)) < origBalance) revert E_FlashLoanNotRepaid();
160: }The below-listed functions use transferFrom() to move funds from the sender to the recipient but fail to verify if the received token amount matches the transferred amount. This could pose an issue with fee-on-transfer tokens, where the post-transfer balance might be less than anticipated, leading to balance inconsistencies. There might be subsequent checks for a second transfer, but an attacker might exploit leftover funds (such as those accidentally sent by another user) to gain unjustified credit. A practical solution is to gauge the balance prior and post-transfer, and consider the differential as the transferred amount, instead of the predefined amount.
Num of instances: 4
Click to show findings
['19']
19: function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value)
20: internal
21: returns (bool, bytes memory)
22: {
23: (bool success, bytes memory data) = address(token).call(abi.encodeCall(IERC20.transferFrom, (from, to, value))); // <= FOUND
24:
25: return isEmptyOrTrueReturn(success, data) ? (true, bytes("")) : (false, data);
26: }['18']
18: function pullAssets(VaultCache memory vaultCache, address from, Assets amount) internal virtual {
19: vaultCache.asset.safeTransferFrom(from, address(this), amount.toUint(), permit2); // <= FOUND
20: vaultStorage.cash = vaultCache.cash = vaultCache.cash + amount;
21: }['101']
101: function swapToSynthGivenIn(uint256 amountIn, address receiver) external returns (uint256) {
102: uint256 amountOut = quoteToSynthGivenIn(amountIn);
103: if (amountIn == 0 || amountOut == 0) {
104: return 0;
105: }
106:
107: underlying.safeTransferFrom(_msgSender(), address(this), amountIn); // <= FOUND
108: synth.mint(receiver, amountOut);
109:
110: return amountOut;
111: }['117']
117: function swapToSynthGivenOut(uint256 amountOut, address receiver) external returns (uint256) {
118: uint256 amountIn = quoteToSynthGivenOut(amountOut);
119: if (amountIn == 0 || amountOut == 0) {
120: return 0;
121: }
122:
123: underlying.safeTransferFrom(_msgSender(), address(this), amountIn); // <= FOUND
124: synth.mint(receiver, amountOut);
125:
126: return amountIn;
127: }Executing a .withdraw function on an externally determined contract can be risky. Even if the target contract is payable, it might not implement a withdraw function or might implement it differently than expected. If the contract doesn't have a withdraw function, a function call will lead to a transaction failure. Moreover, if the external contract has malevolent code in its .withdraw function, it could exploit your contract in unexpected ways. To mitigate this risk, ensure you interact only with trusted contracts, ideally those adhering to standard interfaces like ERC20's transfer. Avoid arbitrary calls to .withdraw without a clear understanding of the target contract's functionality.
Num of instances: 1
Click to show findings
['116']
116: function deallocate(address vault, uint256 amount) external onlyOwner {
117: IEVault(vault).withdraw(amount, address(this), address(this)); // <= FOUND
118: }In smart contract development, particularly for Ethereum, having payable functions without a corresponding withdraw or sweep function can lead to potential issues. Payable functions allow the contract to receive Ether, but without a mechanism to withdraw these funds, the Ether can become locked within the contract indefinitely. This situation might be intentional in some cases (like a burn function), but generally, it’s a design oversight. A withdraw or sweep function is necessary to transfer Ether out of the contract to a specific address, typically the owner's or a designated recipient. Without this, the contract lacks flexibility in managing its funds, potentially leading to lost or inaccessible Ether.
Num of instances: 1
Solidity version 0.8.20 uses the new Shanghai EVM which introduces the PUSH0 opcode, this may not be implemented on all chains and L2 thus reducing the portability and compatability of the code. Consider using a earlier solidity version.
Num of instances: 2
Click to show findings
['3']
3: pragma solidity ^0.8.0; // <= FOUND['3']
3: pragma solidity >=0.8.0; // <= FOUND[Low-13] Require should be used instead of assert as assert in some solidity versions does not refund remaining gas
In Solidity, require should be used instead of assert for input validation and error handling due to its more efficient and flexible nature. While both functions check for a specified condition and revert the transaction if it evaluates to false, assert consumes all remaining gas upon failure, which can lead to higher costs for users. On the other hand, require refunds unused gas, making it a more gas-efficient option. Additionally, require allows for custom error messages, which improves debugging and error reporting.
Num of instances: 2
Click to show findings
['42']
42: assert(msg.sender == address(evc)); // <= FOUND['89']
89: assert(account != CHECKACCOUNT_CALLER); // <= FOUNDIn Solidity versions 0.8.13 and 0.8.14, a known optimizer bug presents potential risks when a variable is used in a separate assembly block from the one in which it was stored. Specifically, the 'mstore' operation could be optimized out due to this bug, leading to the use of uninitialized memory. Although the current code does not exhibit this risky pattern of execution, it does utilize 'mstore' within assembly blocks, which introduces a vulnerability risk for future code modifications. As a preventative measure, it is advisable to avoid the usage of the afflicted Solidity versions, 0.8.13 and 0.8.14. Instead, consider utilizing a version that is not impacted by this optimizer bug to prevent potential memory initialization issues in your smart contract.
Num of instances: 13
Click to show findings
['37']
37: assembly { // <= FOUND
38: sstore(BEACON_SLOT, caller())
39: }['45']
45: assembly { // <= FOUND
46: mstore(trailingData, MAX_TRAILING_DATA_LENGTH)
47: }['59']
59: assembly { // <= FOUND
60:
61: mstore(0, IMPLEMENTATION_SELECTOR)
62:
63: let result := staticcall(gas(), beacon_, 0, 4, 0, 32)
64: let implementation := mload(0)
65:
66:
67: calldatacopy(0, 0, calldatasize())
68: mstore(calldatasize(), metadata0_)
69: mstore(add(32, calldatasize()), metadata1_)
70: mstore(add(64, calldatasize()), metadata2_)
71: mstore(add(96, calldatasize()), metadata3_)
72: result := delegatecall(gas(), implementation, 0, add(metadataLength_, calldatasize()), 0, 0)
73: returndatacopy(0, 0, returndatasize())
74:
75: switch result
76: case 0 { revert(0, returndatasize()) }
77: default { return(0, returndatasize()) }
78: }['155']
155: assembly ("memory-safe") { // <= FOUND
156: mstore(0x14, address())
157:
158:
159: mstore(0x00, 0xd694)
160:
161: mstore8(0x34, 0x01)
162:
163: dToken := keccak256(0x1e, 0x17)
164: }['107']
107: assembly { // <= FOUND
108: let size := sub(calldatasize(), 36)
109: calldatacopy(0, 36, size)
110: let result := delegatecall(gas(), calldataload(4), 0, size, 0, 0)
111: returndatacopy(0, 0, returndatasize())
112: switch result
113: case 0 { revert(0, returndatasize()) }
114: default { return(0, returndatasize()) }
115: }['119']
119: assembly { // <= FOUND
120: calldatacopy(0, 0, calldatasize())
121: let result := delegatecall(gas(), module, 0, calldatasize(), 0, 0)
122: returndatacopy(0, 0, returndatasize())
123: switch result
124: case 0 { revert(0, returndatasize()) }
125: default { return(0, returndatasize()) }
126: }['130']
130: assembly { // <= FOUND
131:
132:
133:
134: mstore(0, 0x1fe8b95300000000000000000000000000000000000000000000000000000000)
135: let strippedCalldataSize := sub(calldatasize(), PROXY_METADATA_LENGTH)
136:
137:
138:
139: mstore(add(24, strippedCalldataSize), caller())
140: mstore(4, module)
141: calldatacopy(36, 0, strippedCalldataSize)
142:
143: let result := staticcall(gas(), address(), 0, add(strippedCalldataSize, 56), 0, 0)
144: returndatacopy(0, 0, returndatasize())
145: switch result
146: case 0 { revert(0, returndatasize()) }
147: default { return(0, returndatasize()) }
148: }['153']
153: assembly { // <= FOUND
154: let mainCalldataLength := sub(calldatasize(), PROXY_METADATA_LENGTH)
155:
156: mstore(0, 0x1f8b521500000000000000000000000000000000000000000000000000000000)
157: mstore(4, address())
158: mstore(36, caller())
159: mstore(68, callvalue())
160: mstore(100, 128)
161: mstore(132, mainCalldataLength)
162: calldatacopy(164, 0, mainCalldataLength)
163:
164:
165:
166: mstore(add(164, mainCalldataLength), 0)
167: let result := call(gas(), _evc, callvalue(), 0, add(164, and(add(mainCalldataLength, 31), not(31))), 0, 0)
168:
169: returndatacopy(0, 0, returndatasize())
170: switch result
171: case 0 { revert(0, returndatasize()) }
172: default { return(64, sub(returndatasize(), 64)) }
173: }['23']
23: assembly ("memory-safe") { // <= FOUND
24: addr := create(0, add(code, 32), mload(code))
25: }['16']
16: assembly { // <= FOUND
17: asset := shr(96, calldataload(sub(calldatasize(), 60)))
18: oracle := shr(96, calldataload(sub(calldatasize(), 40)))
19: unitOfAccount := shr(96, calldataload(sub(calldatasize(), 20)))
20: }['26']
26: assembly { // <= FOUND
27: viewCaller := shr(96, calldataload(sub(calldatasize(), add(PROXY_METADATA_LENGTH, 20))))
28: }['14']
14: assembly { // <= FOUND
15: ['14']
14: assembly { // <= FOUND
15: revert(add(32, errMsg), mload(errMsg))
16: }Ownable2Step further prevents risks posed by centralised privileges as there is a smaller likelihood of the owner being wrongfully changed
Num of instances: 1
[Low-16] Using > when declaring solidity version without specifying an upperbound can cause future vulnerabilities
Such instances should be locked to a particular solidity version or use the ^ attribute instead of > as this is more limited in what versions can be used.
Num of instances: 1
When settings min/max state variables, ensure there a require checks in place to prevent incorrect values from being set
Num of instances: 1
Click to show findings
['42']
42: function setCapacity(address minter, uint128 capacity) external onlyOwner { // <= FOUND
43: minters[minter].capacity = capacity; // <= FOUND
44: emit MinterCapacitySet(minter, capacity); // <= FOUND
45: }The decimals() function in an ERC20 token contract is used to specify how many decimal places the token can be divided into. However, it should be used with caution because not all ERC20 token contracts implement decimals(), and the function isn't required by the ERC20 standard. Calling decimals() on a contract that doesn't implement it will result in a runtime error. Moreover, even when implemented, the returned value can be manipulated or artificially set, which may cause unexpected behavior. Therefore, always verify the decimal count independently if precision is crucial for your contract logic. When interacting with other ERC20 tokens, consider implementing safeguards or checks to handle potential errors from calling decimals().
Num of instances: 2
Click to show findings
['48']
48: quoteAmount = 10 ** IERC20(synth_).decimals(); // <= FOUND['38']
38: return IEVault(eVault).decimals(); // <= FOUNDMinting tokens to the zero address in Solidity is a potential pitfall that should be carefully guarded against. The zero address (0x0) is generally used as a default value and represents an uninitialized or null address. Minting tokens to this address effectively burns them, making them inaccessible and permanently removing them from the total supply. This could lead to unintended token loss if performed accidentally. To prevent this, it's important to include a check in the minting function to ensure that the target address is not the zero address. Using a library like OpenZeppelin's Address can provide utility functions like requireNonZero, which simplifies this check and enhances the security of the minting function.
Num of instances: 2
Click to show findings
['50']
50: function mint(address account, uint256 amount) external nonReentrant {
51: address sender = _msgSender(); // <= FOUND
52: MinterData memory minterCache = minters[sender]; // <= FOUND
53:
54:
55: if (amount == 0) {
56: return; // <= FOUND
57: }
58:
59: if (
60: amount > type(uint128).max - minterCache.minted
61: || minterCache.capacity < uint256(minterCache.minted) + amount
62: ) {
63: revert E_CapacityReached(); // <= FOUND
64: }
65:
66: minterCache.minted += uint128(amount); // <= FOUND
67: minters[sender] = minterCache; // <= FOUND
68:
69: _mint(account, amount); // <= FOUND
70: }['141']
141: function mint(uint256 shares, address receiver) public override nonReentrant returns (uint256) {
142: return super.mint(shares, receiver); // <= FOUND
143: }Dividing by large numbers in Solidity can cause a loss of precision due to the language's inherent integer division behavior. Solidity does not support floating-point arithmetic, and as a result, division between integers yields an integer result, truncating any fractional part. When dividing by a large number, the resulting value may become significantly smaller, leading to a loss of precision, as the fractional part is discarded.
Num of instances: 13
Click to show findings
['27']
27: function toSharesDownUint(Assets amount, VaultCache memory vaultCache) internal pure returns (uint256) { // <= FOUND
28: (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache);
29: unchecked {
30: return amount.toUint() * totalShares / totalAssets; // <= FOUND
31: }
32: }['34']
34: function toSharesUp(Assets amount, VaultCache memory vaultCache) internal pure returns (Shares) { // <= FOUND
35: (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache);
36: unchecked {
37:
38: return TypesLib.toShares((amount.toUint() * totalShares + (totalAssets - 1)) / totalAssets); // <= FOUND
39: }
40: }['237']
237: function interestAccruedFromCache(ESRSlot memory esrSlotCache) internal view returns (uint256) { // <= FOUND
238:
239: if (block.timestamp >= esrSlotCache.interestSmearEnd) {
240: return esrSlotCache.interestLeft;
241: }
242:
243:
244: if (esrSlotCache.lastInterestUpdate == block.timestamp) {
245: return 0;
246: }
247:
248:
249: uint256 totalDuration = esrSlotCache.interestSmearEnd - esrSlotCache.lastInterestUpdate;
250: uint256 timePassed = block.timestamp - esrSlotCache.lastInterestUpdate;
251:
252: return esrSlotCache.interestLeft * timePassed / totalDuration; // <= FOUND
253: }['75']
75: function _computeRate(IRMData memory irmCache) internal view returns (uint216 rate, bool updated) { // <= FOUND
76: updated = false;
77: rate = irmCache.lastRate;
78:
79:
80: if (block.timestamp < irmCache.lastUpdated + ADJUST_INTERVAL) {
81: return (rate, updated);
82: }
83:
84: uint256 quote = oracle.getQuote(quoteAmount, synth, referenceAsset);
85:
86: updated = true;
87:
88: if (quote < targetQuote) {
89:
90: rate = rate * ADJUST_FACTOR / ADJUST_ONE; // <= FOUND
91: } else {
92:
93: rate = rate * ADJUST_ONE / ADJUST_FACTOR; // <= FOUND
94: }
95:
96:
97: if (rate < BASE_RATE) {
98: rate = BASE_RATE;
99: } else if (rate > MAX_RATE) {
100: rate = MAX_RATE;
101: }
102:
103: return (rate, updated);
104: }['37']
37: function getLTV(LTVConfig memory self, bool liquidation) internal view returns (ConfigAmount) { // <= FOUND
38: if (!liquidation) {
39: return self.borrowLTV;
40: }
41:
42: if (block.timestamp >= self.targetTimestamp || self.liquidationLTV >= self.initialLiquidationLTV) {
43: return self.liquidationLTV;
44: }
45:
46: uint256 currentLiquidationLTV = self.initialLiquidationLTV.toUint16();
47:
48: unchecked {
49: uint256 targetLiquidationLTV = self.liquidationLTV.toUint16();
50: uint256 timeRemaining = self.targetTimestamp - block.timestamp;
51:
52:
53: currentLiquidationLTV = targetLiquidationLTV
54: + (currentLiquidationLTV - targetLiquidationLTV) * timeRemaining / self.rampDuration;
55: }
56:
57:
58: return ConfigAmount.wrap(uint16(currentLiquidationLTV));
59: }['40']
40: function mulDiv(Owed self, uint256 multiplier, uint256 divisor) internal pure returns (Owed) { // <= FOUND
41: return TypesLib.toOwed(uint256(Owed.unwrap(self)) * multiplier / divisor); // <= FOUND
42: }['132']
132: function quoteToUnderlyingGivenIn(uint256 amountIn) public view returns (uint256) { // <= FOUND
133: return amountIn * (BPS_SCALE - TO_UNDERLYING_FEE) * conversionPrice / BPS_SCALE / PRICE_SCALE; // <= FOUND
134: }['139']
139: function quoteToUnderlyingGivenOut(uint256 amountOut) public view returns (uint256) { // <= FOUND
140: return amountOut * BPS_SCALE * PRICE_SCALE / (BPS_SCALE - TO_UNDERLYING_FEE) / conversionPrice; // <= FOUND
141: }['146']
146: function quoteToSynthGivenIn(uint256 amountIn) public view returns (uint256) { // <= FOUND
147: return amountIn * (BPS_SCALE - TO_SYNTH_FEE) * PRICE_SCALE / BPS_SCALE / conversionPrice; // <= FOUND
148: }['153']
153: function quoteToSynthGivenOut(uint256 amountOut) public view returns (uint256) { // <= FOUND
154: return amountOut * BPS_SCALE * conversionPrice / (BPS_SCALE - TO_SYNTH_FEE) / PRICE_SCALE; // <= FOUND
155: }['22']
22: function toAssetsDown(Shares amount, VaultCache memory vaultCache) internal pure returns (Assets) { // <= FOUND
23: (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache);
24: unchecked {
25: return TypesLib.toAssets(amount.toUint() * totalAssets / totalShares); // <= FOUND
26: }
27: }['29']
29: function toAssetsUp(Shares amount, VaultCache memory vaultCache) internal pure returns (Assets) { // <= FOUND
30: (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache);
31: unchecked {
32:
33: return TypesLib.toAssets((amount.toUint() * totalAssets + (totalShares - 1)) / totalShares); // <= FOUND
34: }
35: }['37']
37: function mulDiv(Shares self, uint256 multiplier, uint256 divisor) internal pure returns (Shares) { // <= FOUND
38: return TypesLib.toShares(uint256(Shares.unwrap(self)) * multiplier / divisor); // <= FOUND
39: }In Solidity, constructors often take address parameters to initialize important components of a contract, such as owner or linked contracts. However, without a check, there's a risk that an address parameter could be mistakenly set to the zero address (0x0). This could occur due to a mistake or oversight during contract deployment. A zero address in a crucial role can cause serious issues, as it cannot perform actions like a normal address, and any funds sent to it are irretrievable. Therefore, it's crucial to include a zero address check in constructors to prevent such potential problems. If a zero address is detected, the constructor should revert the transaction.
Num of instances: 2
Click to show findings
['31']
31: constructor(address _evc) { // <= FOUND
32: evc = IEVC(AddressUtils.checkContract(_evc));
33: }['58']
58: constructor(IEVC _evc, address _asset, string memory _name, string memory _symbol) // <= FOUND
59: EVCUtil(address(_evc))
60: ERC4626(IERC20(_asset))
61: ERC20(_name, _symbol)
62: {
63: esrSlot.locked = UNLOCKED;
64: }In Solidity, renouncing ownership of a contract essentially transfers ownership to the zero address. This is an irreversible operation and has considerable security implications. If the renounceOwnership function is used, the contract will lose the ability to perform any operations that are limited to the owner. This can be problematic if there are any bugs, flaws, or unexpected events that require owner intervention to resolve. Therefore, in some instances, it is better to disable or omit the renounceOwnership function, and instead implement a secure transferOwnership function. This way, if necessary, ownership can be transferred to a new, trusted party without losing the potential for administrative intervention.
Num of instances: 1
Taking 0 as a valid argument in Solidity without checks can lead to severe security issues. A historical example is the infamous 0x0 address bug where numerous tokens were lost. This happens because '0' can be interpreted as an uninitialized address, leading to transfers to the '0x0' address, effectively burning tokens. Moreover, 0 as a denominator in division operations would cause a runtime exception. It's also often indicative of a logical error in the caller's code. It's important to always validate input and handle edge cases like 0 appropriately. Use require() statements to enforce conditions and provide clear error messages to facilitate debugging and safer code.
Num of instances: 4
Click to show findings
['41']
41: function disableBalanceForwarder() public virtual nonReentrant { // <= FOUND
42: if (address(balanceTracker) == address(0)) revert E_NotSupported();
43:
44: address account = EVCAuthenticate();
45: UserStorage storage user = vaultStorage.users[account];
46:
47: bool wasBalanceForwarderEnabled = user.isBalanceForwarderEnabled();
48:
49: user.setBalanceForwarder(false);
50: balanceTracker.balanceTrackerHook(account, 0, false); // <= FOUND
51:
52: if (wasBalanceForwarderEnabled) emit BalanceForwarderStatus(account, false);
53: }['98']
98: function enforceCollateralTransfer(address collateral, uint256 amount, address from, address receiver)
99: internal
100: virtual
101: {
102: evc.controlCollateral(collateral, from, 0, abi.encodeCall(IERC20.transfer, (receiver, amount))); // <= FOUND
103: }['216']
216: function convertFees() public virtual nonReentrant { // <= FOUND
217: (VaultCache memory vaultCache, address account) = initOperation(OP_CONVERT_FEES, CHECKACCOUNT_NONE);
218:
219: if (vaultCache.accumulatedFees.isZero()) return;
220:
221: (address protocolReceiver, uint16 protocolFee) = protocolConfig.protocolFeeConfig(address(this));
222: address governorReceiver = vaultStorage.feeReceiver;
223:
224: if (governorReceiver == address(0)) {
225: protocolFee = CONFIG_SCALE;
226: } else if (protocolFee > MAX_PROTOCOL_FEE_SHARE) {
227: protocolFee = MAX_PROTOCOL_FEE_SHARE;
228: }
229:
230: Shares governorShares = vaultCache.accumulatedFees.mulDiv(CONFIG_SCALE - protocolFee, CONFIG_SCALE);
231: Shares protocolShares = vaultCache.accumulatedFees - governorShares;
232:
233:
234: vaultStorage.totalShares = vaultCache.totalShares = vaultCache.totalShares - vaultCache.accumulatedFees;
235:
236: vaultStorage.accumulatedFees = vaultCache.accumulatedFees = Shares.wrap(0);
237:
238:
239:
240: if (!governorShares.isZero()) {
241: increaseBalance(vaultCache, governorReceiver, address(0), governorShares, Assets.wrap(0)); // <= FOUND
242: }
243:
244: if (!protocolShares.isZero()) {
245: increaseBalance(vaultCache, protocolReceiver, address(0), protocolShares, Assets.wrap(0)); // <= FOUND
246: }
247:
248: emit ConvertFees(account, protocolReceiver, governorReceiver, protocolShares.toUint(), governorShares.toUint());
249: }['29']
29: function initialize(address proxyCreator) public virtual reentrantOK { // <= FOUND
30: if (initialized) revert E_Initialized();
31: initialized = true;
32:
33:
34:
35:
36: if (msg.data.length != 4 + 32 + PROXY_METADATA_LENGTH) revert E_ProxyMetadata();
37: (IERC20 asset,,) = ProxyUtils.metadata();
38:
39: AddressUtils.checkContract(address(asset));
40:
41:
42:
43:
44: address dToken = address(new DToken());
45:
46:
47:
48: vaultStorage.lastInterestAccumulatorUpdate = uint48(block.timestamp);
49: vaultStorage.interestAccumulator = INITIAL_INTEREST_ACCUMULATOR;
50: vaultStorage.interestFee = DEFAULT_INTEREST_FEE.toConfigAmount();
51: vaultStorage.creator = vaultStorage.governorAdmin = proxyCreator;
52:
53: {
54: string memory underlyingSymbol = getTokenSymbol(address(asset));
55: uint256 seqId = sequenceRegistry.reserveSeqId(underlyingSymbol);
56:
57: vaultStorage.symbol = string(abi.encodePacked("e", underlyingSymbol, "-", uintToString(seqId)));
58: vaultStorage.name = string(abi.encodePacked("EVK Vault ", vaultStorage.symbol));
59: }
60:
61: snapshot.reset();
62:
63:
64:
65: emit EVaultCreated(proxyCreator, address(asset), dToken);
66: logVaultStatus(loadVault(), 0); // <= FOUND
67: }In Solidity, accepting the 'from' address as a public/external function parameter in a non-view/pure function can potentially be misused by an attacker to perform operations on behalf of another user. If not properly checked, an attacker could call the function, pass another user's address as the 'from' parameter, and perform actions like transferring tokens on their behalf. To mitigate this, it's preferable to use 'msg.sender' which refers to the address of the caller of the current function, ensuring that actions are performed by the actual owner of an account or contract. This significantly reduces the risk of unauthorized actions.
Num of instances: 1
Click to show findings
['131']
131: function pullDebt(uint256 amount, address from) public virtual nonReentrant returns (uint256) {
132: (VaultCache memory vaultCache, address account) = initOperation(OP_PULL_DEBT, CHECKACCOUNT_CALLER);
133:
134: if (from == account) revert E_SelfTransfer();
135:
136: Assets assets = amount == type(uint256).max ? getCurrentOwed(vaultCache, from).toAssetsUp() : amount.toAssets();
137:
138: if (assets.isZero()) return 0;
139: transferBorrow(vaultCache, from, account, assets);
140:
141: emit PullDebt(from, account, assets.toUint());
142:
143: return assets.toUint();
144: }The use of fixed decimal values such as 1e18 or 1e8 in Solidity contracts can lead to inaccuracies, bugs, and vulnerabilities, particularly when interacting with tokens having different decimal configurations. Not all ERC20 tokens follow the standard 18 decimal places, and assumptions about decimal places can lead to miscalculations.
Resolution: Always retrieve and use the decimals() function from the token contract itself when performing calculations involving token amounts. This ensures that your contract correctly handles tokens with any number of decimal places, mitigating the risk of numerical errors or under/overflows that could jeopardize contract integrity and user funds.
Num of instances: 6
Click to show findings
['19']
19: uint216 public constant ADJUST_FACTOR = 1.1e18; // <= FOUND['130']
130:
131:
132:
133: uint256 discountFactor = collateralAdjustedValue * 1e18 / liabilityValue; // <= FOUND['135']
135:
136: minDiscountFactor = 1e18 - uint256(1e18) * vaultStorage.maxLiquidationDiscount.toUint16() / CONFIG_SCALE; // <= FOUND['158']
158: uint256 maxYieldValue = maxRepayValue * 1e18 / discountFactor; // <= FOUND['164']
164: maxRepayValue = collateralValue * discountFactor / 1e18; // <= FOUND['23']
23: uint256 public constant PRICE_SCALE = 1e18; // <= FOUNDIt's a good practice to maintain control over the size of array state variables in Solidity, especially if they are dynamically updated. If a contract includes a mechanism to push items into an array, it should ideally also provide a mechanism to remove items. This is because Solidity arrays don't automatically shrink when items are deleted - their length needs to be manually adjusted.
Ignoring this can lead to bloated and inefficient contracts. For instance, iterating over a large array can cause your contract to hit the block gas limit. Additionally, if entries are only marked for deletion but never actually removed, you may end up dealing with stale or irrelevant data, which can cause logical errors.
Therefore, implementing a method to 'pop' items from arrays helps manage contract's state, improve efficiency and prevent potential issues related to gas limits or stale data. Always ensure to handle potential underflow conditions when popping elements from the array.
Num of instances: 1
Low-level assembly calls, such as call, can return a successful status even when the target address contains no executable code. This is due to the fact that a call operation merely checks if the call operation itself was successful, not whether the call was made to an address with code. As a result, these calls might lead to false positives when assessing the success of an operation. To prevent potential issues, consider using Solidity's extcodesize function to check the size of the contract code at the target address. If extcodesize returns zero, this indicates that there is no code at the specified address, and the function can safely revert.
Num of instances: 1
Click to show findings
['153']
153: assembly {
154: let mainCalldataLength := sub(calldatasize(), PROXY_METADATA_LENGTH)
155:
156: mstore(0, 0x1f8b521500000000000000000000000000000000000000000000000000000000)
157: mstore(4, address())
158: mstore(36, caller())
159: mstore(68, callvalue())
160: mstore(100, 128)
161: mstore(132, mainCalldataLength)
162: calldatacopy(164, 0, mainCalldataLength)
163:
164:
165:
166: mstore(add(164, mainCalldataLength), 0)
167: let result := call(gas(), _evc, callvalue(), 0, add(164, and(add(mainCalldataLength, 31), not(31))), 0, 0) // <= FOUND
168:
169: returndatacopy(0, 0, returndatasize())
170: switch result
171: case 0 { revert(0, returndatasize()) }
172: default { return(64, sub(returndatasize(), 64)) }
173: }Initializer functions in contracts often set important parameters or addresses. Failing to check for the zero address (0x0000000000000000000000000000000000000000) in initializers can lead to unintended behavior, as this address typically signifies an unset or default value. Transfers to or interactions with the zero address can result in permanent loss of assets or broken functionality. It's crucial to add checks using require(targetAddress != address(0), "Address cannot be zero") in initializers to prevent accidentally setting important state variables or parameters to this address, ensuring the system's integrity and user asset safety.
Num of instances: 1
Click to show findings
['29']
29: function initialize(address proxyCreator) public virtual reentrantOK {
30: if (initialized) revert E_Initialized();
31: initialized = true;
32:
33:
34:
35:
36: if (msg.data.length != 4 + 32 + PROXY_METADATA_LENGTH) revert E_ProxyMetadata();
37: (IERC20 asset,,) = ProxyUtils.metadata();
38:
39: AddressUtils.checkContract(address(asset));
40:
41:
42:
43:
44: address dToken = address(new DToken());
45:
46:
47:
48: vaultStorage.lastInterestAccumulatorUpdate = uint48(block.timestamp);
49: vaultStorage.interestAccumulator = INITIAL_INTEREST_ACCUMULATOR;
50: vaultStorage.interestFee = DEFAULT_INTEREST_FEE.toConfigAmount();
51: vaultStorage.creator = vaultStorage.governorAdmin = proxyCreator;
52:
53: {
54: string memory underlyingSymbol = getTokenSymbol(address(asset));
55: uint256 seqId = sequenceRegistry.reserveSeqId(underlyingSymbol);
56:
57: vaultStorage.symbol = string(abi.encodePacked("e", underlyingSymbol, "-", uintToString(seqId)));
58: vaultStorage.name = string(abi.encodePacked("EVK Vault ", vaultStorage.symbol));
59: }
60:
61: snapshot.reset();
62:
63:
64:
65: emit EVaultCreated(proxyCreator, address(asset), dToken);
66: logVaultStatus(loadVault(), 0);
67: }Critical functions, especially those affecting protocol parameters or user funds, are potential points of failure or exploitation. To mitigate risks, incorporating a timelock on such functions can be beneficial. A timelock requires a waiting period between the time an action is initiated and when it's executed, giving stakeholders time to react, potentially vetoing malicious or erroneous changes. To implement, integrate a smart contract like OpenZeppelin's TimelockController or build a custom mechanism. This ensures governance decisions or administrative changes are transparent and allows for community or multi-signature interventions, enhancing protocol security and trustworthiness.
Num of instances: 10
Click to show findings
['42']
42: function setCapacity(address minter, uint128 capacity) external onlyOwner // <= FOUND['222']
222: function setGovernorAdmin(address newGovernorAdmin) public virtual override use(MODULE_GOVERNANCE) // <= FOUND['164']
164: function setUpgradeAdmin(address newUpgradeAdmin) external nonReentrant adminOnly // <= FOUND['252']
252: function setGovernorAdmin(address newGovernorAdmin) public virtual nonReentrant governorOnly // <= FOUND['137']
137: function setAdmin(address newAdmin) external onlyAdmin // <= FOUND['148']
148: function setFeeReceiver(address newReceiver) external onlyAdmin // <= FOUND['159']
159: function setProtocolFeeShare(uint16 newProtocolFeeShare) external onlyAdmin // <= FOUND['171']
171: function setInterestFeeRange(uint16 minInterestFee_, uint16 maxInterestFee_) external onlyAdmin // <= FOUND['186']
186: function setVaultInterestFeeRange(address vault, bool exists_, uint16 minInterestFee_, uint16 maxInterestFee_) // <= FOUND
187: external
188: onlyAdmin
189: ['205']
205: function setVaultFeeConfig(address vault, bool exists_, address feeReceiver_, uint16 protocolFeeShare_) // <= FOUND
206: external
207: onlyAdmin
208: Implementing a two-step procedure for updating protocol addresses adds an extra layer of security. In such a system, the first step initiates the change, and the second step, after a predefined delay, confirms and finalizes it. This delay allows stakeholders or monitoring tools to observe and react to unintended or malicious changes. If an unauthorized change is detected, corrective actions can be taken before the change is finalized. To achieve this, introduce a "proposed address" state variable and a "delay period". Upon an update request, set the "proposed address". After the delay, if not contested, the main protocol address can be updated.
Num of instances: 2
Click to show findings
['137']
137: function setAdmin(address newAdmin) external onlyAdmin { // <= FOUND
138: if (newAdmin == address(0)) revert E_InvalidAdmin();
139:
140: admin = newAdmin;
141:
142: emit SetAdmin(newAdmin);
143: }['148']
148: function setFeeReceiver(address newReceiver) external onlyAdmin { // <= FOUND
149: if (newReceiver == address(0)) revert E_InvalidReceiver();
150:
151: feeReceiver = newReceiver;
152:
153: emit SetFeeReceiver(newReceiver);
154: }Low-level calls (such as .call(), .delegatecall(), or .callcode()) in Solidity provide a way to interact with other contracts or addresses. However, when these calls are made to addresses that are provided as parameters or are not well-validated, they pose a significant security risk. Untrusted addresses might contain malicious code leading to unexpected behavior, loss of funds, or vulnerabilities.
Resolution: Prefer using high-level Solidity function calls or interface-based interactions with known contracts to ensure security. If low-level calls are necessary, rigorously validate the addresses and test all possible interactions. Implementing additional checks and fail-safes can help mitigate potential risks associated with low-level calls.
Num of instances: 1
Click to show findings
['28']
28: function safeTransferFrom(IERC20 token, address from, address to, uint256 value, address permit2) internal { // <= FOUND
29: (bool success, bytes memory tryData) = trySafeTransferFrom(token, from, to, value);
30: bytes memory fallbackData;
31: if (!success && permit2 != address(0)) {
32: if (value > type(uint160).max) {
33: revert E_TransferFromFailed(tryData, abi.encodePacked(E_Permit2AmountOverflow.selector));
34: }
35:
36: (success, fallbackData) =
37: permit2.call(abi.encodeCall(IPermit2.transferFrom, (from, to, uint160(value), address(token)))); // <= FOUND
38: }
39:
40: if (!success) revert E_TransferFromFailed(tryData, fallbackData);
41: }In Solidity, when values are being assigned in constructors to unsigned or integer variables, it's crucial to ensure the provided values adhere to the protocol's specific operational boundaries as laid out in the project specifications and documentation. If the constructors lack appropriate validation checks, there's a risk of setting state variables with values that could cause unexpected and potentially detrimental behavior within the contract's operations, violating the intended logic of the protocol. This can compromise the contract's security and impact the maintainability and reliability of the system. In order to avoid such issues, it is recommended to incorporate rigorous validation checks in constructors. These checks should align with the project's defined rules and constraints, making use of Solidity's built-in require function to enforce these conditions. If the validation checks fail, the require function will cause the transaction to revert, ensuring the integrity and adherence to the protocol's expected behavior.
Num of instances: 3
Click to show findings
['22']
22: constructor(uint256 baseRate_, uint256 slope1_, uint256 slope2_, uint256 kink_) {
23: baseRate = baseRate_; // <= FOUND
24: slope1 = slope1_; // <= FOUND
25: slope2 = slope2_; // <= FOUND
26: kink = kink_; // <= FOUND
27: }['39']
39: constructor(address synth_, address referenceAsset_, address oracle_, uint256 targetQuoute_) {
40: if (synth_ == address(0) || referenceAsset_ == address(0) || oracle_ == address(0)) {
41: revert E_ZeroAddress();
42: }
43:
44: synth = synth_;
45: referenceAsset = referenceAsset_;
46: oracle = IPriceOracle(oracle_);
47: targetQuote = targetQuoute_; // <= FOUND
48: quoteAmount = 10 ** IERC20(synth_).decimals();
49:
50:
51: uint256 testQuote = IPriceOracle(oracle_).getQuote(quoteAmount, synth_, referenceAsset_);
52: if (testQuote == 0) {
53: revert E_InvalidQuote();
54: }
55:
56: irmStorage = IRMData({lastUpdated: uint40(block.timestamp), lastRate: BASE_RATE});
57: }['42']
42: constructor(
43: address _evc,
44: address _synth,
45: address _underlying,
46: uint256 toUnderlyingFeeBPS,
47: uint256 toSynthFeeBPS,
48: uint256 _conversionPrice
49: ) EVCUtil(_evc) {
50: if (toUnderlyingFeeBPS >= BPS_SCALE || toSynthFeeBPS >= BPS_SCALE) {
51: revert E_FeeExceedsBPS();
52: }
53:
54: if (_evc == address(0) || _synth == address(0) || _underlying == address(0)) {
55: revert E_ZeroAddress();
56: }
57:
58: synth = ESynth(_synth);
59: underlying = IERC20(_underlying);
60: TO_UNDERLYING_FEE = toUnderlyingFeeBPS;
61: TO_SYNTH_FEE = toSynthFeeBPS;
62: conversionPrice = _conversionPrice; // <= FOUND
63: }For the sake of consistency, stick to using only one of these values throughout the contract. Not doing so in this case can be quite harmful as _msgSender and msg.sender do have some differences, one being that msgSender cannot be used to determine if an account is a EOA but msg.sender can. Differences like these can introduce vulnerabilities is they are not properly acknowledged by the dev team.
Num of instances: 1
Click to show findings
['17']
17: contract ESynth is ERC20Collateral, Ownable {
18: using EnumerableSet for EnumerableSet.AddressSet;
35: Ownable(msg.sender) // <= FOUND
51: address sender = _msgSender(); // <= FOUND
77: address sender = _msgSender(); // <= FOUND
125: function _msgSender() internal view virtual override (ERC20Collateral, Context) returns (address) { // <= FOUND
126: return ERC20Collateral._msgSender(); // <= FOUND
To ensure accuracy in comparisons within programming, especially when dealing with integers, it's often more efficient to use multiplication rather than division. This approach stems from the fact that division operations are generally slower and more complex than multiplication. And in the context of solidity they can cause precision errors.
Suppose you want to compare if a/b is greater than c/d (where a, b, c, and d are integers). Instead of performing division, which is prone to precision errors, you can cross-multiply to avoid division. The comparison a/b > c/d is equivalent to ad > bc. This way, you only use multiplication, which is faster and avoids potential inaccuracies or complexities associated with division.
Num of instances: 2
Click to show findings
['87']
87: if (newInterestAccumulator == intermediate / multiplier) { // <= FOUND['93']
93: if (newTotalBorrows == intermediate / newInterestAccumulator) { // <= FOUNDRecently both increaseAllowance/decreaseAllowance have been deprecated in favour for approve or forceApprove for non standard tokens. As such to maintain compliance with current standards switch to an approve based allowance.
Num of instances: 2
Click to show findings
['87']
87: decreaseAllowance(from, account, shares); // <= FOUND['228']
228: decreaseAllowance(owner, sender, shares); // <= FOUNDIn OpenZeppelin's EnumerableSet library, the add and remove functions return boolean values indicating success or failure. Not checking these return values can lead to unnoticed errors, especially in complex contract logic. It's a best practice to always check the return values of these functions to ensure that the intended modifications to the set were successful. Ignoring them could result in a false assumption of successful addition or removal, potentially leading to security flaws or logical errors in contract execution. Proper handling of these return values contributes to more robust and error-free smart contract code.
Num of instances: 1
Click to show findings
['17']
17: contract ESynth is ERC20Collateral, Ownable { // <= FOUND
18: using EnumerableSet for EnumerableSet.AddressSet;
19:
20: struct MinterData {
21: uint128 capacity;
108: ignoredForTotalSupply.add(vault); // <= FOUND
135: return ignoredForTotalSupply.add(account); // <= FOUND
142: return ignoredForTotalSupply.remove(account); // <= FOUND
In Solidity versions prior to 0.8.21, a bug related to the .selector usage in optimizer settings could lead to incorrect code generation, where function evaluations using .selector might not execute as intended. This issue is classified as low-severity, mainly affecting uncommon code patterns where a function call is used instead of a direct contract name for selector lookup. Files using .selector with affected Solidity versions should be reviewed and updated to mitigate potential issues, ensuring contracts function correctly and securely.
Num of instances: 4
Click to show findings
['349']
349: function setHookConfig(address newHookTarget, uint32 newHookedOps) public virtual nonReentrant governorOnly {
350: if (
351: newHookTarget != address(0)
352: && IHookTarget(newHookTarget).isHookTarget() != IHookTarget.isHookTarget.selector // <= FOUND
353: ) revert E_NotHookTarget();
354:
355: if (newHookedOps >= OP_MAX_VALUE) revert E_NotSupported();
356:
357: vaultStorage.hookTarget = newHookTarget;
358: vaultStorage.hookedOps = Flags.wrap(newHookedOps);
359: emit GovSetHookConfig(newHookTarget, newHookedOps);
360: }['69']
69: function checkAccountStatus(address account, address[] calldata collaterals)
70: public
71: virtual
72: reentrantOK
73: onlyEVCChecks
74: returns (bytes4 magicValue)
75: {
76: checkLiquidity(loadVault(), account, collaterals);
77:
78: magicValue = IEVCVault.checkAccountStatus.selector; // <= FOUND
79: }['83']
83: function checkVaultStatus() public virtual reentrantOK onlyEVCChecks returns (bytes4 magicValue) {
84:
85:
86:
87: VaultCache memory vaultCache = updateVault();
88: uint256 newInterestRate = computeInterestRate(vaultCache);
89:
90: logVaultStatus(vaultCache, newInterestRate);
91:
92:
93:
94:
95: if (vaultCache.snapshotInitialized) {
96: vaultStorage.snapshotInitialized = vaultCache.snapshotInitialized = false;
97:
98: Assets snapshotCash = snapshot.cash;
99: Assets snapshotBorrows = snapshot.borrows;
100:
101: uint256 prevBorrows = snapshotBorrows.toUint();
102: uint256 borrows = vaultCache.totalBorrows.toAssetsUp().toUint();
103:
104: if (borrows > vaultCache.borrowCap && borrows > prevBorrows) revert E_BorrowCapExceeded();
105:
106: uint256 prevSupply = snapshotCash.toUint() + prevBorrows;
107:
108:
109:
110:
111: uint256 supply = vaultCache.cash.toUint() + vaultCache.totalBorrows.toAssetsDown().toUint();
112:
113: if (supply > vaultCache.supplyCap && supply > prevSupply) revert E_SupplyCapExceeded();
114:
115: snapshot.reset();
116: }
117:
118: callHookWithLock(vaultCache.hookedOps, OP_VAULT_STATUS_CHECK, address(evc));
119:
120: magicValue = IEVCVault.checkVaultStatus.selector; // <= FOUND
121: }['28']
28: function safeTransferFrom(IERC20 token, address from, address to, uint256 value, address permit2) internal {
29: (bool success, bytes memory tryData) = trySafeTransferFrom(token, from, to, value);
30: bytes memory fallbackData;
31: if (!success && permit2 != address(0)) {
32: if (value > type(uint160).max) {
33: revert E_TransferFromFailed(tryData, abi.encodePacked(E_Permit2AmountOverflow.selector)); // <= FOUND
34: }
35:
36: (success, fallbackData) =
37: permit2.call(abi.encodeCall(IPermit2.transferFrom, (from, to, uint160(value), address(token))));
38: }
39:
40: if (!success) revert E_TransferFromFailed(tryData, fallbackData);
41: }In smart contracts, especially those dealing with tokens or financial calculations, functions that perform operations based on totalSupply must account for the scenario where totalSupply is zero. Neglecting this edge case can lead to division by zero errors, causing transactions to revert and potentially disrupting contract functionality. This is critical in functions calculating percentages, rewards, or distributions. Proper checks should be implemented to ensure totalSupply is greater than zero before performing division or similar operations. Handling this case ensures the contract remains robust, preventing logical errors and maintaining the integrity of its operations.
Num of instances: 2
Click to show findings
['177']
177: function _convertToShares(uint256 assets, Math.Rounding rounding) internal view override returns (uint256) {
178: return assets.mulDiv(totalSupply() + VIRTUAL_AMOUNT, totalAssets() + VIRTUAL_AMOUNT, rounding); // <= FOUND
179: }['181']
181: function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view override returns (uint256) {
182: return shares.mulDiv(totalAssets() + VIRTUAL_AMOUNT, totalSupply() + VIRTUAL_AMOUNT, rounding); // <= FOUND
183: }Large transfers with some ERC20 tokens may not work due to various reasons. Some tokens may have transfer restrictions built into the contract, such as daily transfer limits or maximum transfer sizes per transaction, to comply with regulatory requirements or to mitigate risks. Others may face issues with rounding errors when dealing with large quantities, especially if they have a high number of decimal places. Resolution involves carefully reading the token's contract to understand its constraints and behaviors and performing transfers accordingly. It may also be necessary to split large transfers into smaller increments if the token enforces specific transfer limits.
Num of instances: 7
Click to show findings
['18']
18: function pullAssets(VaultCache memory vaultCache, address from, Assets amount) internal virtual { // <= FOUND
19: vaultCache.asset.safeTransferFrom(from, address(this), amount.toUint(), permit2); // <= FOUND
20: vaultStorage.cash = vaultCache.cash = vaultCache.cash + amount;
21: }['32']
32: function pushAssets(VaultCache memory vaultCache, address to, Assets amount) internal virtual { // <= FOUND
33: if (
34: to == address(0)
35: || (vaultCache.configFlags.isNotSet(CFG_EVC_COMPATIBLE_ASSET) && isKnownNonOwnerAccount(to))
36: ) {
37: revert E_BadAssetReceiver();
38: }
39:
40: vaultStorage.cash = vaultCache.cash = vaultCache.cash - amount;
41: vaultCache.asset.safeTransfer(to, amount.toUint()); // <= FOUND
42: }['147']
147: function flashLoan(uint256 amount, bytes calldata data) public virtual nonReentrant { // <= FOUND
148: address account = EVCAuthenticate();
149: callHook(vaultStorage.hookedOps, OP_FLASHLOAN, account);
150:
151: (IERC20 asset,,) = ProxyUtils.metadata();
152:
153: uint256 origBalance = asset.balanceOf(address(this));
154:
155: asset.safeTransfer(account, amount); // <= FOUND
156:
157: IFlashLoan(account).onFlashLoan(data);
158:
159: if (asset.balanceOf(address(this)) < origBalance) revert E_FlashLoanNotRepaid();
160: }['69']
69: function swapToUnderlyingGivenIn(uint256 amountIn, address receiver) external returns (uint256) { // <= FOUND
70: uint256 amountOut = quoteToUnderlyingGivenIn(amountIn);
71: if (amountIn == 0 || amountOut == 0) {
72: return 0;
73: }
74:
75: synth.burn(_msgSender(), amountIn);
76: underlying.safeTransfer(receiver, amountOut); // <= FOUND
77:
78: return amountOut;
79: }['85']
85: function swapToUnderlyingGivenOut(uint256 amountOut, address receiver) external returns (uint256) { // <= FOUND
86: uint256 amountIn = quoteToUnderlyingGivenOut(amountOut);
87: if (amountIn == 0 || amountOut == 0) {
88: return 0;
89: }
90:
91: synth.burn(_msgSender(), amountIn);
92: underlying.safeTransfer(receiver, amountOut); // <= FOUND
93:
94: return amountIn;
95: }['101']
101: function swapToSynthGivenIn(uint256 amountIn, address receiver) external returns (uint256) { // <= FOUND
102: uint256 amountOut = quoteToSynthGivenIn(amountIn);
103: if (amountIn == 0 || amountOut == 0) {
104: return 0;
105: }
106:
107: underlying.safeTransferFrom(_msgSender(), address(this), amountIn); // <= FOUND
108: synth.mint(receiver, amountOut);
109:
110: return amountOut;
111: }['117']
117: function swapToSynthGivenOut(uint256 amountOut, address receiver) external returns (uint256) { // <= FOUND
118: uint256 amountIn = quoteToSynthGivenOut(amountOut);
119: if (amountIn == 0 || amountOut == 0) {
120: return 0;
121: }
122:
123: underlying.safeTransferFrom(_msgSender(), address(this), amountIn); // <= FOUND
124: synth.mint(receiver, amountOut);
125:
126: return amountIn;
127: }While adherence to the check-effects-interaction pattern is commendable, the absence of a reentrancy guard in functions, especially where transfer hooks might be present, can expose the protocol users to risks of read-only reentrancies. Such reentrancy vulnerabilities can be exploited to execute malicious actions even without altering the contract state. Without a reentrancy guard, the only potential mitigation would be to blocklist the entire protocol - an extreme and disruptive measure. Therefore, incorporating a reentrancy guard into these functions is vital to bolster security, as it helps protect against both traditional reentrancy attacks and read-only reentrancies, ensuring robust and safe protocol operations.
Num of instances: 6
Click to show findings
['32']
32: function pushAssets(VaultCache memory vaultCache, address to, Assets amount) internal virtual { // <= FOUND
33: if (
34: to == address(0)
35: || (vaultCache.configFlags.isNotSet(CFG_EVC_COMPATIBLE_ASSET) && isKnownNonOwnerAccount(to))
36: ) {
37: revert E_BadAssetReceiver();
38: }
39:
40: vaultStorage.cash = vaultCache.cash = vaultCache.cash - amount;
41: vaultCache.asset.safeTransfer(to, amount.toUint()); // <= FOUND
42: }['69']
69: function swapToUnderlyingGivenIn(uint256 amountIn, address receiver) external returns (uint256) { // <= FOUND
70: uint256 amountOut = quoteToUnderlyingGivenIn(amountIn);
71: if (amountIn == 0 || amountOut == 0) {
72: return 0;
73: }
74:
75: synth.burn(_msgSender(), amountIn);
76: underlying.safeTransfer(receiver, amountOut); // <= FOUND
77:
78: return amountOut;
79: }['85']
85: function swapToUnderlyingGivenOut(uint256 amountOut, address receiver) external returns (uint256) { // <= FOUND
86: uint256 amountIn = quoteToUnderlyingGivenOut(amountOut);
87: if (amountIn == 0 || amountOut == 0) {
88: return 0;
89: }
90:
91: synth.burn(_msgSender(), amountIn);
92: underlying.safeTransfer(receiver, amountOut); // <= FOUND
93:
94: return amountIn;
95: }['32']
32: function pushAssets(VaultCache memory vaultCache, address to, Assets amount) internal virtual {
33: if (
34: to == address(0)
35: || (vaultCache.configFlags.isNotSet(CFG_EVC_COMPATIBLE_ASSET) && isKnownNonOwnerAccount(to))
36: ) {
37: revert E_BadAssetReceiver();
38: }
39:
40: vaultStorage.cash = vaultCache.cash = vaultCache.cash - amount;
41: vaultCache.asset.safeTransfer(to, amount.toUint()); // <= FOUND
42: }['69']
69: function swapToUnderlyingGivenIn(uint256 amountIn, address receiver) external returns (uint256) {
70: uint256 amountOut = quoteToUnderlyingGivenIn(amountIn);
71: if (amountIn == 0 || amountOut == 0) {
72: return 0;
73: }
74:
75: synth.burn(_msgSender(), amountIn);
76: underlying.safeTransfer(receiver, amountOut); // <= FOUND
77:
78: return amountOut;
79: }['85']
85: function swapToUnderlyingGivenOut(uint256 amountOut, address receiver) external returns (uint256) {
86: uint256 amountIn = quoteToUnderlyingGivenOut(amountOut);
87: if (amountIn == 0 || amountOut == 0) {
88: return 0;
89: }
90:
91: synth.burn(_msgSender(), amountIn);
92: underlying.safeTransfer(receiver, amountOut); // <= FOUND
93:
94: return amountIn;
95: }In L2 deployments, incorporating an uptime feed is crucial to mitigate issues arising from sequencer downtime. Downtime can disrupt services, leading to transaction failures or incorrect data readings, affecting overall system reliability. By integrating an uptime feed, you gain insight into the operational status of the L2 network, enabling proactive measures like halting sensitive operations or alerting users. This approach ensures that your contract behaves predictably and securely during network outages, enhancing the robustness and reliability of your decentralized application, which is especially important in mission-critical or high-stakes environments.
Num of instances: 41
Click to show findings
['57']
57: contract BalanceForwarder is BalanceForwarderModule // <= FOUND['10']
10: contract BeaconProxy // <= FOUND['169']
169: contract Borrowing is BorrowingModule // <= FOUND['17']
17: contract Cache is Storage, Errors // <= FOUND['13']
13: contract DToken is IERC20, Errors, Events // <= FOUND['17']
17: contract ESynth is ERC20Collateral, Ownable // <= FOUND['12']
12: contract EVault is Dispatch // <= FOUND['9']
9: contract Errors // <= FOUND['21']
21: contract EulerSavingsRate is EVCUtil, ERC4626 // <= FOUND['21']
21: contract GenericFactory is MetaProxyDeployer // <= FOUND['404']
404: contract Governance is GovernanceModule // <= FOUND['12']
12: contract IRMLinearKink is IIRM // <= FOUND['15']
15: contract IRMSynth is IIRM // <= FOUND['105']
105: contract Initialize is InitializeModule // <= FOUND['237']
237: contract Liquidation is LiquidationModule // <= FOUND['11']
11: contract MetaProxyDeployer // <= FOUND['19']
19: contract PegStabilityModule is EVCUtil // <= FOUND['7']
7: contract ProtocolConfig is IProtocolConfig // <= FOUND['125']
125: contract RiskManager is RiskManagerModule // <= FOUND['14']
14: contract SequenceRegistry is ISequenceRegistry // <= FOUND['101']
101: contract Token is TokenModule // <= FOUND['273']
273: contract Vault is VaultModule // <= FOUND['14']
14: abstract contract AssetTransfers is Base // <= FOUND['14']
14: abstract contract BalanceForwarderModule is IBalanceForwarder, Base // <= FOUND['13']
13: abstract contract BalanceUtils is Base // <= FOUND['21']
21: abstract contract Base is EVCClient, Cache // <= FOUND['15']
15: abstract contract BorrowUtils is Base // <= FOUND['20']
20: abstract contract BorrowingModule is IBorrowing, AssetTransfers, BalanceUtils, LiquidityUtils // <= FOUND['23']
23: abstract contract Dispatch is // <= FOUND
24: InitializeModule,
25: TokenModule,
26: VaultModule,
27: BorrowingModule,
28: LiquidationModule,
29: RiskManagerModule,
30: BalanceForwarderModule,
31: GovernanceModule
32: ['15']
15: abstract contract ERC20Collateral is EVCUtil, ERC20Permit, ReentrancyGuard // <= FOUND['20']
20: abstract contract EVCClient is Storage, Events, Errors // <= FOUND['9']
9: abstract contract Events // <= FOUND['20']
20: abstract contract GovernanceModule is IGovernance, BalanceUtils, BorrowUtils, LTVUtils // <= FOUND['20']
20: abstract contract InitializeModule is IInitialize, BorrowUtils // <= FOUND['12']
12: abstract contract LTVUtils is Storage // <= FOUND['16']
16: abstract contract LiquidationModule is ILiquidation, BalanceUtils, LiquidityUtils // <= FOUND['14']
14: abstract contract LiquidityUtils is BorrowUtils, LTVUtils // <= FOUND['15']
15: abstract contract RiskManagerModule is IRiskManager, LiquidityUtils // <= FOUND['11']
11: abstract contract Storage // <= FOUND['16']
16: abstract contract TokenModule is IToken, BalanceUtils // <= FOUND['18']
18: abstract contract VaultModule is IVault, AssetTransfers, BalanceUtils // <= FOUNDIn struct definitions, using redundant prefixes for attributes is unnecessary. For instance, in a struct named Employee, attributes like employeeName, employeeID, and employeeEmail can be simplified to name, ID, and email respectively, since they are already inherently associated with Employee. By removing these repetitive prefixes, the code becomes more concise and easier to read, maintaining its contextual clarity.
Num of instances: 1
Click to show findings
['20']
20: struct ProtocolFeeConfig { // <= FOUND
21: bool exists;
22: address feeReceiver;
23: uint16 protocolFeeShare; // <= FOUND
24: }[NonCritical-2] Getting a bool return value does not confirm the existence of a function in an external call
External calls to contracts using address.call() might return a boolean indicating success or failure. However, this boolean doesn't guarantee the existence of a called function. If a function isn't present, the call won't revert but will simply return false. This behavior might lead developers into mistakenly believing they're interacting with a legitimate or expected function, whereas it might not exist at all—a scenario sometimes termed as "phantom functions". Resolution: Instead of solely relying on the boolean, further validate the contract you're interacting with, or use interfaces or abstract contracts to enforce the existence of expected functions.
Num of instances: 1
Click to show findings
['127']
127: function invokeHookTarget(address caller) private { // <= FOUND
128: address hookTarget = vaultStorage.hookTarget;
129:
130: if (hookTarget == address(0)) revert E_OperationDisabled();
131:
132: (bool success, bytes memory data) = hookTarget.call(abi.encodePacked(msg.data, caller)); // <= FOUND
133:
134: if (!success) RevertBytes.revertBytes(data);
135: }Allowing a function in Solidity to return the default address (address(0)) can be problematic as it can represent uninitialized or invalid addresses. If such an address is utilized in transfer operations or other sensitive actions, it could lead to loss of funds or unpredicted behavior. It's prudent to include checks in your functions to prevent the return of the zero address, enhancing contract security.
Num of instances: 23
Click to show findings
['12']
12: function checkContract(address addr) internal view returns (address) {
13: if (addr.code.length == 0) revert Errors.E_BadAddress();
14:
15: return addr;
16: }['16']
16: function balanceTrackerAddress() public view virtual reentrantOK returns (address) {
17: return address(balanceTracker);
18: }['81']
81: function initOperation(uint32 operation, address accountToCheck)
82: internal
83: virtual
84: returns (VaultCache memory vaultCache, address account)
85: {
86: vaultCache = updateVault();
87: account = EVCAuthenticateDeferred(CONTROLLER_NEUTRAL_OPS & operation == 0);
88:
89: callHook(vaultCache.hookedOps, operation, account);
90: EVCRequireStatusChecks(accountToCheck == CHECKACCOUNT_CALLER ? account : accountToCheck);
91:
92:
93:
94:
95:
96: if (
97: !vaultCache.snapshotInitialized
98: && !(vaultCache.supplyCap == type(uint256).max && vaultCache.borrowCap == type(uint256).max)
99: ) {
100: vaultStorage.snapshotInitialized = vaultCache.snapshotInitialized = true;
101: snapshot.set(vaultCache.cash, vaultCache.totalBorrows.toAssetsUp());
102: }
103: }['60']
60: function dToken() public view virtual reentrantOK returns (address) {
61: return calculateDTokenAddress();
62: }['92']
92: function asset() external view returns (address) {
93: return IEVault(eVault).asset();
94: }['71']
71: function _msgSender() internal view virtual override (EVCUtil, Context) returns (address) {
72: return EVCUtil._msgSender();
73: }['125']
125: function _msgSender() internal view virtual override (ERC20Collateral, Context) returns (address) {
126: return ERC20Collateral._msgSender();
127: }['41']
41: function EVCAuthenticateDeferred(bool checkController) internal view virtual returns (address) {
42: assert(msg.sender == address(evc));
43:
44: (address onBehalfOfAccount, bool controllerEnabled) =
45: evc.getCurrentOnBehalfOfAccount(checkController ? address(this) : address(0));
46:
47: if (checkController && !controllerEnabled) revert E_ControllerDisabled();
48:
49: return onBehalfOfAccount;
50: }['55']
55: function asset() public view virtual override returns (address) { return super.asset(); }['264']
264: function _msgSender() internal view override (Context, EVCUtil) returns (address) {
265: return EVCUtil._msgSender();
266: }['99']
99: function governorAdmin() public view virtual reentrantOK returns (address) {
100: return vaultStorage.governorAdmin;
101: }['104']
104: function feeReceiver() public view virtual reentrantOK returns (address) {
105: return vaultStorage.feeReceiver;
106: }['114']
114: function interestRateModel() public view virtual reentrantOK returns (address) {
115: return vaultStorage.interestRateModel;
116: }['119']
119: function protocolConfigAddress() public view virtual reentrantOK returns (address) {
120: return address(protocolConfig);
121: }['130']
130: function protocolFeeReceiver() public view virtual reentrantOK returns (address) {
131: (address protocolReceiver,) = protocolConfig.protocolFeeConfig(address(this));
132: return protocolReceiver;
133: }['184']
184: function hookConfig() public view virtual reentrantOK returns (address, uint32) {
185: return (vaultStorage.hookTarget, vaultStorage.hookedOps.toUint32());
186: }['194']
194: function EVC() public view virtual reentrantOK returns (address) {
195: return address(evc);
196: }['199']
199: function unitOfAccount() public view virtual reentrantOK returns (address) {
200: (,, address _unitOfAccount) = ProxyUtils.metadata();
201: return _unitOfAccount;
202: }['205']
205: function oracle() public view virtual reentrantOK returns (address) {
206: (, IPriceOracle _oracle,) = ProxyUtils.metadata();
207: return address(_oracle);
208: }['211']
211: function permit2Address() public view virtual reentrantOK returns (address) {
212: return permit2;
213: }['105']
105: function protocolFeeConfig(address vault) external view returns (address, uint16) {
106: ProtocolFeeConfig memory config = _protocolFeeConfig[vault];
107:
108: if (config.exists) {
109: return (config.feeReceiver, config.protocolFeeShare);
110: }
111:
112: return (feeReceiver, protocolFeeShare);
113: }['23']
23: function asset() public view virtual reentrantOK returns (address) {
24: (IERC20 _asset,,) = ProxyUtils.metadata();
25: return address(_asset);
26: }['119']
119: function creator() public view virtual reentrantOK returns (address) {
120: return vaultStorage.creator;
121: }The approve function in ERC-20 tokens allows a user to permit another user or contract to spend tokens on their behalf. Setting the approval to type(uint256).max is often used as a way to grant an indefinite approval, as this value is the maximum possible value of a uint256 variable in Solidity.
However, some tokens may not function as expected when type(uint256).max is used. These tokens may have an atypical implementation of the transferFrom function, which is used in combination with approve. This function might behave differently when confronted with such a high allowance, possibly due to custom logic in the contract that wasn't designed to handle these edge cases.
Moreover, tokens that have a built-in burning or fees mechanism could behave unpredictably when the maximum allowance is set. This can lead to potential vulnerabilities or misinterpretations of contract behavior.
Resolution: It's advisable to be conservative with the approve function and only approve the specific amount of tokens that need to be spent for the specific operation you're performing. If you need to provide an extensive allowance, ensure you've thoroughly analyzed the token contract to understand how it behaves with high allowances. Alternatively, consider implementing a mechanism in your contract to handle token allowances in a more dynamic way, adjusting them as needed for each operation, rather than relying on a single indefinite approval.
Num of instances: 1
Click to show findings
['45']
45: function approve(address spender, uint256 amount) public virtual override returns (bool) { return super.approve(spender, amount); } // <= FOUNDMany ERC-20 and ERC-721 token contracts implement a safeguard that reverts transactions which attempt to transfer tokens to the zero address. This is because such transfers are often the result of programming errors. The OpenZeppelin library, a popular choice for implementing these standards, includes this safeguard. For token contract developers who want to avoid unintentional transfers to the zero address, it's good practice to include a condition that reverts the transaction if the recipient's address is the zero address.
Num of instances: 9
Click to show findings
['37']
37: function transferFrom(address from, address to, uint256 amount) // <= FOUND
38: public
39: virtual
40: override
41: nonReentrant
42: returns (bool)
43: {
44: return super.transferFrom(from, to, amount); // <= FOUND
45: }['43']
43: function transferFrom(address from, address to, uint256 amount) public virtual override callThroughEVC returns (bool) { return super.transferFrom(from, to, amount); } // <= FOUND['119']
119: function transferFrom(address from, address to, uint256 amount) // <= FOUND
120: public
121: override (ERC20, IERC20)
122: nonReentrant
123: requireAccountStatusCheck(from)
124: returns (bool)
125: {
126: return super.transferFrom(from, to, amount); // <= FOUND
127: }['27']
27: function transfer(address to, uint256 amount) public virtual override nonReentrant returns (bool) {
28: return super.transfer(to, amount); // <= FOUND
29: }['41']
41: function transfer(address to, uint256 amount) public virtual override callThroughEVC returns (bool) { return super.transfer(to, amount); } // <= FOUND['104']
104: function transfer(address to, uint256 amount)
105: public
106: override (ERC20, IERC20)
107: nonReentrant
108: requireAccountStatusCheck(_msgSender())
109: returns (bool)
110: {
111: return super.transfer(to, amount); // <= FOUND
112: }['147']
147: function flashLoan(uint256 amount, bytes calldata data) public virtual nonReentrant {
148: address account = EVCAuthenticate();
149: callHook(vaultStorage.hookedOps, OP_FLASHLOAN, account);
150:
151: (IERC20 asset,,) = ProxyUtils.metadata();
152:
153: uint256 origBalance = asset.balanceOf(address(this));
154:
155: asset.safeTransfer(account, amount); // <= FOUND
156:
157: IFlashLoan(account).onFlashLoan(data);
158:
159: if (asset.balanceOf(address(this)) < origBalance) revert E_FlashLoanNotRepaid();
160: }['69']
69: function swapToUnderlyingGivenIn(uint256 amountIn, address receiver) external returns (uint256) {
70: uint256 amountOut = quoteToUnderlyingGivenIn(amountIn);
71: if (amountIn == 0 || amountOut == 0) {
72: return 0;
73: }
74:
75: synth.burn(_msgSender(), amountIn);
76: underlying.safeTransfer(receiver, amountOut); // <= FOUND
77:
78: return amountOut;
79: }['85']
85: function swapToUnderlyingGivenOut(uint256 amountOut, address receiver) external returns (uint256) {
86: uint256 amountIn = quoteToUnderlyingGivenOut(amountOut);
87: if (amountIn == 0 || amountOut == 0) {
88: return 0;
89: }
90:
91: synth.burn(_msgSender(), amountIn);
92: underlying.safeTransfer(receiver, amountOut); // <= FOUND
93:
94: return amountIn;
95: }Assuming that the symbol() function is always present in an ERC20 contract can lead to unexpected issues, as not all ERC20 tokens implement it. Some tokens may omit this function or use a non-standard implementation. If your contract makes an external call to symbol() on a token that does not implement this function, it will result in a runtime error, possibly breaking your contract's functionality. The recommended solution is to use try/catch statements when calling symbol(). This way, your contract can handle cases where the function is missing or implemented differently, ensuring robust interaction with any ERC20 token.
Num of instances: 1
Click to show findings
['32']
32: return string.concat(IEVault(eVault).symbol(), "-DEBT"); // <= FOUNDAssuming that the name() function is always present in an ERC20 contract can lead to unexpected issues, as not all ERC20 tokens implement it. Some tokens may omit this function or use a non-standard implementation. If your contract makes an external call to name() on a token that does not implement this function, it will result in a runtime error, possibly breaking your contract's functionality. The recommended solution is to use try/catch statements when calling name(). This way, your contract can handle cases where the function is missing or implemented differently, ensuring robust interaction with any ERC20 token.
Num of instances: 1
Click to show findings
['26']
26: return string.concat("Debt token of ", IEVault(eVault).name()); // <= FOUNDNum of instances: 9
Click to show findings
['29']
29: unchecked { // <= FOUND
30: return amount.toUint() * totalShares / totalAssets; // <= FOUND
31: }['36']
36: unchecked { // <= FOUND
37:
38: return TypesLib.toShares((amount.toUint() * totalShares + (totalAssets - 1)) / totalAssets); // <= FOUND
39: }['49']
49: unchecked { // <= FOUND
50: return Assets.wrap(uint112(self.toUint() + b.toUint())); // <= FOUND
51: }['20']
20: unchecked { // <= FOUND
21: totalAssets = // <= FOUND
22: vaultCache.cash.toUint() + vaultCache.totalBorrows.toAssetsUp().toUint() + VIRTUAL_DEPOSIT_AMOUNT;
23: totalShares = vaultCache.totalShares.toUint() + VIRTUAL_DEPOSIT_AMOUNT; // <= FOUND
24: }['45']
45: unchecked { // <= FOUND
46: return Owed.wrap(uint144(self.toUint() + b.toUint())); // <= FOUND
47: }['24']
24: unchecked { // <= FOUND
25: return TypesLib.toAssets(amount.toUint() * totalAssets / totalShares); // <= FOUND
26: }['31']
31: unchecked { // <= FOUND
32:
33: return TypesLib.toAssets((amount.toUint() * totalAssets + (totalShares - 1)) / totalShares); // <= FOUND
34: }['80']
80: unchecked { // <= FOUND
81: uint256 intermediate;
82: (uint256 multiplier, bool overflow) = RPow.rpow(interestRate + 1e27, deltaT, 1e27); // <= FOUND
83:
84:
85: if (!overflow) {
86: intermediate = newInterestAccumulator * multiplier; // <= FOUND
87: if (newInterestAccumulator == intermediate / multiplier) {
88: newInterestAccumulator = intermediate / 1e27;
89: }
90: }
91:
92: intermediate = newTotalBorrows * newInterestAccumulator; // <= FOUND
93: if (newTotalBorrows == intermediate / newInterestAccumulator) {
94: newTotalBorrows = intermediate / vaultCache.interestAccumulator;
95: }
96: }['133']
133: unchecked { // <= FOUND
134:
135: minDiscountFactor = 1e18 - uint256(1e18) * vaultStorage.maxLiquidationDiscount.toUint16() / CONFIG_SCALE; // <= FOUND
136: }Code injection via token names primarily poses a threat to frontends interacting with smart contracts, especially in scenarios where these frontends display token information fetched from the blockchain. This vulnerability arises when contracts store or transmit token names, symbols, or other string metadata that a malicious token contract might manipulate. If the frontend fails to sanitize this data properly, it could execute harmful scripts or display misleading information, leading to security breaches like phishing attacks or data theft.
Resolution: To mitigate this risk, frontend applications should always treat token metadata (names, symbols, etc.) obtained from smart contracts as untrusted data. Implementing rigorous sanitization and validation routines on the frontend can prevent the execution of any embedded scripts or malicious code. This includes encoding or escaping strings before rendering them in the browser, using content security policies (CSP), and avoiding direct DOM manipulation with untrusted data. Regularly updating and auditing the frontend code for vulnerabilities related to handling external data is also crucial for maintaining robust security against potential code injection threats.
Num of instances: 3
Click to show findings
['25']
25: function name() external view returns (string memory) {
26: return string.concat("Debt token of ", IEVault(eVault).name()); // <= FOUND
27: }['29']
29: function initialize(address proxyCreator) public virtual reentrantOK {
30: if (initialized) revert E_Initialized();
31: initialized = true;
32:
33:
34:
35:
36: if (msg.data.length != 4 + 32 + PROXY_METADATA_LENGTH) revert E_ProxyMetadata();
37: (IERC20 asset,,) = ProxyUtils.metadata();
38:
39: AddressUtils.checkContract(address(asset));
40:
41:
42:
43:
44: address dToken = address(new DToken());
45:
46:
47:
48: vaultStorage.lastInterestAccumulatorUpdate = uint48(block.timestamp);
49: vaultStorage.interestAccumulator = INITIAL_INTEREST_ACCUMULATOR;
50: vaultStorage.interestFee = DEFAULT_INTEREST_FEE.toConfigAmount();
51: vaultStorage.creator = vaultStorage.governorAdmin = proxyCreator;
52:
53: {
54: string memory underlyingSymbol = getTokenSymbol(address(asset));
55: uint256 seqId = sequenceRegistry.reserveSeqId(underlyingSymbol);
56:
57: vaultStorage.symbol = string(abi.encodePacked("e", underlyingSymbol, "-", uintToString(seqId)));
58: vaultStorage.name = string(abi.encodePacked("EVK Vault ", vaultStorage.symbol)); // <= FOUND
59: }
60:
61: snapshot.reset();
62:
63:
64:
65: emit EVaultCreated(proxyCreator, address(asset), dToken);
66: logVaultStatus(loadVault(), 0);
67: }['20']
20: function name() public view virtual reentrantOK returns (string memory) {
21: return bytes(vaultStorage.name).length > 0 ? vaultStorage.name : "Unnamed Euler Vault"; // <= FOUND
22: }Centralized ERC20 token burning, where only a select few addresses have control over the burn function, can be viewed negatively. It introduces risks of manipulation, as the power to alter token supply is concentrated in the hands of a small group, potentially affecting token value unfairly. Such centralization can also introduce risks if the admin account(s) is compromised. Consider implementing a timelock feature so users can decide to opt out of the protocol if they do not agree with actions taken.
Num of instances: 1
Click to show findings
['76']
76: function burn(address burnFrom, uint256 amount) external nonReentrant { // <= FOUND
77: address sender = _msgSender();
78: MinterData memory minterCache = minters[sender];
79:
80: if (amount == 0) {
81: return;
82: }
83:
84:
85:
86: if (burnFrom != sender && !(burnFrom == address(this) && sender == owner())) {
87: _spendAllowance(burnFrom, sender, amount);
88: }
89:
90:
91: unchecked {
92:
93: minterCache.minted = minterCache.minted > amount ? minterCache.minted - uint128(amount) : 0;
94: }
95: minters[sender] = minterCache;
96:
97: _burn(burnFrom, amount);
98: }Num of instances: 2
Click to show findings
['3']
3: pragma solidity ^0.8.0; // <= FOUND['3']
3: pragma solidity >=0.8.0; // <= FOUNDIt is general standard to declare interfaces on files separate from regular contract declarations
Num of instances: 1
[NonCritical-13] Events regarding state variable changes should emit the previous state variable value
Modify such events to contain the previous value of the state variable as demonstrated in the example below
Num of instances: 19
Click to show findings
['28']
28: event MinterCapacitySet(address indexed minter, uint256 capacity);['67']
67: event SetImplementation(address indexed newImplementation);['71']
71: event SetUpgradeAdmin(address indexed newUpgradeAdmin);['49']
49: event SetInterestFeeRange(uint16 newMinInterestFee, uint16 newMaxInterestFee);['53']
53: event SetFeeReceiver(address indexed newFeeReceiver);['60']
60: event SetVaultInterestFeeRange(address indexed vault, bool exists, uint16 minInterestFee, uint16 maxInterestFee);['67']
67: event SetFeeConfigSetting(address indexed vault, bool exists, address indexed feeReceiver, uint16 protocolFeeShare);['72']
72: event SetProtocolFeeShare(uint16 protocolFeeShare, uint16 newProtocolFeeShare);['76']
76: event SetAdmin(address indexed newAdmin);['36']
36: event GovSetGovernorAdmin(address indexed newGovernorAdmin);['40']
40: event GovSetFeeReceiver(address indexed newFeeReceiver);['53']
53: event GovSetLTV(
54: address indexed collateral,
55: uint16 borrowLTV,
56: uint16 liquidationLTV,
57: uint16 initialLiquidationLTV,
58: uint48 targetTimestamp,
59: uint32 rampDuration,
60: bool initialized
61: );['64']
64: event GovSetInterestRateModel(address newInterestRateModel);['68']
68: event GovSetMaxLiquidationDiscount(uint16 newDiscount);['73']
73: event GovSetLiquidationCoolOffTime(uint16 newCoolOffTime);['78']
78: event GovSetHookConfig(address indexed newHookTarget, uint32 newHookedOps);['82']
82: event GovSetConfigFlags(uint32 newConfigFlags);['87']
87: event GovSetCaps(uint16 newSupplyCap, uint16 newBorrowCap);['91']
91: event GovSetInterestFee(uint16 newFee);[NonCritical-14] In functions which accept an address as a parameter, there should be a zero address check to prevent bugs
In smart contract development, especially with Solidity, it's crucial to validate inputs to functions. When a function accepts an Ethereum address as a parameter, implementing a zero address check (i.e., ensuring the address is not 0x0) is a best practice to prevent potential bugs and vulnerabilities. The zero address (0x0) is a default value and generally indicates an uninitialized or invalid state. Passing the zero address to certain functions can lead to unintended behaviors, like funds getting locked permanently or transactions failing silently. By checking for and rejecting the zero address, developers can ensure that the function operates as intended and interacts only with valid Ethereum addresses. This check enhances the contract's robustness and security.
Num of instances: 162
Click to show findings
['12']
12: function checkContract(address addr) internal view returns (address) ['18']
18: function pullAssets(VaultCache memory vaultCache, address from, Assets amount) internal virtual ['21']
21: function balanceForwarderEnabled(address account) public view virtual nonReentrantView returns (bool) ['40']
40: function decreaseBalance(
41: VaultCache memory vaultCache,
42: address account,
43: address sender,
44: address receiver,
45: Shares amount,
46: Assets assets
47: ) internal virtual ['106']
106: function setAllowance(address owner, address spender, uint256 amount) internal ['114']
114: function decreaseAllowance(address owner, address spender, Shares amount) internal virtual ['81']
81: function initOperation(uint32 operation, address accountToCheck)
82: internal
83: virtual
84: returns (VaultCache memory vaultCache, address account)
85: ['114']
114: function callHook(Flags hookedOps, uint32 operation, address caller) internal virtual ['121']
121: function callHookWithLock(Flags hookedOps, uint32 operation, address caller) internal virtual ['137']
137: function invokeHookTargetWithLock(address caller) private nonReentrant ['16']
16: function getCurrentOwed(VaultCache memory vaultCache, address account, Owed owed) internal view returns (Owed) ['24']
24: function getCurrentOwed(VaultCache memory vaultCache, address account) internal view returns (Owed) ['28']
28: function loadUserBorrow(VaultCache memory vaultCache, address account)
29: private
30: view
31: returns (Owed newOwed, Owed prevOwed)
32: ['37']
37: function setUserBorrow(VaultCache memory vaultCache, address account, Owed newOwed) private ['44']
44: function increaseBorrow(VaultCache memory vaultCache, address account, Assets assets) internal virtual ['59']
59: function decreaseBorrow(VaultCache memory vaultCache, address account, Assets assets) internal virtual ['75']
75: function transferBorrow(VaultCache memory vaultCache, address from, address to, Assets assets) internal virtual ['167']
167: function logBorrow(address account, Assets amount, Assets prevOwed, Assets owed) private ['174']
174: function logRepay(address account, Assets amount, Assets prevOwed, Assets owed) private ['181']
181: function logDToken(address account, Assets prevOwed, Assets owed) private ['40']
40: function debtOf(address account) public view virtual nonReentrantView returns (uint256) ['45']
45: function debtOfExact(address account) public view virtual nonReentrantView returns (uint256) ['65']
65: function borrow(uint256 amount, address receiver) public virtual nonReentrant returns (uint256) ['81']
81: function repay(uint256 amount, address receiver) public virtual nonReentrant returns (uint256) ['97']
97: function repayWithShares(uint256 amount, address receiver) public virtual nonReentrant returns (uint256, uint256) ['131']
131: function pullDebt(uint256 amount, address from) public virtual nonReentrant returns (uint256) ['50']
50: function balanceOf(address owner) external view returns (uint256) ['57']
57: function allowance(address, address) external pure returns (uint256) ['63']
63: function approve(address, uint256) external pure returns (bool) ['69']
69: function transfer(address, uint256) external pure returns (bool) ['75']
75: function transferFrom(address, address, uint256) external pure returns (bool) ['83']
83: function emitTransfer(address from, address to, uint256 value) external ['118']
118: function delegateToModule(address module) private ['129']
129: function delegateToModuleView(address module) private view ['27']
27: function transfer(address to, uint256 amount) public virtual override nonReentrant returns (bool) ['37']
37: function transferFrom(address from, address to, uint256 amount)
38: public
39: virtual
40: override
41: nonReentrant
42: returns (bool)
43: ['42']
42: function setCapacity(address minter, uint128 capacity) external onlyOwner ['50']
50: function mint(address account, uint256 amount) external nonReentrant ['76']
76: function burn(address burnFrom, uint256 amount) external nonReentrant ['104']
104: function allocate(address vault, uint256 amount) external onlyOwner ['116']
116: function deallocate(address vault, uint256 amount) external onlyOwner ['134']
134: function addIgnoredForTotalSupply(address account) external onlyOwner returns (bool success) ['141']
141: function removeIgnoredForTotalSupply(address account) external onlyOwner returns (bool success) ['147']
147: function isIgnoredForTotalSupply(address account) public view returns (bool) ['35']
35: function disableControllerInternal(address account) internal virtual ['88']
88: function EVCRequireStatusChecks(address account) internal virtual ['98']
98: function enforceCollateralTransfer(address collateral, uint256 amount, address from, address receiver)
99: internal
100: virtual
101: ['105']
105: function forgiveAccountStatusCheck(address account) internal virtual ['109']
109: function hasAnyControllerEnabled(address account) internal view returns (bool) ['113']
113: function getCollaterals(address account) internal view returns (address[] memory) ['117']
117: function isCollateralEnabled(address account, address collateral) internal view returns (bool) ['121']
121: function isAccountStatusCheckDeferred(address account) internal view returns (bool) ['133']
133: function getLastAccountStatusCheckTimestamp(address account) internal view returns (uint256) ['137']
137: function validateController(address account) internal view ['20']
20: function initialize(address proxyCreator) public virtual override use(MODULE_INITIALIZE) ['36']
36: function balanceOf(address account) public view virtual override returns (uint256) ['38']
38: function allowance(address holder, address spender) public view virtual override returns (uint256) ['41']
41: function transfer(address to, uint256 amount) public virtual override callThroughEVC returns (bool) ['43']
43: function transferFrom(address from, address to, uint256 amount) public virtual override callThroughEVC returns (bool) ['45']
45: function approve(address spender, uint256 amount) public virtual override returns (bool) ['47']
47: function transferFromMax(address from, address to) public virtual override callThroughEVC returns (bool) ['63']
63: function maxDeposit(address account) public view virtual override useView(MODULE_VAULT) returns (uint256) ['67']
67: function maxMint(address account) public view virtual override useView(MODULE_VAULT) returns (uint256) ['71']
71: function maxWithdraw(address owner) public view virtual override useView(MODULE_VAULT) returns (uint256) ['75']
75: function maxRedeem(address owner) public view virtual override useView(MODULE_VAULT) returns (uint256) ['86']
86: function deposit(uint256 amount, address receiver) public virtual override callThroughEVC returns (uint256) ['88']
88: function mint(uint256 amount, address receiver) public virtual override callThroughEVC use(MODULE_VAULT) returns (uint256) ['90']
90: function withdraw(uint256 amount, address receiver, address owner) public virtual override callThroughEVC use(MODULE_VAULT) returns (uint256) ['92']
92: function redeem(uint256 amount, address receiver, address owner) public virtual override callThroughEVC use(MODULE_VAULT) returns (uint256) ['94']
94: function skim(uint256 amount, address receiver) public virtual override callThroughEVC use(MODULE_VAULT) returns (uint256) ['108']
108: function debtOf(address account) public view virtual override returns (uint256) ['110']
110: function debtOfExact(address account) public view virtual override useView(MODULE_BORROWING) returns (uint256) ['119']
119: function borrow(uint256 amount, address receiver) public virtual override callThroughEVC use(MODULE_BORROWING) returns (uint256) ['121']
121: function repay(uint256 amount, address receiver) public virtual override callThroughEVC use(MODULE_BORROWING) returns (uint256) ['123']
123: function repayWithShares(uint256 amount, address receiver) public virtual override callThroughEVC use(MODULE_BORROWING) returns (uint256 shares, uint256 debt) ['125']
125: function pullDebt(uint256 amount, address from) public virtual override callThroughEVC use(MODULE_BORROWING) returns (uint256) ['137']
137: function checkLiquidation(address liquidator, address violator, address collateral) public view virtual override useView(MODULE_LIQUIDATION) returns (uint256 maxRepay, uint256 maxYield) ['139']
139: function liquidate(address violator, address collateral, uint256 repayAssets, uint256 minYieldBalance) public virtual override callThroughEVC use(MODULE_LIQUIDATION) ['147']
147: function accountLiquidity(address account, bool liquidation) public view virtual override useView(MODULE_RISKMANAGER) returns (uint256 collateralValue, uint256 liabilityValue) ['149']
149: function accountLiquidityFull(address account, bool liquidation) public view virtual override useView(MODULE_RISKMANAGER) returns (address[] memory collaterals, uint256[] memory collateralValues, uint256 liabilityValue) ['154']
154: function checkAccountStatus(address account, address[] calldata collaterals) public virtual override returns (bytes4) ['166']
166: function balanceForwarderEnabled(address account) public view virtual override useView(MODULE_BALANCE_FORWARDER) returns (bool) ['195']
195: function LTVBorrow(address collateral) public view virtual override useView(MODULE_GOVERNANCE) returns (uint16) ['197']
197: function LTVLiquidation(address collateral) public view virtual override useView(MODULE_GOVERNANCE) returns (uint16) ['199']
199: function LTVFull(address collateral) public view virtual override useView(MODULE_GOVERNANCE) returns (uint16 borrowLTV, uint16 liquidationLTV, uint16 initialLiquidationLTV, uint48 targetTimestamp, uint32 rampDuration) ['222']
222: function setGovernorAdmin(address newGovernorAdmin) public virtual override use(MODULE_GOVERNANCE) ['224']
224: function setFeeReceiver(address newFeeReceiver) public virtual override use(MODULE_GOVERNANCE) ['226']
226: function setHookConfig(address newHookTarget, uint32 newHookedOps) public virtual override use(MODULE_GOVERNANCE) ['228']
228: function setLTV(address collateral, uint16 borrowLTV, uint16 liquidationLTV, uint32 rampDuration) public virtual override use(MODULE_GOVERNANCE) ['230']
230: function clearLTV(address collateral) public virtual override use(MODULE_GOVERNANCE) ['236']
236: function setInterestRateModel(address newModel) public virtual override use(MODULE_GOVERNANCE) ['72']
72: function maxRedeem(address owner) public view override returns (uint256) ['86']
86: function maxWithdraw(address owner) public view override returns (uint256) ['104']
104: function transfer(address to, uint256 amount)
105: public
106: override (ERC20, IERC20)
107: nonReentrant
108: requireAccountStatusCheck(_msgSender())
109: returns (bool)
110: ['119']
119: function transferFrom(address from, address to, uint256 amount)
120: public
121: override (ERC20, IERC20)
122: nonReentrant
123: requireAccountStatusCheck(from)
124: returns (bool)
125: ['133']
133: function deposit(uint256 assets, address receiver) public override nonReentrant returns (uint256) ['141']
141: function mint(uint256 shares, address receiver) public override nonReentrant returns (uint256) ['149']
149: function withdraw(uint256 assets, address receiver, address owner)
150: public
151: override
152: nonReentrant
153: requireAccountStatusCheck(owner)
154: returns (uint256)
155: ['165']
165: function redeem(uint256 shares, address receiver, address owner)
166: public
167: override
168: nonReentrant
169: requireAccountStatusCheck(owner)
170: returns (uint256)
171: ['185']
185: function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override ['190']
190: function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares)
191: internal
192: override
193: ['164']
164: function setUpgradeAdmin(address newUpgradeAdmin) external nonReentrant adminOnly ['174']
174: function getProxyConfig(address proxy) external view returns (ProxyConfig memory config) ['141']
141: function LTVBorrow(address collateral) public view virtual reentrantOK returns (uint16) ['146']
146: function LTVLiquidation(address collateral) public view virtual reentrantOK returns (uint16) ['151']
151: function LTVFull(address collateral)
152: public
153: view
154: virtual
155: reentrantOK
156: returns (uint16, uint16, uint16, uint48, uint32)
157: ['252']
252: function setGovernorAdmin(address newGovernorAdmin) public virtual nonReentrant governorOnly ['258']
258: function setFeeReceiver(address newFeeReceiver) public virtual nonReentrant governorOnly ['274']
274: function setLTV(address collateral, uint16 borrowLTV, uint16 liquidationLTV, uint32 rampDuration)
275: public
276: virtual
277: nonReentrant
278: governorOnly
279: ['315']
315: function clearLTV(address collateral) public virtual nonReentrant governorOnly ['335']
335: function setInterestRateModel(address newModel) public virtual nonReentrant governorOnly ['30']
30: function computeInterestRate(address vault, uint256 cash, uint256 borrows)
31: external
32: view
33: override
34: returns (uint256)
35: ['42']
42: function computeInterestRateView(address vault, uint256 cash, uint256 borrows)
43: external
44: view
45: override
46: returns (uint256)
47: ['51']
51: function computeInterestRateInternal(address, uint256 cash, uint256 borrows) internal view returns (uint256) ['59']
59: function computeInterestRate(address, uint256, uint256) external override returns (uint256) ['70']
70: function computeInterestRateView(address, uint256, uint256) external view override returns (uint256) ['29']
29: function initialize(address proxyCreator) public virtual reentrantOK ['76']
76: function getTokenSymbol(address asset) private view returns (string memory) ['13']
13: function getLTV(address collateral, bool liquidation) internal view virtual returns (ConfigAmount) ['17']
17: function isRecognizedCollateral(address collateral) internal view virtual returns (bool) ['31']
31: function checkLiquidation(address liquidator, address violator, address collateral)
32: public
33: view
34: virtual
35: nonReentrantView
36: returns (uint256 maxRepay, uint256 maxYield)
37: ['46']
46: function liquidate(address violator, address collateral, uint256 repayAssets, uint256 minYieldBalance)
47: public
48: virtual
49: nonReentrant
50: ['59']
59: function calculateLiquidation(
60: VaultCache memory vaultCache,
61: address liquidator,
62: address violator,
63: address collateral,
64: uint256 desiredRepay
65: ) private view returns (LiquidationCache memory liqCache) ['229']
229: function isInLiquidationCoolOff(address account) private view returns (bool) ['18']
18: function calculateLiquidity(
19: VaultCache memory vaultCache,
20: address account,
21: address[] memory collaterals,
22: bool liquidation
23: ) internal view virtual returns (uint256 collateralValue, uint256 liabilityValue) ['36']
36: function checkLiquidity(VaultCache memory vaultCache, address account, address[] memory collaterals)
37: internal
38: view
39: virtual
40: ['61']
61: function checkNoCollateral(address account, address[] memory collaterals) internal view virtual returns (bool) ['73']
73: function getLiabilityValue(VaultCache memory vaultCache, address account, Owed owed, bool liquidation)
74: internal
75: view
76: virtual
77: returns (uint256 value)
78: ['97']
97: function getCollateralValue(VaultCache memory vaultCache, address account, address collateral, bool liquidation)
98: internal
99: view
100: virtual
101: returns (uint256 value)
102: ['69']
69: function swapToUnderlyingGivenIn(uint256 amountIn, address receiver) external returns (uint256) ['85']
85: function swapToUnderlyingGivenOut(uint256 amountOut, address receiver) external returns (uint256) ['101']
101: function swapToSynthGivenIn(uint256 amountIn, address receiver) external returns (uint256) ['117']
117: function swapToSynthGivenOut(uint256 amountOut, address receiver) external returns (uint256) ['94']
94: function isValidInterestFee(address vault, uint16 interestFee) external view returns (bool) ['105']
105: function protocolFeeConfig(address vault) external view returns (address, uint16) ['116']
116: function interestFeeRange(address vault) external view returns (uint16, uint16) ['17']
17: function accountLiquidity(address account, bool liquidation)
18: public
19: view
20: virtual
21: nonReentrantView
22: returns (uint256 collateralValue, uint256 liabilityValue)
23: ['33']
33: function accountLiquidityFull(address account, bool liquidation)
34: public
35: view
36: virtual
37: nonReentrantView
38: returns (address[] memory collaterals, uint256[] memory collateralValues, uint256 liabilityValue)
39: ['69']
69: function checkAccountStatus(address account, address[] calldata collaterals)
70: public
71: virtual
72: reentrantOK
73: onlyEVCChecks
74: returns (bytes4 magicValue)
75: ['19']
19: function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value)
20: internal
21: returns (bool, bytes memory)
22: ['45']
45: function safeTransfer(IERC20 token, address to, uint256 value) internal ['42']
42: function balanceOf(address account) public view virtual nonReentrantView returns (uint256) ['47']
47: function allowance(address holder, address spender) public view virtual nonReentrantView returns (uint256) ['52']
52: function transfer(address to, uint256 amount) public virtual nonReentrant returns (bool) ['58']
58: function transferFromMax(address from, address to) public virtual nonReentrant returns (bool) ['67']
67: function transferFrom(address from, address to, uint256 amount) public virtual nonReentrant returns (bool) ['76']
76: function approve(address spender, uint256 amount) public virtual nonReentrant returns (bool) ['84']
84: function transferFromInternal(address account, address from, address to, Shares shares) private returns (bool) ['95']
95: function validateTransferFromAccount(address from) private pure ['47']
47: function maxDeposit(address account) public view virtual nonReentrantView returns (uint256) ['64']
64: function maxMint(address account) public view virtual nonReentrantView returns (uint256) ['77']
77: function maxWithdraw(address owner) public view virtual nonReentrantView returns (uint256) ['92']
92: function maxRedeem(address owner) public view virtual nonReentrantView returns (uint256) ['124']
124: function deposit(uint256 amount, address receiver) public virtual nonReentrant returns (uint256) ['139']
139: function mint(uint256 amount, address receiver) public virtual nonReentrant returns (uint256) ['153']
153: function withdraw(uint256 amount, address receiver, address owner) public virtual nonReentrant returns (uint256) ['167']
167: function redeem(uint256 amount, address receiver, address owner) public virtual nonReentrant returns (uint256) ['182']
182: function skim(uint256 amount, address receiver) public virtual nonReentrant returns (uint256) ['206']
206: function finalizeDeposit(
207: VaultCache memory vaultCache,
208: Assets assets,
209: Shares shares,
210: address sender,
211: address receiver
212: ) private ['218']
218: function finalizeWithdraw(
219: VaultCache memory vaultCache,
220: Assets assets,
221: Shares shares,
222: address sender,
223: address receiver,
224: address owner
225: ) private ['234']
234: function maxRedeemInternal(VaultCache memory vaultCache, address owner) private view returns (Shares) ['251']
251: function maxMintInternal(VaultCache memory vaultCache, address) private pure returns (Shares) Create a commented enum value to use in place of constant array indexes, this makes the code far easier to understand
Num of instances: 1
Click to show findings
['142']
142: if (controllers[0] != address(this)) revert E_NotController(); // <= FOUNDIn instances where a new variable is defined, there is no need to set it to it's default value.
Num of instances: 1
[NonCritical-17] Revert statements within external and public functions can be used to perform DOS attacks
In Solidity, 'revert' statements are used to undo changes and throw an exception when certain conditions are not met. However, in public and external functions, improper use of revert can be exploited for Denial of Service (DoS) attacks. An attacker can intentionally trigger these 'revert' conditions, causing legitimate transactions to consistently fail. For example, if a function relies on specific conditions from user input or contract state, an attacker could manipulate these to continually force reverts, blocking the function's execution. Therefore, it's crucial to design contract logic to handle exceptions properly and avoid scenarios where revert can be predictably triggered by malicious actors. This includes careful input validation and considering alternative design patterns that are less susceptible to such abuses.
Num of instances: 33
Click to show findings
['63']
63: function approve(address, uint256) external pure returns (bool) {
64: revert E_NotSupported(); // <= FOUND
65: }['69']
69: function transfer(address, uint256) external pure returns (bool) {
70: revert E_NotSupported(); // <= FOUND
71: }['75']
75: function transferFrom(address, address, uint256) external pure returns (bool) {
76: revert E_NotSupported(); // <= FOUND
77: }['83']
83: function emitTransfer(address from, address to, uint256 value) external {
84: if (msg.sender != eVault) revert E_Unauthorized(); // <= FOUND
85:
86: emit Transfer(from, to, value);
87: }['104']
104: function viewDelegate() external payable {
105: if (msg.sender != address(this)) revert E_Unauthorized(); // <= FOUND
106:
107: assembly {
108: let size := sub(calldatasize(), 36)
109: calldatacopy(0, 36, size)
110: let result := delegatecall(gas(), calldataload(4), 0, size, 0, 0)
111: returndatacopy(0, 0, returndatasize())
112: switch result
113: case 0 { revert(0, returndatasize()) } // <= FOUND
114: default { return(0, returndatasize()) }
115: }
116: }['50']
50: function mint(address account, uint256 amount) external nonReentrant {
51: address sender = _msgSender();
52: MinterData memory minterCache = minters[sender];
53:
54:
55: if (amount == 0) {
56: return;
57: }
58:
59: if (
60: amount > type(uint128).max - minterCache.minted
61: || minterCache.capacity < uint256(minterCache.minted) + amount
62: ) {
63: revert E_CapacityReached(); // <= FOUND
64: }
65:
66: minterCache.minted += uint128(amount);
67: minters[sender] = minterCache;
68:
69: _mint(account, amount);
70: }['104']
104: function allocate(address vault, uint256 amount) external onlyOwner {
105: if (IEVault(vault).EVC() != address(evc)) {
106: revert E_NotEVCCompatible(); // <= FOUND
107: }
108: ignoredForTotalSupply.add(vault);
109: _approve(address(this), vault, amount, true);
110: IEVault(vault).deposit(amount, address(this));
111: }['116']
116: function createProxy(address desiredImplementation, bool upgradeable, bytes memory trailingData)
117: external
118: nonReentrant
119: returns (address)
120: {
121: address _implementation = implementation;
122: if (desiredImplementation == address(0)) desiredImplementation = _implementation;
123:
124: if (desiredImplementation == address(0) || desiredImplementation != _implementation) revert E_Implementation(); // <= FOUND
125:
126: address proxy;
127:
128: if (upgradeable) {
129: proxy = address(new BeaconProxy(trailingData));
130: } else {
131: proxy = deployMetaProxy(desiredImplementation, trailingData);
132: }
133:
134: proxyLookup[proxy] =
135: ProxyConfig({upgradeable: upgradeable, implementation: desiredImplementation, trailingData: trailingData});
136:
137: proxyList.push(proxy);
138:
139: IComponent(proxy).initialize(msg.sender);
140:
141: emit ProxyCreated(proxy, upgradeable, desiredImplementation, trailingData);
142:
143: return proxy;
144: }['151']
151: function setImplementation(address newImplementation) external nonReentrant adminOnly {
152: if (newImplementation == address(0)) revert E_BadAddress(); // <= FOUND
153: implementation = newImplementation;
154: emit SetImplementation(newImplementation);
155: }['196']
196: function getProxyListSlice(uint256 start, uint256 end) external view returns (address[] memory list) {
197: if (end == type(uint256).max) end = proxyList.length;
198: if (end < start || end > proxyList.length) revert E_BadQuery(); // <= FOUND
199:
200: list = new address[](end - start);
201: for (uint256 i; i < end - start; ++i) {
202: list[i] = proxyList[start + i];
203: }
204: }['30']
30: function computeInterestRate(address vault, uint256 cash, uint256 borrows)
31: external
32: view
33: override
34: returns (uint256)
35: {
36: if (msg.sender != vault) revert E_IRMUpdateUnauthorized(); // <= FOUND
37:
38: return computeInterestRateInternal(vault, cash, borrows);
39: }['137']
137: function setAdmin(address newAdmin) external onlyAdmin {
138: if (newAdmin == address(0)) revert E_InvalidAdmin(); // <= FOUND
139:
140: admin = newAdmin;
141:
142: emit SetAdmin(newAdmin);
143: }['148']
148: function setFeeReceiver(address newReceiver) external onlyAdmin {
149: if (newReceiver == address(0)) revert E_InvalidReceiver(); // <= FOUND
150:
151: feeReceiver = newReceiver;
152:
153: emit SetFeeReceiver(newReceiver);
154: }['159']
159: function setProtocolFeeShare(uint16 newProtocolFeeShare) external onlyAdmin {
160: if (newProtocolFeeShare > CONFIG_SCALE) revert E_InvalidConfigValue(); // <= FOUND
161:
162: emit SetProtocolFeeShare(protocolFeeShare, newProtocolFeeShare);
163:
164: protocolFeeShare = newProtocolFeeShare;
165: }['171']
171: function setInterestFeeRange(uint16 minInterestFee_, uint16 maxInterestFee_) external onlyAdmin {
172: if (maxInterestFee_ > CONFIG_SCALE || minInterestFee_ > maxInterestFee_) revert E_InvalidConfigValue(); // <= FOUND
173:
174: minInterestFee = minInterestFee_;
175: maxInterestFee = maxInterestFee_;
176:
177: emit SetInterestFeeRange(minInterestFee_, maxInterestFee_);
178: }['186']
186: function setVaultInterestFeeRange(address vault, bool exists_, uint16 minInterestFee_, uint16 maxInterestFee_)
187: external
188: onlyAdmin
189: {
190: if (vault == address(0)) revert E_InvalidVault(); // <= FOUND
191: if (maxInterestFee_ > CONFIG_SCALE || minInterestFee_ > maxInterestFee_) revert E_InvalidConfigValue(); // <= FOUND
192:
193: _interestFeeRanges[vault] =
194: InterestFeeRange({exists: exists_, minInterestFee: minInterestFee_, maxInterestFee: maxInterestFee_});
195:
196: emit SetVaultInterestFeeRange(vault, exists_, minInterestFee_, maxInterestFee_);
197: }['205']
205: function setVaultFeeConfig(address vault, bool exists_, address feeReceiver_, uint16 protocolFeeShare_)
206: external
207: onlyAdmin
208: {
209: if (vault == address(0)) revert E_InvalidVault(); // <= FOUND
210: if (exists_ && feeReceiver_ == address(0)) revert E_InvalidReceiver(); // <= FOUND
211: if (protocolFeeShare_ > CONFIG_SCALE) revert E_InvalidConfigValue(); // <= FOUND
212:
213: _protocolFeeConfig[vault] =
214: ProtocolFeeConfig({exists: exists_, feeReceiver: feeReceiver_, protocolFeeShare: protocolFeeShare_});
215:
216: emit SetFeeConfigSetting(vault, exists_, feeReceiver_, protocolFeeShare_);
217: }['26']
26: function enableBalanceForwarder() public virtual nonReentrant {
27: if (address(balanceTracker) == address(0)) revert E_NotSupported(); // <= FOUND
28:
29: address account = EVCAuthenticate();
30: UserStorage storage user = vaultStorage.users[account];
31:
32: bool wasBalanceForwarderEnabled = user.isBalanceForwarderEnabled();
33:
34: user.setBalanceForwarder(true);
35: balanceTracker.balanceTrackerHook(account, user.getBalance().toUint(), false);
36:
37: if (!wasBalanceForwarderEnabled) emit BalanceForwarderStatus(account, true);
38: }['41']
41: function disableBalanceForwarder() public virtual nonReentrant {
42: if (address(balanceTracker) == address(0)) revert E_NotSupported(); // <= FOUND
43:
44: address account = EVCAuthenticate();
45: UserStorage storage user = vaultStorage.users[account];
46:
47: bool wasBalanceForwarderEnabled = user.isBalanceForwarderEnabled();
48:
49: user.setBalanceForwarder(false);
50: balanceTracker.balanceTrackerHook(account, 0, false);
51:
52: if (wasBalanceForwarderEnabled) emit BalanceForwarderStatus(account, false);
53: }['65']
65: function borrow(uint256 amount, address receiver) public virtual nonReentrant returns (uint256) {
66: (VaultCache memory vaultCache, address account) = initOperation(OP_BORROW, CHECKACCOUNT_CALLER);
67:
68: Assets assets = amount == type(uint256).max ? vaultCache.cash : amount.toAssets();
69: if (assets.isZero()) return 0;
70:
71: if (assets > vaultCache.cash) revert E_InsufficientCash(); // <= FOUND
72:
73: increaseBorrow(vaultCache, account, assets);
74:
75: pushAssets(vaultCache, receiver, assets);
76:
77: return assets.toUint();
78: }['131']
131: function pullDebt(uint256 amount, address from) public virtual nonReentrant returns (uint256) {
132: (VaultCache memory vaultCache, address account) = initOperation(OP_PULL_DEBT, CHECKACCOUNT_CALLER);
133:
134: if (from == account) revert E_SelfTransfer(); // <= FOUND
135:
136: Assets assets = amount == type(uint256).max ? getCurrentOwed(vaultCache, from).toAssetsUp() : amount.toAssets();
137:
138: if (assets.isZero()) return 0;
139: transferBorrow(vaultCache, from, account, assets);
140:
141: emit PullDebt(from, account, assets.toUint());
142:
143: return assets.toUint();
144: }['147']
147: function flashLoan(uint256 amount, bytes calldata data) public virtual nonReentrant {
148: address account = EVCAuthenticate();
149: callHook(vaultStorage.hookedOps, OP_FLASHLOAN, account);
150:
151: (IERC20 asset,,) = ProxyUtils.metadata();
152:
153: uint256 origBalance = asset.balanceOf(address(this));
154:
155: asset.safeTransfer(account, amount);
156:
157: IFlashLoan(account).onFlashLoan(data);
158:
159: if (asset.balanceOf(address(this)) < origBalance) revert E_FlashLoanNotRepaid(); // <= FOUND
160: }['274']
274: function setLTV(address collateral, uint16 borrowLTV, uint16 liquidationLTV, uint32 rampDuration)
275: public
276: virtual
277: nonReentrant
278: governorOnly
279: {
280:
281: if (collateral == address(this)) revert E_InvalidLTVAsset(); // <= FOUND
282:
283: ConfigAmount newBorrowLTV = borrowLTV.toConfigAmount();
284: ConfigAmount newLiquidationLTV = liquidationLTV.toConfigAmount();
285:
286:
287: if (newBorrowLTV > newLiquidationLTV) revert E_LTVBorrow(); // <= FOUND
288:
289: LTVConfig memory currentLTV = vaultStorage.ltvLookup[collateral];
290:
291:
292: if (newLiquidationLTV >= currentLTV.getLTV(true) && rampDuration > 0) revert E_LTVLiquidation(); // <= FOUND
293:
294: LTVConfig memory newLTV = currentLTV.setLTV(newBorrowLTV, newLiquidationLTV, rampDuration);
295:
296: vaultStorage.ltvLookup[collateral] = newLTV;
297:
298: if (!currentLTV.initialized) vaultStorage.ltvList.push(collateral);
299:
300: emit GovSetLTV(
301: collateral,
302: newLTV.borrowLTV.toUint16(),
303: newLTV.liquidationLTV.toUint16(),
304: newLTV.initialLiquidationLTV.toUint16(),
305: newLTV.targetTimestamp,
306: newLTV.rampDuration,
307: !currentLTV.initialized
308: );
309: }['349']
349: function setHookConfig(address newHookTarget, uint32 newHookedOps) public virtual nonReentrant governorOnly {
350: if (
351: newHookTarget != address(0)
352: && IHookTarget(newHookTarget).isHookTarget() != IHookTarget.isHookTarget.selector
353: ) revert E_NotHookTarget(); // <= FOUND
354:
355: if (newHookedOps >= OP_MAX_VALUE) revert E_NotSupported(); // <= FOUND
356:
357: vaultStorage.hookTarget = newHookTarget;
358: vaultStorage.hookedOps = Flags.wrap(newHookedOps);
359: emit GovSetHookConfig(newHookTarget, newHookedOps);
360: }['363']
363: function setConfigFlags(uint32 newConfigFlags) public virtual nonReentrant governorOnly {
364: if (newConfigFlags >= CFG_MAX_VALUE) revert E_NotSupported(); // <= FOUND
365:
366: vaultStorage.configFlags = Flags.wrap(newConfigFlags);
367: emit GovSetConfigFlags(newConfigFlags);
368: }['371']
371: function setCaps(uint16 supplyCap, uint16 borrowCap) public virtual nonReentrant governorOnly {
372: AmountCap _supplyCap = AmountCap.wrap(supplyCap);
373:
374:
375: if (supplyCap != 0 && _supplyCap.resolve() > 2 * MAX_SANE_AMOUNT) revert E_BadSupplyCap(); // <= FOUND
376:
377: AmountCap _borrowCap = AmountCap.wrap(borrowCap);
378: if (borrowCap != 0 && _borrowCap.resolve() > MAX_SANE_AMOUNT) revert E_BadBorrowCap(); // <= FOUND
379:
380: vaultStorage.supplyCap = _supplyCap;
381: vaultStorage.borrowCap = _borrowCap;
382:
383: emit GovSetCaps(supplyCap, borrowCap);
384: }['387']
387: function setInterestFee(uint16 newInterestFee) public virtual nonReentrant governorOnly {
388:
389: VaultCache memory vaultCache = updateVault();
390: logVaultStatus(vaultCache, vaultStorage.interestRate);
391:
392:
393: if (newInterestFee < GUARANTEED_INTEREST_FEE_MIN || newInterestFee > GUARANTEED_INTEREST_FEE_MAX) {
394: if (!protocolConfig.isValidInterestFee(address(this), newInterestFee)) revert E_BadFee(); // <= FOUND
395: }
396:
397: vaultStorage.interestFee = newInterestFee.toConfigAmount();
398:
399: emit GovSetInterestFee(newInterestFee);
400: }['29']
29: function initialize(address proxyCreator) public virtual reentrantOK {
30: if (initialized) revert E_Initialized(); // <= FOUND
31: initialized = true;
32:
33:
34:
35:
36: if (msg.data.length != 4 + 32 + PROXY_METADATA_LENGTH) revert E_ProxyMetadata(); // <= FOUND
37: (IERC20 asset,,) = ProxyUtils.metadata();
38:
39: AddressUtils.checkContract(address(asset));
40:
41:
42:
43:
44: address dToken = address(new DToken());
45:
46:
47:
48: vaultStorage.lastInterestAccumulatorUpdate = uint48(block.timestamp);
49: vaultStorage.interestAccumulator = INITIAL_INTEREST_ACCUMULATOR;
50: vaultStorage.interestFee = DEFAULT_INTEREST_FEE.toConfigAmount();
51: vaultStorage.creator = vaultStorage.governorAdmin = proxyCreator;
52:
53: {
54: string memory underlyingSymbol = getTokenSymbol(address(asset));
55: uint256 seqId = sequenceRegistry.reserveSeqId(underlyingSymbol);
56:
57: vaultStorage.symbol = string(abi.encodePacked("e", underlyingSymbol, "-", uintToString(seqId)));
58: vaultStorage.name = string(abi.encodePacked("EVK Vault ", vaultStorage.symbol));
59: }
60:
61: snapshot.reset();
62:
63:
64:
65: emit EVaultCreated(proxyCreator, address(asset), dToken);
66: logVaultStatus(loadVault(), 0);
67: }['55']
55: function disableController() public virtual nonReentrant {
56: address account = EVCAuthenticate();
57:
58: if (!vaultStorage.users[account].getOwed().isZero()) revert E_OutstandingDebt(); // <= FOUND
59:
60: disableControllerInternal(account);
61: }['83']
83: function checkVaultStatus() public virtual reentrantOK onlyEVCChecks returns (bytes4 magicValue) {
84:
85:
86:
87: VaultCache memory vaultCache = updateVault();
88: uint256 newInterestRate = computeInterestRate(vaultCache);
89:
90: logVaultStatus(vaultCache, newInterestRate);
91:
92:
93:
94:
95: if (vaultCache.snapshotInitialized) {
96: vaultStorage.snapshotInitialized = vaultCache.snapshotInitialized = false;
97:
98: Assets snapshotCash = snapshot.cash;
99: Assets snapshotBorrows = snapshot.borrows;
100:
101: uint256 prevBorrows = snapshotBorrows.toUint();
102: uint256 borrows = vaultCache.totalBorrows.toAssetsUp().toUint();
103:
104: if (borrows > vaultCache.borrowCap && borrows > prevBorrows) revert E_BorrowCapExceeded(); // <= FOUND
105:
106: uint256 prevSupply = snapshotCash.toUint() + prevBorrows;
107:
108:
109:
110:
111: uint256 supply = vaultCache.cash.toUint() + vaultCache.totalBorrows.toAssetsDown().toUint();
112:
113: if (supply > vaultCache.supplyCap && supply > prevSupply) revert E_SupplyCapExceeded(); // <= FOUND
114:
115: snapshot.reset();
116: }
117:
118: callHookWithLock(vaultCache.hookedOps, OP_VAULT_STATUS_CHECK, address(evc));
119:
120: magicValue = IEVCVault.checkVaultStatus.selector;
121: }['124']
124: function deposit(uint256 amount, address receiver) public virtual nonReentrant returns (uint256) {
125: (VaultCache memory vaultCache, address account) = initOperation(OP_DEPOSIT, CHECKACCOUNT_NONE);
126:
127: Assets assets = amount == type(uint256).max ? vaultCache.asset.balanceOf(account).toAssets() : amount.toAssets();
128: if (assets.isZero()) return 0;
129:
130: Shares shares = assets.toSharesDown(vaultCache);
131: if (shares.isZero()) revert E_ZeroShares(); // <= FOUND
132:
133: finalizeDeposit(vaultCache, assets, shares, account, receiver);
134:
135: return shares.toUint();
136: }['167']
167: function redeem(uint256 amount, address receiver, address owner) public virtual nonReentrant returns (uint256) {
168: (VaultCache memory vaultCache, address account) = initOperation(OP_REDEEM, owner);
169:
170: Shares shares = amount == type(uint256).max ? vaultStorage.users[owner].getBalance() : amount.toShares();
171: if (shares.isZero()) return 0;
172:
173: Assets assets = shares.toAssetsDown(vaultCache);
174: if (assets.isZero()) revert E_ZeroAssets(); // <= FOUND
175:
176: finalizeWithdraw(vaultCache, assets, shares, account, receiver, owner);
177:
178: return assets.toUint();
179: }['182']
182: function skim(uint256 amount, address receiver) public virtual nonReentrant returns (uint256) {
183: (VaultCache memory vaultCache, address account) = initOperation(OP_SKIM, CHECKACCOUNT_NONE);
184:
185: Assets balance = vaultCache.asset.balanceOf(address(this)).toAssets();
186: Assets available = balance <= vaultCache.cash ? Assets.wrap(0) : balance.subUnchecked(vaultCache.cash);
187:
188: Assets assets;
189: if (amount == type(uint256).max) {
190: assets = available;
191: } else {
192: assets = amount.toAssets();
193: if (assets > available) revert E_InsufficientAssets(); // <= FOUND
194: }
195: if (assets.isZero()) return 0;
196:
197: Shares shares = assets.toSharesDown(vaultCache);
198: if (shares.isZero()) revert E_ZeroShares(); // <= FOUND
199:
200: vaultStorage.cash = vaultCache.cash = vaultCache.cash + assets;
201: increaseBalance(vaultCache, receiver, account, shares, assets);
202:
203: return shares.toUint();
204: }[NonCritical-18] 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
Num of instances: 21
Click to show findings
['41']
41:
45: uint256 private reentrancyLock; // <= FOUND['19']
19: address internal immutable beacon; // <= FOUND['20']
20: uint256 internal immutable metadataLength; // <= FOUND['21']
21: bytes32 internal immutable metadata0; // <= FOUND['22']
22: bytes32 internal immutable metadata1; // <= FOUND['23']
23: bytes32 internal immutable metadata2; // <= FOUND['24']
24: bytes32 internal immutable metadata3; // <= FOUND['26']
26: EnumerableSet.AddressSet internal ignoredForTotalSupply; // <= FOUND['37']
37: ESRSlot internal esrSlot; // <= FOUND['34']
34: IRMData internal irmStorage; // <= FOUND['34']
34: uint16 internal protocolFeeShare; // <= FOUND['37']
37: uint16 internal minInterestFee; // <= FOUND['39']
39: uint16 internal maxInterestFee; // <= FOUND['22']
22: IProtocolConfig internal immutable protocolConfig; // <= FOUND['24']
24: IBalanceTracker internal immutable balanceTracker; // <= FOUND['25']
25: address internal immutable permit2; // <= FOUND['21']
21: IEVC internal immutable evc; // <= FOUND['13']
13: bool internal initialized; // <= FOUND['17']
17:
18: Snapshot internal snapshot; // <= FOUND['20']
20: VaultStorage internal vaultStorage; // <= FOUND['23']
23: ISequenceRegistry immutable sequenceRegistry;Consider spreading these lines over multiple lines to aid in readability and the support of VIM users everywhere.
Num of instances: 34
Click to show findings
['135']
135: minDiscountFactor = 1e18 - uint256(1e18) * vaultStorage.maxLiquidationDiscount.toUint16() / CONFIG_SCALE; // <= FOUND['127']
127: Assets assets = amount == type(uint256).max ? vaultCache.asset.balanceOf(account).toAssets() : amount.toAssets(); // <= FOUND['154']
154: // https://github.com/Vectorized/solady/blob/229c18cfcdcd474f95c30ad31b0f7d428ee8a31a/src/utils/CREATE3.sol#L82-L90 // <= FOUND['11']
11: /// @dev The responsibility of this contract is call routing. Select functions are embedded, while most are delegated to the modules // <= FOUND['36']
36: function balanceOf(address account) public view virtual override returns (uint256) { return super.balanceOf(account); } // <= FOUND['38']
38: function allowance(address holder, address spender) public view virtual override returns (uint256) { return super.allowance(holder, spender); } // <= FOUND['41']
41: function transfer(address to, uint256 amount) public virtual override callThroughEVC returns (bool) { return super.transfer(to, amount); } // <= FOUND['43']
43: function transferFrom(address from, address to, uint256 amount) public virtual override callThroughEVC returns (bool) { return super.transferFrom(from, to, amount); } // <= FOUND['45']
45: function approve(address spender, uint256 amount) public virtual override returns (bool) { return super.approve(spender, amount); } // <= FOUND['47']
47: function transferFromMax(address from, address to) public virtual override callThroughEVC returns (bool) { return super.transferFromMax(from, to); } // <= FOUND['59']
59: function convertToAssets(uint256 shares) public view virtual override returns (uint256) { return super.convertToAssets(shares); } // <= FOUND['61']
61: function convertToShares(uint256 assets) public view virtual override returns (uint256) { return super.convertToShares(assets); } // <= FOUND['81']
81: function accumulatedFeesAssets() public view virtual override returns (uint256) { return super.accumulatedFeesAssets(); } // <= FOUND['86']
86: function deposit(uint256 amount, address receiver) public virtual override callThroughEVC returns (uint256) { return super.deposit(amount, receiver); } // <= FOUND['88']
88: function mint(uint256 amount, address receiver) public virtual override callThroughEVC use(MODULE_VAULT) returns (uint256) {} // <= FOUND['90']
90: function withdraw(uint256 amount, address receiver, address owner) public virtual override callThroughEVC use(MODULE_VAULT) returns (uint256) {} // <= FOUND['92']
92: function redeem(uint256 amount, address receiver, address owner) public virtual override callThroughEVC use(MODULE_VAULT) returns (uint256) {} // <= FOUND['94']
94: function skim(uint256 amount, address receiver) public virtual override callThroughEVC use(MODULE_VAULT) returns (uint256) {} // <= FOUND['119']
119: function borrow(uint256 amount, address receiver) public virtual override callThroughEVC use(MODULE_BORROWING) returns (uint256) {} // <= FOUND['121']
121: function repay(uint256 amount, address receiver) public virtual override callThroughEVC use(MODULE_BORROWING) returns (uint256) {} // <= FOUND['123']
123: function repayWithShares(uint256 amount, address receiver) public virtual override callThroughEVC use(MODULE_BORROWING) returns (uint256 shares, uint256 debt) {} // <= FOUND['125']
125: function pullDebt(uint256 amount, address from) public virtual override callThroughEVC use(MODULE_BORROWING) returns (uint256) {} // <= FOUND['137']
137: function checkLiquidation(address liquidator, address violator, address collateral) public view virtual override useView(MODULE_LIQUIDATION) returns (uint256 maxRepay, uint256 maxYield) {} // <= FOUND['139']
139: function liquidate(address violator, address collateral, uint256 repayAssets, uint256 minYieldBalance) public virtual override callThroughEVC use(MODULE_LIQUIDATION) {} // <= FOUND['147']
147: function accountLiquidity(address account, bool liquidation) public view virtual override useView(MODULE_RISKMANAGER) returns (uint256 collateralValue, uint256 liabilityValue) {} // <= FOUND['149']
149: function accountLiquidityFull(address account, bool liquidation) public view virtual override useView(MODULE_RISKMANAGER) returns (address[] memory collaterals, uint256[] memory collateralValues, uint256 liabilityValue) {} // <= FOUND['154']
154: function checkAccountStatus(address account, address[] calldata collaterals) public virtual override returns (bytes4) { return super.checkAccountStatus(account, collaterals); } // <= FOUND['166']
166: function balanceForwarderEnabled(address account) public view virtual override useView(MODULE_BALANCE_FORWARDER) returns (bool) {} // <= FOUND['193']
193: function caps() public view virtual override useView(MODULE_GOVERNANCE) returns (uint16 supplyCap, uint16 borrowCap) {} // <= FOUND['197']
197: function LTVLiquidation(address collateral) public view virtual override useView(MODULE_GOVERNANCE) returns (uint16) {} // <= FOUND['199']
199: function LTVFull(address collateral) public view virtual override useView(MODULE_GOVERNANCE) returns (uint16 borrowLTV, uint16 liquidationLTV, uint16 initialLiquidationLTV, uint48 targetTimestamp, uint32 rampDuration) {} // <= FOUND['228']
228: function setLTV(address collateral, uint16 borrowLTV, uint16 liquidationLTV, uint32 rampDuration) public virtual override use(MODULE_GOVERNANCE) {} // <= FOUND['92']
92: (, value) = vaultCache.oracle.getQuotes(owedAssets, address(vaultCache.asset), vaultCache.unitOfAccount); // <= FOUND['26']
26: uint256 private constant BALANCE_FORWARDER_MASK = 0x8000000000000000000000000000000000000000000000000000000000000000; // <= FOUNDIn Solidity, manipulating contract storage comes with significant gas costs. One can optimize gas usage by preventing unnecessary storage updates when the new value is the same as the existing one. If an existing value is the same as the new one, not reassigning it to the storage could potentially save substantial amounts of gas, notably 2900 gas for a 'Gsreset'. This saving may come at the expense of a cold storage load operation ('Gcoldsload'), which costs 2100 gas, or a warm storage access operation ('Gwarmaccess'), which costs 100 gas. Therefore, the gas efficiency of your contract can be significantly improved by adding a check that compares the new value with the current one before any storage update operation. If the values are the same, you can bypass the storage operation, thereby saving gas.
Num of instances: 6
Click to show findings
['42']
42: function setCapacity(address minter, uint128 capacity) external onlyOwner {
43: minters[minter].capacity = capacity;
44: emit MinterCapacitySet(minter, capacity);
45: }['222']
222: function setGovernorAdmin(address newGovernorAdmin) public virtual override use(MODULE_GOVERNANCE) {}['164']
164: function setUpgradeAdmin(address newUpgradeAdmin) external nonReentrant adminOnly {
165: upgradeAdmin = newUpgradeAdmin;
166: emit SetUpgradeAdmin(newUpgradeAdmin);
167: }['252']
252: function setGovernorAdmin(address newGovernorAdmin) public virtual nonReentrant governorOnly {
253: vaultStorage.governorAdmin = newGovernorAdmin;
254: emit GovSetGovernorAdmin(newGovernorAdmin);
255: }['159']
159: function setProtocolFeeShare(uint16 newProtocolFeeShare) external onlyAdmin {
160: if (newProtocolFeeShare > CONFIG_SCALE) revert E_InvalidConfigValue();
161:
162: emit SetProtocolFeeShare(protocolFeeShare, newProtocolFeeShare);
163:
164: protocolFeeShare = newProtocolFeeShare;
165: }['171']
171: function setInterestFeeRange(uint16 minInterestFee_, uint16 maxInterestFee_) external onlyAdmin {
172: if (maxInterestFee_ > CONFIG_SCALE || minInterestFee_ > maxInterestFee_) revert E_InvalidConfigValue();
173:
174: minInterestFee = minInterestFee_;
175: maxInterestFee = maxInterestFee_;
176:
177: emit SetInterestFeeRange(minInterestFee_, maxInterestFee_);
178: }In many cases only some functionality is used from an import. In such cases it makes more sense to use {} to specify what to import and thus save gas whilst improving readability
Num of instances: 21
Click to show findings
['5']
5: import "../Errors.sol";['8']
8: import "./types/Types.sol";['7']
7: import "../Constants.sol";['9']
9: import "../shared/types/Types.sol";['17']
17: import "./shared/Constants.sol";['11']
11: import "./Constants.sol";['5']
5: import "./IIRM.sol";['5']
5: import "../InterestRateModels/IIRM.sol";['6']
6: import "../interfaces/IPriceOracle.sol";['13']
13: import "../shared/Constants.sol";['5']
5: import "./IProtocolConfig.sol";['5']
5: import "../../IEVault.sol";['7']
7: import "./VaultStorage.sol";['8']
8: import "./Snapshot.sol";['9']
9: import "./UserStorage.sol";['11']
11: import "./Shares.sol";['12']
12: import "./Assets.sol";['13']
13: import "./Owed.sol";['14']
14: import "./ConfigAmount.sol";['15']
15: import "./Flags.sol";['16']
16: import "./AmountCap.sol";Using outdated Solidity versions can lead to security risks and inefficiencies. It's recommended to adopt newer versions, ideally the latest, which as of now is 0.8.24. This ensures access to the latest bug fixes, features, and optimizations, particularly crucial for Layer 2 deployments. Regular updates to versions like 0.8.19 or later, up to 0.8.24, enhance contract security and performance.
Num of instances: 2
Click to show findings
['3']
3: pragma solidity ^0.8.0; // <= FOUND['3']
3: pragma solidity >=0.8.0; // <= FOUNDTry to index as much as three variables in event declarations as this is more gas efficient when done on value type variables (uint, address etc) however not for bytes and string variables
Num of instances: 26
Click to show findings
['28']
28: event MinterCapacitySet(address indexed minter, uint256 capacity); // <= FOUND['63']
63: event ProxyCreated(address indexed proxy, bool upgradeable, address implementation, bytes trailingData); // <= FOUND['49']
49: event SetInterestFeeRange(uint16 newMinInterestFee, uint16 newMaxInterestFee); // <= FOUND['60']
60: event SetVaultInterestFeeRange(address indexed vault, bool exists, uint16 minInterestFee, uint16 maxInterestFee); // <= FOUND['67']
67: event SetFeeConfigSetting(address indexed vault, bool exists, address indexed feeReceiver, uint16 protocolFeeShare); // <= FOUND['72']
72: event SetProtocolFeeShare(uint16 protocolFeeShare, uint16 newProtocolFeeShare); // <= FOUND['16']
16: event Transfer(address indexed from, address indexed to, uint256 value); // <= FOUND['22']
22: event Approval(address indexed owner, address indexed spender, uint256 value); // <= FOUND['31']
31: event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); // <= FOUND['49']
49: event EVaultCreated(address indexed creator, address indexed asset, address dToken); // <= FOUND['59']
59: event VaultStatus( // <= FOUND
60: uint256 totalShares,
61: uint256 totalBorrows,
62: uint256 accumulatedFees,
63: uint256 cash,
64: uint256 interestAccumulator,
65: uint256 interestRate,
66: uint256 timestamp
67: );['72']
72: event Borrow(address indexed account, uint256 assets); // <= FOUND['77']
77: event Repay(address indexed account, uint256 assets); // <= FOUND['82']
82: event InterestAccrued(address indexed account, uint256 assets); // <= FOUND['90']
90: event Liquidate( // <= FOUND
91: address indexed liquidator,
92: address indexed violator,
93: address collateral,
94: uint256 repayAssets,
95: uint256 yieldBalance
96: );['102']
102: event PullDebt(address from, address to, uint256 assets); // <= FOUND['107']
107: event DebtSocialized(address indexed account, uint256 assets); // <= FOUND['126']
126: event BalanceForwarderStatus(address indexed account, bool status); // <= FOUND['53']
53: event GovSetLTV( // <= FOUND
54: address indexed collateral,
55: uint16 borrowLTV,
56: uint16 liquidationLTV,
57: uint16 initialLiquidationLTV,
58: uint48 targetTimestamp,
59: uint32 rampDuration,
60: bool initialized
61: );['64']
64: event GovSetInterestRateModel(address newInterestRateModel); // <= FOUND['68']
68: event GovSetMaxLiquidationDiscount(uint16 newDiscount); // <= FOUND['73']
73: event GovSetLiquidationCoolOffTime(uint16 newCoolOffTime); // <= FOUND['78']
78: event GovSetHookConfig(address indexed newHookTarget, uint32 newHookedOps); // <= FOUND['82']
82: event GovSetConfigFlags(uint32 newConfigFlags); // <= FOUND['87']
87: event GovSetCaps(uint16 newSupplyCap, uint16 newBorrowCap); // <= FOUND['91']
91: event GovSetInterestFee(uint16 newFee); // <= FOUND[NonCritical-24] Explicitly define visibility of state variables to prevent misconceptions on what can access the variable
Such state variables should be marked as private as this is the default visibility
Num of instances: 2
Click to show findings
['15']
15:
16: bytes constant BYTECODE_HEAD = hex"600b380380600b3d393df3363d3d373d3d3d3d60368038038091363936013d73"; // <= FOUND['16']
16: bytes constant BYTECODE_TAIL = hex"5af43d3d93803e603457fd5bf3"; // <= FOUND[NonCritical-25] Explicitly define visibility of functions to prevent misconceptions on what can access the function
Such functions should be marked as public as this is the default visibility
Num of instances: 24
Click to show findings
['61']
61: function addAssets(Assets a, Assets b) pure returns (Assets) ['65']
65: function subAssets(Assets a, Assets b) pure returns (Assets) ['69']
69: function eqAssets(Assets a, Assets b) pure returns (bool) ['73']
73: function neqAssets(Assets a, Assets b) pure returns (bool) ['77']
77: function gtAssets(Assets a, Assets b) pure returns (bool) ['81']
81: function gteAssets(Assets a, Assets b) pure returns (bool) ['85']
85: function ltAssets(Assets a, Assets b) pure returns (bool) ['89']
89: function lteAssets(Assets a, Assets b) pure returns (bool) ['25']
25: function gtConfigAmount(ConfigAmount a, ConfigAmount b) pure returns (bool) ['29']
29: function gteConfigAmount(ConfigAmount a, ConfigAmount b) pure returns (bool) ['33']
33: function ltConfigAmount(ConfigAmount a, ConfigAmount b) pure returns (bool) ['37']
37: function lteConfigAmount(ConfigAmount a, ConfigAmount b) pure returns (bool) ['61']
61: function addOwed(Owed a, Owed b) pure returns (Owed) ['65']
65: function subOwed(Owed a, Owed b) pure returns (Owed) ['69']
69: function eqOwed(Owed a, Owed b) pure returns (bool) ['73']
73: function neqOwed(Owed a, Owed b) pure returns (bool) ['77']
77: function gtOwed(Owed a, Owed b) pure returns (bool) ['81']
81: function ltOwed(Owed a, Owed b) pure returns (bool) ['48']
48: function addShares(Shares a, Shares b) pure returns (Shares) ['52']
52: function subShares(Shares a, Shares b) pure returns (Shares) ['56']
56: function eqShares(Shares a, Shares b) pure returns (bool) ['60']
60: function neqShares(Shares a, Shares b) pure returns (bool) ['64']
64: function gtShares(Shares a, Shares b) pure returns (bool) ['68']
68: function ltShares(Shares a, Shares b) pure returns (bool) Contracts should expose all public and external functions through interfaces. This practice ensures a clear and consistent definition of how the contract can be interacted with, promoting better transparency and integration.
Num of instances: 51
Click to show findings
['25']
25: function name() external view returns (string memory) ['31']
31: function symbol() external view returns (string memory) ['37']
37: function decimals() external view returns (uint8) ['43']
43: function totalSupply() external view returns (uint256) ['50']
50: function balanceOf(address owner) external view returns (uint256) ['57']
57: function allowance(address, address) external pure returns (uint256) ['63']
63: function approve(address, uint256) external pure returns (bool) ['69']
69: function transfer(address, uint256) external pure returns (bool) ['75']
75: function transferFrom(address, address, uint256) external pure returns (bool) ['83']
83: function emitTransfer(address from, address to, uint256 value) external ['92']
92: function asset() external view returns (address) ['104']
104: function viewDelegate() external payable ['42']
42: function setCapacity(address minter, uint128 capacity) external onlyOwner ['50']
50: function mint(address account, uint256 amount) external nonReentrant ['76']
76: function burn(address burnFrom, uint256 amount) external nonReentrant ['104']
104: function allocate(address vault, uint256 amount) external onlyOwner ['116']
116: function deallocate(address vault, uint256 amount) external onlyOwner ['134']
134: function addIgnoredForTotalSupply(address account) external onlyOwner returns (bool success) ['141']
141: function removeIgnoredForTotalSupply(address account) external onlyOwner returns (bool success) ['116']
116: function createProxy(address desiredImplementation, bool upgradeable, bytes memory trailingData)
117: external
118: nonReentrant
119: returns (address)
120: ['151']
151: function setImplementation(address newImplementation) external nonReentrant adminOnly ['164']
164: function setUpgradeAdmin(address newUpgradeAdmin) external nonReentrant adminOnly ['174']
174: function getProxyConfig(address proxy) external view returns (ProxyConfig memory config) ['182']
182: function isProxy(address proxy) external view returns (bool) ['188']
188: function getProxyListLength() external view returns (uint256) ['196']
196: function getProxyListSlice(uint256 start, uint256 end) external view returns (address[] memory list) ['106']
106: function getIRMData() external view returns (IRMData memory) ['69']
69: function swapToUnderlyingGivenIn(uint256 amountIn, address receiver) external returns (uint256) ['85']
85: function swapToUnderlyingGivenOut(uint256 amountOut, address receiver) external returns (uint256) ['101']
101: function swapToSynthGivenIn(uint256 amountIn, address receiver) external returns (uint256) ['117']
117: function swapToSynthGivenOut(uint256 amountOut, address receiver) external returns (uint256) ['94']
94: function isValidInterestFee(address vault, uint16 interestFee) external view returns (bool) ['105']
105: function protocolFeeConfig(address vault) external view returns (address, uint16) ['116']
116: function interestFeeRange(address vault) external view returns (uint16, uint16) ['137']
137: function setAdmin(address newAdmin) external onlyAdmin ['148']
148: function setFeeReceiver(address newReceiver) external onlyAdmin ['159']
159: function setProtocolFeeShare(uint16 newProtocolFeeShare) external onlyAdmin ['171']
171: function setInterestFeeRange(uint16 minInterestFee_, uint16 maxInterestFee_) external onlyAdmin ['186']
186: function setVaultInterestFeeRange(address vault, bool exists_, uint16 minInterestFee_, uint16 maxInterestFee_)
187: external
188: onlyAdmin
189: ['205']
205: function setVaultFeeConfig(address vault, bool exists_, address feeReceiver_, uint16 protocolFeeShare_)
206: external
207: onlyAdmin
208: ['25']
25: function reserveSeqId(string calldata designator) external returns (uint256) ['147']
147: function isIgnoredForTotalSupply(address account) public view returns (bool) ['153']
153: function getAllIgnoredForTotalSupply() public view returns (address[] memory) ['199']
199: function gulp() public nonReentrant ['217']
217: function updateInterestAndReturnESRSlotCache() public returns (ESRSlot memory) ['233']
233: function interestAccrued() public view returns (uint256) ['256']
256: function getESRSlot() public view returns (ESRSlot memory) ['132']
132: function quoteToUnderlyingGivenIn(uint256 amountIn) public view returns (uint256) ['139']
139: function quoteToUnderlyingGivenOut(uint256 amountOut) public view returns (uint256) ['146']
146: function quoteToSynthGivenIn(uint256 amountIn) public view returns (uint256) ['153']
153: function quoteToSynthGivenOut(uint256 amountOut) public view returns (uint256) The following order should be used within contracts
constructor
receive function (if exists)
fallback function (if exists)
external
public
internal
private
Rearrange the contract functions and contructors to fit this ordering
Num of instances: 7
Click to show findings
['17']
17: contract Cache is Storage, Errors // <= FOUND['17']
17: contract ESynth is ERC20Collateral, Ownable // <= FOUND['21']
21: contract EulerSavingsRate is EVCUtil, ERC4626 // <= FOUND['15']
15: contract IRMSynth is IIRM // <= FOUND['21']
21: abstract contract Base is EVCClient, Cache // <= FOUND['15']
15: abstract contract BorrowUtils is Base // <= FOUND['20']
20: abstract contract InitializeModule is IInitialize, BorrowUtils // <= FOUNDDouble type casting should be avoided in Solidity contracts to prevent unintended consequences and ensure accurate data representation. Performing multiple type casts in succession can lead to unexpected truncation, rounding errors, or loss of precision, potentially compromising the contract's functionality and reliability. Furthermore, double type casting can make the code less readable and harder to maintain, increasing the likelihood of errors and misunderstandings during development and debugging. To ensure precise and consistent data handling, developers should use appropriate data types and avoid unnecessary or excessive type casting, promoting a more robust and dependable contract execution.
Num of instances: 1
In Solidity, when msg.sender plays a crucial role in a function's logic, it's important for transparency and auditability that any events emitted by this function include msg.sender as a parameter. This practice enhances the traceability and accountability of transactions, allowing users and external observers to easily track who initiated a particular action. Including msg.sender in event logs helps in creating a clear and verifiable record of interactions with the contract, thereby increasing user trust and facilitating easier debugging and analysis of contract behavior. It's a key aspect of writing clear, transparent, and user-friendly smart contracts.
Num of instances: 2
Click to show findings
['83']
83: function emitTransfer(address from, address to, uint256 value) external { // <= FOUND
84: if (msg.sender != eVault) revert E_Unauthorized(); // <= FOUND
85:
86: emit Transfer(from, to, value); // <= FOUND
87: }['116']
116: function createProxy(address desiredImplementation, bool upgradeable, bytes memory trailingData)
117: external
118: nonReentrant
119: returns (address)
120: {
121: address _implementation = implementation;
122: if (desiredImplementation == address(0)) desiredImplementation = _implementation;
123:
124: if (desiredImplementation == address(0) || desiredImplementation != _implementation) revert E_Implementation();
125:
126: address proxy;
127:
128: if (upgradeable) {
129: proxy = address(new BeaconProxy(trailingData));
130: } else {
131: proxy = deployMetaProxy(desiredImplementation, trailingData);
132: }
133:
134: proxyLookup[proxy] =
135: ProxyConfig({upgradeable: upgradeable, implementation: desiredImplementation, trailingData: trailingData});
136:
137: proxyList.push(proxy);
138:
139: IComponent(proxy).initialize(msg.sender); // <= FOUND
140:
141: emit ProxyCreated(proxy, upgradeable, desiredImplementation, trailingData); // <= FOUND
142:
143: return proxy;
144: }Functions in Solidity that accept array parameters should incorporate length checks as a security measure. This is to prevent potential overflow errors, unwanted gas consumption, and manipulation attempts. Without length checks, an attacker could pass excessively large arrays as input, causing excessive computation and potentially causing the function to exceed the block gas limit, leading to a denial-of-service. Additionally, unexpected array sizes could lead to logic errors within the function. As a resolution, always validate array length at the start of functions handling array inputs, ensuring it aligns with the expectations of the function logic. This makes the code more robust and predictable.
Num of instances: 1
Click to show findings
['69']
69: function checkAccountStatus(address account, address[] calldata collaterals) // <= FOUND
70: public
71: virtual
72: reentrantOK
73: onlyEVCChecks
74: returns (bytes4 magicValue)
75: {
76: checkLiquidity(loadVault(), account, collaterals);
77:
78: magicValue = IEVCVault.checkAccountStatus.selector;
79: }[NonCritical-31] A function which defines named returns in it's declaration doesn't need to use return
Refacter the code to assign to the named return variables rather than using a return statement
Num of instances: 6
Click to show findings
['134']
134: function addIgnoredForTotalSupply(address account) external onlyOwner returns (bool success) {
135: return ignoredForTotalSupply.add(account); // <= FOUND
136: }['141']
141: function removeIgnoredForTotalSupply(address account) external onlyOwner returns (bool success) {
142: return ignoredForTotalSupply.remove(account); // <= FOUND
143: }['75']
75: function _computeRate(IRMData memory irmCache) internal view returns (uint216 rate, bool updated) {
76: updated = false;
77: rate = irmCache.lastRate;
78:
79:
80: if (block.timestamp < irmCache.lastUpdated + ADJUST_INTERVAL) {
81: return (rate, updated); // <= FOUND
82: }
83:
84: uint256 quote = oracle.getQuote(quoteAmount, synth, referenceAsset);
85:
86: updated = true;
87:
88: if (quote < targetQuote) {
89:
90: rate = rate * ADJUST_FACTOR / ADJUST_ONE;
91: } else {
92:
93: rate = rate * ADJUST_ONE / ADJUST_FACTOR;
94: }
95:
96:
97: if (rate < BASE_RATE) {
98: rate = BASE_RATE;
99: } else if (rate > MAX_RATE) {
100: rate = MAX_RATE;
101: }
102:
103: return (rate, updated); // <= FOUND
104: }['73']
73: function getLiabilityValue(VaultCache memory vaultCache, address account, Owed owed, bool liquidation)
74: internal
75: view
76: virtual
77: returns (uint256 value)
78: {
79:
80: uint256 owedAssets = getCurrentOwed(vaultCache, account, owed).toAssetsUp().toUint();
81:
82: if (owedAssets == 0) return 0; // <= FOUND
83:
84: if (address(vaultCache.asset) == vaultCache.unitOfAccount) {
85: value = owedAssets;
86: } else {
87: if (liquidation) {
88:
89: value = vaultCache.oracle.getQuote(owedAssets, address(vaultCache.asset), vaultCache.unitOfAccount);
90: } else {
91:
92: (, value) = vaultCache.oracle.getQuotes(owedAssets, address(vaultCache.asset), vaultCache.unitOfAccount);
93: }
94: }
95: }['97']
97: function getCollateralValue(VaultCache memory vaultCache, address account, address collateral, bool liquidation)
98: internal
99: view
100: virtual
101: returns (uint256 value)
102: {
103: ConfigAmount ltv = getLTV(collateral, liquidation);
104: if (ltv.isZero()) return 0; // <= FOUND
105:
106: uint256 balance = IERC20(collateral).balanceOf(account);
107: if (balance == 0) return 0; // <= FOUND
108:
109: uint256 currentCollateralValue;
110:
111: if (liquidation) {
112:
113: currentCollateralValue = vaultCache.oracle.getQuote(balance, collateral, vaultCache.unitOfAccount);
114: } else {
115:
116: (currentCollateralValue,) = vaultCache.oracle.getQuotes(balance, collateral, vaultCache.unitOfAccount);
117: }
118:
119: return currentCollateralValue * ltv.toUint16() / CONFIG_SCALE; // <= FOUND
120: }['17']
17: function accountLiquidity(address account, bool liquidation)
18: public
19: view
20: virtual
21: nonReentrantView
22: returns (uint256 collateralValue, uint256 liabilityValue)
23: {
24: VaultCache memory vaultCache = loadVault();
25:
26: validateController(account);
27: address[] memory collaterals = getCollaterals(account);
28:
29: return calculateLiquidity(vaultCache, account, collaterals, liquidation); // <= FOUND
30: }If these serve no purpose, they should be safely removed
Num of instances: 1
Click to show findings
['14']
14: bytes32 internal constant IMPLEMENTATION_SELECTOR =
15: 0x5c60da1b00000000000000000000000000000000000000000000000000000000; // <= FOUNDPutting constants on the left side of a comparison operator like == or < is a best practice known as "Yoda conditions", which can help prevent accidental assignment instead of comparison. In some programming languages, if a variable is mistakenly put on the left with a single = instead of ==, it assigns the constant's value to the variable without any compiler error. However, doing this with the constant on the left would generate an error, as constants cannot be assigned values. Although Solidity's static typing system prevents accidental assignments within conditionals, adopting this practice enhances code readability and consistency, especially when developers are working across multiple languages that support this convention.
Num of instances: 14
Click to show findings
['55']
55: if (amount == 0) // <= FOUND['55']
55: if (amount == 0) // <= FOUND['52']
52: if (testQuote == 0) // <= FOUND['146']
146: if (collateralValue == 0) // <= FOUND['71']
71: if (amountIn == 0 || amountOut == 0) // <= FOUND['103']
103: if (feeAssets != 0) // <= FOUND['120']
120: if (success && data.length >= 32) // <= FOUND['120']
120: if (success && data.length >= 32) // <= FOUND['69']
69: if (deltaT > 0) // <= FOUND['79']
79: if (evc.getControllers(owner).length > 0) // <= FOUND['183']
183: if (liqCache.repay.toUint() > 0) // <= FOUND['201']
201: if (liqCache.yieldBalance > 0) // <= FOUND['13']
13: if (errMsg.length > 0) // <= FOUND['107']
107: if (maxRepay > 0) // <= FOUNDSuch instances can be replaced with unnamed returns
Num of instances: 10
Click to show findings
['134']
134: function addIgnoredForTotalSupply(address account) external onlyOwner returns (bool success) { // <= FOUND
135: return ignoredForTotalSupply.add(account);
136: }['141']
141: function removeIgnoredForTotalSupply(address account) external onlyOwner returns (bool success) { // <= FOUND
142: return ignoredForTotalSupply.remove(account);
143: }['123']
123: function repayWithShares(uint256 amount, address receiver) public virtual override callThroughEVC use(MODULE_BORROWING) returns (uint256 shares, uint256 debt) {} // <= FOUND['137']
137: function checkLiquidation(address liquidator, address violator, address collateral) public view virtual override useView(MODULE_LIQUIDATION) returns (uint256 maxRepay, uint256 maxYield) {} // <= FOUND['31']
31: function checkLiquidation(address liquidator, address violator, address collateral) // <= FOUND
32: public
33: view
34: virtual
35: nonReentrantView
36: returns (uint256 maxRepay, uint256 maxYield) // <= FOUND
37: {
38: LiquidationCache memory liqCache =
39: calculateLiquidation(loadVault(), liquidator, violator, collateral, type(uint256).max);
40:
41: maxRepay = liqCache.repay.toUint(); // <= FOUND
42: maxYield = liqCache.yieldBalance; // <= FOUND
43: }['147']
147: function accountLiquidity(address account, bool liquidation) public view virtual override useView(MODULE_RISKMANAGER) returns (uint256 collateralValue, uint256 liabilityValue) {} // <= FOUND['17']
17: function accountLiquidity(address account, bool liquidation) // <= FOUND
18: public
19: view
20: virtual
21: nonReentrantView
22: returns (uint256 collateralValue, uint256 liabilityValue) // <= FOUND
23: {
24: VaultCache memory vaultCache = loadVault();
25:
26: validateController(account);
27: address[] memory collaterals = getCollaterals(account);
28:
29: return calculateLiquidity(vaultCache, account, collaterals, liquidation);
30: }['193']
193: function caps() public view virtual override useView(MODULE_GOVERNANCE) returns (uint16 supplyCap, uint16 borrowCap) {} // <= FOUND['199']
199: function LTVFull(address collateral) public view virtual override useView(MODULE_GOVERNANCE) returns (uint16 borrowLTV, uint16 liquidationLTV, uint16 initialLiquidationLTV, uint48 targetTimestamp, uint32 rampDuration) {} // <= FOUND['97']
97: function getCollateralValue(VaultCache memory vaultCache, address account, address collateral, bool liquidation) // <= FOUND
98: internal
99: view
100: virtual
101: returns (uint256 value) // <= FOUND
102: {
103: ConfigAmount ltv = getLTV(collateral, liquidation);
104: if (ltv.isZero()) return 0;
105:
106: uint256 balance = IERC20(collateral).balanceOf(account);
107: if (balance == 0) return 0;
108:
109: uint256 currentCollateralValue;
110:
111: if (liquidation) {
112:
113: currentCollateralValue = vaultCache.oracle.getQuote(balance, collateral, vaultCache.unitOfAccount);
114: } else {
115:
116: (currentCollateralValue,) = vaultCache.oracle.getQuotes(balance, collateral, vaultCache.unitOfAccount);
117: }
118:
119: return currentCollateralValue * ltv.toUint16() / CONFIG_SCALE;
120: }Emitting an event within initializer functions in Solidity is a best practice for providing transparency and traceability. Initializer functions set the initial state and values of an upgradeable contract. Emitting an event during initialization allows anyone to verify and audit the initial state of the contract via the transaction logs. This can be particularly useful for verifying the parameters set during initialization, tracking the contract's deployment, and troubleshooting or debugging. Therefore, developers should include an event emission in their initializer functions, providing a clear record of the contract's initialization and enhancing the contract's transparency and security.
Num of instances: 1
Click to show findings
['20']
20: function initialize(address proxyCreator) public virtual override use(MODULE_INITIALIZE) {} // <= FOUNDMake found instants CAPITAL_CASE
Num of instances: 24
Click to show findings
['19']
19: address internal immutable beacon; // <= FOUND['20']
20: uint256 internal immutable metadataLength; // <= FOUND['21']
21: bytes32 internal immutable metadata0; // <= FOUND['22']
22: bytes32 internal immutable metadata1; // <= FOUND['23']
23: bytes32 internal immutable metadata2; // <= FOUND['24']
24: bytes32 internal immutable metadata3; // <= FOUND['15']
15: address public immutable eVault; // <= FOUND['14']
14: uint256 public immutable baseRate; // <= FOUND['25']
25: IPriceOracle public immutable oracle; // <= FOUND['26']
26: uint256 public immutable targetQuote; // <= FOUND['24']
24: IBalanceTracker internal immutable balanceTracker; // <= FOUND['24']
24: address public immutable referenceAsset; // <= FOUND['27']
27: uint256 public immutable conversionPrice; // <= FOUND['22']
22: IProtocolConfig internal immutable protocolConfig; // <= FOUND['23']
23: ISequenceRegistry immutable sequenceRegistry; // <= FOUND['21']
21: IEVC internal immutable evc; // <= FOUND['26']
26: IERC20 public immutable underlying; // <= FOUND['16']
16: uint256 public immutable slope1; // <= FOUND['18']
18: uint256 public immutable slope2; // <= FOUND['27']
27: uint256 public immutable quoteAmount; // <= FOUND['25']
25: address internal immutable permit2; // <= FOUND['23']
23: address public immutable synth; // <= FOUND['25']
25: ESynth public immutable synth; // <= FOUND['20']
20: uint256 public immutable kink; // <= FOUNDTo maintain readability in code, particularly in Solidity which can involve complex mathematical operations, it is often recommended to limit the number of arithmetic operations to a maximum of 2-3 per line. Too many operations in a single line can make the code difficult to read and understand, increase the likelihood of mistakes, and complicate the process of debugging and reviewing the code. Consider splitting such operations over more than one line, take special care when dealing with division however. Try to limit the number of arithmetic operations to a maximum of 3 per line.
Num of instances: 10
Click to show findings
['26']
26:
27:
28: return 10 ** (amountCap & 63) * (amountCap >> 6) / 100; // <= FOUND['38']
38:
39: return TypesLib.toShares((amount.toUint() * totalShares + (totalAssets - 1)) / totalAssets); // <= FOUND['95']
95: output[--len] = bytes1(uint8(48 + n % 10)); // <= FOUND['53']
53:
54: currentLiquidationLTV = targetLiquidationLTV
55: + (currentLiquidationLTV - targetLiquidationLTV) * timeRemaining / self.rampDuration; // <= FOUND['57']
57: return (owedExact + (1 << INTERNAL_DEBT_PRECISION_SHIFT) - 1) >> INTERNAL_DEBT_PRECISION_SHIFT; // <= FOUND['133']
133: return amountIn * (BPS_SCALE - TO_UNDERLYING_FEE) * conversionPrice / BPS_SCALE / PRICE_SCALE; // <= FOUND['140']
140: return amountOut * BPS_SCALE * PRICE_SCALE / (BPS_SCALE - TO_UNDERLYING_FEE) / conversionPrice; // <= FOUND['147']
147: return amountIn * (BPS_SCALE - TO_SYNTH_FEE) * PRICE_SCALE / BPS_SCALE / conversionPrice; // <= FOUND['154']
154: return amountOut * BPS_SCALE * conversionPrice / (BPS_SCALE - TO_SYNTH_FEE) / PRICE_SCALE; // <= FOUND['33']
33:
34: return TypesLib.toAssets((amount.toUint() * totalAssets + (totalShares - 1)) / totalShares); // <= FOUNDMagic numbers should be avoided in Solidity code to enhance readability, maintainability, and reduce the likelihood of errors. Magic numbers are hard-coded values with no clear meaning or context, which can create confusion and make the code harder to understand for developers. Using well-defined constants or variables with descriptive names instead of magic numbers not only clarifies the purpose and significance of the value but also simplifies code updates and modifications.
Num of instances: 12
Click to show findings
['26']
26:
27:
28: return 10 ** (amountCap & 63) * (amountCap >> 6) / 100; // <= FOUND['88']
88: for (uint256 m = n; m != 0; m /= 10) { // <= FOUND['95']
95: output[--len] = bytes1(uint8(48 + n % 10)); // <= FOUND['96']
96: n /= 10; // <= FOUND['33']
33: return success && data.length >= 32 ? abi.decode(data, (uint8)) : 18; // <= FOUND['375']
375:
376:
377: if (supplyCap != 0 && _supplyCap.resolve() > 2 * MAX_SANE_AMOUNT) revert E_BadSupplyCap(); // <= FOUND['28']
28: switch mod(n, 2) // <= FOUND
29: case 0 {['120']
120: if (success && data.length >= 32) { // <= FOUND['36']
36:
37:
38:
39: if (msg.data.length != 4 + 32 + PROXY_METADATA_LENGTH) revert E_ProxyMetadata(); // <= FOUND['79']
79: return data.length <= 32 ? string(data) : abi.decode(data, (string)); // <= FOUND['51']
51: return callSuccess && (data.length == 0 || (data.length >= 32 && abi.decode(data, (bool)))); // <= FOUND['172']
172: default { return(64, sub(returndatasize(), 64)) } // <= FOUNDNum of instances: 2
Click to show findings
['75']
75: function _computeRate(IRMData memory irmCache) internal view returns (uint216 rate, bool updated) { // <= FOUND
76: updated = false;
77: rate = irmCache.lastRate;
78:
79:
80: if (block.timestamp < irmCache.lastUpdated + ADJUST_INTERVAL) {
81: return (rate, updated);
82: }
83:
84: uint256 quote = oracle.getQuote(quoteAmount, synth, referenceAsset);
85:
86: updated = true;
87:
88: if (quote < targetQuote) {
89:
90: rate = rate * ADJUST_FACTOR / ADJUST_ONE;
91: } else {
92:
93: rate = rate * ADJUST_ONE / ADJUST_FACTOR;
94: }
95:
96:
97: if (rate < BASE_RATE) {
98: rate = BASE_RATE;
99: } else if (rate > MAX_RATE) {
100: rate = MAX_RATE;
101: }
102:
103: return (rate, updated);
104: }[]
97: function getCollateralValue(VaultCache memory vaultCache, address account, address collateral, bool liquidation)
98: internal
99: view
100: virtual
101: returns (uint256 value)
102: {
103: ConfigAmount ltv = getLTV(collateral, liquidation);
104: if (ltv.isZero()) return 0;
105:
106: uint256 balance = IERC20(collateral).balanceOf(account);
107: if (balance == 0) return 0;
108:
109: uint256 currentCollateralValue;
110:
111: if (liquidation) {
112:
113: currentCollateralValue = vaultCache.oracle.getQuote(balance, collateral, vaultCache.unitOfAccount);
114: } else {
115:
116: (currentCollateralValue,) = vaultCache.oracle.getQuotes(balance, collateral, vaultCache.unitOfAccount);
117: }
118:
119: return currentCollateralValue * ltv.toUint16() / CONFIG_SCALE;
120: }Smart contracts are complex entities, and clarity in their operations is fundamental to ensure that they function as intended. Casting a single argument instead of utilizing 'abi.encodePacked()' improves the transparency of the operation. It elucidates the intent of the code, reducing ambiguity and making it easier for auditors and developers to understand the code’s purpose. Such practices promote readability and maintainability, thus reducing the likelihood of errors and misunderstandings. Therefore, it's recommended to employ explicit casts for single arguments where possible, to increase the contract's comprehensibility and ensure a smoother review process.
Num of instances: 4
Click to show findings
['127']
127: function invokeHookTarget(address caller) private {
128: address hookTarget = vaultStorage.hookTarget;
129:
130: if (hookTarget == address(0)) revert E_OperationDisabled();
131:
132: (bool success, bytes memory data) = hookTarget.call(abi.encodePacked(msg.data, caller)); // <= FOUND
133:
134: if (!success) RevertBytes.revertBytes(data);
135: }['29']
29: function initialize(address proxyCreator) public virtual reentrantOK {
30: if (initialized) revert E_Initialized();
31: initialized = true;
32:
33:
34:
35:
36: if (msg.data.length != 4 + 32 + PROXY_METADATA_LENGTH) revert E_ProxyMetadata();
37: (IERC20 asset,,) = ProxyUtils.metadata();
38:
39: AddressUtils.checkContract(address(asset));
40:
41:
42:
43:
44: address dToken = address(new DToken());
45:
46:
47:
48: vaultStorage.lastInterestAccumulatorUpdate = uint48(block.timestamp);
49: vaultStorage.interestAccumulator = INITIAL_INTEREST_ACCUMULATOR;
50: vaultStorage.interestFee = DEFAULT_INTEREST_FEE.toConfigAmount();
51: vaultStorage.creator = vaultStorage.governorAdmin = proxyCreator;
52:
53: {
54: string memory underlyingSymbol = getTokenSymbol(address(asset));
55: uint256 seqId = sequenceRegistry.reserveSeqId(underlyingSymbol);
56:
57: vaultStorage.symbol = string(abi.encodePacked("e", underlyingSymbol, "-", uintToString(seqId))); // <= FOUND
58: vaultStorage.name = string(abi.encodePacked("EVK Vault ", vaultStorage.symbol)); // <= FOUND
59: }
60:
61: snapshot.reset();
62:
63:
64:
65: emit EVaultCreated(proxyCreator, address(asset), dToken);
66: logVaultStatus(loadVault(), 0);
67: }['20']
20: function deployMetaProxy(address targetContract, bytes memory metadata) internal returns (address addr) {
21: bytes memory code = abi.encodePacked(BYTECODE_HEAD, targetContract, BYTECODE_TAIL, metadata); // <= FOUND
22:
23: assembly ("memory-safe") {
24: addr := create(0, add(code, 32), mload(code))
25: }
26:
27: if (addr == address(0)) revert E_DeploymentFailed();
28: }['28']
28: function safeTransferFrom(IERC20 token, address from, address to, uint256 value, address permit2) internal {
29: (bool success, bytes memory tryData) = trySafeTransferFrom(token, from, to, value);
30: bytes memory fallbackData;
31: if (!success && permit2 != address(0)) {
32: if (value > type(uint160).max) {
33: revert E_TransferFromFailed(tryData, abi.encodePacked(E_Permit2AmountOverflow.selector)); // <= FOUND
34: }
35:
36: (success, fallbackData) =
37: permit2.call(abi.encodeCall(IPermit2.transferFrom, (from, to, uint160(value), address(token))));
38: }
39:
40: if (!success) revert E_TransferFromFailed(tryData, fallbackData);
41: }Events in Solidity offer valuable insight into the contract's execution as they log specific instances of state changes or value transfers. However, if events do not include any parameters, their usefulness can be significantly reduced. Events without parameters can provide limited information, mainly signaling that a specific operation occurred, but lacking the context of what exactly changed. It's generally recommended to include relevant parameters, such as state changes or value modifications, in the emitted events.
Num of instances: 16
Click to show findings
['36']
36: emit Transfer(address(0), account, amount.toUint()); // <= FOUND['37']
37: emit Deposit(sender, account, assets.toUint(), amount.toUint()); // <= FOUND['65']
65: emit Transfer(account, address(0), amount.toUint()); // <= FOUND['66']
66: emit Withdraw(sender, receiver, account, assets.toUint(), amount.toUint()); // <= FOUND['101']
101: emit Transfer(from, to, amount.toUint()); // <= FOUND['142']
142: emit VaultStatus( // <= FOUND
143: a.totalShares.toUint(), // <= FOUND
144: a.totalBorrows.toAssetsUp().toUint(), // <= FOUND
145: a.accumulatedFees.toUint(), // <= FOUND
146: a.cash.toUint(), // <= FOUND
147: a.interestAccumulator,
148: interestRate,
149: block.timestamp
150: );['97']
97: emit Genesis(); // <= FOUND['169']
169: if (!interest.isZero()) emit InterestAccrued(account, interest.toUint()); // <= FOUND['170']
170: if (!amount.isZero()) emit Borrow(account, amount.toUint()); // <= FOUND['177']
177: if (!amount.isZero()) emit Repay(account, amount.toUint()); // <= FOUND['141']
141: emit PullDebt(from, account, assets.toUint()); // <= FOUND['248']
248: emit ConvertFees(account, protocolReceiver, governorReceiver, protocolShares.toUint(), governorShares.toUint()); // <= FOUND['300']
300: emit GovSetLTV( // <= FOUND
301: collateral,
302: newLTV.borrowLTV.toUint16(), // <= FOUND
303: newLTV.liquidationLTV.toUint16(), // <= FOUND
304: newLTV.initialLiquidationLTV.toUint16(), // <= FOUND
305: newLTV.targetTimestamp,
306: newLTV.rampDuration,
307: !currentLTV.initialized
308: );['220']
220:
221:
222: emit Withdraw(liqCache.liquidator, address(0), address(0), owedRemaining.toUint(), 0); // <= FOUND['221']
221: emit DebtSocialized(liqCache.violator, owedRemaining.toUint()); // <= FOUND['224']
224: emit Liquidate( // <= FOUND
225: liqCache.liquidator, liqCache.violator, liqCache.collateral, liqCache.repay.toUint(), liqCache.yieldBalance // <= FOUND
226: );When developing smart contracts in Solidity, it's crucial to validate the inputs of your functions. This includes ensuring that the bytes parameters are not empty, especially when they represent crucial data such as addresses, identifiers, or raw data that the contract needs to process.
Missing empty bytes checks can lead to unexpected behaviour in your contract. For instance, certain operations might fail, produce incorrect results, or consume unnecessary gas when performed with empty bytes. Moreover, missing input validation can potentially expose your contract to malicious activity, including exploitation of unhandled edge cases.
To mitigate these issues, always validate that bytes parameters are not empty when the logic of your contract requires it.
Num of instances: 3
Click to show findings
['147']
147: function flashLoan(uint256 amount, bytes calldata data) public virtual nonReentrant {
148: address account = EVCAuthenticate();
149: callHook(vaultStorage.hookedOps, OP_FLASHLOAN, account);
150:
151: (IERC20 asset,,) = ProxyUtils.metadata();
152:
153: uint256 origBalance = asset.balanceOf(address(this));
154:
155: asset.safeTransfer(account, amount);
156:
157: IFlashLoan(account).onFlashLoan(data);
158:
159: if (asset.balanceOf(address(this)) < origBalance) revert E_FlashLoanNotRepaid();
160: }['116']
116: function createProxy(address desiredImplementation, bool upgradeable, bytes memory trailingData)
117: external
118: nonReentrant
119: returns (address)
120: {
121: address _implementation = implementation;
122: if (desiredImplementation == address(0)) desiredImplementation = _implementation;
123:
124: if (desiredImplementation == address(0) || desiredImplementation != _implementation) revert E_Implementation();
125:
126: address proxy;
127:
128: if (upgradeable) {
129: proxy = address(new BeaconProxy(trailingData));
130: } else {
131: proxy = deployMetaProxy(desiredImplementation, trailingData);
132: }
133:
134: proxyLookup[proxy] =
135: ProxyConfig({upgradeable: upgradeable, implementation: desiredImplementation, trailingData: trailingData});
136:
137: proxyList.push(proxy);
138:
139: IComponent(proxy).initialize(msg.sender);
140:
141: emit ProxyCreated(proxy, upgradeable, desiredImplementation, trailingData);
142:
143: return proxy;
144: }['20']
20: function deployMetaProxy(address targetContract, bytes memory metadata) internal returns (address addr) {
21: bytes memory code = abi.encodePacked(BYTECODE_HEAD, targetContract, BYTECODE_TAIL, metadata);
22:
23: assembly ("memory-safe") {
24: addr := create(0, add(code, 32), mload(code))
25: }
26:
27: if (addr == address(0)) revert E_DeploymentFailed();
28: }The function lacks a standard check: it does not validate if the 'to' and 'from' addresses are identical. This omission can lead to unintended outcomes if the same address is used in both parameters. To rectify this, we recommend implementing a comparison check at the beginning of the function. In the context of Solidity, the command require(to != from, "To and From addresses can't be the same"); could be utilized. This addition will generate an error if the 'to' and 'from' addresses are the same, thereby fortifying the function's robustness and security.
Num of instances: 10
Click to show findings
['75']
75: function transferBorrow(VaultCache memory vaultCache, address from, address to, Assets assets) internal virtual { // <= FOUND
76: Owed amount = assets.toOwed();
77:
78: (Owed fromOwed, Owed fromOwedPrev) = loadUserBorrow(vaultCache, from);
79:
80:
81: if (
82: (amount > fromOwed && amount.subUnchecked(fromOwed).isDust())
83: || (amount < fromOwed && fromOwed.subUnchecked(amount).isDust())
84: ) {
85: amount = fromOwed;
86: }
87:
88: if (amount > fromOwed) revert E_InsufficientDebt();
89:
90: fromOwed = fromOwed.subUnchecked(amount);
91: setUserBorrow(vaultCache, from, fromOwed);
92:
93: (Owed toOwed, Owed toOwedPrev) = loadUserBorrow(vaultCache, to);
94:
95: toOwed = toOwed + amount;
96: setUserBorrow(vaultCache, to, toOwed);
97:
98: logRepay(from, assets, fromOwedPrev.toAssetsUp(), fromOwed.toAssetsUp());
99:
100:
101: Assets toPrevAssets = toOwedPrev.toAssetsUp();
102: Assets toAssets = toOwed.toAssetsUp();
103: if (assets + toPrevAssets > toAssets) assets = toAssets - toPrevAssets;
104: logBorrow(to, assets, toPrevAssets, toAssets);
105: }['83']
83: function emitTransfer(address from, address to, uint256 value) external { // <= FOUND
84: if (msg.sender != eVault) revert E_Unauthorized();
85:
86: emit Transfer(from, to, value);
87: }['37']
37: function transferFrom(address from, address to, uint256 amount) // <= FOUND
38: public
39: virtual
40: override
41: nonReentrant
42: returns (bool)
43: {
44: return super.transferFrom(from, to, amount);
45: }['43']
43: function transferFrom(address from, address to, uint256 amount) public virtual override callThroughEVC returns (bool) { return super.transferFrom(from, to, amount); } // <= FOUND['47']
47: function transferFromMax(address from, address to) public virtual override callThroughEVC returns (bool) { return super.transferFromMax(from, to); } // <= FOUND['119']
119: function transferFrom(address from, address to, uint256 amount) // <= FOUND
120: public
121: override (ERC20, IERC20)
122: nonReentrant
123: requireAccountStatusCheck(from)
124: returns (bool)
125: {
126: return super.transferFrom(from, to, amount);
127: }['19']
19: function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) // <= FOUND
20: internal
21: returns (bool, bytes memory)
22: {
23: (bool success, bytes memory data) = address(token).call(abi.encodeCall(IERC20.transferFrom, (from, to, value)));
24:
25: return isEmptyOrTrueReturn(success, data) ? (true, bytes("")) : (false, data);
26: }['28']
28: function safeTransferFrom(IERC20 token, address from, address to, uint256 value, address permit2) internal { // <= FOUND
29: (bool success, bytes memory tryData) = trySafeTransferFrom(token, from, to, value);
30: bytes memory fallbackData;
31: if (!success && permit2 != address(0)) {
32: if (value > type(uint160).max) {
33: revert E_TransferFromFailed(tryData, abi.encodePacked(E_Permit2AmountOverflow.selector));
34: }
35:
36: (success, fallbackData) =
37: permit2.call(abi.encodeCall(IPermit2.transferFrom, (from, to, uint160(value), address(token))));
38: }
39:
40: if (!success) revert E_TransferFromFailed(tryData, fallbackData);
41: }['58']
58: function transferFromMax(address from, address to) public virtual nonReentrant returns (bool) { // <= FOUND
59: validateTransferFromAccount(from);
60:
61: (, address account) = initOperation(OP_TRANSFER, from);
62:
63: return transferFromInternal(account, from, to, vaultStorage.users[from].getBalance());
64: }['67']
67: function transferFrom(address from, address to, uint256 amount) public virtual nonReentrant returns (bool) { // <= FOUND
68: validateTransferFromAccount(from);
69:
70: (, address account) = initOperation(OP_TRANSFER, from);
71:
72: return transferFromInternal(account, from, to, amount.toShares());
73: }In Solidity, when designing functions that return boolean values, it's crucial for clarity and maintainability to explicitly handle both true and false return scenarios. If a function is intended to return true under certain conditions, it should also explicitly return false when these conditions are not met, and vice versa. This approach eliminates ambiguity and makes the code's intent more transparent. Explicitly handling all possible outcomes of a boolean function ensures that future modifications or extensions of the contract do not unintentionally alter its logic. It contributes to better readability, easier debugging, and reduces the risk of bugs related to unintended fall-through cases.
Num of instances: 2
Click to show findings
['76']
76: function approve(address spender, uint256 amount) public virtual nonReentrant returns (bool) {
77: address account = EVCAuthenticate();
78:
79: setAllowance(account, spender, amount);
80:
81: return true; // <= FOUND
82: }['84']
84: function transferFromInternal(address account, address from, address to, Shares shares) private returns (bool) {
85: if (from == to) revert E_SelfTransfer();
86:
87: decreaseAllowance(from, account, shares);
88: transferBalance(from, to, shares);
89:
90: return true; // <= FOUND
91: }Adopting a consistent and clear naming convention enhances code readability and maintainability. In Solidity, appending an underscore at the end of a variable name is unconventional and can lead to confusion. It is generally advisable to stick to accepted naming practices to promote ease of understanding and use.
Num of instances: 10
Click to show findings
['52']
52: address beacon_ = beacon; // <= FOUND['53']
53: uint256 metadataLength_ = metadataLength; // <= FOUND['54']
54: bytes32 metadata0_ = metadata0; // <= FOUND['55']
55: bytes32 metadata1_ = metadata1; // <= FOUND['56']
56: bytes32 metadata2_ = metadata2; // <= FOUND['57']
57: bytes32 metadata3_ = metadata3; // <= FOUND['40']
40: if (synth_ == address(0) || referenceAsset_ == address(0) || oracle_ == address(0)) { // <= FOUND['82']
82: if (admin_ == address(0)) revert E_InvalidAdmin(); // <= FOUND['83']
83: if (feeReceiver_ == address(0)) revert E_InvalidReceiver(); // <= FOUND['210']
210: if (exists_ && feeReceiver_ == address(0)) revert E_InvalidReceiver(); // <= FOUND[NonCritical-46] Use OpenZeppelin's ReentrancyGuard/ReentrancyGuardUpgradeable rather than writing your own
Leveraging OpenZeppelin's ReentrancyGuard or ReentrancyGuardUpgradeable is generally recommended over creating your own versions. The OpenZeppelin modules have undergone rigorous optimization for gas efficiency and have been comprehensively tested for potential security vulnerabilities. As a result, they provide a high degree of reliability and performance. Writing your own reentrancy guard mechanism might introduce unseen bugs or security loopholes, and likely be less gas efficient. Therefore, it's beneficial to rely on the tried-and-tested solutions offered by OpenZeppelin's established and widely-accepted smart contract library.
Num of instances: 4
Click to show findings
['53']
53: modifier nonReentrant() { // <= FOUND
54: if (vaultStorage.reentrancyLocked) revert E_Reentrancy();
55:
56: vaultStorage.reentrancyLocked = true;
57: _;
58: vaultStorage.reentrancyLocked = false;
59: }['61']
61: modifier nonReentrantView() { // <= FOUND
62: if (vaultStorage.reentrancyLocked) {
63: address hookTarget = vaultStorage.hookTarget;
64:
65:
66:
67:
68:
69: if (msg.sender != hookTarget && !(msg.sender == address(this) && ProxyUtils.useViewCaller() == hookTarget))
70: {
71: revert E_Reentrancy();
72: }
73: }
74: _;
75: }['50']
50: modifier nonReentrant() { // <= FOUND
51: if (esrSlot.locked == LOCKED) revert Reentrancy();
52:
53: esrSlot.locked = LOCKED;
54: _;
55: esrSlot.locked = UNLOCKED;
56: }['83']
83: modifier nonReentrant() { // <= FOUND
84: if (reentrancyLock == REENTRANCYLOCK__LOCKED) revert E_Reentrancy();
85:
86: reentrancyLock = REENTRANCYLOCK__LOCKED;
87: _;
88: reentrancyLock = REENTRANCYLOCK__UNLOCKED;
89: }Using the .call method in Solidity enables direct communication with an address, bypassing function existence checks, type checking, and argument packing. While this can save gas and provide flexibility, it can also introduce security risks and potential errors. The absence of these checks can lead to unexpected behavior if the callee contract's interface changes or if the input parameters are not crafted with care. The resolution to these issues is to use Solidity's high-level interface for calling functions when possible, as it automatically manages these aspects. If using .call is necessary, ensure that the inputs are carefully validated and that awareness of the called contract's behavior is maintained.
Num of instances: 6
Click to show findings
['132']
132: (bool success, bytes memory data) = hookTarget.call(abi.encodePacked(msg.data, caller)); // <= FOUND['113']
113: (bool success, bytes memory data) = irm.call( // <= FOUND
114: abi.encodeCall(
115: IIRM.computeInterestRate,
116: (address(this), vaultCache.cash.toUint(), vaultCache.totalBorrows.toAssetsUp().toUint())
117: )
118: );['23']
23: (bool success, bytes memory data) = address(token).call(abi.encodeCall(IERC20.transferFrom, (from, to, value))); // <= FOUND['36']
36:
37: (success, fallbackData) =
38: permit2.call(abi.encodeCall(IPermit2.transferFrom, (from, to, uint160(value), address(token)))); // <= FOUND['46']
46: (bool success, bytes memory data) = address(token).call(abi.encodeCall(IERC20.transfer, (to, value))); // <= FOUND['154']
154: let mainCalldataLength := sub(calldatasize(), PROXY_METADATA_LENGTH)
155:
156: mstore(0, 0x1f8b521500000000000000000000000000000000000000000000000000000000)
157: mstore(4, address())
158: mstore(36, caller())
159: mstore(68, callvalue())
160: mstore(100, 128)
161: mstore(132, mainCalldataLength)
162: calldatacopy(164, 0, mainCalldataLength)
163:
164:
165:
166: mstore(add(164, mainCalldataLength), 0)
167: let result := call(gas(), _evc, callvalue(), 0, add(164, and(add(mainCalldataLength, 31), not(31))), 0, 0) // <= FOUND
168:
169: returndatacopy(0, 0, returndatasize())
170: switch result
171: case 0 { revert(0, returndatasize()) }Cyclomatic complexity is a software metric used to measure the complexity of a program. It quantifies the number of linearly independent paths through a program's source code, giving an idea of how complex the control flow is. High cyclomatic complexity may indicate a higher risk of defects and can make the code harder to understand, test, and maintain. It often suggests that a function or method is trying to do too much, and a refactor might be needed. By breaking down complex functions into smaller, more focused pieces, you can improve readability, ease of testing, and overall maintainability.
Num of instances: 5
Click to show findings
['69']
69: function transferBalance(address from, address to, Shares amount) internal virtual { // <= FOUND
70: if (to == address(0)) revert E_BadSharesReceiver();
71:
72: if (!amount.isZero()) {
73:
74:
75: UserStorage storage user = vaultStorage.users[from];
76:
77: (Shares origFromBalance, bool fromBalanceForwarderEnabled) = user.getBalanceAndBalanceForwarder();
78: if (origFromBalance < amount) revert E_InsufficientBalance();
79:
80: Shares newFromBalance = origFromBalance.subUnchecked(amount);
81: user.setBalance(newFromBalance);
82:
83:
84:
85: user = vaultStorage.users[to];
86:
87: (Shares origToBalance, bool toBalanceForwarderEnabled) = user.getBalanceAndBalanceForwarder();
88:
89: Shares newToBalance = origToBalance + amount;
90: user.setBalance(newToBalance);
91:
92: if (fromBalanceForwarderEnabled) {
93: balanceTracker.balanceTrackerHook(from, newFromBalance.toUint(), isControlCollateralInProgress());
94: }
95:
96: if (toBalanceForwarderEnabled && from != to) {
97: balanceTracker.balanceTrackerHook(to, newToBalance.toUint(), false);
98: }
99: }
100:
101: emit Transfer(from, to, amount.toUint());
102: }['42']
42: function initVaultCache(VaultCache memory vaultCache) private view returns (bool dirty) { // <= FOUND
43: dirty = false;
44:
45:
46:
47: (vaultCache.asset, vaultCache.oracle, vaultCache.unitOfAccount) = ProxyUtils.metadata();
48:
49:
50:
51: vaultCache.lastInterestAccumulatorUpdate = vaultStorage.lastInterestAccumulatorUpdate;
52: vaultCache.cash = vaultStorage.cash;
53: vaultCache.supplyCap = vaultStorage.supplyCap.resolve();
54: vaultCache.borrowCap = vaultStorage.borrowCap.resolve();
55: vaultCache.hookedOps = vaultStorage.hookedOps;
56: vaultCache.snapshotInitialized = vaultStorage.snapshotInitialized;
57:
58: vaultCache.totalShares = vaultStorage.totalShares;
59: vaultCache.totalBorrows = vaultStorage.totalBorrows;
60:
61: vaultCache.accumulatedFees = vaultStorage.accumulatedFees;
62: vaultCache.configFlags = vaultStorage.configFlags;
63:
64: vaultCache.interestAccumulator = vaultStorage.interestAccumulator;
65:
66:
67:
68: uint256 deltaT = block.timestamp - vaultCache.lastInterestAccumulatorUpdate;
69: if (deltaT > 0) {
70: dirty = true;
71:
72:
73:
74: ConfigAmount interestFee = vaultStorage.interestFee;
75: uint256 interestRate = vaultStorage.interestRate;
76:
77: uint256 newInterestAccumulator = vaultCache.interestAccumulator;
78: uint256 newTotalBorrows = vaultCache.totalBorrows.toUint();
79:
80: unchecked {
81: uint256 intermediate;
82: (uint256 multiplier, bool overflow) = RPow.rpow(interestRate + 1e27, deltaT, 1e27);
83:
84:
85: if (!overflow) {
86: intermediate = newInterestAccumulator * multiplier;
87: if (newInterestAccumulator == intermediate / multiplier) {
88: newInterestAccumulator = intermediate / 1e27;
89: }
90: }
91:
92: intermediate = newTotalBorrows * newInterestAccumulator;
93: if (newTotalBorrows == intermediate / newInterestAccumulator) {
94: newTotalBorrows = intermediate / vaultCache.interestAccumulator;
95: }
96: }
97:
98: uint256 newAccumulatedFees = vaultCache.accumulatedFees.toUint();
99: uint256 newTotalShares = vaultCache.totalShares.toUint();
100: uint256 feeAssets = (newTotalBorrows - vaultCache.totalBorrows.toUint()) * interestFee.toUint16()
101: / (uint256(CONFIG_SCALE) << INTERNAL_DEBT_PRECISION_SHIFT);
102:
103: if (feeAssets != 0) {
104:
105:
106:
107:
108:
109:
110:
111: uint256 newTotalAssets = vaultCache.cash.toUint() + OwedLib.toAssetsUpUint(newTotalBorrows);
112: newTotalShares = newTotalAssets * newTotalShares / (newTotalAssets - feeAssets);
113: newAccumulatedFees += newTotalShares - vaultCache.totalShares.toUint();
114: }
115:
116:
117:
118: if (newTotalBorrows <= MAX_SANE_DEBT_AMOUNT) {
119: vaultCache.totalBorrows = newTotalBorrows.toOwed();
120: vaultCache.interestAccumulator = newInterestAccumulator;
121: vaultCache.lastInterestAccumulatorUpdate = uint48(block.timestamp);
122:
123: if (newTotalShares != vaultCache.totalShares.toUint() && newTotalShares <= MAX_SANE_AMOUNT) {
124: vaultCache.accumulatedFees = newAccumulatedFees.toShares();
125: vaultCache.totalShares = newTotalShares.toShares();
126: }
127: }
128: }
129: }['216']
216: function convertFees() public virtual nonReentrant { // <= FOUND
217: (VaultCache memory vaultCache, address account) = initOperation(OP_CONVERT_FEES, CHECKACCOUNT_NONE);
218:
219: if (vaultCache.accumulatedFees.isZero()) return;
220:
221: (address protocolReceiver, uint16 protocolFee) = protocolConfig.protocolFeeConfig(address(this));
222: address governorReceiver = vaultStorage.feeReceiver;
223:
224: if (governorReceiver == address(0)) {
225: protocolFee = CONFIG_SCALE;
226: } else if (protocolFee > MAX_PROTOCOL_FEE_SHARE) {
227: protocolFee = MAX_PROTOCOL_FEE_SHARE;
228: }
229:
230: Shares governorShares = vaultCache.accumulatedFees.mulDiv(CONFIG_SCALE - protocolFee, CONFIG_SCALE);
231: Shares protocolShares = vaultCache.accumulatedFees - governorShares;
232:
233:
234: vaultStorage.totalShares = vaultCache.totalShares = vaultCache.totalShares - vaultCache.accumulatedFees;
235:
236: vaultStorage.accumulatedFees = vaultCache.accumulatedFees = Shares.wrap(0);
237:
238:
239:
240: if (!governorShares.isZero()) {
241: increaseBalance(vaultCache, governorReceiver, address(0), governorShares, Assets.wrap(0));
242: }
243:
244: if (!protocolShares.isZero()) {
245: increaseBalance(vaultCache, protocolReceiver, address(0), protocolShares, Assets.wrap(0));
246: }
247:
248: emit ConvertFees(account, protocolReceiver, governorReceiver, protocolShares.toUint(), governorShares.toUint());
249: }['75']
75: function _computeRate(IRMData memory irmCache) internal view returns (uint216 rate, bool updated) { // <= FOUND
76: updated = false;
77: rate = irmCache.lastRate;
78:
79:
80: if (block.timestamp < irmCache.lastUpdated + ADJUST_INTERVAL) {
81: return (rate, updated);
82: }
83:
84: uint256 quote = oracle.getQuote(quoteAmount, synth, referenceAsset);
85:
86: updated = true;
87:
88: if (quote < targetQuote) {
89:
90: rate = rate * ADJUST_FACTOR / ADJUST_ONE;
91: } else {
92:
93: rate = rate * ADJUST_ONE / ADJUST_FACTOR;
94: }
95:
96:
97: if (rate < BASE_RATE) {
98: rate = BASE_RATE;
99: } else if (rate > MAX_RATE) {
100: rate = MAX_RATE;
101: }
102:
103: return (rate, updated);
104: }['12']
12: function rpow(uint256 x, uint256 n, uint256 scalar) internal pure returns (uint256 z, bool overflow) { // <= FOUND
13:
14: assembly {
15: switch x
16: case 0 {
17: switch n
18: case 0 {
19:
20: z := scalar
21: }
22: default {
23:
24: z := 0
25: }
26: }
27: default {
28: switch mod(n, 2)
29: case 0 {
30:
31: z := scalar
32: }
33: default {
34:
35: z := x
36: }
37:
38:
39: let half := shr(1, scalar)
40:
41: for {
42:
43: n := shr(1, n)
44: } n {
45:
46: n := shr(1, n)
47: } {
48:
49:
50: if shr(128, x) {
51: overflow := 1
52: break
53: }
54:
55:
56: let xx := mul(x, x)
57:
58:
59: let xxRound := add(xx, half)
60:
61:
62: if lt(xxRound, xx) {
63: overflow := 1
64: break
65: }
66:
67:
68: x := div(xxRound, scalar)
69:
70:
71: if mod(n, 2) {
72:
73: let zx := mul(z, x)
74:
75:
76: if iszero(eq(div(zx, x), z)) {
77:
78: if iszero(iszero(x)) {
79: overflow := 1
80: break
81: }
82: }
83:
84:
85: let zxRound := add(zx, half)
86:
87:
88: if lt(zxRound, zx) {
89: overflow := 1
90: break
91: }
92:
93:
94: z := div(zxRound, scalar)
95: }
96: }
97: }
98: }
99: }Contracts should define structs before modifiers and modifiers before functions.
Num of instances: 1
If these serve no purpose, they should be safely removed
Num of instances: 1
Click to show findings
['5']
5: import {IVault, IEVault, IERC4626} from "../IEVault.sol"; // <= FOUND[NonCritical-51] Events may be emitted out of order due to code not follow the best practice of check-effects-interaction
The "check-effects-interaction" pattern also impacts event ordering. When a contract doesn't adhere to this pattern, events might be emitted in a sequence that doesn't reflect the actual logical flow of operations. This can cause confusion during event tracking, potentially leading to erroneous off-chain interpretations. To rectify this, always ensure that checks are performed first, state modifications come next, and interactions with external contracts or addresses are done last. This will ensure events are emitted in a logical, consistent manner, providing a clear and accurate chronological record of on-chain actions for off-chain systems and observers.
Num of instances: 2
Click to show findings
['131']
131: function pullDebt(uint256 amount, address from) public virtual nonReentrant returns (uint256) { // <= FOUND
132: (VaultCache memory vaultCache, address account) = initOperation(OP_PULL_DEBT, CHECKACCOUNT_CALLER);
133:
134: if (from == account) revert E_SelfTransfer();
135:
136: Assets assets = amount == type(uint256).max ? getCurrentOwed(vaultCache, from).toAssetsUp() : amount.toAssets(); // <= FOUND
137:
138: if (assets.isZero()) return 0;
139: transferBorrow(vaultCache, from, account, assets);
140:
141: emit PullDebt(from, account, assets.toUint()); // <= FOUND
142:
143: return assets.toUint();
144: }['349']
349: function setHookConfig(address newHookTarget, uint32 newHookedOps) public virtual nonReentrant governorOnly { // <= FOUND
350: if (
351: newHookTarget != address(0)
352: && IHookTarget(newHookTarget).isHookTarget() != IHookTarget.isHookTarget.selector // <= FOUND
353: ) revert E_NotHookTarget();
354:
355: if (newHookedOps >= OP_MAX_VALUE) revert E_NotSupported();
356:
357: vaultStorage.hookTarget = newHookTarget;
358: vaultStorage.hookedOps = Flags.wrap(newHookedOps);
359: emit GovSetHookConfig(newHookTarget, newHookedOps); // <= FOUND
360: }Sensitive setter functions in smart contracts often alter critical state variables. Without events emitted in these functions, external observers or dApps cannot easily track or react to these state changes. Missing events can obscure contract activity, hampering transparency and making integration more challenging. To resolve this, incorporate appropriate event emissions within these functions. Events offer an efficient way to log crucial changes, aiding in real-time tracking and post-transaction verification.
Num of instances: 19
Click to show findings
['37']
37: function setUserBorrow(VaultCache memory vaultCache, address account, Owed newOwed) private { // <= FOUND
38: UserStorage storage user = vaultStorage.users[account];
39:
40: user.setOwed(newOwed);
41: user.interestAccumulator = vaultCache.interestAccumulator;
42: }['222']
222: function setGovernorAdmin(address newGovernorAdmin) public virtual override use(MODULE_GOVERNANCE) {} // <= FOUND['224']
224: function setFeeReceiver(address newFeeReceiver) public virtual override use(MODULE_GOVERNANCE) {} // <= FOUND['226']
226: function setHookConfig(address newHookTarget, uint32 newHookedOps) public virtual override use(MODULE_GOVERNANCE) {} // <= FOUND['228']
228: function setLTV(address collateral, uint16 borrowLTV, uint16 liquidationLTV, uint32 rampDuration) public virtual override use(MODULE_GOVERNANCE) {} // <= FOUND['232']
232: function setMaxLiquidationDiscount(uint16 newDiscount) public virtual override use(MODULE_GOVERNANCE) {} // <= FOUND['234']
234: function setLiquidationCoolOffTime(uint16 newCoolOffTime) public virtual override use(MODULE_GOVERNANCE) {} // <= FOUND['236']
236: function setInterestRateModel(address newModel) public virtual override use(MODULE_GOVERNANCE) {} // <= FOUND['238']
238: function setConfigFlags(uint32 newConfigFlags) public virtual override use(MODULE_GOVERNANCE) {} // <= FOUND['240']
240: function setCaps(uint16 supplyCap, uint16 borrowCap) public virtual override use(MODULE_GOVERNANCE) {} // <= FOUND['242']
242: function setInterestFee(uint16 newFee) public virtual override use(MODULE_GOVERNANCE) {} // <= FOUND['61']
61: function setLTV(LTVConfig memory self, ConfigAmount borrowLTV, ConfigAmount liquidationLTV, uint32 rampDuration) // <= FOUND
62: internal
63: view
64: returns (LTVConfig memory newLTV)
65: {
66: newLTV.borrowLTV = borrowLTV;
67: newLTV.liquidationLTV = liquidationLTV;
68: newLTV.initialLiquidationLTV = self.getLTV(true);
69: newLTV.targetTimestamp = uint48(block.timestamp + rampDuration);
70: newLTV.rampDuration = rampDuration;
71: newLTV.initialized = true;
72: }['27']
27: function set(Snapshot storage self, Assets cash, Assets borrows) internal { // <= FOUND
28: self.cash = cash;
29: self.borrows = borrows;
30: self.stamp = STAMP;
31: }['48']
48: function setBalanceForwarder(UserStorage storage self, bool newValue) internal { // <= FOUND
49: uint256 data = PackedUserSlot.unwrap(self.data);
50:
51: uint256 newFlag = newValue ? BALANCE_FORWARDER_MASK : 0;
52: self.data = PackedUserSlot.wrap(newFlag | (data & ~BALANCE_FORWARDER_MASK));
53: }['55']
55: function setOwed(UserStorage storage self, Owed owed) internal { // <= FOUND
56: uint256 data = PackedUserSlot.unwrap(self.data);
57:
58: self.data = PackedUserSlot.wrap((owed.toUint() << OWED_OFFSET) | (data & ~OWED_MASK));
59: }['61']
61: function setBalance(UserStorage storage self, Shares balance) internal { // <= FOUND
62: uint256 data = PackedUserSlot.unwrap(self.data);
63:
64: self.data = PackedUserSlot.wrap(balance.toUint() | (data & ~SHARES_MASK));
65: }['22']
22: function updateVault() internal virtual returns (VaultCache memory vaultCache) { // <= FOUND
23: if (initVaultCache(vaultCache)) {
24: vaultStorage.lastInterestAccumulatorUpdate = vaultCache.lastInterestAccumulatorUpdate;
25: vaultStorage.accumulatedFees = vaultCache.accumulatedFees;
26:
27: vaultStorage.totalShares = vaultCache.totalShares;
28: vaultStorage.totalBorrows = vaultCache.totalBorrows;
29:
30: vaultStorage.interestAccumulator = vaultCache.interestAccumulator;
31: }
32: }['217']
217: function updateInterestAndReturnESRSlotCache() public returns (ESRSlot memory) { // <= FOUND
218: ESRSlot memory esrSlotCache = esrSlot;
219: uint256 accruedInterest = interestAccruedFromCache(esrSlotCache);
220:
221:
222: esrSlotCache.interestLeft -= uint168(accruedInterest);
223: esrSlotCache.lastInterestUpdate = uint40(block.timestamp);
224:
225: esrSlot = esrSlotCache;
226:
227: _totalAssets = _totalAssets + accruedInterest;
228:
229: return esrSlotCache;
230: }['58']
58: function _update(address from, address to, uint256 value) internal virtual override { // <= FOUND
59: super._update(from, to, value);
60:
61: if (from != address(0)) {
62: evc.requireAccountStatusCheck(from);
63: }
64: }block.timestamp represents the current block's timestamp and can be influenced, within limits, by miners. For short time intervals, this malleability can be exploited, potentially allowing miners to manipulate contract behavior. For instance, they might fast-forward an expiration or delay an event. When designing smart contracts, if precise time checks are needed for short intervals, alternatives like block numbers can be considered. However, for longer durations where a few seconds of deviation is inconsequential, block.timestamp is generally safe and efficient. Always assess the implications of time manipulations for the specific use-case before utilizing block.timestamp. In practice, if you're using block.timestamp to measure intervals that are a matter of days, weeks, or longer, the potential manipulation by miners becomes less significant. Always prioritize the security and integrity of your smart contract operations when making these decisions.
Num of instances: 2
Click to show findings
['208']
208: esrSlotCache.interestSmearEnd = uint40(block.timestamp + INTEREST_SMEAR); // <= FOUND['69']
69: newLTV.targetTimestamp = uint48(block.timestamp + rampDuration); // <= FOUNDFunction parameters in Solidity are passed by value, meaning they are essentially local copies. Mutating them can lead to confusion and errors because the changes don't persist outside the function. By keeping function parameters immutable, you ensure clarity in code behavior, preventing unintended side-effects. If you need to modify a value based on a parameter, use a local variable inside the function, leaving the original parameter unaltered. By adhering to this practice, you maintain a clear distinction between input data and the internal processing logic, improving code readability and reducing the potential for bugs.
Num of instances: 3
Click to show findings
['75']
75: function transferBorrow(VaultCache memory vaultCache, address from, address to, Assets assets) internal virtual { // <= FOUND
76: Owed amount = assets.toOwed();
77:
78: (Owed fromOwed, Owed fromOwedPrev) = loadUserBorrow(vaultCache, from);
79:
80:
81: if (
82: (amount > fromOwed && amount.subUnchecked(fromOwed).isDust())
83: || (amount < fromOwed && fromOwed.subUnchecked(amount).isDust())
84: ) {
85: amount = fromOwed;
86: }
87:
88: if (amount > fromOwed) revert E_InsufficientDebt();
89:
90: fromOwed = fromOwed.subUnchecked(amount);
91: setUserBorrow(vaultCache, from, fromOwed);
92:
93: (Owed toOwed, Owed toOwedPrev) = loadUserBorrow(vaultCache, to);
94:
95: toOwed = toOwed + amount;
96: setUserBorrow(vaultCache, to, toOwed);
97:
98: logRepay(from, assets, fromOwedPrev.toAssetsUp(), fromOwed.toAssetsUp());
99:
100:
101: Assets toPrevAssets = toOwedPrev.toAssetsUp();
102: Assets toAssets = toOwed.toAssetsUp();
103: if (assets + toPrevAssets > toAssets) assets = toAssets - toPrevAssets; // <= FOUND
104: logBorrow(to, assets, toPrevAssets, toAssets);
105: }['116']
116: function createProxy(address desiredImplementation, bool upgradeable, bytes memory trailingData)
117: external
118: nonReentrant
119: returns (address)
120: {
121: address _implementation = implementation;
122: if (desiredImplementation == address(0)) desiredImplementation = _implementation; // <= FOUND
123:
124: if (desiredImplementation == address(0) || desiredImplementation != _implementation) revert E_Implementation();
125:
126: address proxy;
127:
128: if (upgradeable) {
129: proxy = address(new BeaconProxy(trailingData));
130: } else {
131: proxy = deployMetaProxy(desiredImplementation, trailingData);
132: }
133:
134: proxyLookup[proxy] =
135: ProxyConfig({upgradeable: upgradeable, implementation: desiredImplementation, trailingData: trailingData});
136:
137: proxyList.push(proxy);
138:
139: IComponent(proxy).initialize(msg.sender);
140:
141: emit ProxyCreated(proxy, upgradeable, desiredImplementation, trailingData);
142:
143: return proxy;
144: }['196']
196: function getProxyListSlice(uint256 start, uint256 end) external view returns (address[] memory list) { // <= FOUND
197: if (end == type(uint256).max) end = proxyList.length; // <= FOUND
198: if (end < start || end > proxyList.length) revert E_BadQuery();
199:
200: list = new address[](end - start);
201: for (uint256 i; i < end - start; ++i) {
202: list[i] = proxyList[start + i];
203: }
204: }Relying on Solidity's default arithmetic operator precedence can lead to misunderstood or overlooked nuances in code execution. Misinterpretation risks can be significant, especially when different developers or auditors review the code. To ensure clarity and minimize potential errors, it's recommended to use parentheses. These not only override the default precedence when needed but also emphasize the intended order of operations. By deliberately showing the intended calculation sequence using parentheses, developers make the code's logic explicit, reducing the risk of unintended behaviors and making the codebase more maintainable and understandable for all stakeholders.
Num of instances: 10
Click to show findings
['252']
252: return esrSlotCache.interestLeft * timePassed / totalDuration; // <= FOUND['17']
17: uint216 public constant MAX_RATE = 1e27 * 1.5 / SECONDS_PER_YEAR; // <= FOUND['18']
18: uint216 public constant BASE_RATE = 1e27 * 0.005 / SECONDS_PER_YEAR; // <= FOUND['90']
90:
91: rate = rate * ADJUST_FACTOR / ADJUST_ONE; // <= FOUND['93']
93:
94: rate = rate * ADJUST_ONE / ADJUST_FACTOR; // <= FOUND['108']
108: liqCache.yieldBalance = desiredRepay * liqCache.yieldBalance / maxRepay; // <= FOUND['130']
130:
131:
132:
133: uint256 discountFactor = collateralAdjustedValue * 1e18 / liabilityValue; // <= FOUND['158']
158: uint256 maxYieldValue = maxRepayValue * 1e18 / discountFactor; // <= FOUND['164']
164: maxRepayValue = collateralValue * discountFactor / 1e18; // <= FOUND['169']
169: liqCache.yieldBalance = maxYieldValue * collateralBalance / collateralValue; // <= FOUND[NonCritical-56] A event should be emitted if a non immutable state variable is set in a constructor
Num of instances: 3
Click to show findings
['39']
39: constructor(address synth_, address referenceAsset_, address oracle_, uint256 targetQuoute_) {
40: if (synth_ == address(0) || referenceAsset_ == address(0) || oracle_ == address(0)) {
41: revert E_ZeroAddress();
42: }
43:
44: synth = synth_;
45: referenceAsset = referenceAsset_;
46: oracle = IPriceOracle(oracle_);
47: targetQuote = targetQuoute_;
48: quoteAmount = 10 ** IERC20(synth_).decimals();
49:
50:
51: uint256 testQuote = IPriceOracle(oracle_).getQuote(quoteAmount, synth_, referenceAsset_);
52: if (testQuote == 0) {
53: revert E_InvalidQuote();
54: }
55:
56: irmStorage = IRMData({lastUpdated: uint40(block.timestamp), lastRate: BASE_RATE}); // <= FOUND
57: }['81']
81: constructor(address admin_, address feeReceiver_) {
82: if (admin_ == address(0)) revert E_InvalidAdmin();
83: if (feeReceiver_ == address(0)) revert E_InvalidReceiver();
84:
85: admin = admin_; // <= FOUND
86: feeReceiver = feeReceiver_; // <= FOUND
87:
88: minInterestFee = 0.1e4; // <= FOUND
89: maxInterestFee = 1e4; // <= FOUND
90: protocolFeeShare = 0.1e4; // <= FOUND
91: }['70']
70: constructor() {
71: initialized = true; // <= FOUND
72: }Num of instances: 1
Click to show findings
['29']
29: function initialize(address proxyCreator) public virtual reentrantOK {
30: if (initialized) revert E_Initialized();
31: initialized = true;
32:
33:
34:
35:
36: if (msg.data.length != 4 + 32 + PROXY_METADATA_LENGTH) revert E_ProxyMetadata();
37: (IERC20 asset,,) = ProxyUtils.metadata();
38:
39: AddressUtils.checkContract(address(asset));
40:
41:
42:
43:
44: address dToken = address(new DToken());
45:
46:
47:
48: vaultStorage.lastInterestAccumulatorUpdate = uint48(block.timestamp);
49: vaultStorage.interestAccumulator = INITIAL_INTEREST_ACCUMULATOR;
50: vaultStorage.interestFee = DEFAULT_INTEREST_FEE.toConfigAmount(); // <= FOUND
51: vaultStorage.creator = vaultStorage.governorAdmin = proxyCreator;
52:
53: {
54: string memory underlyingSymbol = getTokenSymbol(address(asset));
55: uint256 seqId = sequenceRegistry.reserveSeqId(underlyingSymbol);
56:
57: vaultStorage.symbol = string(abi.encodePacked("e", underlyingSymbol, "-", uintToString(seqId)));
58: vaultStorage.name = string(abi.encodePacked("EVK Vault ", vaultStorage.symbol));
59: }
60:
61: snapshot.reset();
62:
63:
64:
65: emit EVaultCreated(proxyCreator, address(asset), dToken);
66: logVaultStatus(loadVault(), 0);
67: }Non-constant or non-immutable state variables lacking a setter function can create inflexibility in contract operations. If there's no way to update these variables post-deployment, the contract might not adapt to changing conditions or requirements, which can be a significant drawback, especially in upgradable or long-lived contracts. To resolve this, implement setter functions guarded by appropriate access controls, like onlyOwner or similar modifiers, so that these variables can be updated as required while maintaining security. This enables smoother contract maintenance and feature upgrades.
Num of instances: 1
Num of instances: 2
Click to show findings
['12']
12: contract IRMLinearKink is IIRM // <= FOUND['15']
15: contract IRMSynth is IIRM // <= FOUNDA large number such as 2000000 is far more readable as 2_000_000, this will help prevent unintended bugs in the code
Num of instances: 4
Click to show findings
['17']
17: uint256 constant SECONDS_PER_YEAR = 365.2425 * 86400; // <= FOUND['19']
19: uint256 constant MAX_ALLOWED_INTEREST_RATE = 291867278914945094175; // <= FOUND['16']
16: uint216 internal constant SECONDS_PER_YEAR = 365.2425 * 86400; // <= FOUND['18']
18: uint216 public constant BASE_RATE = 1e27 * 0.005 / SECONDS_PER_YEAR; // <= FOUNDInconsistent use of floating pragma declarations, like ^ for a specific version or >=/<= for a range, can lead to version ambiguities and potential compatibility issues. Consistency in specifying compiler versions is crucial for ensuring predictable behavior across different environments. Using one consistent method, either fixed or ranged, throughout the project is recommended for clarity and safety.
Num of instances: 2
Click to show findings
['3']
3: pragma solidity ^0.8.0; // <= FOUND['3']
3: pragma solidity >=0.8.0; // <= FOUND[NonCritical-62] Use 'using' keyword when using specific imports rather than calling the specific import directly
In Solidity, the using keyword can streamline the use of library functions for specific types. Instead of calling library functions directly with their full import paths, you can declare a library once with using for a specific type. This approach makes your code more readable and concise. For example, instead of LibraryName.functionName(variable), you would first declare using LibraryName for TypeName; at the contract level. After this, you can call library functions directly on variables of TypeName like variable.functionName(). This method not only enhances code clarity but also promotes cleaner and more organized code, especially when multiple functions from the same library are used frequently.
Num of instances: 35
Click to show findings
['24']
24: return TypesLib.toShares(toSharesDownUint(amount, vaultCache)); // <= FOUND 'TypesLib.'['38']
38:
39: return TypesLib.toShares((amount.toUint() * totalShares + (totalAssets - 1)) / totalAssets); // <= FOUND 'TypesLib.'['44']
44: return TypesLib.toOwed(self.toUint() << INTERNAL_DEBT_PRECISION_SHIFT); // <= FOUND 'TypesLib.'['62']
62: return TypesLib.toAssets(a.toUint() + b.toUint()); // <= FOUND 'TypesLib.'['22']
22: return TypesLib.toAssets(toAssetsUpUint(Owed.unwrap(amount))); // <= FOUND 'TypesLib.'['28']
28: return TypesLib.toAssets(Owed.unwrap(amount) >> INTERNAL_DEBT_PRECISION_SHIFT); // <= FOUND 'TypesLib.'['41']
41: return TypesLib.toOwed(uint256(Owed.unwrap(self)) * multiplier / divisor); // <= FOUND 'TypesLib.'['62']
62: return TypesLib.toOwed(uint256(Owed.unwrap(a)) + uint256(Owed.unwrap(b))); // <= FOUND 'TypesLib.'['25']
25: return TypesLib.toAssets(amount.toUint() * totalAssets / totalShares); // <= FOUND 'TypesLib.'['33']
33:
34: return TypesLib.toAssets((amount.toUint() * totalAssets + (totalShares - 1)) / totalShares); // <= FOUND 'TypesLib.'['38']
38: return TypesLib.toShares(uint256(Shares.unwrap(self)) * multiplier / divisor); // <= FOUND 'TypesLib.'['49']
49: return TypesLib.toShares(uint256(Shares.unwrap(a)) + uint256(Shares.unwrap(b))); // <= FOUND 'TypesLib.'['43']
43: protocolConfig = IProtocolConfig(AddressUtils.checkContract(integrations.protocolConfig)); // <= FOUND 'AddressUtils.'['44']
44: sequenceRegistry = ISequenceRegistry(AddressUtils.checkContract(integrations.sequenceRegistry)); // <= FOUND 'AddressUtils.'['64']
64: MODULE_INITIALIZE = AddressUtils.checkContract(modules.initialize); // <= FOUND 'AddressUtils.'['65']
65: MODULE_TOKEN = AddressUtils.checkContract(modules.token); // <= FOUND 'AddressUtils.'['66']
66: MODULE_VAULT = AddressUtils.checkContract(modules.vault); // <= FOUND 'AddressUtils.'['67']
67: MODULE_BORROWING = AddressUtils.checkContract(modules.borrowing); // <= FOUND 'AddressUtils.'['68']
68: MODULE_LIQUIDATION = AddressUtils.checkContract(modules.liquidation); // <= FOUND 'AddressUtils.'['69']
69: MODULE_RISKMANAGER = AddressUtils.checkContract(modules.riskManager); // <= FOUND 'AddressUtils.'['70']
70: MODULE_BALANCE_FORWARDER = AddressUtils.checkContract(modules.balanceForwarder); // <= FOUND 'AddressUtils.'['71']
71: MODULE_GOVERNANCE = AddressUtils.checkContract(modules.governance); // <= FOUND 'AddressUtils.'['32']
32: evc = IEVC(AddressUtils.checkContract(_evc)); // <= FOUND 'AddressUtils.'['39']
39:
40: AddressUtils.checkContract(address(asset)); // <= FOUND 'AddressUtils.'['23']
23: (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache); // <= FOUND 'ConversionHelpers.'['111']
111:
112:
113:
114:
115:
116:
117:
118: uint256 newTotalAssets = vaultCache.cash.toUint() + OwedLib.toAssetsUpUint(newTotalBorrows); // <= FOUND 'OwedLib.'['69']
69:
70:
71:
72:
73: if (msg.sender != hookTarget && !(msg.sender == address(this) && ProxyUtils.useViewCaller() == hookTarget)) // <= FOUND 'ProxyUtils.'
74: {['151']
151: (IERC20 asset,,) = ProxyUtils.metadata(); // <= FOUND 'ProxyUtils.'['47']
47:
48:
49: (vaultCache.asset, vaultCache.oracle, vaultCache.unitOfAccount) = ProxyUtils.metadata(); // <= FOUND 'ProxyUtils.'['200']
200: (,, address _unitOfAccount) = ProxyUtils.metadata(); // <= FOUND 'ProxyUtils.'['206']
206: (, IPriceOracle _oracle,) = ProxyUtils.metadata(); // <= FOUND 'ProxyUtils.'['24']
24: (IERC20 _asset,,) = ProxyUtils.metadata(); // <= FOUND 'ProxyUtils.'['82']
82: (uint256 multiplier, bool overflow) = RPow.rpow(interestRate + 1e27, deltaT, 1e27); // <= FOUND 'RPow.'['134']
134: if (!success) RevertBytes.revertBytes(data); // <= FOUND 'RevertBytes.'['47']
47: if (!isEmptyOrTrueReturn(success, data)) RevertBytes.revertBytes(data); // <= FOUND 'RevertBytes.'Only some address parameters are checked against address(0), to ensure consistency ensure all address parameters are checked.
Num of instances: 6
Click to show findings
['16']
16: function increaseBalance(
17: VaultCache memory vaultCache,
18: address account, // <= FOUND 'address account'
19: address sender, // <= FOUND 'address sender'
20: Shares amount,
21: Assets assets
22: ) internal virtual {
23: if (account == address(0)) revert E_BadSharesReceiver();
24: UserStorage storage user = vaultStorage.users[account];
25:
26: (Shares origBalance, bool balanceForwarderEnabled) = user.getBalanceAndBalanceForwarder();
27: Shares newBalance = origBalance + amount;
28:
29: user.setBalance(newBalance);
30: vaultStorage.totalShares = vaultCache.totalShares = vaultCache.totalShares + amount;
31:
32: if (balanceForwarderEnabled) {
33: balanceTracker.balanceTrackerHook(account, newBalance.toUint(), false);
34: }
35:
36: emit Transfer(address(0), account, amount.toUint());
37: emit Deposit(sender, account, assets.toUint(), amount.toUint());
38: }['127']
127: function invokeHookTarget(address caller) private { // <= FOUND ' function invokeHookTarget(address caller) private '
128: address hookTarget = vaultStorage.hookTarget;
129:
130: if (hookTarget == address(0)) revert E_OperationDisabled();
131:
132: (bool success, bytes memory data) = hookTarget.call(abi.encodePacked(msg.data, caller));
133:
134: if (!success) RevertBytes.revertBytes(data);
135: }['58']
58: function _update(address from, address to, uint256 value) internal virtual override { // <= FOUND 'address from'
59: super._update(from, to, value);
60:
61: if (from != address(0)) {
62: evc.requireAccountStatusCheck(from);
63: }
64: }['83']
83: function isKnownNonOwnerAccount(address account) internal view returns (bool) { // <= FOUND ' function isKnownNonOwnerAccount(address account) internal view returns (bool) '
84: address owner = evc.getAccountOwner(account); // <= FOUND 'address owner'
85: return owner != address(0) && owner != account;
86: }['20']
20: function deployMetaProxy(address targetContract, bytes memory metadata) internal returns (address addr) { // <= FOUND ' function deployMetaProxy(address targetContract, bytes memory metadata) internal returns (address addr) '
21: bytes memory code = abi.encodePacked(BYTECODE_HEAD, targetContract, BYTECODE_TAIL, metadata);
22:
23: assembly ("memory-safe") {
24: addr := create(0, add(code, 32), mload(code))
25: }
26:
27: if (addr == address(0)) revert E_DeploymentFailed();
28: }['28']
28: function safeTransferFrom(IERC20 token, address from, address to, uint256 value, address permit2) internal { // <= FOUND 'address from'
29: (bool success, bytes memory tryData) = trySafeTransferFrom(token, from, to, value);
30: bytes memory fallbackData;
31: if (!success && permit2 != address(0)) {
32: if (value > type(uint160).max) {
33: revert E_TransferFromFailed(tryData, abi.encodePacked(E_Permit2AmountOverflow.selector));
34: }
35:
36: (success, fallbackData) =
37: permit2.call(abi.encodeCall(IPermit2.transferFrom, (from, to, uint160(value), address(token))));
38: }
39:
40: if (!success) revert E_TransferFromFailed(tryData, fallbackData);
41: }Having such variables can create confusion in both developers and in users of the project. Consider renaming these variables to improve code clarity.
Num of instances: 36
Click to show findings
['75']
75: uint256 interestRate = vaultStorage.interestRate; // <= FOUND['118']
118: uint256 allowance = user.eTokenAllowance[spender]; // <= FOUND['52']
52: uint256 totalAssets = cash + borrows; // <= FOUND['44']
44:
45:
46:
47:
48: address dToken = address(new DToken()); // <= FOUND['59']
59:
67: event VaultStatus(
68: uint256 totalShares,
69: uint256 totalBorrows, // <= FOUND
70: uint256 accumulatedFees, // <= FOUND
71: uint256 cash, // <= FOUND
72: uint256 interestAccumulator, // <= FOUND
73: uint256 interestRate, // <= FOUND
74: uint256 timestamp
75: );['17']
17:
22: function computeInterestRate(address vault, uint256 cash, uint256 borrows) external returns (uint256); // <= FOUND['24']
24:
29: function computeInterestRateView(address vault, uint256 cash, uint256 borrows) external view returns (uint256); // <= FOUND['30']
30:
31: function computeInterestRate(address vault, uint256 cash, uint256 borrows) // <= FOUND
32: external
33: view
34: override
35: returns (uint256)
36: {['42']
42:
43: function computeInterestRateView(address vault, uint256 cash, uint256 borrows) // <= FOUND
44: external
45: view
46: override
47: returns (uint256)
48: {['51']
51: function computeInterestRateInternal(address, uint256 cash, uint256 borrows) internal view returns (uint256) { // <= FOUND['23']
23: (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache); // <= FOUND['15']
15: function conversionTotals(VaultCache memory vaultCache)
16: internal
17: pure
18: returns (uint256 totalAssets, uint256 totalShares) // <= FOUND
19: {['72']
72:
75: event SetProtocolFeeShare(uint16 protocolFeeShare, uint16 newProtocolFeeShare); // <= FOUND['35']
35:
36: uint256 interestAccumulator; // <= FOUND['23']
23: uint16 protocolFeeShare; // <= FOUND['41']
41:
42: uint16 liquidationCoolOffTime; // <= FOUND['53']
53: address initialize; // <= FOUND['62']
62:
63: address creator; // <= FOUND['65']
65:
66: address governorAdmin; // <= FOUND['22']
22: address feeReceiver; // <= FOUND['22']
22:
23: address feeReceiver; // <= FOUND['50']
50:
52: address interestRateModel; // <= FOUND['20']
20:
21: address unitOfAccount; // <= FOUND['57']
57:
58: string name; // <= FOUND['59']
59:
60: string symbol; // <= FOUND['141']
141: function logVaultStatus(VaultCache memory a, uint256 interestRate) internal { // <= FOUND['18']
18:
19: mapping(address spender => uint256 allowance) eTokenAllowance; // <= FOUND['18']
18:
26: function isValidInterestFee(address vault, uint16 interestFee) external view returns (bool); // <= FOUND['94']
94:
95: function isValidInterestFee(address vault, uint16 interestFee) external view returns (bool) { // <= FOUND['67']
67:
72: event SetFeeConfigSetting(address indexed vault, bool exists, address indexed feeReceiver, uint16 protocolFeeShare); // <= FOUND['152']
152: function calculateDTokenAddress() internal view virtual returns (address dToken) { // <= FOUND['49']
49:
55: event EVaultCreated(address indexed creator, address indexed asset, address dToken); // <= FOUND['76']
76:
78: function getTokenSymbol(address asset) private view returns (string memory) { // <= FOUND['13']
13:
15: function initialize(address creator) external; // <= FOUND['15']
15: function metadata() internal pure returns (IERC20 asset, IPriceOracle oracle, address unitOfAccount) { // <= FOUND['26']
26: (Shares origBalance, bool balanceForwarderEnabled) = user.getBalanceAndBalanceForwarder(); // <= FOUNDEmitting an event in a constructor of a smart contract provides transparency and traceability in blockchain applications. This event logs the contract’s creation, aiding in monitoring and verifying contract deployment. Although constructors are executed only once, the emitted event ensures the contract's initialization is recorded on the blockchain.
Num of instances: 10
Click to show findings
['42']
42: constructor(Integrations memory integrations) EVCClient(integrations.evc) { // <= FOUND
43: protocolConfig = IProtocolConfig(AddressUtils.checkContract(integrations.protocolConfig));
44: sequenceRegistry = ISequenceRegistry(AddressUtils.checkContract(integrations.sequenceRegistry));
45: balanceTracker = IBalanceTracker(integrations.balanceTracker);
46: permit2 = integrations.permit2;
47: }['17']
17: constructor() { // <= FOUND
18: eVault = msg.sender;
19: }['63']
63: constructor(Integrations memory integrations, DeployedModules memory modules) Base(integrations) { // <= FOUND
64: MODULE_INITIALIZE = AddressUtils.checkContract(modules.initialize);
65: MODULE_TOKEN = AddressUtils.checkContract(modules.token);
66: MODULE_VAULT = AddressUtils.checkContract(modules.vault);
67: MODULE_BORROWING = AddressUtils.checkContract(modules.borrowing);
68: MODULE_LIQUIDATION = AddressUtils.checkContract(modules.liquidation);
69: MODULE_RISKMANAGER = AddressUtils.checkContract(modules.riskManager);
70: MODULE_BALANCE_FORWARDER = AddressUtils.checkContract(modules.balanceForwarder);
71: MODULE_GOVERNANCE = AddressUtils.checkContract(modules.governance);
72: }['31']
31: constructor(address _evc) { // <= FOUND
32: evc = IEVC(AddressUtils.checkContract(_evc));
33: }['58']
58: constructor(IEVC _evc, address _asset, string memory _name, string memory _symbol) // <= FOUND
59: EVCUtil(address(_evc))
60: ERC4626(IERC20(_asset))
61: ERC20(_name, _symbol)
62: {
63: esrSlot.locked = UNLOCKED;
64: }['22']
22: constructor(uint256 baseRate_, uint256 slope1_, uint256 slope2_, uint256 kink_) { // <= FOUND
23: baseRate = baseRate_;
24: slope1 = slope1_;
25: slope2 = slope2_;
26: kink = kink_;
27: }['39']
39: constructor(address synth_, address referenceAsset_, address oracle_, uint256 targetQuoute_) { // <= FOUND
40: if (synth_ == address(0) || referenceAsset_ == address(0) || oracle_ == address(0)) {
41: revert E_ZeroAddress();
42: }
43:
44: synth = synth_;
45: referenceAsset = referenceAsset_;
46: oracle = IPriceOracle(oracle_);
47: targetQuote = targetQuoute_;
48: quoteAmount = 10 ** IERC20(synth_).decimals();
49:
50:
51: uint256 testQuote = IPriceOracle(oracle_).getQuote(quoteAmount, synth_, referenceAsset_);
52: if (testQuote == 0) {
53: revert E_InvalidQuote();
54: }
55:
56: irmStorage = IRMData({lastUpdated: uint40(block.timestamp), lastRate: BASE_RATE});
57: }['70']
70: constructor() { // <= FOUND
71: initialized = true;
72: }['42']
42: constructor( // <= FOUND
43: address _evc,
44: address _synth,
45: address _underlying,
46: uint256 toUnderlyingFeeBPS,
47: uint256 toSynthFeeBPS,
48: uint256 _conversionPrice
49: ) EVCUtil(_evc) {
50: if (toUnderlyingFeeBPS >= BPS_SCALE || toSynthFeeBPS >= BPS_SCALE) {
51: revert E_FeeExceedsBPS();
52: }
53:
54: if (_evc == address(0) || _synth == address(0) || _underlying == address(0)) {
55: revert E_ZeroAddress();
56: }
57:
58: synth = ESynth(_synth);
59: underlying = IERC20(_underlying);
60: TO_UNDERLYING_FEE = toUnderlyingFeeBPS;
61: TO_SYNTH_FEE = toSynthFeeBPS;
62: conversionPrice = _conversionPrice;
63: }['81']
81: constructor(address admin_, address feeReceiver_) { // <= FOUND
82: if (admin_ == address(0)) revert E_InvalidAdmin();
83: if (feeReceiver_ == address(0)) revert E_InvalidReceiver();
84:
85: admin = admin_;
86: feeReceiver = feeReceiver_;
87:
88: minInterestFee = 0.1e4;
89: maxInterestFee = 1e4;
90: protocolFeeShare = 0.1e4;
91: }Using a fixed compiler version in Solidity contract and abstract files ensures consistency and predictability in smart contract behavior. A fixed version prevents unforeseen issues arising from compiler updates, which might introduce breaking changes or new bugs. It guarantees that the contract's behavior remains stable and consistent with the version used during its development and testing.
Num of instances: 41
Click to show findings
[]
57: contract BalanceForwarder is BalanceForwarderModule []
10: contract BeaconProxy []
169: contract Borrowing is BorrowingModule []
17: contract Cache is Storage, Errors []
13: contract DToken is IERC20, Errors, Events []
17: contract ESynth is ERC20Collateral, Ownable []
12: contract EVault is Dispatch []
9: contract Errors []
21: contract EulerSavingsRate is EVCUtil, ERC4626 []
21: contract GenericFactory is MetaProxyDeployer []
404: contract Governance is GovernanceModule []
12: contract IRMLinearKink is IIRM []
15: contract IRMSynth is IIRM []
105: contract Initialize is InitializeModule []
237: contract Liquidation is LiquidationModule []
11: contract MetaProxyDeployer []
19: contract PegStabilityModule is EVCUtil []
7: contract ProtocolConfig is IProtocolConfig []
125: contract RiskManager is RiskManagerModule []
14: contract SequenceRegistry is ISequenceRegistry []
101: contract Token is TokenModule []
273: contract Vault is VaultModule []
14: abstract contract AssetTransfers is Base []
14: abstract contract BalanceForwarderModule is IBalanceForwarder, Base []
13: abstract contract BalanceUtils is Base []
21: abstract contract Base is EVCClient, Cache []
15: abstract contract BorrowUtils is Base []
20: abstract contract BorrowingModule is IBorrowing, AssetTransfers, BalanceUtils, LiquidityUtils []
23: abstract contract Dispatch is
24: InitializeModule,
25: TokenModule,
26: VaultModule,
27: BorrowingModule,
28: LiquidationModule,
29: RiskManagerModule,
30: BalanceForwarderModule,
31: GovernanceModule
32: []
15: abstract contract ERC20Collateral is EVCUtil, ERC20Permit, ReentrancyGuard []
20: abstract contract EVCClient is Storage, Events, Errors []
9: abstract contract Events []
20: abstract contract GovernanceModule is IGovernance, BalanceUtils, BorrowUtils, LTVUtils []
20: abstract contract InitializeModule is IInitialize, BorrowUtils []
12: abstract contract LTVUtils is Storage []
16: abstract contract LiquidationModule is ILiquidation, BalanceUtils, LiquidityUtils []
14: abstract contract LiquidityUtils is BorrowUtils, LTVUtils []
15: abstract contract RiskManagerModule is IRiskManager, LiquidityUtils []
11: abstract contract Storage []
16: abstract contract TokenModule is IToken, BalanceUtils []
18: abstract contract VaultModule is IVault, AssetTransfers, BalanceUtils Failing to validate for empty array inputs in constructors can lead to vulnerabilities or logical errors in smart contracts. Constructors often initialize key contract settings, and arrays may represent crucial data like access controls, initial states, or configuration parameters. Without empty array checks, a contract might be deployed in an unintended state, lacking necessary data for its operations or security measures.
Num of instances: 3
Click to show findings
['16']
16: constructor(IEVC _evc_, string memory _name_, string memory _symbol_)
17: EVCUtil(address(_evc_))
18: ERC20(_name_, _symbol_)
19: ERC20Permit(_name_)
20: {}['33']
33: constructor(IEVC evc_, string memory name_, string memory symbol_)
34: ERC20Collateral(evc_, name_, symbol_)
35: Ownable(msg.sender)
36: {}['58']
58: constructor(IEVC _evc, address _asset, string memory _name, string memory _symbol)
59: EVCUtil(address(_evc))
60: ERC4626(IERC20(_asset))
61: ERC20(_name, _symbol)
62: {
63: esrSlot.locked = UNLOCKED;
64: }In Solidity, events are crucial for logging and informing external consumers about smart contract activities. Using parameters in events is essential for conveying detailed information about these activities. Parameters enable developers to specify what data is emitted, facilitating efficient and targeted data retrieval by front-end applications or off-chain monitoring tools. Well-defined event parameters improve transparency, enable better data analysis, and enhance the contract's interface with the external world, making interactions with the blockchain more informative and accessible.
Num of instances: 2
Click to show findings
['56']
56: event Genesis(); // <= FOUND['56']
56:
59: event Genesis(); // <= FOUNDIn Solidity, custom errors with parameters offer a gas-efficient way to convey detailed information about issues encountered during contract execution. Unlike revert messages, which are strings consuming more gas, custom errors defined with parameters allow developers to specify types and details of errors succinctly. This method enhances debugging, provides clearer insights into contract failures, and improves the developer's and end-user's understanding of what went wrong, all while optimizing for gas usage and maintaining contract efficiency.
Num of instances: 129
Click to show findings
['30']
30: error E_CapacityReached(); // <= FOUND['31']
31: error E_NotEVCCompatible(); // <= FOUND['10']
10: error E_Initialized(); // <= FOUND['11']
11: error E_ProxyMetadata(); // <= FOUND['12']
12: error E_SelfApproval(); // <= FOUND['13']
13: error E_SelfTransfer(); // <= FOUND['14']
14: error E_InsufficientAllowance(); // <= FOUND['15']
15: error E_InsufficientCash(); // <= FOUND['16']
16: error E_InsufficientAssets(); // <= FOUND['17']
17: error E_InsufficientBalance(); // <= FOUND['18']
18: error E_InsufficientDebt(); // <= FOUND['19']
19: error E_FlashLoanNotRepaid(); // <= FOUND['75']
75: error E_Reentrancy(); // <= FOUND['21']
21: error E_OperationDisabled(); // <= FOUND['22']
22: error E_OutstandingDebt(); // <= FOUND['23']
23: error E_AmountTooLargeToEncode(); // <= FOUND['24']
24: error E_DebtAmountTooLargeToEncode(); // <= FOUND['25']
25: error E_RepayTooMuch(); // <= FOUND['26']
26: error E_TransientState(); // <= FOUND['27']
27: error E_SelfLiquidation(); // <= FOUND['28']
28: error E_ControllerDisabled(); // <= FOUND['29']
29: error E_CollateralDisabled(); // <= FOUND['30']
30: error E_ViolatorLiquidityDeferred(); // <= FOUND['31']
31: error E_LiquidationCoolOff(); // <= FOUND['32']
32: error E_ExcessiveRepayAmount(); // <= FOUND['33']
33: error E_MinYield(); // <= FOUND['78']
78: error E_BadAddress(); // <= FOUND['35']
35: error E_ZeroAssets(); // <= FOUND['36']
36: error E_ZeroShares(); // <= FOUND['76']
76: error E_Unauthorized(); // <= FOUND['38']
38: error E_CheckUnauthorized(); // <= FOUND['39']
39: error E_NotSupported(); // <= FOUND['40']
40: error E_EmptyError(); // <= FOUND['41']
41: error E_BadBorrowCap(); // <= FOUND['42']
42: error E_BadSupplyCap(); // <= FOUND['43']
43: error E_BadCollateral(); // <= FOUND['44']
44: error E_AccountLiquidity(); // <= FOUND['45']
45: error E_NoLiability(); // <= FOUND['46']
46: error E_NotController(); // <= FOUND['47']
47: error E_BadFee(); // <= FOUND['48']
48: error E_SupplyCapExceeded(); // <= FOUND['49']
49: error E_BorrowCapExceeded(); // <= FOUND['50']
50: error E_InvalidLTVAsset(); // <= FOUND['51']
51: error E_NoPriceOracle(); // <= FOUND['52']
52: error E_ConfigAmountTooLargeToEncode(); // <= FOUND['53']
53: error E_BadAssetReceiver(); // <= FOUND['54']
54: error E_BadSharesOwner(); // <= FOUND['55']
55: error E_BadSharesReceiver(); // <= FOUND['56']
56: error E_LTVBorrow(); // <= FOUND['57']
57: error E_LTVLiquidation(); // <= FOUND['58']
58: error E_NotHookTarget(); // <= FOUND['40']
40: error Reentrancy(); // <= FOUND['75']
75:
77: error E_Reentrancy(); // <= FOUND['77']
77: error E_Implementation(); // <= FOUND['79']
79: error E_BadQuery(); // <= FOUND['10']
10: error E_IRMUpdateUnauthorized(); // <= FOUND['36']
36: error E_ZeroAddress(); // <= FOUND['37']
37: error E_InvalidQuote(); // <= FOUND['12']
12: error E_DeploymentFailed(); // <= FOUND['33']
33: error E_FeeExceedsBPS(); // <= FOUND['8']
8: error E_OnlyAdmin(); // <= FOUND['9']
9: error E_InvalidVault(); // <= FOUND['10']
10: error E_InvalidReceiver(); // <= FOUND['11']
11: error E_InvalidConfigValue(); // <= FOUND['12']
12: error E_InvalidAdmin(); // <= FOUND['15']
15: error E_Permit2AmountOverflow(); // <= FOUND['30']
30: error E_CapacityReached(); // <= FOUND['31']
31: error E_NotEVCCompatible(); // <= FOUND['10']
10: error E_Initialized(); // <= FOUND['11']
11: error E_ProxyMetadata(); // <= FOUND['12']
12: error E_SelfApproval(); // <= FOUND['13']
13: error E_SelfTransfer(); // <= FOUND['14']
14: error E_InsufficientAllowance(); // <= FOUND['15']
15: error E_InsufficientCash(); // <= FOUND['16']
16: error E_InsufficientAssets(); // <= FOUND['17']
17: error E_InsufficientBalance(); // <= FOUND['18']
18: error E_InsufficientDebt(); // <= FOUND['19']
19: error E_FlashLoanNotRepaid(); // <= FOUND['75']
75: error E_Reentrancy(); // <= FOUND['21']
21: error E_OperationDisabled(); // <= FOUND['22']
22: error E_OutstandingDebt(); // <= FOUND['23']
23: error E_AmountTooLargeToEncode(); // <= FOUND['24']
24: error E_DebtAmountTooLargeToEncode(); // <= FOUND['25']
25: error E_RepayTooMuch(); // <= FOUND['26']
26: error E_TransientState(); // <= FOUND['27']
27: error E_SelfLiquidation(); // <= FOUND['28']
28: error E_ControllerDisabled(); // <= FOUND['29']
29: error E_CollateralDisabled(); // <= FOUND['30']
30: error E_ViolatorLiquidityDeferred(); // <= FOUND['31']
31: error E_LiquidationCoolOff(); // <= FOUND['32']
32: error E_ExcessiveRepayAmount(); // <= FOUND['33']
33: error E_MinYield(); // <= FOUND['78']
78: error E_BadAddress(); // <= FOUND['35']
35: error E_ZeroAssets(); // <= FOUND['36']
36: error E_ZeroShares(); // <= FOUND['76']
76: error E_Unauthorized(); // <= FOUND['38']
38: error E_CheckUnauthorized(); // <= FOUND['39']
39: error E_NotSupported(); // <= FOUND['40']
40: error E_EmptyError(); // <= FOUND['41']
41: error E_BadBorrowCap(); // <= FOUND['42']
42: error E_BadSupplyCap(); // <= FOUND['43']
43: error E_BadCollateral(); // <= FOUND['44']
44: error E_AccountLiquidity(); // <= FOUND['45']
45: error E_NoLiability(); // <= FOUND['46']
46: error E_NotController(); // <= FOUND['47']
47: error E_BadFee(); // <= FOUND['48']
48: error E_SupplyCapExceeded(); // <= FOUND['49']
49: error E_BorrowCapExceeded(); // <= FOUND['50']
50: error E_InvalidLTVAsset(); // <= FOUND['51']
51: error E_NoPriceOracle(); // <= FOUND['52']
52: error E_ConfigAmountTooLargeToEncode(); // <= FOUND['53']
53: error E_BadAssetReceiver(); // <= FOUND['54']
54: error E_BadSharesOwner(); // <= FOUND['55']
55: error E_BadSharesReceiver(); // <= FOUND['56']
56: error E_LTVBorrow(); // <= FOUND['57']
57: error E_LTVLiquidation(); // <= FOUND['58']
58: error E_NotHookTarget(); // <= FOUND['40']
40: error Reentrancy(); // <= FOUND['77']
77: error E_Implementation(); // <= FOUND['79']
79: error E_BadQuery(); // <= FOUND['36']
36: error E_ZeroAddress(); // <= FOUND['37']
37: error E_InvalidQuote(); // <= FOUND['12']
12: error E_DeploymentFailed(); // <= FOUND['33']
33: error E_FeeExceedsBPS(); // <= FOUND['8']
8: error E_OnlyAdmin(); // <= FOUND['9']
9: error E_InvalidVault(); // <= FOUND['10']
10: error E_InvalidReceiver(); // <= FOUND['11']
11: error E_InvalidConfigValue(); // <= FOUND['12']
12: error E_InvalidAdmin(); // <= FOUND[NonCritical-70] Consider using OpenZeppelins SafeCall library when making calls to arbitrary contracts
Using OpenZeppelin's SafeCall library for interactions with arbitrary contracts is a best practice in smart contract development. This library provides functions that ensure safer external calls by validating that calls are successfully completed, helping to prevent common pitfalls such as reentrancy attacks or unexpected failures. It encapsulates low-level call operations with safety checks, reducing the risk of vulnerabilities associated with direct interactions with unknown code. Incorporating SafeCall enhances contract security and robustness, making it a wise choice for developers aiming to safeguard their applications.
Num of instances: 3
Click to show findings
['127']
127: function invokeHookTarget(address caller) private { // <= FOUND
128: address hookTarget = vaultStorage.hookTarget;
129:
130: if (hookTarget == address(0)) revert E_OperationDisabled();
131:
132: (bool success, bytes memory data) = hookTarget.call(abi.encodePacked(msg.data, caller)); // <= FOUND
133:
134: if (!success) RevertBytes.revertBytes(data);
135: }['107']
107: function computeInterestRate(VaultCache memory vaultCache) internal virtual returns (uint256) { // <= FOUND
108:
109: address irm = vaultStorage.interestRateModel;
110: uint256 newInterestRate = vaultStorage.interestRate;
111:
112: if (irm != address(0)) {
113: (bool success, bytes memory data) = irm.call( // <= FOUND
114: abi.encodeCall(
115: IIRM.computeInterestRate,
116: (address(this), vaultCache.cash.toUint(), vaultCache.totalBorrows.toAssetsUp().toUint())
117: )
118: );
119:
120: if (success && data.length >= 32) {
121: newInterestRate = abi.decode(data, (uint256));
122: if (newInterestRate > MAX_ALLOWED_INTEREST_RATE) newInterestRate = MAX_ALLOWED_INTEREST_RATE;
123: vaultStorage.interestRate = uint72(newInterestRate);
124: }
125: }
126:
127: return newInterestRate;
128: }['28']
28: function safeTransferFrom(IERC20 token, address from, address to, uint256 value, address permit2) internal { // <= FOUND
29: (bool success, bytes memory tryData) = trySafeTransferFrom(token, from, to, value);
30: bytes memory fallbackData;
31: if (!success && permit2 != address(0)) {
32: if (value > type(uint160).max) {
33: revert E_TransferFromFailed(tryData, abi.encodePacked(E_Permit2AmountOverflow.selector));
34: }
35:
36: (success, fallbackData) =
37: permit2.call(abi.encodeCall(IPermit2.transferFrom, (from, to, uint160(value), address(token)))); // <= FOUND
38: }
39:
40: if (!success) revert E_TransferFromFailed(tryData, fallbackData);
41: }Using 'owner' or '_owner' as a parameter name in functions, especially in contracts inheriting from or interacting with OpenZeppelin's Ownable contract, can lead to confusion and potential bugs. These contracts often have a state variable named owner, managed by the Ownable pattern for access control. Using the same name for function parameters may obscure the contract's owner state variable, complicating code readability and maintenance. Prefer alternative descriptive names for parameters to maintain clarity and prevent conflicts with established ownership patterns.
Num of instances: 18
Click to show findings
['106']
106: function setAllowance(address owner, address spender, uint256 amount) internal // <= FOUND['114']
114: function decreaseAllowance(address owner, address spender, Shares amount) internal virtual // <= FOUND['50']
50: function balanceOf(address owner) external view returns (uint256) // <= FOUND['71']
71: function maxWithdraw(address owner) public view virtual override useView(MODULE_VAULT) returns (uint256) // <= FOUND['75']
75: function maxRedeem(address owner) public view virtual override useView(MODULE_VAULT) returns (uint256) // <= FOUND['90']
90: function withdraw(uint256 amount, address receiver, address owner) public virtual override callThroughEVC use(MODULE_VAULT) returns (uint256) // <= FOUND['92']
92: function redeem(uint256 amount, address receiver, address owner) public virtual override callThroughEVC use(MODULE_VAULT) returns (uint256) // <= FOUND['72']
72: function maxRedeem(address owner) public view override returns (uint256) // <= FOUND['86']
86: function maxWithdraw(address owner) public view override returns (uint256) // <= FOUND['149']
149: function withdraw(uint256 assets, address receiver, address owner) // <= FOUND
150: public
151: override
152: nonReentrant
153: requireAccountStatusCheck(owner)
154: returns (uint256)
155: ['165']
165: function redeem(uint256 shares, address receiver, address owner) // <= FOUND
166: public
167: override
168: nonReentrant
169: requireAccountStatusCheck(owner)
170: returns (uint256)
171: ['190']
190: function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares) // <= FOUND
191: internal
192: override
193: ['77']
77: function maxWithdraw(address owner) public view virtual nonReentrantView returns (uint256) // <= FOUND['92']
92: function maxRedeem(address owner) public view virtual nonReentrantView returns (uint256) // <= FOUND['153']
153: function withdraw(uint256 amount, address receiver, address owner) public virtual nonReentrant returns (uint256) // <= FOUND['167']
167: function redeem(uint256 amount, address receiver, address owner) public virtual nonReentrant returns (uint256) // <= FOUND['218']
218: function finalizeWithdraw(
219: VaultCache memory vaultCache,
220: Assets assets,
221: Shares shares,
222: address sender,
223: address receiver,
224: address owner // <= FOUND
225: ) private ['234']
234: function maxRedeemInternal(VaultCache memory vaultCache, address owner) private view returns (Shares) // <= FOUNDAvoiding arithmetic directly within array indices, like test[i + 2], is recommended to prevent errors such as index out of bounds or incorrect data access. This practice enhances code readability and maintainability. Instead, calculate the index beforehand, store it in a variable, and then use that variable to access the array element. This approach reduces mistakes, especially in complex loops or when handling dynamic data, ensuring that each access is clear and verifiable. It's about keeping code clean and understandable, minimizing potential bugs.
Num of instances: 1
Tagging assembly blocks as "memory safe" in Solidity helps maintainers and auditors quickly identify areas of the code less likely to introduce vulnerabilities due to improper memory access. This practice promotes safer contract development by clearly communicating assumptions about memory handling, aiding in the review process, and reducing the risk of introducing memory-related bugs. It's a part of best coding practices, enhancing code clarity and security, especially in complex, low-level operations where Solidity's safety checks are bypassed.
Num of instances: 3
Click to show findings
['16']
16: assembly {
17: asset := shr(96, calldataload(sub(calldatasize(), 60)))
18: oracle := shr(96, calldataload(sub(calldatasize(), 40)))
19: unitOfAccount := shr(96, calldataload(sub(calldatasize(), 20)))
20: }['26']
26: assembly {
27: viewCaller := shr(96, calldataload(sub(calldatasize(), add(PROXY_METADATA_LENGTH, 20))))
28: }['14']
14: assembly {
15: switch x
16: case 0 {
17: switch n
18: case 0 {
19:
20: z := scalar
21: }
22: default {
23:
24: z := 0
25: }
26: }
27: default {
28: switch mod(n, 2)
29: case 0 {
30:
31: z := scalar
32: }
33: default {
34:
35: z := x
36: }
37:
38:
39: let half := shr(1, scalar)
40:
41: for {
42:
43: n := shr(1, n)
44: } n {
45:
46: n := shr(1, n)
47: } {
48:
49:
50: if shr(128, x) {
51: overflow := 1
52: break
53: }
54:
55:
56: let xx := mul(x, x)
57:
58:
59: let xxRound := add(xx, half)
60:
61:
62: if lt(xxRound, xx) {
63: overflow := 1
64: break
65: }
66:
67:
68: x := div(xxRound, scalar)
69:
70:
71: if mod(n, 2) {
72:
73: let zx := mul(z, x)
74:
75:
76: if iszero(eq(div(zx, x), z)) {
77:
78: if iszero(iszero(x)) {
79: overflow := 1
80: break
81: }
82: }
83:
84:
85: let zxRound := add(zx, half)
86:
87:
88: if lt(zxRound, zx) {
89: overflow := 1
90: break
91: }
92:
93:
94: z := div(zxRound, scalar)
95: }
96: }
97: }
98: }Pure functions by definition should not read or write to storage (state variables), however due to a bug with storage pointers this can slip past the compiler and thus a pure function can at times read storage whilst being declared as pure. Below are such instances
Num of instances: 1
Click to show findings
['15']
15: function metadata() internal pure returns (IERC20 asset, IPriceOracle oracle, address unitOfAccount) { // <= FOUND
16: assembly {
17: asset := shr(96, calldataload(sub(calldatasize(), 60)))
18: oracle := shr(96, calldataload(sub(calldatasize(), 40))) // <= FOUND
19: unitOfAccount := shr(96, calldataload(sub(calldatasize(), 20)))
20: }
21: }Functions which only return a predefined constant have a similar redundancy as empty code blocks. If these blocks are not used to satisfy interface enforcements, or similar rulings, they should be removed.
Num of instances: 1
Click to show findings
['57']
57: function allowance(address, address) external pure returns (uint256) {
58: return 0; // <= FOUND
59: }ERC777 is an advanced token standard that introduces hooks, allowing operators to execute additional logic during transfers. While this feature offers greater flexibility, it also opens up the possibility of reentrancy attacks. Specifically, when tokens are sent, the receiving contract's tokensReceived hook gets called, and this external call can execute arbitrary code. An attacker can exploit this feature to re-enter the original function, potentially leading to double-spending or other types of financial manipulation.
To mitigate reentrancy risks with ERC777, it's crucial to adopt established security measures, such as utilizing reentrancy guards or following the check-effects-interactions pattern. Some developers opt to stick with the simpler ERC20 standard, which does not have these hooks, to minimize this risk. If you do choose to use ERC777, extreme caution and thorough auditing are advised to secure against potential reentrancy vulnerabilities.
Num of instances: 7
Click to show findings
['18']
18: function pullAssets(VaultCache memory vaultCache, address from, Assets amount) internal virtual { // <= FOUND
19: vaultCache.asset.safeTransferFrom(from, address(this), amount.toUint(), permit2); // <= FOUND
20: vaultStorage.cash = vaultCache.cash = vaultCache.cash + amount;
21: }['75']
75: function transferFrom(address, address, uint256) external pure returns (bool) { // <= FOUND
76: revert E_NotSupported();
77: }['43']
43: function transferFrom(address from, address to, uint256 amount) public virtual override callThroughEVC returns (bool) { return super.transferFrom(from, to, amount); } // <= FOUND['101']
101: function swapToSynthGivenIn(uint256 amountIn, address receiver) external returns (uint256) { // <= FOUND
102: uint256 amountOut = quoteToSynthGivenIn(amountIn);
103: if (amountIn == 0 || amountOut == 0) {
104: return 0;
105: }
106:
107: underlying.safeTransferFrom(_msgSender(), address(this), amountIn); // <= FOUND
108: synth.mint(receiver, amountOut);
109:
110: return amountOut;
111: }['117']
117: function swapToSynthGivenOut(uint256 amountOut, address receiver) external returns (uint256) { // <= FOUND
118: uint256 amountIn = quoteToSynthGivenOut(amountOut);
119: if (amountIn == 0 || amountOut == 0) {
120: return 0;
121: }
122:
123: underlying.safeTransferFrom(_msgSender(), address(this), amountIn); // <= FOUND
124: synth.mint(receiver, amountOut);
125:
126: return amountIn;
127: }['19']
19: function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) // <= FOUND
20: internal
21: returns (bool, bytes memory)
22: {
23: (bool success, bytes memory data) = address(token).call(abi.encodeCall(IERC20.transferFrom, (from, to, value)));
24:
25: return isEmptyOrTrueReturn(success, data) ? (true, bytes("")) : (false, data);
26: }['28']
28: function safeTransferFrom(IERC20 token, address from, address to, uint256 value, address permit2) internal { // <= FOUND
29: (bool success, bytes memory tryData) = trySafeTransferFrom(token, from, to, value); // <= FOUND
30: bytes memory fallbackData;
31: if (!success && permit2 != address(0)) {
32: if (value > type(uint160).max) {
33: revert E_TransferFromFailed(tryData, abi.encodePacked(E_Permit2AmountOverflow.selector));
34: }
35:
36: (success, fallbackData) =
37: permit2.call(abi.encodeCall(IPermit2.transferFrom, (from, to, uint160(value), address(token))));
38: }
39:
40: if (!success) revert E_TransferFromFailed(tryData, fallbackData);
41: }External calls in Solidity are costly in terms of gas usage. This can significantly impact contract efficiency and cost. Functions that make repetitive calls to fetch the same data from other contracts can cause unnecessary gas expenditure. To optimize this, it's advisable to store the returned value of these function calls in a state variable, essentially caching the data. This data can be updated at regular intervals or under specific conditions instead of fetching it from the external contract on every invocation. Be sure to analyze the frequency of data change in the external contract to balance data freshness with gas efficiency when implementing caching.
Num of instances: 5
Click to show findings
['114']
114: function decreaseAllowance(address owner, address spender, Shares amount) internal virtual {
115: if (amount.isZero() || owner == spender) return;
116: UserStorage storage user = vaultStorage.users[owner];
117:
118: uint256 allowance = user.eTokenAllowance[spender];
119: if (allowance != type(uint256).max) {
120: if (allowance < amount.toUint()) revert E_InsufficientAllowance(); // <= FOUND
121: unchecked {
122: allowance -= amount.toUint(); // <= FOUND
123: }
124: user.eTokenAllowance[spender] = allowance;
125: }
126: }['131']
131: function pullDebt(uint256 amount, address from) public virtual nonReentrant returns (uint256) {
132: (VaultCache memory vaultCache, address account) = initOperation(OP_PULL_DEBT, CHECKACCOUNT_CALLER);
133:
134: if (from == account) revert E_SelfTransfer();
135:
136: Assets assets = amount == type(uint256).max ? getCurrentOwed(vaultCache, from).toAssetsUp() : amount.toAssets();
137:
138: if (assets.isZero()) return 0;
139: transferBorrow(vaultCache, from, account, assets);
140:
141: emit PullDebt(from, account, assets.toUint()); // <= FOUND
142:
143: return assets.toUint(); // <= FOUND
144: }['42']
42: function initVaultCache(VaultCache memory vaultCache) private view returns (bool dirty) {
43: dirty = false;
44:
45:
46:
47: (vaultCache.asset, vaultCache.oracle, vaultCache.unitOfAccount) = ProxyUtils.metadata();
48:
49:
50:
51: vaultCache.lastInterestAccumulatorUpdate = vaultStorage.lastInterestAccumulatorUpdate;
52: vaultCache.cash = vaultStorage.cash;
53: vaultCache.supplyCap = vaultStorage.supplyCap.resolve();
54: vaultCache.borrowCap = vaultStorage.borrowCap.resolve();
55: vaultCache.hookedOps = vaultStorage.hookedOps;
56: vaultCache.snapshotInitialized = vaultStorage.snapshotInitialized;
57:
58: vaultCache.totalShares = vaultStorage.totalShares;
59: vaultCache.totalBorrows = vaultStorage.totalBorrows;
60:
61: vaultCache.accumulatedFees = vaultStorage.accumulatedFees;
62: vaultCache.configFlags = vaultStorage.configFlags;
63:
64: vaultCache.interestAccumulator = vaultStorage.interestAccumulator;
65:
66:
67:
68: uint256 deltaT = block.timestamp - vaultCache.lastInterestAccumulatorUpdate;
69: if (deltaT > 0) {
70: dirty = true;
71:
72:
73:
74: ConfigAmount interestFee = vaultStorage.interestFee;
75: uint256 interestRate = vaultStorage.interestRate;
76:
77: uint256 newInterestAccumulator = vaultCache.interestAccumulator;
78: uint256 newTotalBorrows = vaultCache.totalBorrows.toUint(); // <= FOUND
79:
80: unchecked {
81: uint256 intermediate;
82: (uint256 multiplier, bool overflow) = RPow.rpow(interestRate + 1e27, deltaT, 1e27);
83:
84:
85: if (!overflow) {
86: intermediate = newInterestAccumulator * multiplier;
87: if (newInterestAccumulator == intermediate / multiplier) {
88: newInterestAccumulator = intermediate / 1e27;
89: }
90: }
91:
92: intermediate = newTotalBorrows * newInterestAccumulator;
93: if (newTotalBorrows == intermediate / newInterestAccumulator) {
94: newTotalBorrows = intermediate / vaultCache.interestAccumulator;
95: }
96: }
97:
98: uint256 newAccumulatedFees = vaultCache.accumulatedFees.toUint();
99: uint256 newTotalShares = vaultCache.totalShares.toUint(); // <= FOUND
100: uint256 feeAssets = (newTotalBorrows - vaultCache.totalBorrows.toUint()) * interestFee.toUint16() // <= FOUND
101: / (uint256(CONFIG_SCALE) << INTERNAL_DEBT_PRECISION_SHIFT);
102:
103: if (feeAssets != 0) {
104:
105:
106:
107:
108:
109:
110:
111: uint256 newTotalAssets = vaultCache.cash.toUint() + OwedLib.toAssetsUpUint(newTotalBorrows);
112: newTotalShares = newTotalAssets * newTotalShares / (newTotalAssets - feeAssets);
113: newAccumulatedFees += newTotalShares - vaultCache.totalShares.toUint(); // <= FOUND
114: }
115:
116:
117:
118: if (newTotalBorrows <= MAX_SANE_DEBT_AMOUNT) {
119: vaultCache.totalBorrows = newTotalBorrows.toOwed();
120: vaultCache.interestAccumulator = newInterestAccumulator;
121: vaultCache.lastInterestAccumulatorUpdate = uint48(block.timestamp);
122:
123: if (newTotalShares != vaultCache.totalShares.toUint() && newTotalShares <= MAX_SANE_AMOUNT) { // <= FOUND
124: vaultCache.accumulatedFees = newAccumulatedFees.toShares();
125: vaultCache.totalShares = newTotalShares.toShares();
126: }
127: }
128: }
129: }['174']
174: function executeLiquidation(VaultCache memory vaultCache, LiquidationCache memory liqCache, uint256 minYieldBalance)
175: private
176: {
177:
178:
179: if (minYieldBalance > liqCache.yieldBalance) revert E_MinYield();
180:
181:
182:
183: if (liqCache.repay.toUint() > 0) { // <= FOUND
184: transferBorrow(vaultCache, liqCache.violator, liqCache.liquidator, liqCache.repay);
185: }
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201: if (liqCache.yieldBalance > 0) {
202: enforceCollateralTransfer(
203: liqCache.collateral, liqCache.yieldBalance, liqCache.violator, liqCache.liquidator
204: );
205:
206: forgiveAccountStatusCheck(liqCache.violator);
207: }
208:
209:
210:
211: if (
212: vaultCache.configFlags.isNotSet(CFG_DONT_SOCIALIZE_DEBT) && liqCache.liability > liqCache.repay
213: && checkNoCollateral(liqCache.violator, liqCache.collaterals)
214: ) {
215: Assets owedRemaining = liqCache.liability.subUnchecked(liqCache.repay);
216: decreaseBorrow(vaultCache, liqCache.violator, owedRemaining);
217:
218:
219:
220: emit Withdraw(liqCache.liquidator, address(0), address(0), owedRemaining.toUint(), 0);
221: emit DebtSocialized(liqCache.violator, owedRemaining.toUint());
222: }
223:
224: emit Liquidate(
225: liqCache.liquidator, liqCache.violator, liqCache.collateral, liqCache.repay.toUint(), liqCache.yieldBalance // <= FOUND
226: );
227: }['83']
83: function checkVaultStatus() public virtual reentrantOK onlyEVCChecks returns (bytes4 magicValue) {
84:
85:
86:
87: VaultCache memory vaultCache = updateVault();
88: uint256 newInterestRate = computeInterestRate(vaultCache);
89:
90: logVaultStatus(vaultCache, newInterestRate);
91:
92:
93:
94:
95: if (vaultCache.snapshotInitialized) {
96: vaultStorage.snapshotInitialized = vaultCache.snapshotInitialized = false;
97:
98: Assets snapshotCash = snapshot.cash;
99: Assets snapshotBorrows = snapshot.borrows;
100:
101: uint256 prevBorrows = snapshotBorrows.toUint();
102: uint256 borrows = vaultCache.totalBorrows.toAssetsUp().toUint(); // <= FOUND
103:
104: if (borrows > vaultCache.borrowCap && borrows > prevBorrows) revert E_BorrowCapExceeded();
105:
106: uint256 prevSupply = snapshotCash.toUint() + prevBorrows;
107:
108:
109:
110:
111: uint256 supply = vaultCache.cash.toUint() + vaultCache.totalBorrows.toAssetsDown().toUint(); // <= FOUND
112:
113: if (supply > vaultCache.supplyCap && supply > prevSupply) revert E_SupplyCapExceeded();
114:
115: snapshot.reset();
116: }
117:
118: callHookWithLock(vaultCache.hookedOps, OP_VAULT_STATUS_CHECK, address(evc));
119:
120: magicValue = IEVCVault.checkVaultStatus.selector;
121: }Num of instances: 5
Click to show findings
['27']
27: function set(Snapshot storage self, Assets cash, Assets borrows) internal { // <= FOUND
28: self.cash = cash; // <= FOUND
29: self.borrows = borrows; // <= FOUND
30: self.stamp = STAMP; // <= FOUND
31: }['43']
43: function getBalanceAndBalanceForwarder(UserStorage storage self) internal view returns (Shares, bool) { // <= FOUND
44: PackedUserSlot data = self.data; // <= FOUND
45: return (unpackBalance(data), unpackBalanceForwarder(data));
46: }['48']
48: function setBalanceForwarder(UserStorage storage self, bool newValue) internal { // <= FOUND
49: uint256 data = PackedUserSlot.unwrap(self.data);
50:
51: uint256 newFlag = newValue ? BALANCE_FORWARDER_MASK : 0;
52: self.data = PackedUserSlot.wrap(newFlag | (data & ~BALANCE_FORWARDER_MASK)); // <= FOUND
53: }['55']
55: function setOwed(UserStorage storage self, Owed owed) internal { // <= FOUND
56: uint256 data = PackedUserSlot.unwrap(self.data);
57:
58: self.data = PackedUserSlot.wrap((owed.toUint() << OWED_OFFSET) | (data & ~OWED_MASK)); // <= FOUND
59: }['61']
61: function setBalance(UserStorage storage self, Shares balance) internal { // <= FOUND
62: uint256 data = PackedUserSlot.unwrap(self.data);
63:
64: self.data = PackedUserSlot.wrap(balance.toUint() | (data & ~SHARES_MASK)); // <= FOUND
65: }Num of instances: 195
Click to show findings
['12']
12: function checkContract(address addr) internal view returns (address) {
13: if (addr.code.length == 0) revert Errors.E_BadAddress();
14:
15: return addr; // <= FOUND
16: }['16']
16: function balanceTrackerAddress() public view virtual reentrantOK returns (address) {
17: return address(balanceTracker); // <= FOUND
18: }['21']
21: function balanceForwarderEnabled(address account) public view virtual nonReentrantView returns (bool) {
22: return vaultStorage.users[account].isBalanceForwarderEnabled(); // <= FOUND
23: }['108']
108: function isOperationDisabled(Flags hookedOps, uint32 operation) internal view returns (bool) {
109: return hookedOps.isSet(operation) && vaultStorage.hookTarget == address(0); // <= FOUND
110: }['16']
16: function getCurrentOwed(VaultCache memory vaultCache, address account, Owed owed) internal view returns (Owed) {
17:
18: if (owed.isZero()) return Owed.wrap(0); // <= FOUND
19:
20:
21: return owed.mulDiv(vaultCache.interestAccumulator, vaultStorage.users[account].interestAccumulator); // <= FOUND
22: }['24']
24: function getCurrentOwed(VaultCache memory vaultCache, address account) internal view returns (Owed) {
25: return getCurrentOwed(vaultCache, account, vaultStorage.users[account].getOwed()); // <= FOUND
26: }['130']
130: function computeInterestRateView(VaultCache memory vaultCache) internal view virtual returns (uint256) {
131:
132: address irm = vaultStorage.interestRateModel;
133: uint256 newInterestRate = vaultStorage.interestRate;
134:
135: if (irm != address(0) && isVaultStatusCheckDeferred()) {
136: (bool success, bytes memory data) = irm.staticcall(
137: abi.encodeCall(
138: IIRM.computeInterestRateView,
139: (address(this), vaultCache.cash.toUint(), vaultCache.totalBorrows.toAssetsUp().toUint())
140: )
141: );
142:
143: if (success && data.length >= 32) {
144: newInterestRate = abi.decode(data, (uint256));
145: if (newInterestRate > MAX_ALLOWED_INTEREST_RATE) newInterestRate = MAX_ALLOWED_INTEREST_RATE;
146: }
147: }
148:
149: return newInterestRate; // <= FOUND
150: }['25']
25: function totalBorrows() public view virtual nonReentrantView returns (uint256) {
26: return loadVault().totalBorrows.toAssetsUp().toUint(); // <= FOUND
27: }['30']
30: function totalBorrowsExact() public view virtual nonReentrantView returns (uint256) {
31: return loadVault().totalBorrows.toUint(); // <= FOUND
32: }['35']
35: function cash() public view virtual nonReentrantView returns (uint256) {
36: return vaultStorage.cash.toUint(); // <= FOUND
37: }['40']
40: function debtOf(address account) public view virtual nonReentrantView returns (uint256) {
41: return getCurrentOwed(loadVault(), account).toAssetsUp().toUint(); // <= FOUND
42: }['45']
45: function debtOfExact(address account) public view virtual nonReentrantView returns (uint256) {
46: return getCurrentOwed(loadVault(), account).toUint(); // <= FOUND
47: }['50']
50: function interestRate() public view virtual nonReentrantView returns (uint256) {
51: return computeInterestRateView(loadVault()); // <= FOUND
52: }['55']
55: function interestAccumulator() public view virtual nonReentrantView returns (uint256) {
56: return loadVault().interestAccumulator; // <= FOUND
57: }['60']
60: function dToken() public view virtual reentrantOK returns (address) {
61: return calculateDTokenAddress(); // <= FOUND
62: }['25']
25: function name() external view returns (string memory) {
26: return string.concat("Debt token of ", IEVault(eVault).name()); // <= FOUND
27: }['31']
31: function symbol() external view returns (string memory) {
32: return string.concat(IEVault(eVault).symbol(), "-DEBT"); // <= FOUND
33: }['37']
37: function decimals() external view returns (uint8) {
38: return IEVault(eVault).decimals(); // <= FOUND
39: }['43']
43: function totalSupply() external view returns (uint256) {
44: return IEVault(eVault).totalBorrows(); // <= FOUND
45: }['50']
50: function balanceOf(address owner) external view returns (uint256) {
51: return IEVault(eVault).debtOf(owner); // <= FOUND
52: }['92']
92: function asset() external view returns (address) {
93: return IEVault(eVault).asset(); // <= FOUND
94: }['71']
71: function _msgSender() internal view virtual override (EVCUtil, Context) returns (address) {
72: return EVCUtil._msgSender(); // <= FOUND
73: }['125']
125: function _msgSender() internal view virtual override (ERC20Collateral, Context) returns (address) {
126: return ERC20Collateral._msgSender(); // <= FOUND
127: }['147']
147: function isIgnoredForTotalSupply(address account) public view returns (bool) {
148: return ignoredForTotalSupply.contains(account); // <= FOUND
149: }['153']
153: function getAllIgnoredForTotalSupply() public view returns (address[] memory) {
154: return ignoredForTotalSupply.values(); // <= FOUND
155: }['160']
160: function totalSupply() public view override returns (uint256) {
161: uint256 total = super.totalSupply();
162:
163: uint256 ignoredLength = ignoredForTotalSupply.length();
164: for (uint256 i = 0; i < ignoredLength; i++) {
165: total -= balanceOf(ignoredForTotalSupply.at(i));
166: }
167: return total; // <= FOUND
168: }['41']
41: function EVCAuthenticateDeferred(bool checkController) internal view virtual returns (address) {
42: assert(msg.sender == address(evc));
43:
44: (address onBehalfOfAccount, bool controllerEnabled) =
45: evc.getCurrentOnBehalfOfAccount(checkController ? address(this) : address(0));
46:
47: if (checkController && !controllerEnabled) revert E_ControllerDisabled();
48:
49: return onBehalfOfAccount; // <= FOUND
50: }['53']
53: function EVCAuthenticate() internal view virtual returns (address) {
54: if (msg.sender == address(evc)) {
55: (address onBehalfOfAccount,) = evc.getCurrentOnBehalfOfAccount(address(0));
56:
57: return onBehalfOfAccount; // <= FOUND
58: }
59: return msg.sender; // <= FOUND
60: }['64']
64: function EVCAuthenticateGovernor() internal view virtual returns (address) {
65: if (msg.sender == address(evc)) {
66: (address onBehalfOfAccount,) = evc.getCurrentOnBehalfOfAccount(address(0));
67:
68: if (
69: isKnownNonOwnerAccount(onBehalfOfAccount) || evc.isOperatorAuthenticated()
70: || evc.isControlCollateralInProgress()
71: ) {
72: revert E_Unauthorized();
73: }
74:
75: return onBehalfOfAccount; // <= FOUND
76: }
77: return msg.sender; // <= FOUND
78: }['83']
83: function isKnownNonOwnerAccount(address account) internal view returns (bool) {
84: address owner = evc.getAccountOwner(account);
85: return owner != address(0) && owner != account; // <= FOUND
86: }['109']
109: function hasAnyControllerEnabled(address account) internal view returns (bool) {
110: return evc.getControllers(account).length > 0; // <= FOUND
111: }['113']
113: function getCollaterals(address account) internal view returns (address[] memory) {
114: return evc.getCollaterals(account); // <= FOUND
115: }['117']
117: function isCollateralEnabled(address account, address collateral) internal view returns (bool) {
118: return evc.isCollateralEnabled(account, collateral); // <= FOUND
119: }['121']
121: function isAccountStatusCheckDeferred(address account) internal view returns (bool) {
122: return evc.isAccountStatusCheckDeferred(account); // <= FOUND
123: }['125']
125: function isVaultStatusCheckDeferred() internal view returns (bool) {
126: return evc.isVaultStatusCheckDeferred(address(this)); // <= FOUND
127: }['129']
129: function isControlCollateralInProgress() internal view returns (bool) {
130: return evc.isControlCollateralInProgress(); // <= FOUND
131: }['133']
133: function getLastAccountStatusCheckTimestamp(address account) internal view returns (uint256) {
134: return evc.getLastAccountStatusCheckTimestamp(account); // <= FOUND
135: }['32']
32: function decimals() public view virtual override returns (uint8) { return super.decimals(); } // <= FOUND['36']
36: function balanceOf(address account) public view virtual override returns (uint256) { return super.balanceOf(account); } // <= FOUND['38']
38: function allowance(address holder, address spender) public view virtual override returns (uint256) { return super.allowance(holder, spender); } // <= FOUND['55']
55: function asset() public view virtual override returns (address) { return super.asset(); } // <= FOUND['59']
59: function convertToAssets(uint256 shares) public view virtual override returns (uint256) { return super.convertToAssets(shares); } // <= FOUND['61']
61: function convertToShares(uint256 assets) public view virtual override returns (uint256) { return super.convertToShares(assets); } // <= FOUND['79']
79: function accumulatedFees() public view virtual override returns (uint256) { return super.accumulatedFees(); } // <= FOUND['81']
81: function accumulatedFeesAssets() public view virtual override returns (uint256) { return super.accumulatedFeesAssets(); } // <= FOUND['108']
108: function debtOf(address account) public view virtual override returns (uint256) { return super.debtOf(account); } // <= FOUND['112']
112: function interestRate() public view virtual override returns (uint256) { return super.interestRate(); } // <= FOUND['183']
183: function interestFee() public view virtual override returns (uint16) { return super.interestFee(); } // <= FOUND['68']
68: function totalAssets() public view override returns (uint256) {
69: return _totalAssets + interestAccrued(); // <= FOUND
70: }['72']
72: function maxRedeem(address owner) public view override returns (uint256) {
73:
74:
75:
76:
77:
78:
79: if (evc.getControllers(owner).length > 0) {
80: return 0; // <= FOUND
81: }
82:
83: return super.maxRedeem(owner); // <= FOUND
84: }['86']
86: function maxWithdraw(address owner) public view override returns (uint256) {
87:
88:
89:
90:
91:
92:
93: if (evc.getControllers(owner).length > 0) {
94: return 0; // <= FOUND
95: }
96:
97: return super.maxWithdraw(owner); // <= FOUND
98: }['177']
177: function _convertToShares(uint256 assets, Math.Rounding rounding) internal view override returns (uint256) {
178: return assets.mulDiv(totalSupply() + VIRTUAL_AMOUNT, totalAssets() + VIRTUAL_AMOUNT, rounding); // <= FOUND
179: }['181']
181: function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view override returns (uint256) {
182: return shares.mulDiv(totalAssets() + VIRTUAL_AMOUNT, totalSupply() + VIRTUAL_AMOUNT, rounding); // <= FOUND
183: }['233']
233: function interestAccrued() public view returns (uint256) {
234: return interestAccruedFromCache(esrSlot); // <= FOUND
235: }['237']
237: function interestAccruedFromCache(ESRSlot memory esrSlotCache) internal view returns (uint256) {
238:
239: if (block.timestamp >= esrSlotCache.interestSmearEnd) {
240: return esrSlotCache.interestLeft; // <= FOUND
241: }
242:
243:
244: if (esrSlotCache.lastInterestUpdate == block.timestamp) {
245: return 0; // <= FOUND
246: }
247:
248:
249: uint256 totalDuration = esrSlotCache.interestSmearEnd - esrSlotCache.lastInterestUpdate;
250: uint256 timePassed = block.timestamp - esrSlotCache.lastInterestUpdate;
251:
252: return esrSlotCache.interestLeft * timePassed / totalDuration; // <= FOUND
253: }['256']
256: function getESRSlot() public view returns (ESRSlot memory) {
257: return esrSlot; // <= FOUND
258: }['264']
264: function _msgSender() internal view override (Context, EVCUtil) returns (address) {
265: return EVCUtil._msgSender(); // <= FOUND
266: }['182']
182: function isProxy(address proxy) external view returns (bool) {
183: return proxyLookup[proxy].implementation != address(0); // <= FOUND
184: }['188']
188: function getProxyListLength() external view returns (uint256) {
189: return proxyList.length; // <= FOUND
190: }['99']
99: function governorAdmin() public view virtual reentrantOK returns (address) {
100: return vaultStorage.governorAdmin; // <= FOUND
101: }['104']
104: function feeReceiver() public view virtual reentrantOK returns (address) {
105: return vaultStorage.feeReceiver; // <= FOUND
106: }['109']
109: function interestFee() public view virtual reentrantOK returns (uint16) {
110: return vaultStorage.interestFee.toUint16(); // <= FOUND
111: }['114']
114: function interestRateModel() public view virtual reentrantOK returns (address) {
115: return vaultStorage.interestRateModel; // <= FOUND
116: }['119']
119: function protocolConfigAddress() public view virtual reentrantOK returns (address) {
120: return address(protocolConfig); // <= FOUND
121: }['124']
124: function protocolFeeShare() public view virtual reentrantOK returns (uint256) {
125: (, uint256 protocolShare) = protocolConfig.protocolFeeConfig(address(this));
126: return protocolShare; // <= FOUND
127: }['130']
130: function protocolFeeReceiver() public view virtual reentrantOK returns (address) {
131: (address protocolReceiver,) = protocolConfig.protocolFeeConfig(address(this));
132: return protocolReceiver; // <= FOUND
133: }['136']
136: function caps() public view virtual reentrantOK returns (uint16, uint16) {
137: return (vaultStorage.supplyCap.toRawUint16(), vaultStorage.borrowCap.toRawUint16()); // <= FOUND
138: }['141']
141: function LTVBorrow(address collateral) public view virtual reentrantOK returns (uint16) {
142: return getLTV(collateral, false).toUint16(); // <= FOUND
143: }['146']
146: function LTVLiquidation(address collateral) public view virtual reentrantOK returns (uint16) {
147: return getLTV(collateral, true).toUint16(); // <= FOUND
148: }['151']
151: function LTVFull(address collateral)
152: public
153: view
154: virtual
155: reentrantOK
156: returns (uint16, uint16, uint16, uint48, uint32)
157: {
158: LTVConfig memory ltv = vaultStorage.ltvLookup[collateral];
159: return ( // <= FOUND
160: ltv.borrowLTV.toUint16(),
161: ltv.liquidationLTV.toUint16(),
162: ltv.initialLiquidationLTV.toUint16(),
163: ltv.targetTimestamp,
164: ltv.rampDuration
165: );
166: }['169']
169: function LTVList() public view virtual reentrantOK returns (address[] memory) {
170: return vaultStorage.ltvList; // <= FOUND
171: }['174']
174: function maxLiquidationDiscount() public view virtual reentrantOK returns (uint16) {
175: return vaultStorage.maxLiquidationDiscount.toUint16(); // <= FOUND
176: }['179']
179: function liquidationCoolOffTime() public view virtual reentrantOK returns (uint16) {
180: return vaultStorage.liquidationCoolOffTime; // <= FOUND
181: }['184']
184: function hookConfig() public view virtual reentrantOK returns (address, uint32) {
185: return (vaultStorage.hookTarget, vaultStorage.hookedOps.toUint32()); // <= FOUND
186: }['189']
189: function configFlags() public view virtual reentrantOK returns (uint32) {
190: return vaultStorage.configFlags.toUint32(); // <= FOUND
191: }['194']
194: function EVC() public view virtual reentrantOK returns (address) {
195: return address(evc); // <= FOUND
196: }['199']
199: function unitOfAccount() public view virtual reentrantOK returns (address) {
200: (,, address _unitOfAccount) = ProxyUtils.metadata();
201: return _unitOfAccount; // <= FOUND
202: }['205']
205: function oracle() public view virtual reentrantOK returns (address) {
206: (, IPriceOracle _oracle,) = ProxyUtils.metadata();
207: return address(_oracle); // <= FOUND
208: }['211']
211: function permit2Address() public view virtual reentrantOK returns (address) {
212: return permit2; // <= FOUND
213: }['30']
30: function computeInterestRate(address vault, uint256 cash, uint256 borrows)
31: external
32: view
33: override
34: returns (uint256)
35: {
36: if (msg.sender != vault) revert E_IRMUpdateUnauthorized();
37:
38: return computeInterestRateInternal(vault, cash, borrows); // <= FOUND
39: }['42']
42: function computeInterestRateView(address vault, uint256 cash, uint256 borrows)
43: external
44: view
45: override
46: returns (uint256)
47: {
48: return computeInterestRateInternal(vault, cash, borrows); // <= FOUND
49: }['51']
51: function computeInterestRateInternal(address, uint256 cash, uint256 borrows) internal view returns (uint256) {
52: uint256 totalAssets = cash + borrows;
53:
54: uint32 utilization = totalAssets == 0
55: ? 0
56: : uint32(borrows * type(uint32).max / totalAssets);
57:
58: uint256 ir = baseRate;
59:
60: if (utilization <= kink) {
61: ir += utilization * slope1;
62: } else {
63: ir += kink * slope1;
64:
65: uint256 utilizationOverKink;
66: unchecked {
67: utilizationOverKink = utilization - kink;
68: }
69: ir += slope2 * utilizationOverKink;
70: }
71:
72: return ir; // <= FOUND
73: }['70']
70: function computeInterestRateView(address, uint256, uint256) external view override returns (uint256) {
71: (uint216 rate,) = _computeRate(irmStorage);
72: return rate; // <= FOUND
73: }['75']
75: function _computeRate(IRMData memory irmCache) internal view returns (uint216 rate, bool updated) {
76: updated = false;
77: rate = irmCache.lastRate;
78:
79:
80: if (block.timestamp < irmCache.lastUpdated + ADJUST_INTERVAL) {
81: return (rate, updated); // <= FOUND
82: }
83:
84: uint256 quote = oracle.getQuote(quoteAmount, synth, referenceAsset);
85:
86: updated = true;
87:
88: if (quote < targetQuote) {
89:
90: rate = rate * ADJUST_FACTOR / ADJUST_ONE;
91: } else {
92:
93: rate = rate * ADJUST_ONE / ADJUST_FACTOR;
94: }
95:
96:
97: if (rate < BASE_RATE) {
98: rate = BASE_RATE;
99: } else if (rate > MAX_RATE) {
100: rate = MAX_RATE;
101: }
102:
103: return (rate, updated); // <= FOUND
104: }['106']
106: function getIRMData() external view returns (IRMData memory) {
107: return irmStorage; // <= FOUND
108: }['76']
76: function getTokenSymbol(address asset) private view returns (string memory) {
77: (bool success, bytes memory data) = address(asset).staticcall(abi.encodeCall(IERC20.symbol, ()));
78: if (!success) return "UNDEFINED"; // <= FOUND
79: return data.length <= 32 ? string(data) : abi.decode(data, (string)); // <= FOUND
80: }['37']
37: function getLTV(LTVConfig memory self, bool liquidation) internal view returns (ConfigAmount) {
38: if (!liquidation) {
39: return self.borrowLTV; // <= FOUND
40: }
41:
42: if (block.timestamp >= self.targetTimestamp || self.liquidationLTV >= self.initialLiquidationLTV) {
43: return self.liquidationLTV; // <= FOUND
44: }
45:
46: uint256 currentLiquidationLTV = self.initialLiquidationLTV.toUint16();
47:
48: unchecked {
49: uint256 targetLiquidationLTV = self.liquidationLTV.toUint16();
50: uint256 timeRemaining = self.targetTimestamp - block.timestamp;
51:
52:
53: currentLiquidationLTV = targetLiquidationLTV
54: + (currentLiquidationLTV - targetLiquidationLTV) * timeRemaining / self.rampDuration;
55: }
56:
57:
58: return ConfigAmount.wrap(uint16(currentLiquidationLTV)); // <= FOUND
59: }['13']
13: function getLTV(address collateral, bool liquidation) internal view virtual returns (ConfigAmount) {
14: return vaultStorage.ltvLookup[collateral].getLTV(liquidation); // <= FOUND
15: }['17']
17: function isRecognizedCollateral(address collateral) internal view virtual returns (bool) {
18: return vaultStorage.ltvLookup[collateral].isRecognizedCollateral(); // <= FOUND
19: }['59']
59: function calculateLiquidation(
60: VaultCache memory vaultCache,
61: address liquidator,
62: address violator,
63: address collateral,
64: uint256 desiredRepay
65: ) private view returns (LiquidationCache memory liqCache) {
66:
67:
68: liqCache.liquidator = liquidator;
69: liqCache.violator = violator;
70: liqCache.collateral = collateral;
71:
72: liqCache.repay = Assets.wrap(0);
73: liqCache.yieldBalance = 0;
74: liqCache.liability = getCurrentOwed(vaultCache, violator).toAssetsUp();
75: liqCache.collaterals = getCollaterals(violator);
76:
77:
78:
79:
80: if (liqCache.violator == liqCache.liquidator) revert E_SelfLiquidation();
81:
82: if (!isRecognizedCollateral(liqCache.collateral)) revert E_BadCollateral();
83:
84: validateController(liqCache.violator);
85:
86: if (!isCollateralEnabled(liqCache.violator, liqCache.collateral)) revert E_CollateralDisabled();
87:
88:
89: if (isAccountStatusCheckDeferred(violator)) revert E_ViolatorLiquidityDeferred();
90:
91:
92: if (isInLiquidationCoolOff(violator)) revert E_LiquidationCoolOff();
93:
94:
95: if (liqCache.liability.isZero()) return liqCache; // <= FOUND
96:
97:
98:
99: liqCache = calculateMaxLiquidation(liqCache, vaultCache);
100:
101:
102:
103: if (desiredRepay != type(uint256).max) {
104: uint256 maxRepay = liqCache.repay.toUint();
105: if (desiredRepay > maxRepay) revert E_ExcessiveRepayAmount();
106:
107: if (maxRepay > 0) {
108: liqCache.yieldBalance = desiredRepay * liqCache.yieldBalance / maxRepay;
109: liqCache.repay = desiredRepay.toAssets();
110: }
111: }
112: }['114']
114: function calculateMaxLiquidation(LiquidationCache memory liqCache, VaultCache memory vaultCache)
115: private
116: view
117: returns (LiquidationCache memory)
118: {
119:
120:
121: (uint256 collateralAdjustedValue, uint256 liabilityValue) =
122: calculateLiquidity(vaultCache, liqCache.violator, liqCache.collaterals, true);
123:
124:
125: if (collateralAdjustedValue > liabilityValue) return liqCache; // <= FOUND
126:
127:
128:
129:
130: uint256 discountFactor = collateralAdjustedValue * 1e18 / liabilityValue;
131: {
132: uint256 minDiscountFactor;
133: unchecked {
134:
135: minDiscountFactor = 1e18 - uint256(1e18) * vaultStorage.maxLiquidationDiscount.toUint16() / CONFIG_SCALE;
136: }
137: if (discountFactor < minDiscountFactor) discountFactor = minDiscountFactor;
138: }
139:
140:
141:
142: uint256 collateralBalance = IERC20(liqCache.collateral).balanceOf(liqCache.violator);
143: uint256 collateralValue =
144: vaultCache.oracle.getQuote(collateralBalance, liqCache.collateral, vaultCache.unitOfAccount);
145:
146: if (collateralValue == 0) {
147:
148:
149:
150:
151:
152:
153: liqCache.yieldBalance = collateralBalance;
154: return liqCache; // <= FOUND
155: }
156:
157: uint256 maxRepayValue = liabilityValue;
158: uint256 maxYieldValue = maxRepayValue * 1e18 / discountFactor;
159:
160:
161:
162:
163: if (collateralValue < maxYieldValue) {
164: maxRepayValue = collateralValue * discountFactor / 1e18;
165: maxYieldValue = collateralValue;
166: }
167:
168: liqCache.repay = (maxRepayValue * liqCache.liability.toUint() / liabilityValue).toAssets();
169: liqCache.yieldBalance = maxYieldValue * collateralBalance / collateralValue;
170:
171: return liqCache; // <= FOUND
172: }['229']
229: function isInLiquidationCoolOff(address account) private view returns (bool) {
230: unchecked {
231: return block.timestamp < getLastAccountStatusCheckTimestamp(account) + vaultStorage.liquidationCoolOffTime; // <= FOUND
232: }
233: }['61']
61: function checkNoCollateral(address account, address[] memory collaterals) internal view virtual returns (bool) {
62: for (uint256 i; i < collaterals.length; ++i) {
63: address collateral = collaterals[i];
64:
65: if (!isRecognizedCollateral(collateral)) continue;
66:
67: if (IERC20(collateral).balanceOf(account) > 0) return false; // <= FOUND
68: }
69:
70: return true; // <= FOUND
71: }['73']
73: function getLiabilityValue(VaultCache memory vaultCache, address account, Owed owed, bool liquidation)
74: internal
75: view
76: virtual
77: returns (uint256 value)
78: {
79:
80: uint256 owedAssets = getCurrentOwed(vaultCache, account, owed).toAssetsUp().toUint();
81:
82: if (owedAssets == 0) return 0; // <= FOUND
83:
84: if (address(vaultCache.asset) == vaultCache.unitOfAccount) {
85: value = owedAssets;
86: } else {
87: if (liquidation) {
88:
89: value = vaultCache.oracle.getQuote(owedAssets, address(vaultCache.asset), vaultCache.unitOfAccount);
90: } else {
91:
92: (, value) = vaultCache.oracle.getQuotes(owedAssets, address(vaultCache.asset), vaultCache.unitOfAccount);
93: }
94: }
95: }['97']
97: function getCollateralValue(VaultCache memory vaultCache, address account, address collateral, bool liquidation)
98: internal
99: view
100: virtual
101: returns (uint256 value)
102: {
103: ConfigAmount ltv = getLTV(collateral, liquidation);
104: if (ltv.isZero()) return 0; // <= FOUND
105:
106: uint256 balance = IERC20(collateral).balanceOf(account);
107: if (balance == 0) return 0; // <= FOUND
108:
109: uint256 currentCollateralValue;
110:
111: if (liquidation) {
112:
113: currentCollateralValue = vaultCache.oracle.getQuote(balance, collateral, vaultCache.unitOfAccount);
114: } else {
115:
116: (currentCollateralValue,) = vaultCache.oracle.getQuotes(balance, collateral, vaultCache.unitOfAccount);
117: }
118:
119: return currentCollateralValue * ltv.toUint16() / CONFIG_SCALE; // <= FOUND
120: }['132']
132: function quoteToUnderlyingGivenIn(uint256 amountIn) public view returns (uint256) {
133: return amountIn * (BPS_SCALE - TO_UNDERLYING_FEE) * conversionPrice / BPS_SCALE / PRICE_SCALE; // <= FOUND
134: }['139']
139: function quoteToUnderlyingGivenOut(uint256 amountOut) public view returns (uint256) {
140: return amountOut * BPS_SCALE * PRICE_SCALE / (BPS_SCALE - TO_UNDERLYING_FEE) / conversionPrice; // <= FOUND
141: }['146']
146: function quoteToSynthGivenIn(uint256 amountIn) public view returns (uint256) {
147: return amountIn * (BPS_SCALE - TO_SYNTH_FEE) * PRICE_SCALE / BPS_SCALE / conversionPrice; // <= FOUND
148: }['153']
153: function quoteToSynthGivenOut(uint256 amountOut) public view returns (uint256) {
154: return amountOut * BPS_SCALE * conversionPrice / (BPS_SCALE - TO_SYNTH_FEE) / PRICE_SCALE; // <= FOUND
155: }['94']
94: function isValidInterestFee(address vault, uint16 interestFee) external view returns (bool) {
95: InterestFeeRange memory range = _interestFeeRanges[vault];
96:
97: if (range.exists) {
98: return interestFee >= range.minInterestFee && interestFee <= range.maxInterestFee; // <= FOUND
99: }
100:
101: return interestFee >= minInterestFee && interestFee <= maxInterestFee; // <= FOUND
102: }['105']
105: function protocolFeeConfig(address vault) external view returns (address, uint16) {
106: ProtocolFeeConfig memory config = _protocolFeeConfig[vault];
107:
108: if (config.exists) {
109: return (config.feeReceiver, config.protocolFeeShare); // <= FOUND
110: }
111:
112: return (feeReceiver, protocolFeeShare); // <= FOUND
113: }['116']
116: function interestFeeRange(address vault) external view returns (uint16, uint16) {
117: InterestFeeRange memory ranges = _interestFeeRanges[vault];
118:
119: if (ranges.exists) {
120: return (ranges.minInterestFee, ranges.maxInterestFee); // <= FOUND
121: }
122:
123: return (minInterestFee, maxInterestFee); // <= FOUND
124: }['17']
17: function accountLiquidity(address account, bool liquidation)
18: public
19: view
20: virtual
21: nonReentrantView
22: returns (uint256 collateralValue, uint256 liabilityValue)
23: {
24: VaultCache memory vaultCache = loadVault();
25:
26: validateController(account);
27: address[] memory collaterals = getCollaterals(account);
28:
29: return calculateLiquidity(vaultCache, account, collaterals, liquidation); // <= FOUND
30: }['20']
20: function name() public view virtual reentrantOK returns (string memory) {
21: return bytes(vaultStorage.name).length > 0 ? vaultStorage.name : "Unnamed Euler Vault"; // <= FOUND
22: }['25']
25: function symbol() public view virtual reentrantOK returns (string memory) {
26: return bytes(vaultStorage.symbol).length > 0 ? vaultStorage.symbol : "UNKNOWN"; // <= FOUND
27: }['30']
30: function decimals() public view virtual reentrantOK returns (uint8) {
31: (IERC20 asset,,) = ProxyUtils.metadata();
32: (bool success, bytes memory data) = address(asset).staticcall(abi.encodeCall(IERC20.decimals, ()));
33: return success && data.length >= 32 ? abi.decode(data, (uint8)) : 18; // <= FOUND
34: }['37']
37: function totalSupply() public view virtual nonReentrantView returns (uint256) {
38: return loadVault().totalShares.toUint(); // <= FOUND
39: }['42']
42: function balanceOf(address account) public view virtual nonReentrantView returns (uint256) {
43: return vaultStorage.users[account].getBalance().toUint(); // <= FOUND
44: }['47']
47: function allowance(address holder, address spender) public view virtual nonReentrantView returns (uint256) {
48: return vaultStorage.users[holder].eTokenAllowance[spender]; // <= FOUND
49: }['31']
31: function isBalanceForwarderEnabled(UserStorage storage self) internal view returns (bool) {
32: return unpackBalanceForwarder(self.data); // <= FOUND
33: }['35']
35: function getOwed(UserStorage storage self) internal view returns (Owed) {
36: return Owed.wrap(uint144((PackedUserSlot.unwrap(self.data) & OWED_MASK) >> OWED_OFFSET)); // <= FOUND
37: }['39']
39: function getBalance(UserStorage storage self) internal view returns (Shares) {
40: return unpackBalance(self.data); // <= FOUND
41: }['43']
43: function getBalanceAndBalanceForwarder(UserStorage storage self) internal view returns (Shares, bool) {
44: PackedUserSlot data = self.data;
45: return (unpackBalance(data), unpackBalanceForwarder(data)); // <= FOUND
46: }['23']
23: function asset() public view virtual reentrantOK returns (address) {
24: (IERC20 _asset,,) = ProxyUtils.metadata();
25: return address(_asset); // <= FOUND
26: }['29']
29: function totalAssets() public view virtual nonReentrantView returns (uint256) {
30: VaultCache memory vaultCache = loadVault();
31: return totalAssetsInternal(vaultCache); // <= FOUND
32: }['35']
35: function convertToAssets(uint256 shares) public view virtual nonReentrantView returns (uint256) {
36: VaultCache memory vaultCache = loadVault();
37: return shares.toShares().toAssetsDown(vaultCache).toUint(); // <= FOUND
38: }['41']
41: function convertToShares(uint256 assets) public view virtual nonReentrantView returns (uint256) {
42: VaultCache memory vaultCache = loadVault();
43: return assets.toAssets().toSharesDown(vaultCache).toUint(); // <= FOUND
44: }['47']
47: function maxDeposit(address account) public view virtual nonReentrantView returns (uint256) {
48: VaultCache memory vaultCache = loadVault();
49: if (isOperationDisabled(vaultCache.hookedOps, OP_DEPOSIT)) return 0; // <= FOUND
50:
51:
52: Assets max = maxMintInternal(vaultCache, account).toAssetsDown(vaultCache);
53:
54:
55: return max.toSharesDown(vaultCache).toUint() == 0 ? 0 : max.toUint(); // <= FOUND
56: }['59']
59: function previewDeposit(uint256 assets) public view virtual nonReentrantView returns (uint256) {
60: return convertToShares(assets); // <= FOUND
61: }['64']
64: function maxMint(address account) public view virtual nonReentrantView returns (uint256) {
65: VaultCache memory vaultCache = loadVault();
66:
67: return isOperationDisabled(vaultCache.hookedOps, OP_MINT) ? 0 : maxMintInternal(vaultCache, account).toUint(); // <= FOUND
68: }['71']
71: function previewMint(uint256 shares) public view virtual nonReentrantView returns (uint256) {
72: VaultCache memory vaultCache = loadVault();
73: return shares.toShares().toAssetsUp(vaultCache).toUint(); // <= FOUND
74: }['77']
77: function maxWithdraw(address owner) public view virtual nonReentrantView returns (uint256) {
78: VaultCache memory vaultCache = loadVault();
79:
80: return isOperationDisabled(vaultCache.hookedOps, OP_WITHDRAW) // <= FOUND
81: ? 0
82: : maxRedeemInternal(vaultCache, owner).toAssetsDown(vaultCache).toUint();
83: }['86']
86: function previewWithdraw(uint256 assets) public view virtual nonReentrantView returns (uint256) {
87: VaultCache memory vaultCache = loadVault();
88: return assets.toAssets().toSharesUp(vaultCache).toUint(); // <= FOUND
89: }['92']
92: function maxRedeem(address owner) public view virtual nonReentrantView returns (uint256) {
93: VaultCache memory vaultCache = loadVault();
94: if (isOperationDisabled(vaultStorage.hookedOps, OP_REDEEM)) return 0; // <= FOUND
95:
96: Shares max = maxRedeemInternal(vaultCache, owner);
97:
98: return max.toAssetsDown(vaultCache).toUint() == 0 ? 0 : max.toUint(); // <= FOUND
99: }['102']
102: function previewRedeem(uint256 shares) public view virtual nonReentrantView returns (uint256) {
103: return convertToAssets(shares); // <= FOUND
104: }['107']
107: function accumulatedFees() public view virtual nonReentrantView returns (uint256) {
108: return loadVault().accumulatedFees.toUint(); // <= FOUND
109: }['112']
112: function accumulatedFeesAssets() public view virtual nonReentrantView returns (uint256) {
113: VaultCache memory vaultCache = loadVault();
114:
115: return vaultCache.accumulatedFees.toAssetsDown(vaultCache).toUint(); // <= FOUND
116: }['119']
119: function creator() public view virtual reentrantOK returns (address) {
120: return vaultStorage.creator; // <= FOUND
121: }['234']
234: function maxRedeemInternal(VaultCache memory vaultCache, address owner) private view returns (Shares) {
235: Shares max = vaultStorage.users[owner].getBalance();
236:
237:
238:
239:
240:
241:
242:
243: if (max.isZero() || hasAnyControllerEnabled(owner)) return Shares.wrap(0); // <= FOUND
244:
245: Shares cash = vaultCache.cash.toSharesDown(vaultCache);
246: max = max > cash ? cash : max;
247:
248: return max; // <= FOUND
249: }['61']
61: function addAssets(Assets a, Assets b) pure returns (Assets) {
62: return TypesLib.toAssets(a.toUint() + b.toUint()); // <= FOUND
63: }['65']
65: function subAssets(Assets a, Assets b) pure returns (Assets) {
66: return Assets.wrap((Assets.unwrap(a) - Assets.unwrap(b))); // <= FOUND
67: }['69']
69: function eqAssets(Assets a, Assets b) pure returns (bool) {
70: return Assets.unwrap(a) == Assets.unwrap(b); // <= FOUND
71: }['73']
73: function neqAssets(Assets a, Assets b) pure returns (bool) {
74: return Assets.unwrap(a) != Assets.unwrap(b); // <= FOUND
75: }['77']
77: function gtAssets(Assets a, Assets b) pure returns (bool) {
78: return Assets.unwrap(a) > Assets.unwrap(b); // <= FOUND
79: }['81']
81: function gteAssets(Assets a, Assets b) pure returns (bool) {
82: return Assets.unwrap(a) >= Assets.unwrap(b); // <= FOUND
83: }['85']
85: function ltAssets(Assets a, Assets b) pure returns (bool) {
86: return Assets.unwrap(a) < Assets.unwrap(b); // <= FOUND
87: }['89']
89: function lteAssets(Assets a, Assets b) pure returns (bool) {
90: return Assets.unwrap(a) <= Assets.unwrap(b); // <= FOUND
91: }['25']
25: function gtConfigAmount(ConfigAmount a, ConfigAmount b) pure returns (bool) {
26: return a.toUint16() > b.toUint16(); // <= FOUND
27: }['29']
29: function gteConfigAmount(ConfigAmount a, ConfigAmount b) pure returns (bool) {
30: return a.toUint16() >= b.toUint16(); // <= FOUND
31: }['33']
33: function ltConfigAmount(ConfigAmount a, ConfigAmount b) pure returns (bool) {
34: return a.toUint16() < b.toUint16(); // <= FOUND
35: }['37']
37: function lteConfigAmount(ConfigAmount a, ConfigAmount b) pure returns (bool) {
38: return a.toUint16() <= b.toUint16(); // <= FOUND
39: }['61']
61: function addOwed(Owed a, Owed b) pure returns (Owed) {
62: return TypesLib.toOwed(uint256(Owed.unwrap(a)) + uint256(Owed.unwrap(b))); // <= FOUND
63: }['65']
65: function subOwed(Owed a, Owed b) pure returns (Owed) {
66: return Owed.wrap((Owed.unwrap(a) - Owed.unwrap(b))); // <= FOUND
67: }['69']
69: function eqOwed(Owed a, Owed b) pure returns (bool) {
70: return Owed.unwrap(a) == Owed.unwrap(b); // <= FOUND
71: }['73']
73: function neqOwed(Owed a, Owed b) pure returns (bool) {
74: return Owed.unwrap(a) != Owed.unwrap(b); // <= FOUND
75: }['77']
77: function gtOwed(Owed a, Owed b) pure returns (bool) {
78: return Owed.unwrap(a) > Owed.unwrap(b); // <= FOUND
79: }['81']
81: function ltOwed(Owed a, Owed b) pure returns (bool) {
82: return Owed.unwrap(a) < Owed.unwrap(b); // <= FOUND
83: }['48']
48: function addShares(Shares a, Shares b) pure returns (Shares) {
49: return TypesLib.toShares(uint256(Shares.unwrap(a)) + uint256(Shares.unwrap(b))); // <= FOUND
50: }['52']
52: function subShares(Shares a, Shares b) pure returns (Shares) {
53: return Shares.wrap((Shares.unwrap(a) - Shares.unwrap(b))); // <= FOUND
54: }['56']
56: function eqShares(Shares a, Shares b) pure returns (bool) {
57: return Shares.unwrap(a) == Shares.unwrap(b); // <= FOUND
58: }['60']
60: function neqShares(Shares a, Shares b) pure returns (bool) {
61: return Shares.unwrap(a) != Shares.unwrap(b); // <= FOUND
62: }['64']
64: function gtShares(Shares a, Shares b) pure returns (bool) {
65: return Shares.unwrap(a) > Shares.unwrap(b); // <= FOUND
66: }['68']
68: function ltShares(Shares a, Shares b) pure returns (bool) {
69: return Shares.unwrap(a) < Shares.unwrap(b); // <= FOUND
70: }['18']
18: function resolve(AmountCap self) internal pure returns (uint256) {
19: uint256 amountCap = AmountCap.unwrap(self);
20:
21: if (amountCap == 0) return type(uint256).max; // <= FOUND
22:
23: unchecked {
24:
25:
26: return 10 ** (amountCap & 63) * (amountCap >> 6) / 100; // <= FOUND
27: }
28: }['30']
30: function toRawUint16(AmountCap self) internal pure returns (uint16) {
31: return AmountCap.unwrap(self); // <= FOUND
32: }['15']
15: function toUint(Assets self) internal pure returns (uint256) {
16: return Assets.unwrap(self); // <= FOUND
17: }['19']
19: function isZero(Assets self) internal pure returns (bool) {
20: return Assets.unwrap(self) == 0; // <= FOUND
21: }['23']
23: function toSharesDown(Assets amount, VaultCache memory vaultCache) internal pure returns (Shares) {
24: return TypesLib.toShares(toSharesDownUint(amount, vaultCache)); // <= FOUND
25: }['27']
27: function toSharesDownUint(Assets amount, VaultCache memory vaultCache) internal pure returns (uint256) {
28: (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache);
29: unchecked {
30: return amount.toUint() * totalShares / totalAssets; // <= FOUND
31: }
32: }['34']
34: function toSharesUp(Assets amount, VaultCache memory vaultCache) internal pure returns (Shares) {
35: (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache);
36: unchecked {
37:
38: return TypesLib.toShares((amount.toUint() * totalShares + (totalAssets - 1)) / totalAssets); // <= FOUND
39: }
40: }['42']
42: function toOwed(Assets self) internal pure returns (Owed) {
43: unchecked {
44: return TypesLib.toOwed(self.toUint() << INTERNAL_DEBT_PRECISION_SHIFT); // <= FOUND
45: }
46: }['48']
48: function addUnchecked(Assets self, Assets b) internal pure returns (Assets) {
49: unchecked {
50: return Assets.wrap(uint112(self.toUint() + b.toUint())); // <= FOUND
51: }
52: }['54']
54: function subUnchecked(Assets self, Assets b) internal pure returns (Assets) {
55: unchecked {
56: return Assets.wrap(uint112(self.toUint() - b.toUint())); // <= FOUND
57: }
58: }['131']
131: function totalAssetsInternal(VaultCache memory vaultCache) internal pure virtual returns (uint256) {
132:
133: return vaultCache.cash.toUint() + vaultCache.totalBorrows.toAssetsUp().toUint(); // <= FOUND
134: }['16']
16: function isZero(ConfigAmount self) internal pure returns (bool) {
17: return self.toUint16() == 0; // <= FOUND
18: }['20']
20: function toUint16(ConfigAmount self) internal pure returns (uint16) {
21: return ConfigAmount.unwrap(self); // <= FOUND
22: }['57']
57: function allowance(address, address) external pure returns (uint256) {
58: return 0; // <= FOUND
59: }['13']
13: function isSet(Flags self, uint32 bitMask) internal pure returns (bool) {
14: return (Flags.unwrap(self) & bitMask) == bitMask; // <= FOUND
15: }['18']
18: function isNotSet(Flags self, uint32 bitMask) internal pure returns (bool) {
19: return (Flags.unwrap(self) & bitMask) == 0; // <= FOUND
20: }['22']
22: function toUint32(Flags self) internal pure returns (uint32) {
23: return Flags.unwrap(self); // <= FOUND
24: }['83']
83: function uintToString(uint256 n) private pure returns (string memory) {
84: unchecked {
85: if (n == 0) return "0"; // <= FOUND
86:
87: uint256 len;
88: for (uint256 m = n; m != 0; m /= 10) {
89: len++;
90: }
91:
92: bytes memory output = new bytes(len);
93:
94: while (len > 0) {
95: output[--len] = bytes1(uint8(48 + n % 10));
96: n /= 10;
97: }
98:
99: return string(output); // <= FOUND
100: }
101: }['31']
31: function isRecognizedCollateral(LTVConfig memory self) internal pure returns (bool) {
32: return self.targetTimestamp != 0; // <= FOUND
33: }['15']
15: function toUint(Owed self) internal pure returns (uint256) {
16: return Owed.unwrap(self); // <= FOUND
17: }['19']
19: function toAssetsUp(Owed amount) internal pure returns (Assets) {
20: if (Owed.unwrap(amount) == 0) return Assets.wrap(0); // <= FOUND
21:
22: return TypesLib.toAssets(toAssetsUpUint(Owed.unwrap(amount))); // <= FOUND
23: }['25']
25: function toAssetsDown(Owed amount) internal pure returns (Assets) {
26: if (Owed.unwrap(amount) == 0) return Assets.wrap(0); // <= FOUND
27:
28: return TypesLib.toAssets(Owed.unwrap(amount) >> INTERNAL_DEBT_PRECISION_SHIFT); // <= FOUND
29: }['31']
31: function isDust(Owed self) internal pure returns (bool) {
32:
33: return Owed.unwrap(self) < (1 << INTERNAL_DEBT_PRECISION_SHIFT); // <= FOUND
34: }['36']
36: function isZero(Owed self) internal pure returns (bool) {
37: return Owed.unwrap(self) == 0; // <= FOUND
38: }['40']
40: function mulDiv(Owed self, uint256 multiplier, uint256 divisor) internal pure returns (Owed) {
41: return TypesLib.toOwed(uint256(Owed.unwrap(self)) * multiplier / divisor); // <= FOUND
42: }['44']
44: function addUnchecked(Owed self, Owed b) internal pure returns (Owed) {
45: unchecked {
46: return Owed.wrap(uint144(self.toUint() + b.toUint())); // <= FOUND
47: }
48: }['50']
50: function subUnchecked(Owed self, Owed b) internal pure returns (Owed) {
51: unchecked {
52: return Owed.wrap(uint144(self.toUint() - b.toUint())); // <= FOUND
53: }
54: }['56']
56: function toAssetsUpUint(uint256 owedExact) internal pure returns (uint256) {
57: return (owedExact + (1 << INTERNAL_DEBT_PRECISION_SHIFT) - 1) >> INTERNAL_DEBT_PRECISION_SHIFT; // <= FOUND
58: }['50']
50: function isEmptyOrTrueReturn(bool callSuccess, bytes memory data) private pure returns (bool) {
51: return callSuccess && (data.length == 0 || (data.length >= 32 && abi.decode(data, (bool)))); // <= FOUND
52: }['14']
14: function toUint(Shares self) internal pure returns (uint256) {
15: return Shares.unwrap(self); // <= FOUND
16: }['18']
18: function isZero(Shares self) internal pure returns (bool) {
19: return Shares.unwrap(self) == 0; // <= FOUND
20: }['22']
22: function toAssetsDown(Shares amount, VaultCache memory vaultCache) internal pure returns (Assets) {
23: (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache);
24: unchecked {
25: return TypesLib.toAssets(amount.toUint() * totalAssets / totalShares); // <= FOUND
26: }
27: }['29']
29: function toAssetsUp(Shares amount, VaultCache memory vaultCache) internal pure returns (Assets) {
30: (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache);
31: unchecked {
32:
33: return TypesLib.toAssets((amount.toUint() * totalAssets + (totalShares - 1)) / totalShares); // <= FOUND
34: }
35: }['37']
37: function mulDiv(Shares self, uint256 multiplier, uint256 divisor) internal pure returns (Shares) {
38: return TypesLib.toShares(uint256(Shares.unwrap(self)) * multiplier / divisor); // <= FOUND
39: }['41']
41: function subUnchecked(Shares self, Shares b) internal pure returns (Shares) {
42: unchecked {
43: return Shares.wrap(uint112(self.toUint() - b.toUint())); // <= FOUND
44: }
45: }['63']
63: function toShares(uint256 amount) internal pure returns (Shares) {
64: if (amount > MAX_SANE_AMOUNT) revert Errors.E_AmountTooLargeToEncode();
65: return Shares.wrap(uint112(amount)); // <= FOUND
66: }['68']
68: function toAssets(uint256 amount) internal pure returns (Assets) {
69: if (amount > MAX_SANE_AMOUNT) revert Errors.E_AmountTooLargeToEncode();
70: return Assets.wrap(uint112(amount)); // <= FOUND
71: }['73']
73: function toOwed(uint256 amount) internal pure returns (Owed) {
74: if (amount > MAX_SANE_DEBT_AMOUNT) revert Errors.E_DebtAmountTooLargeToEncode();
75: return Owed.wrap(uint144(amount)); // <= FOUND
76: }['78']
78: function toConfigAmount(uint16 amount) internal pure returns (ConfigAmount) {
79: if (amount > CONFIG_SCALE) revert Errors.E_ConfigAmountTooLargeToEncode();
80: return ConfigAmount.wrap(amount); // <= FOUND
81: }['67']
67: function unpackBalance(PackedUserSlot data) private pure returns (Shares) {
68: return Shares.wrap(uint112(PackedUserSlot.unwrap(data) & SHARES_MASK)); // <= FOUND
69: }['71']
71: function unpackBalanceForwarder(PackedUserSlot data) private pure returns (bool) {
72: return (PackedUserSlot.unwrap(data) & BALANCE_FORWARDER_MASK) > 0; // <= FOUND
73: }['251']
251: function maxMintInternal(VaultCache memory vaultCache, address) private pure returns (Shares) {
252: uint256 supply = totalAssetsInternal(vaultCache);
253: if (supply >= vaultCache.supplyCap) return Shares.wrap(0); // <= FOUND
254:
255: unchecked {
256:
257: uint256 max = vaultCache.supplyCap - supply;
258:
259:
260: uint256 limit = MAX_SANE_AMOUNT - vaultCache.cash.toUint();
261: max = limit < max ? limit : max;
262:
263:
264: max = max.toAssets().toSharesDownUint(vaultCache);
265: limit = MAX_SANE_AMOUNT - vaultCache.totalShares.toUint();
266:
267: return (limit < max ? limit : max).toShares(); // <= FOUND
268: }
269: }Caching immutable state variables results in storing data which is already known at compile time. As such, developers should call immutable state variables directly to save computation and storage which will result in gas savings.
Num of instances: 1
Click to show findings
['51']
51: function computeInterestRateInternal(address, uint256 cash, uint256 borrows) internal view returns (uint256) { // <= FOUND
52: uint256 totalAssets = cash + borrows; // <= FOUND
53:
54: uint32 utilization = totalAssets == 0 // <= FOUND
55: ? 0
56: : uint32(borrows * type(uint32).max / totalAssets); // <= FOUND
57:
58: uint256 ir = baseRate; // <= FOUND
59:
60: if (utilization <= kink) {
61: ir += utilization * slope1;
62: } else {
63: ir += kink * slope1;
64:
65: uint256 utilizationOverKink; // <= FOUND
66: unchecked {
67: utilizationOverKink = utilization - kink;
68: }
69: ir += slope2 * utilizationOverKink;
70: }
71:
72: return ir;
73: }Public functions that aren't used internally in Solidity contracts should be made external to optimize gas usage and improve contract efficiency. External functions can only be called from outside the contract, and their arguments are directly read from the calldata, which is more gas-efficient than loading them into memory, as is the case for public functions. By using external visibility, developers can reduce gas consumption for external calls and ensure that the contract operates more cost-effectively for users. Moreover, setting the appropriate visibility level for functions also enhances code readability and maintainability, promoting a more secure and well-structured contract design.
Num of instances: 5
Click to show findings
['147']
147: function isIgnoredForTotalSupply(address account) public view returns (bool) ['153']
153: function getAllIgnoredForTotalSupply() public view returns (address[] memory) ['148']
148: function setFeeReceiver(address newReceiver) external onlyAdmin ['199']
199: function gulp() public nonReentrant ['256']
256: function getESRSlot() public view returns (ESRSlot memory) When using a smaller int/uint type it first needs to be converted to it's 258 bit counterpart to be operated, this increases the gass cost and thus should be avoided. However it does make sense to use smaller int/uint values within structs provided you pack the struct properly.
Num of instances: 132
Click to show findings
['24']
24: uint8 internal constant UNLOCKED = 1; // <= FOUND['25']
25: uint8 internal constant LOCKED = 2; // <= FOUND['34']
34: uint8 locked; // <= FOUND['24']
24: uint8 internal constant UNLOCKED = 1; // <= FOUND['25']
25: uint8 internal constant LOCKED = 2; // <= FOUND['21']
21: uint16 constant CONFIG_SCALE = 1e4; // <= FOUND['193']
193: function caps() public view virtual override useView(MODULE_GOVERNANCE) returns (uint16 supplyCap, uint16 borrowCap) {} // <= FOUND['199']
199: function LTVFull(address collateral) public view virtual override useView(MODULE_GOVERNANCE) returns (uint16 borrowLTV, uint16 liquidationLTV, uint16 initialLiquidationLTV, uint48 targetTimestamp, uint32 rampDuration) {} // <= FOUND['228']
228: function setLTV(address collateral, uint16 borrowLTV, uint16 liquidationLTV, uint32 rampDuration) public virtual override use(MODULE_GOVERNANCE) {} // <= FOUND['232']
232: function setMaxLiquidationDiscount(uint16 newDiscount) public virtual override use(MODULE_GOVERNANCE) {} // <= FOUND['234']
234: function setLiquidationCoolOffTime(uint16 newCoolOffTime) public virtual override use(MODULE_GOVERNANCE) {} // <= FOUND['240']
240: function setCaps(uint16 supplyCap, uint16 borrowCap) public virtual override use(MODULE_GOVERNANCE) {} // <= FOUND['242']
242: function setInterestFee(uint16 newFee) public virtual override use(MODULE_GOVERNANCE) {} // <= FOUND['26']
26:
29: uint16 internal constant MAX_PROTOCOL_FEE_SHARE = 0.5e4; // <= FOUND['30']
30:
33: uint16 internal constant GUARANTEED_INTEREST_FEE_MIN = 0.1e4; // <= FOUND['32']
32:
33: uint16 internal constant GUARANTEED_INTEREST_FEE_MAX = 1e4; // <= FOUND['53']
53:
64: event GovSetLTV(
65: address indexed collateral,
66: uint16 borrowLTV, // <= FOUND
67: uint16 liquidationLTV, // <= FOUND
68: uint16 initialLiquidationLTV, // <= FOUND
69: uint48 targetTimestamp, // <= FOUND
70: uint32 rampDuration, // <= FOUND
71: bool initialized
72: );['68']
68:
70: event GovSetMaxLiquidationDiscount(uint16 newDiscount); // <= FOUND['73']
73:
76: event GovSetLiquidationCoolOffTime(uint16 newCoolOffTime); // <= FOUND['87']
87:
90: event GovSetCaps(uint16 newSupplyCap, uint16 newBorrowCap); // <= FOUND['91']
91:
93: event GovSetInterestFee(uint16 newFee); // <= FOUND['221']
221: (address protocolReceiver, uint16 protocolFee) = protocolConfig.protocolFeeConfig(address(this)); // <= FOUND['274']
274:
285: function setLTV(address collateral, uint16 borrowLTV, uint16 liquidationLTV, uint32 rampDuration) // <= FOUND
286: public
287: virtual
288: nonReentrant
289: governorOnly
290: {['316']
316: uint16 originalLTV = getLTV(collateral, true).toUint16(); // <= FOUND['323']
323:
324: function setMaxLiquidationDiscount(uint16 newDiscount) public virtual nonReentrant governorOnly { // <= FOUND['329']
329:
330: function setLiquidationCoolOffTime(uint16 newCoolOffTime) public virtual nonReentrant governorOnly { // <= FOUND['371']
371:
372: function setCaps(uint16 supplyCap, uint16 borrowCap) public virtual nonReentrant governorOnly { // <= FOUND['387']
387:
388: function setInterestFee(uint16 newInterestFee) public virtual nonReentrant governorOnly { // <= FOUND['394']
394:
397: function caps() external view returns (uint16 supplyCap, uint16 borrowCap); // <= FOUND['416']
416:
424: function LTVFull(address collateral)
425: external
426: view
427: returns (
428: uint16 borrowLTV, // <= FOUND
429: uint16 liquidationLTV, // <= FOUND
430: uint16 initialLiquidationLTV, // <= FOUND
431: uint48 targetTimestamp, // <= FOUND
432: uint32 rampDuration // <= FOUND
433: );['484']
484:
489: function setLTV(address collateral, uint16 borrowLTV, uint16 liquidationLTV, uint32 rampDuration) external; // <= FOUND['495']
495:
499: function setMaxLiquidationDiscount(uint16 newDiscount) external; // <= FOUND['502']
502:
507: function setLiquidationCoolOffTime(uint16 newCoolOffTime) external; // <= FOUND['521']
521:
524: function setCaps(uint16 supplyCap, uint16 borrowCap) external; // <= FOUND['525']
525:
527: function setInterestFee(uint16 newFee) external; // <= FOUND['18']
18:
26: function isValidInterestFee(address vault, uint16 interestFee) external view returns (bool); // <= FOUND['26']
26:
27: uint16 internal constant DEFAULT_INTEREST_FEE = 0.1e4; // <= FOUND['16']
16: uint16 minInterestFee; // <= FOUND['17']
17: uint16 maxInterestFee; // <= FOUND['23']
23: uint16 protocolFeeShare; // <= FOUND['27']
27:
28: uint16 internal constant CONFIG_SCALE = 1e4; // <= FOUND['34']
34:
35: uint16 internal protocolFeeShare; // <= FOUND['37']
37:
38: uint16 internal minInterestFee; // <= FOUND['39']
39:
40: uint16 internal maxInterestFee; // <= FOUND['49']
49:
52: event SetInterestFeeRange(uint16 newMinInterestFee, uint16 newMaxInterestFee); // <= FOUND['60']
60:
65: event SetVaultInterestFeeRange(address indexed vault, bool exists, uint16 minInterestFee, uint16 maxInterestFee); // <= FOUND['67']
67:
72: event SetFeeConfigSetting(address indexed vault, bool exists, address indexed feeReceiver, uint16 protocolFeeShare); // <= FOUND['72']
72:
75: event SetProtocolFeeShare(uint16 protocolFeeShare, uint16 newProtocolFeeShare); // <= FOUND['94']
94:
95: function isValidInterestFee(address vault, uint16 interestFee) external view returns (bool) { // <= FOUND['159']
159:
162: function setProtocolFeeShare(uint16 newProtocolFeeShare) external onlyAdmin { // <= FOUND['171']
171:
175: function setInterestFeeRange(uint16 minInterestFee_, uint16 maxInterestFee_) external onlyAdmin { // <= FOUND['186']
186:
192: function setVaultInterestFeeRange(address vault, bool exists_, uint16 minInterestFee_, uint16 maxInterestFee_) // <= FOUND
193: external
194: onlyAdmin
195: {['205']
205:
211: function setVaultFeeConfig(address vault, bool exists_, address feeReceiver_, uint16 protocolFeeShare_) // <= FOUND
212: external
213: onlyAdmin
214: {['78']
78: function toConfigAmount(uint16 amount) internal pure returns (ConfigAmount) { // <= FOUND['41']
41:
42: uint16 liquidationCoolOffTime; // <= FOUND['27']
27: uint16 internal constant CONFIG_SCALE = 1e4; // <= FOUND['34']
34: uint16 internal protocolFeeShare; // <= FOUND['37']
37: uint16 internal minInterestFee; // <= FOUND['39']
39: uint16 internal maxInterestFee; // <= FOUND['26']
26: uint16 internal constant MAX_PROTOCOL_FEE_SHARE = 0.5e4; // <= FOUND['30']
30: uint16 internal constant GUARANTEED_INTEREST_FEE_MIN = 0.1e4; // <= FOUND['32']
32: uint16 internal constant GUARANTEED_INTEREST_FEE_MAX = 1e4; // <= FOUND['26']
26: uint16 internal constant DEFAULT_INTEREST_FEE = 0.1e4; // <= FOUND['81']
81:
85: function initOperation(uint32 operation, address accountToCheck) // <= FOUND
86: internal
87: virtual
88: returns (VaultCache memory vaultCache, address account)
89: {['108']
108:
111: function isOperationDisabled(Flags hookedOps, uint32 operation) internal view returns (bool) { // <= FOUND['114']
114:
116: function callHook(Flags hookedOps, uint32 operation, address caller) internal virtual { // <= FOUND['121']
121:
122: function callHookWithLock(Flags hookedOps, uint32 operation, address caller) internal virtual { // <= FOUND['32']
32: uint32 constant OP_DEPOSIT = 1 << 0; // <= FOUND['33']
33: uint32 constant OP_MINT = 1 << 1; // <= FOUND['34']
34: uint32 constant OP_WITHDRAW = 1 << 2; // <= FOUND['35']
35: uint32 constant OP_REDEEM = 1 << 3; // <= FOUND['36']
36: uint32 constant OP_TRANSFER = 1 << 4; // <= FOUND['37']
37: uint32 constant OP_SKIM = 1 << 5; // <= FOUND['38']
38: uint32 constant OP_BORROW = 1 << 6; // <= FOUND['39']
39: uint32 constant OP_REPAY = 1 << 7; // <= FOUND['40']
40: uint32 constant OP_REPAY_WITH_SHARES = 1 << 8; // <= FOUND['41']
41: uint32 constant OP_PULL_DEBT = 1 << 9; // <= FOUND['42']
42: uint32 constant OP_CONVERT_FEES = 1 << 10; // <= FOUND['43']
43: uint32 constant OP_LIQUIDATE = 1 << 11; // <= FOUND['44']
44: uint32 constant OP_FLASHLOAN = 1 << 12; // <= FOUND['45']
45: uint32 constant OP_TOUCH = 1 << 13; // <= FOUND['46']
46: uint32 constant OP_VAULT_STATUS_CHECK = 1 << 14; // <= FOUND['48']
48: uint32 constant OP_MAX_VALUE = 1 << 15; // <= FOUND['53']
53: uint32 constant CFG_DONT_SOCIALIZE_DEBT = 1 << 0; // <= FOUND['56']
56: uint32 constant CFG_EVC_COMPATIBLE_ASSET = 1 << 1; // <= FOUND['58']
58: uint32 constant CFG_MAX_VALUE = 1 << 2; // <= FOUND['63']
63: uint32 constant CONTROLLER_NEUTRAL_OPS = OP_DEPOSIT | OP_MINT | OP_WITHDRAW | OP_REDEEM | OP_TRANSFER | OP_SKIM // <= FOUND
64: | OP_REPAY | OP_REPAY_WITH_SHARES | OP_CONVERT_FEES | OP_FLASHLOAN | OP_TOUCH | OP_VAULT_STATUS_CHECK;['226']
226: function setHookConfig(address newHookTarget, uint32 newHookedOps) public virtual override use(MODULE_GOVERNANCE) {} // <= FOUND['238']
238: function setConfigFlags(uint32 newConfigFlags) public virtual override use(MODULE_GOVERNANCE) {} // <= FOUND['13']
13:
14: function isSet(Flags self, uint32 bitMask) internal pure returns (bool) { // <= FOUND['18']
18:
19: function isNotSet(Flags self, uint32 bitMask) internal pure returns (bool) { // <= FOUND['78']
78:
81: event GovSetHookConfig(address indexed newHookTarget, uint32 newHookedOps); // <= FOUND['82']
82:
84: event GovSetConfigFlags(uint32 newConfigFlags); // <= FOUND['349']
349:
350: function setHookConfig(address newHookTarget, uint32 newHookedOps) public virtual nonReentrant governorOnly { // <= FOUND['363']
363:
364: function setConfigFlags(uint32 newConfigFlags) public virtual nonReentrant governorOnly { // <= FOUND['444']
444:
447: function hookConfig() external view returns (address hookTarget, uint32 hookedOps); // <= FOUND['512']
512:
516: function setHookConfig(address newHookTarget, uint32 newHookedOps) external; // <= FOUND['516']
516:
518: function setConfigFlags(uint32 newConfigFlags) external; // <= FOUND['54']
54: uint32 utilization = totalAssets == 0 // <= FOUND
55: ? 0
56: : uint32(borrows * type(uint32).max / totalAssets);['20']
20:
21: uint32 rampDuration; // <= FOUND['61']
61: function setLTV(LTVConfig memory self, ConfigAmount borrowLTV, ConfigAmount liquidationLTV, uint32 rampDuration) // <= FOUND
62: internal
63: view
64: returns (LTVConfig memory newLTV)
65: {['17']
17:
18: uint32 stamp; // <= FOUND['25']
25: uint32 private constant STAMP = 1; // <= FOUND['25']
25: uint32 private constant STAMP = 1; // <= FOUND['31']
31: uint40 lastInterestUpdate; // <= FOUND['32']
32: uint40 interestSmearEnd; // <= FOUND['30']
30: uint40 lastUpdated; // <= FOUND['18']
18:
19: uint48 targetTimestamp; // <= FOUND['25']
25:
28: uint48 lastInterestAccumulatorUpdate; // <= FOUND['25']
25:
27: uint48 lastInterestAccumulatorUpdate; // <= FOUND['54']
54:
55: uint72 interestRate; // <= FOUND['21']
21: uint128 capacity; // <= FOUND['22']
22: uint128 minted; // <= FOUND['42']
42:
46: function setCapacity(address minter, uint128 capacity) external onlyOwner { // <= FOUND['15']
15:
20: function transferFrom(address from, address to, uint160 amount, address token) external; // <= FOUND['33']
33: uint168 interestLeft; // <= FOUND['16']
16: uint216 internal constant SECONDS_PER_YEAR = 365.2425 * 86400; // <= FOUND['17']
17: uint216 public constant MAX_RATE = 1e27 * 1.5 / SECONDS_PER_YEAR; // <= FOUND['18']
18: uint216 public constant BASE_RATE = 1e27 * 0.005 / SECONDS_PER_YEAR; // <= FOUND['19']
19: uint216 public constant ADJUST_FACTOR = 1.1e18; // <= FOUND['20']
20: uint216 public constant ADJUST_ONE = 1.0e18; // <= FOUND['21']
21: uint216 public constant ADJUST_INTERVAL = 1 hours; // <= FOUND['31']
31: uint216 lastRate; // <= FOUND['61']
61: (uint216 rate, bool updated) = _computeRate(irmCache); // <= FOUND['71']
71: (uint216 rate,) = _computeRate(irmStorage); // <= FOUND['75']
75: function _computeRate(IRMData memory irmCache) internal view returns (uint216 rate, bool updated) { // <= FOUND['16']
16: uint216 internal constant SECONDS_PER_YEAR = 365.2425 * 86400; // <= FOUND['17']
17: uint216 public constant MAX_RATE = 1e27 * 1.5 / SECONDS_PER_YEAR; // <= FOUND['18']
18: uint216 public constant BASE_RATE = 1e27 * 0.005 / SECONDS_PER_YEAR; // <= FOUND['19']
19: uint216 public constant ADJUST_FACTOR = 1.1e18; // <= FOUND['20']
20: uint216 public constant ADJUST_ONE = 1.0e18; // <= FOUND['21']
21: uint216 public constant ADJUST_INTERVAL = 1 hours; // <= FOUNDWhere possible use mappings in place of arrays as they are far cheaper during iterations and are easier to use. There are instances such as struct arrays where arrays make more sense.
Num of instances: 2
Click to show findings
['201']
201: for (uint256 i; i < end - start; ++i) {
202: list[i] = proxyList[start + i]; // <= FOUND
203: }['201']
201: for (uint256 i; i < end - start; ++i) {
202: list[i] = proxyList[start + i]; // <= FOUND
203: }Replace spotted instances with != 0 for uints as this uses less gas
Num of instances: 14
Click to show findings
['69']
69: if (deltaT > 0) { // <= FOUND['110']
110: return evc.getControllers(account).length > 0; // <= FOUND['79']
79:
80:
81:
82:
83:
84:
85: if (evc.getControllers(owner).length > 0) { // <= FOUND['292']
292:
293: if (newLiquidationLTV >= currentLTV.getLTV(true) && rampDuration > 0) revert E_LTVLiquidation(); // <= FOUND['94']
94: while (len > 0) { // <= FOUND['107']
107: if (maxRepay > 0) { // <= FOUND['183']
183:
184:
185: if (liqCache.repay.toUint() > 0) { // <= FOUND['201']
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215: if (liqCache.yieldBalance > 0) { // <= FOUND['67']
67: if (IERC20(collateral).balanceOf(account) > 0) return false; // <= FOUND['13']
13: if (errMsg.length > 0) { // <= FOUND['21']
21: return bytes(vaultStorage.name).length > 0 ? vaultStorage.name : "Unnamed Euler Vault"; // <= FOUND['26']
26: return bytes(vaultStorage.symbol).length > 0 ? vaultStorage.symbol : "UNKNOWN"; // <= FOUND['72']
72: return (PackedUserSlot.unwrap(data) & BALANCE_FORWARDER_MASK) > 0; // <= FOUND[]
}
pragma solidity >=0.8.0; // <= FOUNDUsing unchecked increments in Solidity can save on gas fees by bypassing built-in overflow checks, thus optimizing gas usage, but requires careful assessment of potential risks and edge cases to avoid unintended consequences.
Num of instances: 1
Click to show findings
['25']
25: function reserveSeqId(string calldata designator) external returns (uint256) { // <= FOUND
26: uint256 seqId = ++counters[designator]; // <= FOUND
27:
28: emit SequenceIdReserved(designator, seqId, msg.sender);
29:
30: return seqId;
31: }Using .delete is better than resetting a Solidity variable to its default value manually because it frees up storage space on the Ethereum blockchain, resulting in gas cost savings.
Num of instances: 3
Click to show findings
['42']
42: function initVaultCache(VaultCache memory vaultCache) private view returns (bool dirty) { // <= FOUND
43: dirty = false; // <= FOUND
44:
45:
46:
47: (vaultCache.asset, vaultCache.oracle, vaultCache.unitOfAccount) = ProxyUtils.metadata();
48:
49:
50:
51: vaultCache.lastInterestAccumulatorUpdate = vaultStorage.lastInterestAccumulatorUpdate;
52: vaultCache.cash = vaultStorage.cash;
53: vaultCache.supplyCap = vaultStorage.supplyCap.resolve();
54: vaultCache.borrowCap = vaultStorage.borrowCap.resolve();
55: vaultCache.hookedOps = vaultStorage.hookedOps;
56: vaultCache.snapshotInitialized = vaultStorage.snapshotInitialized;
57:
58: vaultCache.totalShares = vaultStorage.totalShares;
59: vaultCache.totalBorrows = vaultStorage.totalBorrows;
60:
61: vaultCache.accumulatedFees = vaultStorage.accumulatedFees;
62: vaultCache.configFlags = vaultStorage.configFlags;
63:
64: vaultCache.interestAccumulator = vaultStorage.interestAccumulator;
65:
66:
67:
68: uint256 deltaT = block.timestamp - vaultCache.lastInterestAccumulatorUpdate;
69: if (deltaT > 0) {
70: dirty = true;
71:
72:
73:
74: ConfigAmount interestFee = vaultStorage.interestFee;
75: uint256 interestRate = vaultStorage.interestRate;
76:
77: uint256 newInterestAccumulator = vaultCache.interestAccumulator;
78: uint256 newTotalBorrows = vaultCache.totalBorrows.toUint();
79:
80: unchecked {
81: uint256 intermediate;
82: (uint256 multiplier, bool overflow) = RPow.rpow(interestRate + 1e27, deltaT, 1e27);
83:
84:
85: if (!overflow) {
86: intermediate = newInterestAccumulator * multiplier;
87: if (newInterestAccumulator == intermediate / multiplier) {
88: newInterestAccumulator = intermediate / 1e27;
89: }
90: }
91:
92: intermediate = newTotalBorrows * newInterestAccumulator;
93: if (newTotalBorrows == intermediate / newInterestAccumulator) {
94: newTotalBorrows = intermediate / vaultCache.interestAccumulator;
95: }
96: }
97:
98: uint256 newAccumulatedFees = vaultCache.accumulatedFees.toUint();
99: uint256 newTotalShares = vaultCache.totalShares.toUint();
100: uint256 feeAssets = (newTotalBorrows - vaultCache.totalBorrows.toUint()) * interestFee.toUint16()
101: / (uint256(CONFIG_SCALE) << INTERNAL_DEBT_PRECISION_SHIFT);
102:
103: if (feeAssets != 0) {
104:
105:
106:
107:
108:
109:
110:
111: uint256 newTotalAssets = vaultCache.cash.toUint() + OwedLib.toAssetsUpUint(newTotalBorrows);
112: newTotalShares = newTotalAssets * newTotalShares / (newTotalAssets - feeAssets);
113: newAccumulatedFees += newTotalShares - vaultCache.totalShares.toUint();
114: }
115:
116:
117:
118: if (newTotalBorrows <= MAX_SANE_DEBT_AMOUNT) {
119: vaultCache.totalBorrows = newTotalBorrows.toOwed();
120: vaultCache.interestAccumulator = newInterestAccumulator;
121: vaultCache.lastInterestAccumulatorUpdate = uint48(block.timestamp);
122:
123: if (newTotalShares != vaultCache.totalShares.toUint() && newTotalShares <= MAX_SANE_AMOUNT) {
124: vaultCache.accumulatedFees = newAccumulatedFees.toShares();
125: vaultCache.totalShares = newTotalShares.toShares();
126: }
127: }
128: }
129: }['75']
75: function _computeRate(IRMData memory irmCache) internal view returns (uint216 rate, bool updated) { // <= FOUND
76: updated = false; // <= FOUND
77: rate = irmCache.lastRate;
78:
79:
80: if (block.timestamp < irmCache.lastUpdated + ADJUST_INTERVAL) {
81: return (rate, updated);
82: }
83:
84: uint256 quote = oracle.getQuote(quoteAmount, synth, referenceAsset);
85:
86: updated = true;
87:
88: if (quote < targetQuote) {
89:
90: rate = rate * ADJUST_FACTOR / ADJUST_ONE;
91: } else {
92:
93: rate = rate * ADJUST_ONE / ADJUST_FACTOR;
94: }
95:
96:
97: if (rate < BASE_RATE) {
98: rate = BASE_RATE;
99: } else if (rate > MAX_RATE) {
100: rate = MAX_RATE;
101: }
102:
103: return (rate, updated);
104: }['83']
83: function checkVaultStatus() public virtual reentrantOK onlyEVCChecks returns (bytes4 magicValue) { // <= FOUND
84:
85:
86:
87: VaultCache memory vaultCache = updateVault();
88: uint256 newInterestRate = computeInterestRate(vaultCache);
89:
90: logVaultStatus(vaultCache, newInterestRate);
91:
92:
93:
94:
95: if (vaultCache.snapshotInitialized) {
96: vaultStorage.snapshotInitialized = vaultCache.snapshotInitialized = false; // <= FOUND
97:
98: Assets snapshotCash = snapshot.cash;
99: Assets snapshotBorrows = snapshot.borrows;
100:
101: uint256 prevBorrows = snapshotBorrows.toUint();
102: uint256 borrows = vaultCache.totalBorrows.toAssetsUp().toUint();
103:
104: if (borrows > vaultCache.borrowCap && borrows > prevBorrows) revert E_BorrowCapExceeded();
105:
106: uint256 prevSupply = snapshotCash.toUint() + prevBorrows;
107:
108:
109:
110:
111: uint256 supply = vaultCache.cash.toUint() + vaultCache.totalBorrows.toAssetsDown().toUint();
112:
113: if (supply > vaultCache.supplyCap && supply > prevSupply) revert E_SupplyCapExceeded();
114:
115: snapshot.reset();
116: }
117:
118: callHookWithLock(vaultCache.hookedOps, OP_VAULT_STATUS_CHECK, address(evc));
119:
120: magicValue = IEVCVault.checkVaultStatus.selector;
121: }Using .delete is better than resetting a Solidity variable to its default value manually because it frees up storage space on the Ethereum blockchain, resulting in gas cost savings.
Num of instances: 3
Click to show findings
['79']
79: self.targetTimestamp = 0; // <= FOUND['80']
80: self.rampDuration = 0; // <= FOUND['73']
73: liqCache.yieldBalance = 0; // <= FOUNDMaking function calls or external calls within loops in Solidity can lead to inefficient gas usage, potential bottlenecks, and increased vulnerability to attacks. Each function call or external call consumes gas, and when executed within a loop, the gas cost multiplies, potentially causing the transaction to run out of gas or exceed block gas limits. This can result in transaction failure or unpredictable behavior.
Num of instances: 10
Click to show findings
['164']
164: for (uint256 i = 0; i < ignoredLength; i++) {
165: total -= balanceOf(ignoredForTotalSupply.at(i)); // <= FOUND
166: }['62']
62: for (uint256 i; i < collaterals.length; ++i) {
63: address collateral = collaterals[i];
64:
65: if (!isRecognizedCollateral(collateral)) continue; // <= FOUND
66:
67: if (IERC20(collateral).balanceOf(account) > 0) return false; // <= FOUND
68: }['164']
164: for (uint256 i = 0; i < ignoredLength; i++) {
165: total -= balanceOf(ignoredForTotalSupply.at(i)); // <= FOUND
166: }['62']
62: for (uint256 i; i < collaterals.length; ++i) {
63: address collateral = collaterals[i];
64:
65: if (!isRecognizedCollateral(collateral)) continue; // <= FOUND
66:
67: if (IERC20(collateral).balanceOf(account) > 0) return false; // <= FOUND
68: }['26']
26: for (uint256 i; i < collaterals.length; ++i) {
27: collateralValue += getCollateralValue(vaultCache, account, collaterals[i], liquidation); // <= FOUND
28: }['49']
49: for (uint256 i; i < collaterals.length; ++i) {
50: collateralValue += getCollateralValue(vaultCache, account, collaterals[i], false); // <= FOUND
51: if (collateralValue > liabilityValue) return;
52: }['47']
47: for (uint256 i; i < collaterals.length; ++i) {
48: collateralValues[i] = getCollateralValue(vaultCache, account, collaterals[i], liquidation); // <= FOUND
49: }['26']
26: for (uint256 i; i < collaterals.length; ++i) {
27: collateralValue += getCollateralValue(vaultCache, account, collaterals[i], liquidation); // <= FOUND
28: }['49']
49: for (uint256 i; i < collaterals.length; ++i) {
50: collateralValue += getCollateralValue(vaultCache, account, collaterals[i], false); // <= FOUND
51: if (collateralValue > liabilityValue) return;
52: }['47']
47: for (uint256 i; i < collaterals.length; ++i) {
48: collateralValues[i] = getCollateralValue(vaultCache, account, collaterals[i], liquidation); // <= FOUND
49: }Using assembly for address comparisons in Solidity can save gas because it allows for more direct access to the Ethereum Virtual Machine (EVM), reducing the overhead of higher-level operations. Solidity's high-level abstraction simplifies coding but can introduce additional gas costs. Using assembly for simple operations like address comparisons can be more gas-efficient.
Num of instances: 22
Click to show findings
['32']
32: function pushAssets(VaultCache memory vaultCache, address to, Assets amount) internal virtual {
33: if (
34: to == address(0) // <= FOUND
35: || (vaultCache.configFlags.isNotSet(CFG_EVC_COMPATIBLE_ASSET) && isKnownNonOwnerAccount(to))
36: ) {
37: revert E_BadAssetReceiver();
38: }
39:
40: vaultStorage.cash = vaultCache.cash = vaultCache.cash - amount;
41: vaultCache.asset.safeTransfer(to, amount.toUint());
42: }['26']
26: function enableBalanceForwarder() public virtual nonReentrant {
27: if (address(balanceTracker) == address(0)) revert E_NotSupported(); // <= FOUND
28:
29: address account = EVCAuthenticate();
30: UserStorage storage user = vaultStorage.users[account];
31:
32: bool wasBalanceForwarderEnabled = user.isBalanceForwarderEnabled();
33:
34: user.setBalanceForwarder(true);
35: balanceTracker.balanceTrackerHook(account, user.getBalance().toUint(), false);
36:
37: if (!wasBalanceForwarderEnabled) emit BalanceForwarderStatus(account, true);
38: }['41']
41: function disableBalanceForwarder() public virtual nonReentrant {
42: if (address(balanceTracker) == address(0)) revert E_NotSupported(); // <= FOUND
43:
44: address account = EVCAuthenticate();
45: UserStorage storage user = vaultStorage.users[account];
46:
47: bool wasBalanceForwarderEnabled = user.isBalanceForwarderEnabled();
48:
49: user.setBalanceForwarder(false);
50: balanceTracker.balanceTrackerHook(account, 0, false);
51:
52: if (wasBalanceForwarderEnabled) emit BalanceForwarderStatus(account, false);
53: }['16']
16: function increaseBalance(
17: VaultCache memory vaultCache,
18: address account,
19: address sender,
20: Shares amount,
21: Assets assets
22: ) internal virtual {
23: if (account == address(0)) revert E_BadSharesReceiver(); // <= FOUND
24: UserStorage storage user = vaultStorage.users[account];
25:
26: (Shares origBalance, bool balanceForwarderEnabled) = user.getBalanceAndBalanceForwarder();
27: Shares newBalance = origBalance + amount;
28:
29: user.setBalance(newBalance);
30: vaultStorage.totalShares = vaultCache.totalShares = vaultCache.totalShares + amount;
31:
32: if (balanceForwarderEnabled) {
33: balanceTracker.balanceTrackerHook(account, newBalance.toUint(), false);
34: }
35:
36: emit Transfer(address(0), account, amount.toUint());
37: emit Deposit(sender, account, assets.toUint(), amount.toUint());
38: }['69']
69: function transferBalance(address from, address to, Shares amount) internal virtual {
70: if (to == address(0)) revert E_BadSharesReceiver(); // <= FOUND
71:
72: if (!amount.isZero()) {
73:
74:
75: UserStorage storage user = vaultStorage.users[from];
76:
77: (Shares origFromBalance, bool fromBalanceForwarderEnabled) = user.getBalanceAndBalanceForwarder();
78: if (origFromBalance < amount) revert E_InsufficientBalance();
79:
80: Shares newFromBalance = origFromBalance.subUnchecked(amount);
81: user.setBalance(newFromBalance);
82:
83:
84:
85: user = vaultStorage.users[to];
86:
87: (Shares origToBalance, bool toBalanceForwarderEnabled) = user.getBalanceAndBalanceForwarder();
88:
89: Shares newToBalance = origToBalance + amount;
90: user.setBalance(newToBalance);
91:
92: if (fromBalanceForwarderEnabled) {
93: balanceTracker.balanceTrackerHook(from, newFromBalance.toUint(), isControlCollateralInProgress());
94: }
95:
96: if (toBalanceForwarderEnabled && from != to) {
97: balanceTracker.balanceTrackerHook(to, newToBalance.toUint(), false);
98: }
99: }
100:
101: emit Transfer(from, to, amount.toUint());
102: }['108']
108: function isOperationDisabled(Flags hookedOps, uint32 operation) internal view returns (bool) {
109: return hookedOps.isSet(operation) && vaultStorage.hookTarget == address(0); // <= FOUND
110: }['127']
127: function invokeHookTarget(address caller) private {
128: address hookTarget = vaultStorage.hookTarget;
129:
130: if (hookTarget == address(0)) revert E_OperationDisabled(); // <= FOUND
131:
132: (bool success, bytes memory data) = hookTarget.call(abi.encodePacked(msg.data, caller));
133:
134: if (!success) RevertBytes.revertBytes(data);
135: }['107']
107: function computeInterestRate(VaultCache memory vaultCache) internal virtual returns (uint256) {
108:
109: address irm = vaultStorage.interestRateModel;
110: uint256 newInterestRate = vaultStorage.interestRate;
111:
112: if (irm != address(0)) { // <= FOUND
113: (bool success, bytes memory data) = irm.call(
114: abi.encodeCall(
115: IIRM.computeInterestRate,
116: (address(this), vaultCache.cash.toUint(), vaultCache.totalBorrows.toAssetsUp().toUint())
117: )
118: );
119:
120: if (success && data.length >= 32) {
121: newInterestRate = abi.decode(data, (uint256));
122: if (newInterestRate > MAX_ALLOWED_INTEREST_RATE) newInterestRate = MAX_ALLOWED_INTEREST_RATE;
123: vaultStorage.interestRate = uint72(newInterestRate);
124: }
125: }
126:
127: return newInterestRate;
128: }['130']
130: function computeInterestRateView(VaultCache memory vaultCache) internal view virtual returns (uint256) {
131:
132: address irm = vaultStorage.interestRateModel;
133: uint256 newInterestRate = vaultStorage.interestRate;
134:
135: if (irm != address(0) && isVaultStatusCheckDeferred()) { // <= FOUND
136: (bool success, bytes memory data) = irm.staticcall(
137: abi.encodeCall(
138: IIRM.computeInterestRateView,
139: (address(this), vaultCache.cash.toUint(), vaultCache.totalBorrows.toAssetsUp().toUint())
140: )
141: );
142:
143: if (success && data.length >= 32) {
144: newInterestRate = abi.decode(data, (uint256));
145: if (newInterestRate > MAX_ALLOWED_INTEREST_RATE) newInterestRate = MAX_ALLOWED_INTEREST_RATE;
146: }
147: }
148:
149: return newInterestRate;
150: }['58']
58: function _update(address from, address to, uint256 value) internal virtual override {
59: super._update(from, to, value);
60:
61: if (from != address(0)) { // <= FOUND
62: evc.requireAccountStatusCheck(from);
63: }
64: }['83']
83: function isKnownNonOwnerAccount(address account) internal view returns (bool) {
84: address owner = evc.getAccountOwner(account);
85: return owner != address(0) && owner != account; // <= FOUND
86: }['116']
116: function createProxy(address desiredImplementation, bool upgradeable, bytes memory trailingData)
117: external
118: nonReentrant
119: returns (address)
120: {
121: address _implementation = implementation;
122: if (desiredImplementation == address(0)) desiredImplementation = _implementation; // <= FOUND
123:
124: if (desiredImplementation == address(0) || desiredImplementation != _implementation) revert E_Implementation(); // <= FOUND
125:
126: address proxy;
127:
128: if (upgradeable) {
129: proxy = address(new BeaconProxy(trailingData));
130: } else {
131: proxy = deployMetaProxy(desiredImplementation, trailingData);
132: }
133:
134: proxyLookup[proxy] =
135: ProxyConfig({upgradeable: upgradeable, implementation: desiredImplementation, trailingData: trailingData});
136:
137: proxyList.push(proxy);
138:
139: IComponent(proxy).initialize(msg.sender);
140:
141: emit ProxyCreated(proxy, upgradeable, desiredImplementation, trailingData);
142:
143: return proxy;
144: }['151']
151: function setImplementation(address newImplementation) external nonReentrant adminOnly {
152: if (newImplementation == address(0)) revert E_BadAddress(); // <= FOUND
153: implementation = newImplementation;
154: emit SetImplementation(newImplementation);
155: }['182']
182: function isProxy(address proxy) external view returns (bool) {
183: return proxyLookup[proxy].implementation != address(0); // <= FOUND
184: }['216']
216: function convertFees() public virtual nonReentrant {
217: (VaultCache memory vaultCache, address account) = initOperation(OP_CONVERT_FEES, CHECKACCOUNT_NONE);
218:
219: if (vaultCache.accumulatedFees.isZero()) return;
220:
221: (address protocolReceiver, uint16 protocolFee) = protocolConfig.protocolFeeConfig(address(this));
222: address governorReceiver = vaultStorage.feeReceiver;
223:
224: if (governorReceiver == address(0)) { // <= FOUND
225: protocolFee = CONFIG_SCALE;
226: } else if (protocolFee > MAX_PROTOCOL_FEE_SHARE) {
227: protocolFee = MAX_PROTOCOL_FEE_SHARE;
228: }
229:
230: Shares governorShares = vaultCache.accumulatedFees.mulDiv(CONFIG_SCALE - protocolFee, CONFIG_SCALE);
231: Shares protocolShares = vaultCache.accumulatedFees - governorShares;
232:
233:
234: vaultStorage.totalShares = vaultCache.totalShares = vaultCache.totalShares - vaultCache.accumulatedFees;
235:
236: vaultStorage.accumulatedFees = vaultCache.accumulatedFees = Shares.wrap(0);
237:
238:
239:
240: if (!governorShares.isZero()) {
241: increaseBalance(vaultCache, governorReceiver, address(0), governorShares, Assets.wrap(0));
242: }
243:
244: if (!protocolShares.isZero()) {
245: increaseBalance(vaultCache, protocolReceiver, address(0), protocolShares, Assets.wrap(0));
246: }
247:
248: emit ConvertFees(account, protocolReceiver, governorReceiver, protocolShares.toUint(), governorShares.toUint());
249: }['349']
349: function setHookConfig(address newHookTarget, uint32 newHookedOps) public virtual nonReentrant governorOnly {
350: if (
351: newHookTarget != address(0) // <= FOUND
352: && IHookTarget(newHookTarget).isHookTarget() != IHookTarget.isHookTarget.selector
353: ) revert E_NotHookTarget();
354:
355: if (newHookedOps >= OP_MAX_VALUE) revert E_NotSupported();
356:
357: vaultStorage.hookTarget = newHookTarget;
358: vaultStorage.hookedOps = Flags.wrap(newHookedOps);
359: emit GovSetHookConfig(newHookTarget, newHookedOps);
360: }['122']
122: function validateOracle(VaultCache memory vaultCache) internal pure {
123: if (address(vaultCache.oracle) == address(0)) revert E_NoPriceOracle(); // <= FOUND
124: }['137']
137: function setAdmin(address newAdmin) external onlyAdmin {
138: if (newAdmin == address(0)) revert E_InvalidAdmin(); // <= FOUND
139:
140: admin = newAdmin;
141:
142: emit SetAdmin(newAdmin);
143: }['148']
148: function setFeeReceiver(address newReceiver) external onlyAdmin {
149: if (newReceiver == address(0)) revert E_InvalidReceiver(); // <= FOUND
150:
151: feeReceiver = newReceiver;
152:
153: emit SetFeeReceiver(newReceiver);
154: }['186']
186: function setVaultInterestFeeRange(address vault, bool exists_, uint16 minInterestFee_, uint16 maxInterestFee_)
187: external
188: onlyAdmin
189: {
190: if (vault == address(0)) revert E_InvalidVault(); // <= FOUND
191: if (maxInterestFee_ > CONFIG_SCALE || minInterestFee_ > maxInterestFee_) revert E_InvalidConfigValue();
192:
193: _interestFeeRanges[vault] =
194: InterestFeeRange({exists: exists_, minInterestFee: minInterestFee_, maxInterestFee: maxInterestFee_});
195:
196: emit SetVaultInterestFeeRange(vault, exists_, minInterestFee_, maxInterestFee_);
197: }['205']
205: function setVaultFeeConfig(address vault, bool exists_, address feeReceiver_, uint16 protocolFeeShare_)
206: external
207: onlyAdmin
208: {
209: if (vault == address(0)) revert E_InvalidVault(); // <= FOUND
210: if (exists_ && feeReceiver_ == address(0)) revert E_InvalidReceiver(); // <= FOUND
211: if (protocolFeeShare_ > CONFIG_SCALE) revert E_InvalidConfigValue();
212:
213: _protocolFeeConfig[vault] =
214: ProtocolFeeConfig({exists: exists_, feeReceiver: feeReceiver_, protocolFeeShare: protocolFeeShare_});
215:
216: emit SetFeeConfigSetting(vault, exists_, feeReceiver_, protocolFeeShare_);
217: }['28']
28: function safeTransferFrom(IERC20 token, address from, address to, uint256 value, address permit2) internal {
29: (bool success, bytes memory tryData) = trySafeTransferFrom(token, from, to, value);
30: bytes memory fallbackData;
31: if (!success && permit2 != address(0)) { // <= FOUND
32: if (value > type(uint160).max) {
33: revert E_TransferFromFailed(tryData, abi.encodePacked(E_Permit2AmountOverflow.selector));
34: }
35:
36: (success, fallbackData) =
37: permit2.call(abi.encodeCall(IPermit2.transferFrom, (from, to, uint160(value), address(token))));
38: }
39:
40: if (!success) revert E_TransferFromFailed(tryData, fallbackData);
41: }In Solidity, performing unnecessary operations can consume more gas than needed, leading to cost inefficiencies. For instance, if a transfer function doesn't have a zero amount check and someone calls it with a zero amount, unnecessary gas will be consumed in executing the function, even though the state of the contract remains the same. By implementing a zero amount check, such unnecessary function calls can be avoided, thereby saving gas and making the contract more efficient.
Num of instances: 3
Click to show findings
['27']
27: function transfer(address to, uint256 amount) public virtual override nonReentrant returns (bool) {
28: return super.transfer(to, amount); // <= FOUND
29: }['41']
41: function transfer(address to, uint256 amount) public virtual override callThroughEVC returns (bool) { return super.transfer(to, amount); } // <= FOUND['104']
104: function transfer(address to, uint256 amount)
105: public
106: override (ERC20, IERC20)
107: nonReentrant
108: requireAccountStatusCheck(_msgSender())
109: returns (bool)
110: {
111: return super.transfer(to, amount); // <= FOUND
112: }In Solidity, each storage slot has a size of 32 bytes. If a struct contains multiple uint values, it's efficient to pack these into as few storage slots as possible to optimize gas usage. The EVM (Ethereum Virtual Machine) charges gas for each storage operation, so minimizing the number of slots used can result in substantial gas savings. This can be achieved by ordering struct fields according to their size or by using smaller data types where possible. However, developers must balance these optimizations with the need for code clarity and the precision requirements of their application. Always ensure that data packing does not compromise the functionality or security of the contract.
Num of instances: 2
Click to show findings
['12']
12: struct UserStorage {
13:
14: PackedUserSlot data;
15:
16: uint256 interestAccumulator; // <= FOUND
17:
18: mapping(address spender => uint256 allowance) eTokenAllowance; // <= FOUND
19: }['12']
12: struct VaultCache {
13:
16: IERC20 asset;
17:
18: IPriceOracle oracle;
19:
20: address unitOfAccount;
21:
25: uint48 lastInterestAccumulatorUpdate;
26:
27: Assets cash;
28:
29: Owed totalBorrows;
30:
31: Shares totalShares;
32:
33: Shares accumulatedFees;
34:
35: uint256 interestAccumulator; // <= FOUND
36:
40: uint256 supplyCap; // <= FOUND
41:
42: uint256 borrowCap; // <= FOUND
43:
44: Flags hookedOps;
45:
46: Flags configFlags;
47:
51: bool snapshotInitialized;
52: }From a gas efficiency perspective, using _msgSender() in a contract not intended to support EIP-2771 could add unnecessary overhead. The _msgSender() function includes checks to determine if the transaction was forwarded, which involves extra function calls that consume more gas than a simple msg.sender.
If a contract doesn't require EIP-2771 meta-transaction support, using msg.sender directly is more gas efficient. msg.sender is a globally accessible variable in Solidity that doesn't require an extra function call, making it a less costly choice.
In the context of Ethereum, where every operation has a gas cost, it's crucial to eliminate unnecessary computations to optimize contract execution and minimize transaction fees. Therefore, if EIP-2771 support isn't necessary, it's recommended to use msg.sender instead of _msgSender().
Num of instances: 9
Click to show findings
['71']
71:
76: function _msgSender() internal view virtual override (EVCUtil, Context) returns (address) { // <= FOUND['72']
72: return EVCUtil._msgSender(); // <= FOUND['51']
51: address sender = _msgSender(); // <= FOUND['125']
125:
130: function _msgSender() internal view virtual override (ERC20Collateral, Context) returns (address) { // <= FOUND['126']
126: return ERC20Collateral._msgSender(); // <= FOUND['104']
104:
108: function transfer(address to, uint256 amount)
109: public
110: override (ERC20, IERC20)
111: nonReentrant
112: requireAccountStatusCheck(_msgSender()) // <= FOUND
113: returns (bool)
114: {['264']
264:
268: function _msgSender() internal view override (Context, EVCUtil) returns (address) { // <= FOUND['75']
75: synth.burn(_msgSender(), amountIn); // <= FOUND['107']
107: underlying.safeTransferFrom(_msgSender(), address(this), amountIn); // <= FOUNDThe Ethereum network automatically appends certain data, including block.number and block.timestamp, to every event emitted by smart contracts. This inherent feature means that explicitly adding these parameters to your event logs isn't necessary, as they're already accessible through the transaction receipt. In fact, manually adding these details to your events can lead to wasted gas because it increases the data payload of the transaction, thereby upping the associated gas cost. For gas-efficient coding, it's best to rely on the default inclusions of block.number and block.timestamp rather than duplicating these in your events.
Num of instances: 3
Click to show findings
['53']
53: event GovSetLTV(
54: address indexed collateral,
55: uint16 borrowLTV,
56: uint16 liquidationLTV,
57: uint16 initialLiquidationLTV,
58: uint48 targetTimestamp, // <= FOUND
59: uint32 rampDuration,
60: bool initialized
61: );['73']
73: event GovSetLiquidationCoolOffTime(uint16 newCoolOffTime); // <= FOUND['59']
59: event VaultStatus(
60: uint256 totalShares,
61: uint256 totalBorrows,
62: uint256 accumulatedFees,
63: uint256 cash,
64: uint256 interestAccumulator,
65: uint256 interestRate,
66: uint256 timestamp // <= FOUND
67: );Private functions which are only called once can be inlined to save GAS.
Num of instances: 9
Click to show findings
['137']
137: function invokeHookTargetWithLock(address caller) private nonReentrant // <= FOUND['118']
118: function delegateToModule(address module) private // <= FOUND['129']
129: function delegateToModuleView(address module) private view // <= FOUND['151']
151: function callThroughEVCInternal() private // <= FOUND['76']
76: function getTokenSymbol(address asset) private view returns (string memory) // <= FOUND['83']
83: function uintToString(uint256 n) private pure returns (string memory) // <= FOUND['114']
114: function calculateMaxLiquidation(LiquidationCache memory liqCache, VaultCache memory vaultCache) // <= FOUND
115: private
116: view
117: returns (LiquidationCache memory)
118: ['174']
174: function executeLiquidation(VaultCache memory vaultCache, LiquidationCache memory liqCache, uint256 minYieldBalance) // <= FOUND
175: private
176: ['229']
229: function isInLiquidationCoolOff(address account) private view returns (bool) // <= FOUNDBitmaps in Solidity are essentially a way of representing a set of boolean values within an integer type variable such as uint256. Each bit in the integer represents a true or false value (1 or 0), thus allowing efficient storage of multiple boolean values.
Bitmaps can save gas in the Ethereum network because they condense a lot of information into a small amount of storage. In Ethereum, storage is one of the most significant costs in terms of gas usage. By reducing the amount of storage space needed, you can potentially save on gas fees.
Here's a quick comparison:
If you were to represent 256 different boolean values in the traditional way, you would have to declare 256 different bool variables. Given that each bool occupies a storage slot and each storage slot costs 20,000 gas to initialize, you would end up paying a considerable amount of gas.
On the other hand, if you were to use a bitmap, you could store these 256 boolean values within a single uint256 variable. In other words, you'd only pay for a single storage slot, resulting in significant gas savings.
However, it's important to note that while bitmaps can provide gas efficiencies, they do add complexity to the code, making it harder to read and maintain. Also, using bitmaps is efficient only when dealing with a large number of boolean variables that are frequently changed or accessed together.
In contrast, the straightforward counterpart to bitmaps would be using arrays or mappings to store boolean values, with each bool value occupying its own storage slot. This approach is simpler and more readable but could potentially be more expensive in terms of gas usage.
Num of instances: 10
Click to show findings
['56']
56: vaultStorage.reentrancyLocked = true; // <= FOUND['100']
100: vaultStorage.snapshotInitialized = vaultCache.snapshotInitialized = true; // <= FOUND['70']
70: dirty = true; // <= FOUND['86']
86: updated = true; // <= FOUND['31']
31: initialized = true; // <= FOUND['71']
71: newLTV.initialized = true; // <= FOUND['58']
58: vaultStorage.reentrancyLocked = false; // <= FOUND['43']
43: dirty = false; // <= FOUND['76']
76: updated = false; // <= FOUND['96']
96: vaultStorage.snapshotInitialized = vaultCache.snapshotInitialized = false; // <= FOUNDWith the use of inline assembly in Solidity, we can take advantage of low-level features like scratch space and the free memory pointer, offering more gas-efficient ways of emitting events. The scratch space is a certain area of memory where we can temporarily store data, and the free memory pointer indicates the next available memory slot. Using these, we can efficiently assemble event data without incurring additional memory expansion costs. However, safety is paramount: to avoid overwriting or leakage, we must cache the free memory pointer before use and restore it afterward, ensuring that it points to the correct memory location post-operation.
Num of instances: 43
Click to show findings
['37']
37: if (!wasBalanceForwarderEnabled) emit BalanceForwarderStatus(account, true); // <= FOUND['52']
52: if (wasBalanceForwarderEnabled) emit BalanceForwarderStatus(account, false); // <= FOUND['36']
36: emit Transfer(address(0), account, amount.toUint()); // <= FOUND['37']
37: emit Deposit(sender, account, assets.toUint(), amount.toUint()); // <= FOUND['65']
65: emit Transfer(account, address(0), amount.toUint()); // <= FOUND['66']
66: emit Withdraw(sender, receiver, account, assets.toUint(), amount.toUint()); // <= FOUND['101']
101: emit Transfer(from, to, amount.toUint()); // <= FOUND['110']
110: emit Approval(owner, spender, amount); // <= FOUND['142']
142: emit VaultStatus( // <= FOUND
143: a.totalShares.toUint(),
144: a.totalBorrows.toAssetsUp().toUint(),
145: a.accumulatedFees.toUint(),
146: a.cash.toUint(),
147: a.interestAccumulator,
148: interestRate,
149: block.timestamp
150: );['97']
97: emit Genesis(); // <= FOUND['169']
169: if (!interest.isZero()) emit InterestAccrued(account, interest.toUint()); // <= FOUND['170']
170: if (!amount.isZero()) emit Borrow(account, amount.toUint()); // <= FOUND['177']
177: if (!amount.isZero()) emit Repay(account, amount.toUint()); // <= FOUND['141']
141: emit PullDebt(from, account, assets.toUint()); // <= FOUND['86']
86: emit Transfer(from, to, value); // <= FOUND['44']
44: emit MinterCapacitySet(minter, capacity); // <= FOUND['105']
105: emit SetUpgradeAdmin(admin); // <= FOUND['141']
141: emit ProxyCreated(proxy, upgradeable, desiredImplementation, trailingData); // <= FOUND['154']
154: emit SetImplementation(newImplementation); // <= FOUND['166']
166: emit SetUpgradeAdmin(newUpgradeAdmin); // <= FOUND['248']
248: emit ConvertFees(account, protocolReceiver, governorReceiver, protocolShares.toUint(), governorShares.toUint()); // <= FOUND['254']
254: emit GovSetGovernorAdmin(newGovernorAdmin); // <= FOUND['260']
260: emit GovSetFeeReceiver(newFeeReceiver); // <= FOUND['300']
300: emit GovSetLTV( // <= FOUND
301: collateral,
302: newLTV.borrowLTV.toUint16(),
303: newLTV.liquidationLTV.toUint16(),
304: newLTV.initialLiquidationLTV.toUint16(),
305: newLTV.targetTimestamp,
306: newLTV.rampDuration,
307: !currentLTV.initialized
308: );['319']
319: emit GovSetLTV(collateral, 0, 0, originalLTV, 0, 0, false); // <= FOUND['325']
325: emit GovSetMaxLiquidationDiscount(newDiscount); // <= FOUND['331']
331: emit GovSetLiquidationCoolOffTime(newCoolOffTime); // <= FOUND['345']
345: emit GovSetInterestRateModel(newModel); // <= FOUND['359']
359: emit GovSetHookConfig(newHookTarget, newHookedOps); // <= FOUND['367']
367: emit GovSetConfigFlags(newConfigFlags); // <= FOUND['383']
383: emit GovSetCaps(supplyCap, borrowCap); // <= FOUND['399']
399: emit GovSetInterestFee(newInterestFee); // <= FOUND['65']
65:
66:
67: emit EVaultCreated(proxyCreator, address(asset), dToken); // <= FOUND['220']
220:
221:
222: emit Withdraw(liqCache.liquidator, address(0), address(0), owedRemaining.toUint(), 0); // <= FOUND['221']
221: emit DebtSocialized(liqCache.violator, owedRemaining.toUint()); // <= FOUND['224']
224: emit Liquidate( // <= FOUND
225: liqCache.liquidator, liqCache.violator, liqCache.collateral, liqCache.repay.toUint(), liqCache.yieldBalance
226: );['142']
142: emit SetAdmin(newAdmin); // <= FOUND['153']
153: emit SetFeeReceiver(newReceiver); // <= FOUND['162']
162: emit SetProtocolFeeShare(protocolFeeShare, newProtocolFeeShare); // <= FOUND['177']
177: emit SetInterestFeeRange(minInterestFee_, maxInterestFee_); // <= FOUND['196']
196: emit SetVaultInterestFeeRange(vault, exists_, minInterestFee_, maxInterestFee_); // <= FOUND['216']
216: emit SetFeeConfigSetting(vault, exists_, feeReceiver_, protocolFeeShare_); // <= FOUND['28']
28: emit SequenceIdReserved(designator, seqId, msg.sender); // <= FOUNDThe 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
Num of instances: 7
Click to show findings
['5']
5: import {Ownable} from "openzeppelin-contracts/access/Ownable.sol"; // <= FOUND['5']
5: import {ERC20, Context} from "openzeppelin-contracts/token/ERC20/ERC20.sol"; // <= FOUND['6']
6: import {ERC20Permit} from "openzeppelin-contracts/token/ERC20/extensions/ERC20Permit.sol"; // <= FOUND['6']
6: import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol"; // <= FOUND['7']
7: import {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol"; // <= FOUND['8']
8: import {ERC4626} from "openzeppelin-contracts/token/ERC20/extensions/ERC4626.sol"; // <= FOUND['7']
7: import {SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; // <= FOUNDUsing inline assembly to extract calldata values can be more gas-efficient than using abi.decode in Solidity. Inline assembly gives more direct access to EVM operations, enabling optimized usage of calldata. However, assembly should be used judiciously as it's more prone to errors. Opt for this approach when performance is critical and the complexity it introduces is manageable.
Num of instances: 5
Click to show findings
['48']
48: (metadata0, metadata1, metadata2, metadata3) = abi.decode(trailingData, (bytes32, bytes32, bytes32, bytes32)); // <= FOUND['121']
121: newInterestRate = abi.decode(data, (uint256)); // <= FOUND['79']
79: return data.length <= 32 ? string(data) : abi.decode(data, (string)); // <= FOUND['51']
51: return callSuccess && (data.length == 0 || (data.length >= 32 && abi.decode(data, (bool)))); // <= FOUND['33']
33: return success && data.length >= 32 ? abi.decode(data, (uint8)) : 18; // <= FOUNDUsing private visibility for constants and immutables in Solidity instead of public can save gas. This is because private elements are not included in the contract's ABI, reducing the deployment and interaction costs. To achieve better efficiency, it is recommended to use private visibility when external access is not needed.
Num of instances: 8
Click to show findings
['28']
28: uint256 public constant INTEREST_SMEAR = 2 weeks; // <= FOUND['17']
17: uint216 public constant MAX_RATE = 1e27 * 1.5 / SECONDS_PER_YEAR; // <= FOUND['18']
18: uint216 public constant BASE_RATE = 1e27 * 0.005 / SECONDS_PER_YEAR; // <= FOUND['19']
19: uint216 public constant ADJUST_FACTOR = 1.1e18; // <= FOUND['20']
20: uint216 public constant ADJUST_ONE = 1.0e18; // <= FOUND['21']
21: uint216 public constant ADJUST_INTERVAL = 1 hours; // <= FOUND['22']
22: uint256 public constant BPS_SCALE = 100_00; // <= FOUND['23']
23: uint256 public constant PRICE_SCALE = 1e18; // <= FOUNDIn Solidity 0.8.x and above, arithmetic operations like modulus automatically check for underflows and overflows, and revert the transaction if such a condition is met. This built-in safety feature provides a layer of security against potential numerical errors. However, these automatic checks also come with additional gas costs. In some situations, you may already have a guard condition, like a require() statement or an if statement, that ensures the safety of the arithmetic operation. In such cases, the automatic check becomes redundant and leads to unnecessary gas expenditure.
Num of instances: 1
In the context of smart contracts, deploying multiple identical contracts can lead to inefficient use of gas and unnecessarily duplicate code on the blockchain. A more gas-efficient approach is to use a "clone" pattern. By deploying a master contract and then creating clones of it, only the differences between the instances are stored for each clone. This approach leverages the EIP-1167 standard, which defines a minimal proxy contract that points to the implementation contract. Clones can be far cheaper to deploy compared to full instances. So, the resolution is to replace identical deployments with clones, saving on gas and storage space.
Num of instances: 1
In Solidity, marking functions as payable allows them to accept Ether. If a function is known to revert for regular users (non-admin or specific roles) but needs to be accessible to others, marking it as payable can be beneficial. This ensures that even if a regular user accidentally sends Ether to the function, the Ether won't be trapped, as the function reverts, returning the funds. This can save gas by avoiding unnecessary failure handling in the function itself. Resolution: Carefully assess the roles and access patterns, and mark functions that should revert for regular users as payable to handle accidental Ether transfers.
Num of instances: 17
Click to show findings
['42']
42: function setCapacity(address minter, uint128 capacity) external onlyOwner {
43: minters[minter].capacity = capacity;
44: emit MinterCapacitySet(minter, capacity);
45: }['104']
104: function allocate(address vault, uint256 amount) external onlyOwner {
105: if (IEVault(vault).EVC() != address(evc)) {
106: revert E_NotEVCCompatible();
107: }
108: ignoredForTotalSupply.add(vault);
109: _approve(address(this), vault, amount, true);
110: IEVault(vault).deposit(amount, address(this));
111: }['116']
116: function deallocate(address vault, uint256 amount) external onlyOwner {
117: IEVault(vault).withdraw(amount, address(this), address(this));
118: }['134']
134: function addIgnoredForTotalSupply(address account) external onlyOwner returns (bool success) {
135: return ignoredForTotalSupply.add(account);
136: }['141']
141: function removeIgnoredForTotalSupply(address account) external onlyOwner returns (bool success) {
142: return ignoredForTotalSupply.remove(account);
143: }['179']
179: function governorAdmin() public view virtual override useView(MODULE_GOVERNANCE) returns (address) {}['222']
222: function setGovernorAdmin(address newGovernorAdmin) public virtual override use(MODULE_GOVERNANCE) {}['164']
164: function setUpgradeAdmin(address newUpgradeAdmin) external nonReentrant adminOnly {
165: upgradeAdmin = newUpgradeAdmin;
166: emit SetUpgradeAdmin(newUpgradeAdmin);
167: }['99']
99: function governorAdmin() public view virtual reentrantOK returns (address) {
100: return vaultStorage.governorAdmin;
101: }['252']
252: function setGovernorAdmin(address newGovernorAdmin) public virtual nonReentrant governorOnly {
253: vaultStorage.governorAdmin = newGovernorAdmin;
254: emit GovSetGovernorAdmin(newGovernorAdmin);
255: }['137']
137: function setAdmin(address newAdmin) external onlyAdmin {
138: if (newAdmin == address(0)) revert E_InvalidAdmin();
139:
140: admin = newAdmin;
141:
142: emit SetAdmin(newAdmin);
143: }['148']
148: function setFeeReceiver(address newReceiver) external onlyAdmin {
149: if (newReceiver == address(0)) revert E_InvalidReceiver();
150:
151: feeReceiver = newReceiver;
152:
153: emit SetFeeReceiver(newReceiver);
154: }['159']
159: function setProtocolFeeShare(uint16 newProtocolFeeShare) external onlyAdmin {
160: if (newProtocolFeeShare > CONFIG_SCALE) revert E_InvalidConfigValue();
161:
162: emit SetProtocolFeeShare(protocolFeeShare, newProtocolFeeShare);
163:
164: protocolFeeShare = newProtocolFeeShare;
165: }['171']
171: function setInterestFeeRange(uint16 minInterestFee_, uint16 maxInterestFee_) external onlyAdmin {
172: if (maxInterestFee_ > CONFIG_SCALE || minInterestFee_ > maxInterestFee_) revert E_InvalidConfigValue();
173:
174: minInterestFee = minInterestFee_;
175: maxInterestFee = maxInterestFee_;
176:
177: emit SetInterestFeeRange(minInterestFee_, maxInterestFee_);
178: }['186']
186: function setVaultInterestFeeRange(address vault, bool exists_, uint16 minInterestFee_, uint16 maxInterestFee_)
187: external
188: onlyAdmin
189: {
190: if (vault == address(0)) revert E_InvalidVault();
191: if (maxInterestFee_ > CONFIG_SCALE || minInterestFee_ > maxInterestFee_) revert E_InvalidConfigValue();
192:
193: _interestFeeRanges[vault] =
194: InterestFeeRange({exists: exists_, minInterestFee: minInterestFee_, maxInterestFee: maxInterestFee_});
195:
196: emit SetVaultInterestFeeRange(vault, exists_, minInterestFee_, maxInterestFee_);
197: }['205']
205: function setVaultFeeConfig(address vault, bool exists_, address feeReceiver_, uint16 protocolFeeShare_)
206: external
207: onlyAdmin
208: {
209: if (vault == address(0)) revert E_InvalidVault();
210: if (exists_ && feeReceiver_ == address(0)) revert E_InvalidReceiver();
211: if (protocolFeeShare_ > CONFIG_SCALE) revert E_InvalidConfigValue();
212:
213: _protocolFeeConfig[vault] =
214: ProtocolFeeConfig({exists: exists_, feeReceiver: feeReceiver_, protocolFeeShare: protocolFeeShare_});
215:
216: emit SetFeeConfigSetting(vault, exists_, feeReceiver_, protocolFeeShare_);
217: }['76']
76: function burn(address burnFrom, uint256 amount) external nonReentrant {
77: address sender = _msgSender();
78: MinterData memory minterCache = minters[sender];
79:
80: if (amount == 0) {
81: return;
82: }
83:
84:
85:
86: if (burnFrom != sender && !(burnFrom == address(this) && sender == owner())) {
87: _spendAllowance(burnFrom, sender, amount);
88: }
89:
90:
91: unchecked {
92:
93: minterCache.minted = minterCache.minted > amount ? minterCache.minted - uint128(amount) : 0;
94: }
95: minters[sender] = minterCache;
96:
97: _burn(burnFrom, amount);
98: }In Solidity, the unchecked block allows arithmetic operations to not revert on overflow. Without using unchecked in loops, extra gas is consumed due to overflow checks. If it's certain that overflows won't occur within the loop, using unchecked can make the loop more gas-efficient by skipping unnecessary checks.
Num of instances: 13
Click to show findings
['164']
164: for (uint256 i = 0; i < ignoredLength; i++) {
165: total -= balanceOf(ignoredForTotalSupply.at(i));
166: }['201']
201: for (uint256 i; i < end - start; ++i) {
202: list[i] = proxyList[start + i];
203: }['26']
26: for (uint256 i; i < collaterals.length; ++i) {
27: collateralValue += getCollateralValue(vaultCache, account, collaterals[i], liquidation);
28: }['49']
49: for (uint256 i; i < collaterals.length; ++i) {
50: collateralValue += getCollateralValue(vaultCache, account, collaterals[i], false);
51: if (collateralValue > liabilityValue) return;
52: }['62']
62: for (uint256 i; i < collaterals.length; ++i) {
63: address collateral = collaterals[i];
64:
65: if (!isRecognizedCollateral(collateral)) continue;
66:
67: if (IERC20(collateral).balanceOf(account) > 0) return false;
68: }['47']
47: for (uint256 i; i < collaterals.length; ++i) {
48: collateralValues[i] = getCollateralValue(vaultCache, account, collaterals[i], liquidation);
49: }['164']
164: for (uint256 i = 0; i < ignoredLength; i++) {
165: total -= balanceOf(ignoredForTotalSupply.at(i));
166: }['201']
201: for (uint256 i; i < end - start; ++i) {
202: list[i] = proxyList[start + i];
203: }['88']
88: for (uint256 m = n; m != 0; m /= 10) {
89: len++;
90: }['26']
26: for (uint256 i; i < collaterals.length; ++i) {
27: collateralValue += getCollateralValue(vaultCache, account, collaterals[i], liquidation);
28: }['49']
49: for (uint256 i; i < collaterals.length; ++i) {
50: collateralValue += getCollateralValue(vaultCache, account, collaterals[i], false);
51: if (collateralValue > liabilityValue) return;
52: }['62']
62: for (uint256 i; i < collaterals.length; ++i) {
63: address collateral = collaterals[i];
64:
65: if (!isRecognizedCollateral(collateral)) continue;
66:
67: if (IERC20(collateral).balanceOf(account) > 0) return false;
68: }['47']
47: for (uint256 i; i < collaterals.length; ++i) {
48: collateralValues[i] = getCollateralValue(vaultCache, account, collaterals[i], liquidation);
49: }Indexing dynamic types in Ethereum events can be problematic because the indexed data is stored in topics, which have a size limit. If the dynamic data exceeds this limit, the contract will run out of gas. Dynamic types like strings or arrays can vary in length, making them unpredictable in size when indexed. Resolution: Use static-sized data types or hashes of dynamic data as indexed event parameters. For example, instead of indexing a string directly, use its keccak256 hash. This approach ensures that the indexed data size is constant, preventing potential out-of-gas errors.
Num of instances: 1
Click to show findings
['22']
22: event SequenceIdReserved(string indexed designator, uint256 indexed id, address indexed caller); // <= FOUNDCasting values multiple times in Solidity can be gas-inefficient. When a value undergoes repeated type conversions, the EVM must execute additional operations for each cast, consuming more gas than necessary. To optimize for gas efficiency, cache the result of the initial cast in a local variable and reuse it, rather than performing multiple casts. This not only conserves gas but also enhances code readability, reducing potential error points. For example, instead of repeatedly casting an address to uint256, cast once, store the result in a local variable, and reference that variable in subsequent operations.
Num of instances: 2
Click to show findings
['29']
29: function initialize(address proxyCreator) public virtual reentrantOK {
30: if (initialized) revert E_Initialized();
31: initialized = true;
32:
33:
34:
35:
36: if (msg.data.length != 4 + 32 + PROXY_METADATA_LENGTH) revert E_ProxyMetadata();
37: (IERC20 asset,,) = ProxyUtils.metadata();
38:
39: AddressUtils.checkContract(address(asset)); // <= FOUND 'address(asset)'
40:
41:
42:
43:
44: address dToken = address(new DToken());
45:
46:
47:
48: vaultStorage.lastInterestAccumulatorUpdate = uint48(block.timestamp);
49: vaultStorage.interestAccumulator = INITIAL_INTEREST_ACCUMULATOR;
50: vaultStorage.interestFee = DEFAULT_INTEREST_FEE.toConfigAmount();
51: vaultStorage.creator = vaultStorage.governorAdmin = proxyCreator;
52:
53: {
54: string memory underlyingSymbol = getTokenSymbol(address(asset)); // <= FOUND 'address(asset)'
55: uint256 seqId = sequenceRegistry.reserveSeqId(underlyingSymbol);
56:
57: vaultStorage.symbol = string(abi.encodePacked("e", underlyingSymbol, "-", uintToString(seqId)));
58: vaultStorage.name = string(abi.encodePacked("EVK Vault ", vaultStorage.symbol));
59: }
60:
61: snapshot.reset();
62:
63:
64:
65: emit EVaultCreated(proxyCreator, address(asset), dToken); // <= FOUND 'address(asset)'
66: logVaultStatus(loadVault(), 0);
67: }['73']
73: function getLiabilityValue(VaultCache memory vaultCache, address account, Owed owed, bool liquidation)
74: internal
75: view
76: virtual
77: returns (uint256 value)
78: {
79:
80: uint256 owedAssets = getCurrentOwed(vaultCache, account, owed).toAssetsUp().toUint();
81:
82: if (owedAssets == 0) return 0;
83:
84: if (address(vaultCache.asset) == vaultCache.unitOfAccount) { // <= FOUND 'address(vaultCache.asset)'
85: value = owedAssets;
86: } else {
87: if (liquidation) {
88:
89: value = vaultCache.oracle.getQuote(owedAssets, address(vaultCache.asset), vaultCache.unitOfAccount); // <= FOUND 'address(vaultCache.asset)'
90: } else {
91:
92: (, value) = vaultCache.oracle.getQuotes(owedAssets, address(vaultCache.asset), vaultCache.unitOfAccount); // <= FOUND 'address(vaultCache.asset)'
93: }
94: }
95: }If a variable is only once, it makes more sense to use the value the variable holds directly
Num of instances: 5
Click to show findings
['59']
59: assembly {
60:
61: mstore(0, IMPLEMENTATION_SELECTOR)
62:
63: let result := staticcall(gas(), beacon_, 0, 4, 0, 32) // <= FOUND
64: let implementation := mload(0) // <= FOUND
65:
66:
67: calldatacopy(0, 0, calldatasize())
68: mstore(calldatasize(), metadata0_)
69: mstore(add(32, calldatasize()), metadata1_)
70: mstore(add(64, calldatasize()), metadata2_)
71: mstore(add(96, calldatasize()), metadata3_)
72: result := delegatecall(gas(), implementation, 0, add(metadataLength_, calldatasize()), 0, 0) // <= FOUND
73: returndatacopy(0, 0, returndatasize())
74:
75: switch result // <= FOUND
76: case 0 { revert(0, returndatasize()) }
77: default { return(0, returndatasize()) }
78: }['107']
107: assembly {
108: let size := sub(calldatasize(), 36)
109: calldatacopy(0, 36, size)
110: let result := delegatecall(gas(), calldataload(4), 0, size, 0, 0) // <= FOUND
111: returndatacopy(0, 0, returndatasize())
112: switch result // <= FOUND
113: case 0 { revert(0, returndatasize()) }
114: default { return(0, returndatasize()) }
115: }['119']
119: assembly {
120: calldatacopy(0, 0, calldatasize())
121: let result := delegatecall(gas(), module, 0, calldatasize(), 0, 0) // <= FOUND
122: returndatacopy(0, 0, returndatasize())
123: switch result // <= FOUND
124: case 0 { revert(0, returndatasize()) }
125: default { return(0, returndatasize()) }
126: }['130']
130: assembly {
131:
132:
133:
134: mstore(0, 0x1fe8b95300000000000000000000000000000000000000000000000000000000)
135: let strippedCalldataSize := sub(calldatasize(), PROXY_METADATA_LENGTH)
136:
137:
138:
139: mstore(add(24, strippedCalldataSize), caller())
140: mstore(4, module)
141: calldatacopy(36, 0, strippedCalldataSize)
142:
143: let result := staticcall(gas(), address(), 0, add(strippedCalldataSize, 56), 0, 0) // <= FOUND
144: returndatacopy(0, 0, returndatasize())
145: switch result // <= FOUND
146: case 0 { revert(0, returndatasize()) }
147: default { return(0, returndatasize()) }
148: }['153']
153: assembly {
154: let mainCalldataLength := sub(calldatasize(), PROXY_METADATA_LENGTH)
155:
156: mstore(0, 0x1f8b521500000000000000000000000000000000000000000000000000000000)
157: mstore(4, address())
158: mstore(36, caller())
159: mstore(68, callvalue())
160: mstore(100, 128)
161: mstore(132, mainCalldataLength)
162: calldatacopy(164, 0, mainCalldataLength)
163:
164:
165:
166: mstore(add(164, mainCalldataLength), 0)
167: let result := call(gas(), _evc, callvalue(), 0, add(164, and(add(mainCalldataLength, 31), not(31))), 0, 0) // <= FOUND
168:
169: returndatacopy(0, 0, returndatasize())
170: switch result // <= FOUND
171: case 0 { revert(0, returndatasize()) }
172: default { return(64, sub(returndatasize(), 64)) }
173: }Utilizing assembly for validating msg.sender can potentially save gas as it allows for more direct and efficient access to Ethereum’s EVM opcodes, bypassing some of the overhead introduced by Solidity’s higher-level abstractions. However, this practice requires deep expertise in EVM, as incorrect implementation can introduce critical vulnerabilities. It is a trade-off between gas efficiency and code safety.
Num of instances: 5
Click to show findings
['84']
84: if (msg.sender != eVault) revert E_Unauthorized(); // <= FOUND['105']
105: if (msg.sender != address(this)) revert E_Unauthorized(); // <= FOUND['92']
92: if (msg.sender != upgradeAdmin) revert E_Unauthorized(); // <= FOUND['36']
36: if (msg.sender != vault) revert E_IRMUpdateUnauthorized(); // <= FOUND['130']
130: if (msg.sender != admin) revert E_OnlyAdmin(); // <= FOUNDUnnecessary casting of a variable to the same type is redundant and can contribute to gas inefficiency and code clutter. This situation commonly arises when developers, perhaps due to oversight or misunderstanding, explicitly cast a variable to its existing type. For example, casting a uint256 variable to uint256 again does not change its type or value but adds unnecessary operations to the code.
Resolution: Developers should scrutinize their code to identify and remove any unnecessary type casting. Utilizing linters or static analysis tools can aid in detecting such redundancies. Ensuring that the code is clean and efficient not only saves gas but also enhances readability and maintainability.
Num of instances: 1
Click to show findings
['76']
76: function getTokenSymbol(address asset) private view returns (string memory) {
77: (bool success, bytes memory data) = address(asset).staticcall(abi.encodeCall(IERC20.symbol, ())); // <= FOUND
78: if (!success) return "UNDEFINED";
79: return data.length <= 32 ? string(data) : abi.decode(data, (string));
80: }Using assembly for simple zero checks on unsigned integers can save gas due to lower-level, optimized operations.
Resolution: Implement inline assembly with Solidity's assembly block to perform zero checks. Ensure thorough testing and verification, as assembly lacks the safety checks of high-level Solidity, potentially introducing vulnerabilities if not used carefully.
Num of instances: 8
Click to show findings
['21']
21: if (amountCap == 0) return type(uint256).max; // <= FOUND['87']
87: account = EVCAuthenticateDeferred(CONTROLLER_NEUTRAL_OPS & operation == 0); // <= FOUND['54']
54: uint32 utilization = totalAssets == 0 // <= FOUND
55: ? 0
56: : uint32(borrows * type(uint32).max / totalAssets);['85']
85: if (n == 0) return "0"; // <= FOUND['82']
82: if (owedAssets == 0) return 0; // <= FOUND['107']
107: if (balance == 0) return 0; // <= FOUND['32']
32: uint32 constant OP_DEPOSIT = 1 << 0; // <= FOUND['53']
53: uint32 constant CFG_DONT_SOCIALIZE_DEBT = 1 << 0; // <= FOUNDUsing nested if statements instead of logical AND (&&) operators can potentially save gas in Solidity contracts. When a series of conditions are connected with &&, all conditions must be evaluated even if the first one fails. In contrast, nested if statements allow for short-circuiting; if the first condition fails, the rest are skipped, saving gas. This approach is more gas-efficient, especially when dealing with complex or gas-intensive conditions. However, it's crucial to balance gas savings with code readability and maintainability, ensuring that the code remains clear and easy to understand.
Num of instances: 36
Click to show findings
['33']
33: if (
34: to == address(0)
35: || (vaultCache.configFlags.isNotSet(CFG_EVC_COMPATIBLE_ASSET) && isKnownNonOwnerAccount(to)) // <= FOUND
36: ) {
37: revert E_BadAssetReceiver();
38: }['96']
96: if (
97: !vaultCache.snapshotInitialized
98: && !(vaultCache.supplyCap == type(uint256).max && vaultCache.borrowCap == type(uint256).max) // <= FOUND
99: ) {
100: vaultStorage.snapshotInitialized = vaultCache.snapshotInitialized = true;
101: snapshot.set(vaultCache.cash, vaultCache.totalBorrows.toAssetsUp());
102: }['81']
81: if (
82: (amount > fromOwed && amount.subUnchecked(fromOwed).isDust()) // <= FOUND
83: || (amount < fromOwed && fromOwed.subUnchecked(amount).isDust()) // <= FOUND
84: ) {
85: amount = fromOwed;
86: }['135']
135: if (irm != address(0) && isVaultStatusCheckDeferred()) { // <= FOUND
136: (bool success, bytes memory data) = irm.staticcall(
137: abi.encodeCall(
138: IIRM.computeInterestRateView,
139: (address(this), vaultCache.cash.toUint(), vaultCache.totalBorrows.toAssetsUp().toUint())
140: )
141: );
142:
143: if (success && data.length >= 32) { // <= FOUND
144: newInterestRate = abi.decode(data, (uint256));
145: if (newInterestRate > MAX_ALLOWED_INTEREST_RATE) newInterestRate = MAX_ALLOWED_INTEREST_RATE;
146: }
147: }['86']
86: if (burnFrom != sender && !(burnFrom == address(this) && sender == owner())) { // <= FOUND
87: _spendAllowance(burnFrom, sender, amount);
88: }['211']
211: if (
212: vaultCache.configFlags.isNotSet(CFG_DONT_SOCIALIZE_DEBT) && liqCache.liability > liqCache.repay // <= FOUND
213: && checkNoCollateral(liqCache.violator, liqCache.collaterals) // <= FOUND
214: ) {
215: Assets owedRemaining = liqCache.liability.subUnchecked(liqCache.repay);
216: decreaseBorrow(vaultCache, liqCache.violator, owedRemaining);
217:
218:
219:
220: emit Withdraw(liqCache.liquidator, address(0), address(0), owedRemaining.toUint(), 0);
221: emit DebtSocialized(liqCache.violator, owedRemaining.toUint());
222: }['31']
31: if (!success && permit2 != address(0)) { // <= FOUND
32: if (value > type(uint160).max) {
33: revert E_TransferFromFailed(tryData, abi.encodePacked(E_Permit2AmountOverflow.selector));
34: }
35:
36: (success, fallbackData) =
37: permit2.call(abi.encodeCall(IPermit2.transferFrom, (from, to, uint160(value), address(token))));
38: }['96']
96: if (toBalanceForwarderEnabled && from != to) { // <= FOUND
97: balanceTracker.balanceTrackerHook(to, newToBalance.toUint(), false);
98: }['69']
69: if (msg.sender != hookTarget && !(msg.sender == address(this) && ProxyUtils.useViewCaller() == hookTarget)) // <= FOUND
70: {
71: revert E_Reentrancy();
72: }['120']
120: if (success && data.length >= 32) { // <= FOUND
121: newInterestRate = abi.decode(data, (uint256));
122: if (newInterestRate > MAX_ALLOWED_INTEREST_RATE) newInterestRate = MAX_ALLOWED_INTEREST_RATE;
123: vaultStorage.interestRate = uint72(newInterestRate);
124: }['120']
120: if (success && data.length >= 32) { // <= FOUND
121: newInterestRate = abi.decode(data, (uint256));
122: if (newInterestRate > MAX_ALLOWED_INTEREST_RATE) newInterestRate = MAX_ALLOWED_INTEREST_RATE;
123: }['33']
33: if (
34: to == address(0)
35: || (vaultCache.configFlags.isNotSet(CFG_EVC_COMPATIBLE_ASSET) && isKnownNonOwnerAccount(to)) // <= FOUND
36: ) {['96']
96: if (toBalanceForwarderEnabled && from != to) { // <= FOUND['69']
69:
70:
71:
72:
73: if (msg.sender != hookTarget && !(msg.sender == address(this) && ProxyUtils.useViewCaller() == hookTarget)) // <= FOUND
74: {['96']
96:
97:
98:
99:
100: if (
101: !vaultCache.snapshotInitialized
102: && !(vaultCache.supplyCap == type(uint256).max && vaultCache.borrowCap == type(uint256).max) // <= FOUND
103: ) {['109']
109: return hookedOps.isSet(operation) && vaultStorage.hookTarget == address(0); // <= FOUND['81']
81:
82: if (
83: (amount > fromOwed && amount.subUnchecked(fromOwed).isDust()) // <= FOUND
84: || (amount < fromOwed && fromOwed.subUnchecked(amount).isDust()) // <= FOUND
85: ) {['120']
120: if (success && data.length >= 32) { // <= FOUND['135']
135: if (irm != address(0) && isVaultStatusCheckDeferred()) { // <= FOUND['123']
123: if (newTotalShares != vaultCache.totalShares.toUint() && newTotalShares <= MAX_SANE_AMOUNT) { // <= FOUND['86']
86:
87:
88: if (burnFrom != sender && !(burnFrom == address(this) && sender == owner())) { // <= FOUND['47']
47: if (checkController && !controllerEnabled) revert E_ControllerDisabled(); // <= FOUND['85']
85: return owner != address(0) && owner != account; // <= FOUND['292']
292:
293: if (newLiquidationLTV >= currentLTV.getLTV(true) && rampDuration > 0) revert E_LTVLiquidation(); // <= FOUND['350']
350: if (
351: newHookTarget != address(0)
352: && IHookTarget(newHookTarget).isHookTarget() != IHookTarget.isHookTarget.selector // <= FOUND
353: ) revert E_NotHookTarget();['375']
375:
376:
377: if (supplyCap != 0 && _supplyCap.resolve() > 2 * MAX_SANE_AMOUNT) revert E_BadSupplyCap(); // <= FOUND['378']
378: if (borrowCap != 0 && _borrowCap.resolve() > MAX_SANE_AMOUNT) revert E_BadBorrowCap(); // <= FOUND['211']
211:
212:
213: if (
214: vaultCache.configFlags.isNotSet(CFG_DONT_SOCIALIZE_DEBT) && liqCache.liability > liqCache.repay // <= FOUND
215: && checkNoCollateral(liqCache.violator, liqCache.collaterals) // <= FOUND
216: ) {['98']
98: return interestFee >= range.minInterestFee && interestFee <= range.maxInterestFee; // <= FOUND['101']
101: return interestFee >= minInterestFee && interestFee <= maxInterestFee; // <= FOUND['210']
210: if (exists_ && feeReceiver_ == address(0)) revert E_InvalidReceiver(); // <= FOUND['104']
104: if (borrows > vaultCache.borrowCap && borrows > prevBorrows) revert E_BorrowCapExceeded(); // <= FOUND['113']
113: if (supply > vaultCache.supplyCap && supply > prevSupply) revert E_SupplyCapExceeded(); // <= FOUND['31']
31: if (!success && permit2 != address(0)) { // <= FOUND['51']
51: return callSuccess && (data.length == 0 || (data.length >= 32 && abi.decode(data, (bool)))); // <= FOUND['33']
33: return success && data.length >= 32 ? abi.decode(data, (uint8)) : 18; // <= FOUNDWhen emitting events in Solidity, using stack variables (local variables within a function) instead of state variables can lead to significant gas savings. Stack variables reside in memory only for the duration of the function execution and are less costly to access compared to state variables, which are stored on the blockchain. When an event is emitted, accessing these stack variables requires less gas than fetching data from state variables, which involves reading from the contract's storage - a more expensive operation. Thus, for efficiency, prefer using local variables within functions for event emission, especially in functions that are called frequently.
Num of instances: 2
Click to show findings
['162']
162: emit SetProtocolFeeShare(protocolFeeShare, newProtocolFeeShare); // <= FOUND['105']
105: emit SetUpgradeAdmin(admin); // <= FOUNDOptimizing low-level calls using assembly in Solidity can be beneficial, particularly when dealing with function return data. Typically, even if return data from a low-level call is not used, Solidity still allocates memory to store it, which incurs gas costs. By using assembly, developers can bypass the automatic memory allocation for unused return data. This manual optimization involves handling the call at the assembly level and deliberately choosing not to store the return data in memory when it's not needed.
Num of instances: 10
Click to show findings
['132']
132: (bool success, bytes memory data) = hookTarget.call(abi.encodePacked(msg.data, caller)); // <= FOUND['113']
113: (bool success, bytes memory data) = irm.call( // <= FOUND
114: abi.encodeCall(
115: IIRM.computeInterestRate,
116: (address(this), vaultCache.cash.toUint(), vaultCache.totalBorrows.toAssetsUp().toUint())
117: )
118: );['23']
23: (bool success, bytes memory data) = address(token).call(abi.encodeCall(IERC20.transferFrom, (from, to, value))); // <= FOUND['36']
36:
37: (success, fallbackData) =
38: permit2.call(abi.encodeCall(IPermit2.transferFrom, (from, to, uint160(value), address(token)))); // <= FOUND['46']
46: (bool success, bytes memory data) = address(token).call(abi.encodeCall(IERC20.transfer, (to, value))); // <= FOUND['132']
132: (bool success, bytes memory data) = hookTarget.call(abi.encodePacked(msg.data, caller)); // <= FOUND['113']
113: (bool success, bytes memory data) = irm.call( // <= FOUND
114: abi.encodeCall(
115: IIRM.computeInterestRate,
116: (address(this), vaultCache.cash.toUint(), vaultCache.totalBorrows.toAssetsUp().toUint())
117: )
118: );['23']
23: (bool success, bytes memory data) = address(token).call(abi.encodeCall(IERC20.transferFrom, (from, to, value))); // <= FOUND['36']
36:
37: (success, fallbackData) =
38: permit2.call(abi.encodeCall(IPermit2.transferFrom, (from, to, uint160(value), address(token)))); // <= FOUND['46']
46: (bool success, bytes memory data) = address(token).call(abi.encodeCall(IERC20.transfer, (to, value))); // <= FOUNDNum of instances: 1
Click to show findings
['49']
49: vaultStorage.interestAccumulator = INITIAL_INTEREST_ACCUMULATOR; // <= FOUNDIn Solidity, optimizing gas usage is crucial, particularly for frequently executed operations. For memory structs, using explicit assignment (e.g., s.x = s.x + y) instead of shorthand operations (e.g., s.x += y) can result in a minor gas saving, around 100 gas. This difference arises from the way the Solidity compiler optimizes bytecode. While such savings might seem small, they can add up in contracts with high transaction volume. This optimization applies to other compound assignment operators like -= and *= as well. It's a subtle efficiency gain that developers can leverage, especially in complex contracts where every gas unit counts.
Num of instances: 3
Click to show findings
['42']
42: function initVaultCache(VaultCache memory vaultCache) private view returns (bool dirty) { // <= FOUND
43: dirty = false;
44:
45:
46:
47: (vaultCache.asset, vaultCache.oracle, vaultCache.unitOfAccount) = ProxyUtils.metadata(); // <= FOUND
48:
49:
50:
51: vaultCache.lastInterestAccumulatorUpdate = vaultStorage.lastInterestAccumulatorUpdate; // <= FOUND
52: vaultCache.cash = vaultStorage.cash; // <= FOUND
53: vaultCache.supplyCap = vaultStorage.supplyCap.resolve(); // <= FOUND
54: vaultCache.borrowCap = vaultStorage.borrowCap.resolve(); // <= FOUND
55: vaultCache.hookedOps = vaultStorage.hookedOps; // <= FOUND
56: vaultCache.snapshotInitialized = vaultStorage.snapshotInitialized; // <= FOUND
57:
58: vaultCache.totalShares = vaultStorage.totalShares; // <= FOUND
59: vaultCache.totalBorrows = vaultStorage.totalBorrows; // <= FOUND
60:
61: vaultCache.accumulatedFees = vaultStorage.accumulatedFees; // <= FOUND
62: vaultCache.configFlags = vaultStorage.configFlags; // <= FOUND
63:
64: vaultCache.interestAccumulator = vaultStorage.interestAccumulator; // <= FOUND
65:
66:
67:
68: uint256 deltaT = block.timestamp - vaultCache.lastInterestAccumulatorUpdate; // <= FOUND
69: if (deltaT > 0) {
70: dirty = true;
71:
72:
73:
74: ConfigAmount interestFee = vaultStorage.interestFee;
75: uint256 interestRate = vaultStorage.interestRate;
76:
77: uint256 newInterestAccumulator = vaultCache.interestAccumulator; // <= FOUND
78: uint256 newTotalBorrows = vaultCache.totalBorrows.toUint(); // <= FOUND
79:
80: unchecked {
81: uint256 intermediate;
82: (uint256 multiplier, bool overflow) = RPow.rpow(interestRate + 1e27, deltaT, 1e27);
83:
84:
85: if (!overflow) {
86: intermediate = newInterestAccumulator * multiplier;
87: if (newInterestAccumulator == intermediate / multiplier) {
88: newInterestAccumulator = intermediate / 1e27;
89: }
90: }
91:
92: intermediate = newTotalBorrows * newInterestAccumulator;
93: if (newTotalBorrows == intermediate / newInterestAccumulator) {
94: newTotalBorrows = intermediate / vaultCache.interestAccumulator; // <= FOUND
95: }
96: }
97:
98: uint256 newAccumulatedFees = vaultCache.accumulatedFees.toUint(); // <= FOUND
99: uint256 newTotalShares = vaultCache.totalShares.toUint(); // <= FOUND
100: uint256 feeAssets = (newTotalBorrows - vaultCache.totalBorrows.toUint()) * interestFee.toUint16() // <= FOUND
101: / (uint256(CONFIG_SCALE) << INTERNAL_DEBT_PRECISION_SHIFT);
102:
103: if (feeAssets != 0) {
104:
105:
106:
107:
108:
109:
110:
111: uint256 newTotalAssets = vaultCache.cash.toUint() + OwedLib.toAssetsUpUint(newTotalBorrows); // <= FOUND
112: newTotalShares = newTotalAssets * newTotalShares / (newTotalAssets - feeAssets);
113: newAccumulatedFees += newTotalShares - vaultCache.totalShares.toUint(); // <= FOUND
114: }
115:
116:
117:
118: if (newTotalBorrows <= MAX_SANE_DEBT_AMOUNT) {
119: vaultCache.totalBorrows = newTotalBorrows.toOwed(); // <= FOUND
120: vaultCache.interestAccumulator = newInterestAccumulator; // <= FOUND
121: vaultCache.lastInterestAccumulatorUpdate = uint48(block.timestamp); // <= FOUND
122:
123: if (newTotalShares != vaultCache.totalShares.toUint() && newTotalShares <= MAX_SANE_AMOUNT) { // <= FOUND
124: vaultCache.accumulatedFees = newAccumulatedFees.toShares(); // <= FOUND
125: vaultCache.totalShares = newTotalShares.toShares(); // <= FOUND
126: }
127: }
128: }
129: }['199']
199: function gulp() public nonReentrant { // <= FOUND
200: ESRSlot memory esrSlotCache = updateInterestAndReturnESRSlotCache();
201:
202: uint256 assetBalance = IERC20(asset()).balanceOf(address(this));
203: uint256 toGulp = assetBalance - _totalAssets - esrSlotCache.interestLeft; // <= FOUND
204:
205: uint256 maxGulp = type(uint168).max - esrSlotCache.interestLeft; // <= FOUND
206: if (toGulp > maxGulp) toGulp = maxGulp;
207:
208: esrSlotCache.interestSmearEnd = uint40(block.timestamp + INTEREST_SMEAR); // <= FOUND
209: esrSlotCache.interestLeft += uint168(toGulp); // <= FOUND
210:
211:
212: esrSlot = esrSlotCache;
213: }['217']
217: function updateInterestAndReturnESRSlotCache() public returns (ESRSlot memory) { // <= FOUND
218: ESRSlot memory esrSlotCache = esrSlot;
219: uint256 accruedInterest = interestAccruedFromCache(esrSlotCache);
220:
221:
222: esrSlotCache.interestLeft -= uint168(accruedInterest); // <= FOUND
223: esrSlotCache.lastInterestUpdate = uint40(block.timestamp); // <= FOUND
224:
225: esrSlot = esrSlotCache;
226:
227: _totalAssets = _totalAssets + accruedInterest;
228:
229: return esrSlotCache;
230: }Solidity version 0.8.19 introduced a array of gas optimizations which make contracts which use it more efficient. Provided compatability it may be beneficial to upgrade to this version
Num of instances: 2
Please elaborate and generalise the following with detail and feel free to use your own knowledge and lmit ur words to 100 words please:
Num of instances: 3
Click to show findings
['88']
88: for (uint256 m = n; m != 0; m /= 10) { // <= FOUND
89: len++;
90: } // <= FOUND['62']
62: for (uint256 i; i < collaterals.length; ++i) { // <= FOUND
63: address collateral = collaterals[i]; // <= FOUND
64:
65: if (!isRecognizedCollateral(collateral)) continue;
66:
67: if (IERC20(collateral).balanceOf(account) > 0) return false;
68: }['62']
62: for (uint256 i; i < collaterals.length; ++i) { // <= FOUND
63: address collateral = collaterals[i]; // <= FOUND
64:
65: if (!isRecognizedCollateral(collateral)) continue;
66:
67: if (IERC20(collateral).balanceOf(account) > 0) return false;
68: }Rather than calling .length for an array in a for loop declaration, it is far more gas efficient to cache this length before and use that instead. This will prevent the array length from being called every loop iteration
Num of instances: 8
Click to show findings
['47']
47: for (uint256 i; i < collaterals.length; ++i) // <= FOUND['47']
47: for (uint256 i; i < collaterals.length; ++i) // <= FOUND['47']
47: for (uint256 i; i < collaterals.length; ++i) // <= FOUND['47']
47: for (uint256 i; i < collaterals.length; ++i) // <= FOUND['47']
47: for (uint256 i; i < collaterals.length; ++i) // <= FOUND['47']
47: for (uint256 i; i < collaterals.length; ++i) // <= FOUND['47']
47: for (uint256 i; i < collaterals.length; ++i) // <= FOUND['47']
47: for (uint256 i; i < collaterals.length; ++i) // <= FOUNDIf a internal function is only used once it doesn't make sense to modularise it unless the function which does call the function would be overly long and complex otherwise
Num of instances: 24
Click to show findings
['69']
69: function transferBalance(address from, address to, Shares amount) internal virtual // <= FOUND['106']
106: function setAllowance(address owner, address spender, uint256 amount) internal // <= FOUND['121']
121: function callHookWithLock(Flags hookedOps, uint32 operation, address caller) internal virtual // <= FOUND['44']
44: function increaseBorrow(VaultCache memory vaultCache, address account, Assets assets) internal virtual // <= FOUND['35']
35: function disableControllerInternal(address account) internal virtual // <= FOUND['41']
41: function EVCAuthenticateDeferred(bool checkController) internal view virtual returns (address) // <= FOUND['64']
64: function EVCAuthenticateGovernor() internal view virtual returns (address) // <= FOUND['88']
88: function EVCRequireStatusChecks(address account) internal virtual // <= FOUND['98']
98: function enforceCollateralTransfer(address collateral, uint256 amount, address from, address receiver) // <= FOUND
99: internal
100: virtual
101: ['109']
109: function hasAnyControllerEnabled(address account) internal view returns (bool) // <= FOUND['13']
13: function isSet(Flags self, uint32 bitMask) internal pure returns (bool) // <= FOUND['228']
228: function setLTV(address collateral, uint16 borrowLTV, uint16 liquidationLTV, uint32 rampDuration) public virtual override use(MODULE_GOVERNANCE) // <= FOUND['274']
274: function setLTV(address collateral, uint16 borrowLTV, uint16 liquidationLTV, uint32 rampDuration) // <= FOUND
275: public
276: virtual
277: nonReentrant
278: governorOnly
279: ['61']
61: function setLTV(LTVConfig memory self, ConfigAmount borrowLTV, ConfigAmount liquidationLTV, uint32 rampDuration) // <= FOUND
62: internal
63: view
64: returns (LTVConfig memory newLTV)
65: ['230']
230: function clearLTV(address collateral) public virtual override use(MODULE_GOVERNANCE) // <= FOUND['315']
315: function clearLTV(address collateral) public virtual nonReentrant governorOnly // <= FOUND['75']
75: function clear(LTVConfig storage self) internal // <= FOUND['36']
36: function checkLiquidity(VaultCache memory vaultCache, address account, address[] memory collaterals) // <= FOUND
37: internal
38: view
39: virtual
40: ['61']
61: function checkNoCollateral(address account, address[] memory collaterals) internal view virtual returns (bool) // <= FOUND['20']
20: function deployMetaProxy(address targetContract, bytes memory metadata) internal returns (address addr) // <= FOUND['25']
25: function useViewCaller() internal pure returns (address viewCaller) // <= FOUND['12']
12: function rpow(uint256 x, uint256 n, uint256 scalar) internal pure returns (uint256 z, bool overflow) // <= FOUND['19']
19: function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) // <= FOUND
20: internal
21: returns (bool, bytes memory)
22: ['55']
55: function setOwed(UserStorage storage self, Owed owed) internal // <= FOUNDNum of instances: 16
Click to show findings
['126']
126: constructor(Integrations memory integrations) Base(integrations) {}['42']
42: constructor(Integrations memory integrations) EVCClient(integrations.evc) {
43: protocolConfig = IProtocolConfig(AddressUtils.checkContract(integrations.protocolConfig));
44: sequenceRegistry = ISequenceRegistry(AddressUtils.checkContract(integrations.sequenceRegistry));
45: balanceTracker = IBalanceTracker(integrations.balanceTracker);
46: permit2 = integrations.permit2;
47: }['28']
28: constructor(bytes memory trailingData) {
29: emit Genesis();
30:
31: require(trailingData.length <= MAX_TRAILING_DATA_LENGTH, "trailing data too long");
32:
33:
34: beacon = msg.sender;
35:
36:
37: assembly {
38: sstore(BEACON_SLOT, caller())
39: }
40:
41:
42: metadataLength = trailingData.length;
43:
44:
45: assembly {
46: mstore(trailingData, MAX_TRAILING_DATA_LENGTH)
47: }
48: (metadata0, metadata1, metadata2, metadata3) = abi.decode(trailingData, (bytes32, bytes32, bytes32, bytes32));
49: }['17']
17: constructor() {
18: eVault = msg.sender;
19: }['63']
63: constructor(Integrations memory integrations, DeployedModules memory modules) Base(integrations) {
64: MODULE_INITIALIZE = AddressUtils.checkContract(modules.initialize);
65: MODULE_TOKEN = AddressUtils.checkContract(modules.token);
66: MODULE_VAULT = AddressUtils.checkContract(modules.vault);
67: MODULE_BORROWING = AddressUtils.checkContract(modules.borrowing);
68: MODULE_LIQUIDATION = AddressUtils.checkContract(modules.liquidation);
69: MODULE_RISKMANAGER = AddressUtils.checkContract(modules.riskManager);
70: MODULE_BALANCE_FORWARDER = AddressUtils.checkContract(modules.balanceForwarder);
71: MODULE_GOVERNANCE = AddressUtils.checkContract(modules.governance);
72: }['16']
16: constructor(IEVC _evc_, string memory _name_, string memory _symbol_)
17: EVCUtil(address(_evc_))
18: ERC20(_name_, _symbol_)
19: ERC20Permit(_name_)
20: {}['33']
33: constructor(IEVC evc_, string memory name_, string memory symbol_)
34: ERC20Collateral(evc_, name_, symbol_)
35: Ownable(msg.sender)
36: {}['31']
31: constructor(address _evc) {
32: evc = IEVC(AddressUtils.checkContract(_evc));
33: }['13']
13: constructor(Integrations memory integrations, DeployedModules memory modules) Dispatch(integrations, modules) {}['58']
58: constructor(IEVC _evc, address _asset, string memory _name, string memory _symbol)
59: EVCUtil(address(_evc))
60: ERC4626(IERC20(_asset))
61: ERC20(_name, _symbol)
62: {
63: esrSlot.locked = UNLOCKED;
64: }['96']
96: constructor(address admin) {
97: emit Genesis();
98:
99: if (admin == address(0)) revert E_BadAddress();
100:
101: reentrancyLock = REENTRANCYLOCK__UNLOCKED;
102:
103: upgradeAdmin = admin;
104:
105: emit SetUpgradeAdmin(admin);
106: }['22']
22: constructor(uint256 baseRate_, uint256 slope1_, uint256 slope2_, uint256 kink_) {
23: baseRate = baseRate_;
24: slope1 = slope1_;
25: slope2 = slope2_;
26: kink = kink_;
27: }['39']
39: constructor(address synth_, address referenceAsset_, address oracle_, uint256 targetQuoute_) {
40: if (synth_ == address(0) || referenceAsset_ == address(0) || oracle_ == address(0)) {
41: revert E_ZeroAddress();
42: }
43:
44: synth = synth_;
45: referenceAsset = referenceAsset_;
46: oracle = IPriceOracle(oracle_);
47: targetQuote = targetQuoute_;
48: quoteAmount = 10 ** IERC20(synth_).decimals();
49:
50:
51: uint256 testQuote = IPriceOracle(oracle_).getQuote(quoteAmount, synth_, referenceAsset_);
52: if (testQuote == 0) {
53: revert E_InvalidQuote();
54: }
55:
56: irmStorage = IRMData({lastUpdated: uint40(block.timestamp), lastRate: BASE_RATE});
57: }['70']
70: constructor() {
71: initialized = true;
72: }['42']
42: constructor(
43: address _evc,
44: address _synth,
45: address _underlying,
46: uint256 toUnderlyingFeeBPS,
47: uint256 toSynthFeeBPS,
48: uint256 _conversionPrice
49: ) EVCUtil(_evc) {
50: if (toUnderlyingFeeBPS >= BPS_SCALE || toSynthFeeBPS >= BPS_SCALE) {
51: revert E_FeeExceedsBPS();
52: }
53:
54: if (_evc == address(0) || _synth == address(0) || _underlying == address(0)) {
55: revert E_ZeroAddress();
56: }
57:
58: synth = ESynth(_synth);
59: underlying = IERC20(_underlying);
60: TO_UNDERLYING_FEE = toUnderlyingFeeBPS;
61: TO_SYNTH_FEE = toSynthFeeBPS;
62: conversionPrice = _conversionPrice;
63: }['81']
81: constructor(address admin_, address feeReceiver_) {
82: if (admin_ == address(0)) revert E_InvalidAdmin();
83: if (feeReceiver_ == address(0)) revert E_InvalidReceiver();
84:
85: admin = admin_;
86: feeReceiver = feeReceiver_;
87:
88: minInterestFee = 0.1e4;
89: maxInterestFee = 1e4;
90: protocolFeeShare = 0.1e4;
91: }Rather defining the struct in a single line, it is more efficient to declare an empty struct and then assign each struct element individually. This can net quite a large gas saving of 130 per instance.
Num of instances: 4
Click to show findings
['116']
116: function createProxy(address desiredImplementation, bool upgradeable, bytes memory trailingData)
117: external
118: nonReentrant
119: returns (address)
120: {
121: address _implementation = implementation;
122: if (desiredImplementation == address(0)) desiredImplementation = _implementation;
123:
124: if (desiredImplementation == address(0) || desiredImplementation != _implementation) revert E_Implementation();
125:
126: address proxy;
127:
128: if (upgradeable) {
129: proxy = address(new BeaconProxy(trailingData));
130: } else {
131: proxy = deployMetaProxy(desiredImplementation, trailingData);
132: }
133:
134: proxyLookup[proxy] = // <= FOUND
135: ProxyConfig({upgradeable: upgradeable, implementation: desiredImplementation, trailingData: trailingData});
136:
137: proxyList.push(proxy);
138:
139: IComponent(proxy).initialize(msg.sender);
140:
141: emit ProxyCreated(proxy, upgradeable, desiredImplementation, trailingData);
142:
143: return proxy;
144: }['59']
59: function computeInterestRate(address, uint256, uint256) external override returns (uint256) { // <= FOUND
60: IRMData memory irmCache = irmStorage;
61: (uint216 rate, bool updated) = _computeRate(irmCache);
62:
63: if (updated) {
64: irmStorage = IRMData({lastUpdated: uint40(block.timestamp), lastRate: rate}); // <= FOUND
65: }
66:
67: return rate;
68: }['186']
186: function setVaultInterestFeeRange(address vault, bool exists_, uint16 minInterestFee_, uint16 maxInterestFee_)
187: external
188: onlyAdmin
189: {
190: if (vault == address(0)) revert E_InvalidVault();
191: if (maxInterestFee_ > CONFIG_SCALE || minInterestFee_ > maxInterestFee_) revert E_InvalidConfigValue();
192:
193: _interestFeeRanges[vault] = // <= FOUND
194: InterestFeeRange({exists: exists_, minInterestFee: minInterestFee_, maxInterestFee: maxInterestFee_});
195:
196: emit SetVaultInterestFeeRange(vault, exists_, minInterestFee_, maxInterestFee_);
197: }['205']
205: function setVaultFeeConfig(address vault, bool exists_, address feeReceiver_, uint16 protocolFeeShare_)
206: external
207: onlyAdmin
208: {
209: if (vault == address(0)) revert E_InvalidVault();
210: if (exists_ && feeReceiver_ == address(0)) revert E_InvalidReceiver();
211: if (protocolFeeShare_ > CONFIG_SCALE) revert E_InvalidConfigValue();
212:
213: _protocolFeeConfig[vault] = // <= FOUND
214: ProtocolFeeConfig({exists: exists_, feeReceiver: feeReceiver_, protocolFeeShare: protocolFeeShare_});
215:
216: emit SetFeeConfigSetting(vault, exists_, feeReceiver_, protocolFeeShare_);
217: }Emitting events in setter functions of smart contracts only when state variables change saves gas. This is because emitting events consumes gas, and unnecessary events, where no actual state change occurs, lead to wasteful consumption.
Num of instances: 11
Click to show findings
['42']
42: function setCapacity(address minter, uint128 capacity) external onlyOwner { // <= FOUND
43: minters[minter].capacity = capacity;
44: emit MinterCapacitySet(minter, capacity); // <= FOUND
45: }['164']
164: function setUpgradeAdmin(address newUpgradeAdmin) external nonReentrant adminOnly { // <= FOUND
165: upgradeAdmin = newUpgradeAdmin;
166: emit SetUpgradeAdmin(newUpgradeAdmin); // <= FOUND
167: }['252']
252: function setGovernorAdmin(address newGovernorAdmin) public virtual nonReentrant governorOnly { // <= FOUND
253: vaultStorage.governorAdmin = newGovernorAdmin;
254: emit GovSetGovernorAdmin(newGovernorAdmin); // <= FOUND
255: }['258']
258: function setFeeReceiver(address newFeeReceiver) public virtual nonReentrant governorOnly { // <= FOUND
259: vaultStorage.feeReceiver = newFeeReceiver;
260: emit GovSetFeeReceiver(newFeeReceiver); // <= FOUND
261: }['323']
323: function setMaxLiquidationDiscount(uint16 newDiscount) public virtual nonReentrant governorOnly { // <= FOUND
324: vaultStorage.maxLiquidationDiscount = newDiscount.toConfigAmount();
325: emit GovSetMaxLiquidationDiscount(newDiscount); // <= FOUND
326: }['329']
329: function setLiquidationCoolOffTime(uint16 newCoolOffTime) public virtual nonReentrant governorOnly { // <= FOUND
330: vaultStorage.liquidationCoolOffTime = newCoolOffTime;
331: emit GovSetLiquidationCoolOffTime(newCoolOffTime); // <= FOUND
332: }['335']
335: function setInterestRateModel(address newModel) public virtual nonReentrant governorOnly { // <= FOUND
336: VaultCache memory vaultCache = updateVault();
337:
338: vaultStorage.interestRateModel = newModel;
339: vaultStorage.interestRate = 0;
340:
341: uint256 newInterestRate = computeInterestRate(vaultCache);
342:
343: logVaultStatus(vaultCache, newInterestRate);
344:
345: emit GovSetInterestRateModel(newModel); // <= FOUND
346: }['363']
363: function setConfigFlags(uint32 newConfigFlags) public virtual nonReentrant governorOnly { // <= FOUND
364: if (newConfigFlags >= CFG_MAX_VALUE) revert E_NotSupported();
365:
366: vaultStorage.configFlags = Flags.wrap(newConfigFlags);
367: emit GovSetConfigFlags(newConfigFlags); // <= FOUND
368: }['387']
387: function setInterestFee(uint16 newInterestFee) public virtual nonReentrant governorOnly { // <= FOUND
388:
389: VaultCache memory vaultCache = updateVault();
390: logVaultStatus(vaultCache, vaultStorage.interestRate);
391:
392:
393: if (newInterestFee < GUARANTEED_INTEREST_FEE_MIN || newInterestFee > GUARANTEED_INTEREST_FEE_MAX) {
394: if (!protocolConfig.isValidInterestFee(address(this), newInterestFee)) revert E_BadFee();
395: }
396:
397: vaultStorage.interestFee = newInterestFee.toConfigAmount();
398:
399: emit GovSetInterestFee(newInterestFee); // <= FOUND
400: }['159']
159: function setProtocolFeeShare(uint16 newProtocolFeeShare) external onlyAdmin { // <= FOUND
160: if (newProtocolFeeShare > CONFIG_SCALE) revert E_InvalidConfigValue();
161:
162: emit SetProtocolFeeShare(protocolFeeShare, newProtocolFeeShare); // <= FOUND
163:
164: protocolFeeShare = newProtocolFeeShare;
165: }['171']
171: function setInterestFeeRange(uint16 minInterestFee_, uint16 maxInterestFee_) external onlyAdmin { // <= FOUND
172: if (maxInterestFee_ > CONFIG_SCALE || minInterestFee_ > maxInterestFee_) revert E_InvalidConfigValue();
173:
174: minInterestFee = minInterestFee_;
175: maxInterestFee = maxInterestFee_;
176:
177: emit SetInterestFeeRange(minInterestFee_, maxInterestFee_); // <= FOUND
178: }Emitting variable literals (true, false, 'hello', 1 etc...) in events is inefficient, as it consumes extra gas without providing added value. These literals are fixed values that can be accessed or hardcoded elsewhere in the smart contract or application, making their inclusion in events redundant and an unnecessary drain on resources during transaction execution.
Num of instances: 6
Click to show findings
['319']
319: emit GovSetLTV(collateral, 0, 0, originalLTV, 0, 0, false); // <= FOUND['220']
220:
221:
222: emit Withdraw(liqCache.liquidator, address(0), address(0), owedRemaining.toUint(), 0); // <= FOUND['36']
36: emit Transfer(address(0), account, amount.toUint()); // <= FOUND['65']
65: emit Transfer(account, address(0), amount.toUint()); // <= FOUND['52']
52: if (wasBalanceForwarderEnabled) emit BalanceForwarderStatus(account, false); // <= FOUND['37']
37: if (!wasBalanceForwarderEnabled) emit BalanceForwarderStatus(account, true); // <= FOUNDShort-circuit evaluation in programming optimizes conditional statements by evaluating expressions in order of least computational complexity. Applying this to smart contracts, especially those on blockchain networks where computation costs gas, can significantly reduce unnecessary operations and associated costs. Prioritizing checks against constants, literals, and local state over function calls ensures that if a condition can be resolved without interacting with function calls, it is.
Num of instances: 4
Click to show findings
['115']
115: if (amount.isZero() || owner == spender) return; // <= FOUND['243']
243:
244:
245:
246:
247:
248:
249: if (max.isZero() || hasAnyControllerEnabled(owner)) return Shares.wrap(0); // <= FOUND['81']
81:
82: if ( // <= FOUND
83: (amount > fromOwed && amount.subUnchecked(fromOwed).isDust())
84: || (amount < fromOwed && fromOwed.subUnchecked(amount).isDust()) // <= FOUND
85: ) {['68']
68: if ( // <= FOUND
69: isKnownNonOwnerAccount(onBehalfOfAccount) || evc.isOperatorAuthenticated() // <= FOUND
70: || evc.isControlCollateralInProgress() // <= FOUND
71: ) {The OpenZeppelin Array.unsafeAccess() method is a optimization strategy for Solidity, aimed at reducing gas costs by bypassing automatic length checks on storage array accesses. In Solidity, every access to an array element involves a hidden gas cost due to a length check, ensuring that accesses do not exceed the array bounds. However, if a developer has already verified the array's bounds earlier in the function or knows through logic that the access is safe, directly accessing the array elements without redundant length checks can save gas. This approach requires careful consideration to avoid out-of-bounds errors, as it trades off safety checks for efficiency.
Num of instances: 5
Click to show findings
['202']
202: list[i] = proxyList[start + i]; // <= FOUND['27']
27: collateralValue += getCollateralValue(vaultCache, account, collaterals[i], liquidation); // <= FOUND['50']
50: collateralValue += getCollateralValue(vaultCache, account, collaterals[i], false); // <= FOUND['63']
63: address collateral = collaterals[i]; // <= FOUND['48']
48: collateralValues[i] = getCollateralValue(vaultCache, account, collaterals[i], liquidation); // <= FOUNDReading a state variable inside a loop in a smart contract can unnecessarily increase gas consumption, as each read operation from the blockchain state is costly. This inefficiency becomes pronounced in loops with many iterations. To optimize gas usage, it's advisable to read the state variable once before the loop starts, store its value in a local (memory) variable, and then use this local variable within the loop. This approach minimizes the number of state read operations, thereby reducing the gas cost associated with executing the contract function, making the smart contract more efficient and cost-effective to run.
Num of instances: 4
Click to show findings
['201']
201: for (uint256 i; i < end - start; ++i) {
202: list[i] = proxyList[start + i]; // <= FOUND
203: }['201']
201: for (uint256 i; i < end - start; ++i) {
202: list[i] = proxyList[start + i]; // <= FOUND
203: }['164']
164: for (uint256 i = 0; i < ignoredLength; i++) {
165: total -= balanceOf(ignoredForTotalSupply.at(i));
166: }['164']
164: for (uint256 i = 0; i < ignoredLength; i++) {
165: total -= balanceOf(ignoredForTotalSupply.at(i));
166: }In Solidity, the use of uint256 values instead of boolean for certain state variables can result in gas savings. This is due to how Ethereum's storage optimization works: changing a variable from 0 to a non-zero value (like flipping false to true) incurs a higher gas cost compared to modifying an already non-zero value. By using uint256 with values 1 and 2 instead of true and false, you avoid the higher cost associated with the 0 to non-zero change, since 1 and 2 are both non-zero. This approach is notably used in OpenZeppelin's ReentrancyGuard as a gas optimization technique. However, this should be applied where it makes sense and where gas optimization is critical, as it can decrease code readability.
Num of instances: 10
Click to show findings
['56']
56: vaultStorage.reentrancyLocked = true; // <= FOUND['100']
100: vaultStorage.snapshotInitialized = vaultCache.snapshotInitialized = true; // <= FOUND['70']
70: dirty = true; // <= FOUND['86']
86: updated = true; // <= FOUND['31']
31: initialized = true; // <= FOUND['71']
71: newLTV.initialized = true; // <= FOUND['58']
58: vaultStorage.reentrancyLocked = false; // <= FOUND['43']
43: dirty = false; // <= FOUND['76']
76: updated = false; // <= FOUND['96']
96: vaultStorage.snapshotInitialized = vaultCache.snapshotInitialized = false; // <= FOUND[Gas-51] Write direct outcome, instead of performing mathematical operations for constant state variables
In Solidity, it's highly efficient to directly assign constant values to state variables when these values are known at compile time and will not change. This practice avoids unnecessary computational operations and reduces gas costs for deploying and interacting with smart contracts.
Num of instances: 3
Click to show findings
['16']
16: uint216 internal constant SECONDS_PER_YEAR = 365.2425 * 86400; // <= FOUND['17']
17: uint216 public constant MAX_RATE = 1e27 * 1.5 / SECONDS_PER_YEAR; // <= FOUND['18']
18: uint216 public constant BASE_RATE = 1e27 * 0.005 / SECONDS_PER_YEAR; // <= FOUNDConsider saving the address(this) value within a constant using foundry's script.sol or solady's LibRlp.sol to save gas
Num of instances: 22
Click to show findings
['19']
19: vaultCache.asset.safeTransferFrom(from, address(this), amount.toUint(), permit2); // <= FOUND['69']
69:
70:
71:
72:
73: if (msg.sender != hookTarget && !(msg.sender == address(this) && ProxyUtils.useViewCaller() == hookTarget)) // <= FOUND
74: {['113']
113: (bool success, bytes memory data) = irm.call(
114: abi.encodeCall(
115: IIRM.computeInterestRate,
116: (address(this), vaultCache.cash.toUint(), vaultCache.totalBorrows.toAssetsUp().toUint()) // <= FOUND
117: )
118: );['136']
136: (bool success, bytes memory data) = irm.staticcall(
137: abi.encodeCall(
138: IIRM.computeInterestRateView,
139: (address(this), vaultCache.cash.toUint(), vaultCache.totalBorrows.toAssetsUp().toUint()) // <= FOUND
140: )
141: );['153']
153: uint256 origBalance = asset.balanceOf(address(this)); // <= FOUND['159']
159: if (asset.balanceOf(address(this)) < origBalance) revert E_FlashLoanNotRepaid(); // <= FOUND['105']
105: if (msg.sender != address(this)) revert E_Unauthorized(); // <= FOUND['86']
86:
87:
88: if (burnFrom != sender && !(burnFrom == address(this) && sender == owner())) { // <= FOUND['109']
109: _approve(address(this), vault, amount, true); // <= FOUND['110']
110: IEVault(vault).deposit(amount, address(this)); // <= FOUND['117']
117: IEVault(vault).withdraw(amount, address(this), address(this)); // <= FOUND['44']
44: (address onBehalfOfAccount, bool controllerEnabled) =
45: evc.getCurrentOnBehalfOfAccount(checkController ? address(this) : address(0)); // <= FOUND['126']
126: return evc.isVaultStatusCheckDeferred(address(this)); // <= FOUND['142']
142: if (controllers[0] != address(this)) revert E_NotController(); // <= FOUND['202']
202: uint256 assetBalance = IERC20(asset()).balanceOf(address(this)); // <= FOUND['125']
125: (, uint256 protocolShare) = protocolConfig.protocolFeeConfig(address(this)); // <= FOUND['131']
131: (address protocolReceiver,) = protocolConfig.protocolFeeConfig(address(this)); // <= FOUND['221']
221: (address protocolReceiver, uint16 protocolFee) = protocolConfig.protocolFeeConfig(address(this)); // <= FOUND['281']
281:
282: if (collateral == address(this)) revert E_InvalidLTVAsset(); // <= FOUND['394']
394: if (!protocolConfig.isValidInterestFee(address(this), newInterestFee)) revert E_BadFee(); // <= FOUND['107']
107: underlying.safeTransferFrom(_msgSender(), address(this), amountIn); // <= FOUND['185']
185: Assets balance = vaultCache.asset.balanceOf(address(this)).toAssets(); // <= FOUNDIn Solidity, choosing between memory and storage for variables, especially when dealing with structs or arrays, is crucial for optimizing gas costs. Variables declared as storage are pointers to the blockchain data, leading to lower gas consumption when fields are accessed or modified, as they don't require reading the entire structure. In contrast, memory variables copy the entire struct or array from storage, incurring significant gas costs, especially for large or complex structures. Therefore, use storage for state variables or when working within functions to manipulate existing contract data. Reserve memory for temporary data or when data needs to be passed to external functions as copies, ensuring efficient use of gas and avoiding unnecessary costs.
Num of instances: 6
Click to show findings
['218']
218: ESRSlot memory esrSlotCache = esrSlot; // <= FOUND['60']
60: IRMData memory irmCache = irmStorage; // <= FOUND['52']
52: MinterData memory minterCache = minters[sender]; // <= FOUND['95']
95: InterestFeeRange memory range = _interestFeeRanges[vault]; // <= FOUND['117']
117: InterestFeeRange memory ranges = _interestFeeRanges[vault]; // <= FOUND['106']
106: ProtocolFeeConfig memory config = _protocolFeeConfig[vault]; // <= FOUNDPublic functions that aren't used internally in Solidity contracts should be made external to optimize gas usage and improve contract efficiency. External functions can only be called from outside the contract, and their arguments are directly read from the calldata, which is more gas-efficient than loading them into memory, as is the case for public functions. By using external visibility, developers can reduce gas consumption for external calls and ensure that the contract operates more cost-effectively for users. Moreover, setting the appropriate visibility level for functions also enhances code readability and maintainability, promoting a more secure and well-structured contract design.
Num of instances: 5
Click to show findings
['147']
147: function isIgnoredForTotalSupply(address account) public view returns (bool) ['153']
153: function getAllIgnoredForTotalSupply() public view returns (address[] memory) ['148']
148: function setFeeReceiver(address newReceiver) external onlyAdmin ['199']
199: function gulp() public nonReentrant ['256']
256: function getESRSlot() public view returns (ESRSlot memory) Empty code blocks (i.e., {}) in a Solidity contract can be harmful as they can lead to ambiguity, misinterpretation, and unintended behavior. When developers encounter empty code blocks, it may be unclear whether the absence of code is intentional or the result of an oversight. This uncertainty can cause confusion during development, testing, and debugging, increasing the likelihood of introducing errors or vulnerabilities. Moreover, empty code blocks may give a false impression of implemented functionality or security measures, creating a misleading sense of assurance. To ensure clarity and maintainability, it is essential to avoid empty code blocks and explicitly document the intended behavior or any intentional omissions.
Num of instances: 70
Click to show findings
['20']
20: function initialize(address proxyCreator) public virtual override use(MODULE_INITIALIZE) {}['28']
28: function name() public view virtual override useView(MODULE_TOKEN) returns (string memory) {}['30']
30: function symbol() public view virtual override useView(MODULE_TOKEN) returns (string memory) {}['34']
34: function totalSupply() public view virtual override useView(MODULE_TOKEN) returns (uint256) {}['57']
57: function totalAssets() public view virtual override useView(MODULE_VAULT) returns (uint256) {}['63']
63: function maxDeposit(address account) public view virtual override useView(MODULE_VAULT) returns (uint256) {}['65']
65: function previewDeposit(uint256 assets) public view virtual override useView(MODULE_VAULT) returns (uint256) {}['67']
67: function maxMint(address account) public view virtual override useView(MODULE_VAULT) returns (uint256) {}['69']
69: function previewMint(uint256 shares) public view virtual override useView(MODULE_VAULT) returns (uint256) {}['71']
71: function maxWithdraw(address owner) public view virtual override useView(MODULE_VAULT) returns (uint256) {}['73']
73: function previewWithdraw(uint256 assets) public view virtual override useView(MODULE_VAULT) returns (uint256) {}['75']
75: function maxRedeem(address owner) public view virtual override useView(MODULE_VAULT) returns (uint256) {}['77']
77: function previewRedeem(uint256 shares) public view virtual override useView(MODULE_VAULT) returns (uint256) {}['83']
83: function creator() public view virtual override useView(MODULE_VAULT) returns (address) {}['88']
88: function mint(uint256 amount, address receiver) public virtual override callThroughEVC use(MODULE_VAULT) returns (uint256) {}['90']
90: function withdraw(uint256 amount, address receiver, address owner) public virtual override callThroughEVC use(MODULE_VAULT) returns (uint256) {}['92']
92: function redeem(uint256 amount, address receiver, address owner) public virtual override callThroughEVC use(MODULE_VAULT) returns (uint256) {}['94']
94: function skim(uint256 amount, address receiver) public virtual override callThroughEVC use(MODULE_VAULT) returns (uint256) {}['102']
102: function totalBorrows() public view virtual override useView(MODULE_BORROWING) returns (uint256) {}['104']
104: function totalBorrowsExact() public view virtual override useView(MODULE_BORROWING) returns (uint256) {}['106']
106: function cash() public view virtual override useView(MODULE_BORROWING) returns (uint256) {}['110']
110: function debtOfExact(address account) public view virtual override useView(MODULE_BORROWING) returns (uint256) {}['114']
114: function interestAccumulator() public view virtual override useView(MODULE_BORROWING) returns (uint256) {}['116']
116: function dToken() public view virtual override useView(MODULE_BORROWING) returns (address) {}['119']
119: function borrow(uint256 amount, address receiver) public virtual override callThroughEVC use(MODULE_BORROWING) returns (uint256) {}['121']
121: function repay(uint256 amount, address receiver) public virtual override callThroughEVC use(MODULE_BORROWING) returns (uint256) {}['123']
123: function repayWithShares(uint256 amount, address receiver) public virtual override callThroughEVC use(MODULE_BORROWING) returns (uint256 shares, uint256 debt) {}['125']
125: function pullDebt(uint256 amount, address from) public virtual override callThroughEVC use(MODULE_BORROWING) returns (uint256) {}['127']
127: function flashLoan(uint256 amount, bytes calldata data) public virtual override use(MODULE_BORROWING) {}['129']
129: function touch() public virtual override callThroughEVC use(MODULE_BORROWING) {}['137']
137: function checkLiquidation(address liquidator, address violator, address collateral) public view virtual override useView(MODULE_LIQUIDATION) returns (uint256 maxRepay, uint256 maxYield) {}['139']
139: function liquidate(address violator, address collateral, uint256 repayAssets, uint256 minYieldBalance) public virtual override callThroughEVC use(MODULE_LIQUIDATION) {}['147']
147: function accountLiquidity(address account, bool liquidation) public view virtual override useView(MODULE_RISKMANAGER) returns (uint256 collateralValue, uint256 liabilityValue) {}['149']
149: function accountLiquidityFull(address account, bool liquidation) public view virtual override useView(MODULE_RISKMANAGER) returns (address[] memory collaterals, uint256[] memory collateralValues, uint256 liabilityValue) {}['152']
152: function disableController() public virtual override use(MODULE_RISKMANAGER) {}['164']
164: function balanceTrackerAddress() public view virtual override useView(MODULE_BALANCE_FORWARDER) returns (address) {}['166']
166: function balanceForwarderEnabled(address account) public view virtual override useView(MODULE_BALANCE_FORWARDER) returns (bool) {}['169']
169: function enableBalanceForwarder() public virtual override use(MODULE_BALANCE_FORWARDER) {}['171']
171: function disableBalanceForwarder() public virtual override use(MODULE_BALANCE_FORWARDER) {}['179']
179: function governorAdmin() public view virtual override useView(MODULE_GOVERNANCE) returns (address) {}['181']
181: function feeReceiver() public view virtual override useView(MODULE_GOVERNANCE) returns (address) {}['185']
185: function interestRateModel() public view virtual override useView(MODULE_GOVERNANCE) returns (address) {}['187']
187: function protocolConfigAddress() public view virtual override useView(MODULE_GOVERNANCE) returns (address) {}['189']
189: function protocolFeeShare() public view virtual override useView(MODULE_GOVERNANCE) returns (uint256) {}['191']
191: function protocolFeeReceiver() public view virtual override useView(MODULE_GOVERNANCE) returns (address) {}['193']
193: function caps() public view virtual override useView(MODULE_GOVERNANCE) returns (uint16 supplyCap, uint16 borrowCap) {}['195']
195: function LTVBorrow(address collateral) public view virtual override useView(MODULE_GOVERNANCE) returns (uint16) {}['197']
197: function LTVLiquidation(address collateral) public view virtual override useView(MODULE_GOVERNANCE) returns (uint16) {}['199']
199: function LTVFull(address collateral) public view virtual override useView(MODULE_GOVERNANCE) returns (uint16 borrowLTV, uint16 liquidationLTV, uint16 initialLiquidationLTV, uint48 targetTimestamp, uint32 rampDuration) {}['201']
201: function LTVList() public view virtual override useView(MODULE_GOVERNANCE) returns (address[] memory) {}['203']
203: function maxLiquidationDiscount() public view virtual override useView(MODULE_GOVERNANCE) returns (uint16) {}['205']
205: function liquidationCoolOffTime() public view virtual override useView(MODULE_GOVERNANCE) returns (uint16) {}['207']
207: function hookConfig() public view virtual override useView(MODULE_GOVERNANCE) returns (address, uint32) {}['209']
209: function configFlags() public view virtual override useView(MODULE_GOVERNANCE) returns (uint32) {}['211']
211: function EVC() public view virtual override useView(MODULE_GOVERNANCE) returns (address) {}['213']
213: function unitOfAccount() public view virtual override useView(MODULE_GOVERNANCE) returns (address) {}['215']
215: function oracle() public view virtual override useView(MODULE_GOVERNANCE) returns (address) {}['217']
217: function permit2Address() public view virtual override useView(MODULE_GOVERNANCE) returns (address) {}['220']
220: function convertFees() public virtual override callThroughEVC use(MODULE_GOVERNANCE) {}['222']
222: function setGovernorAdmin(address newGovernorAdmin) public virtual override use(MODULE_GOVERNANCE) {}['224']
224: function setFeeReceiver(address newFeeReceiver) public virtual override use(MODULE_GOVERNANCE) {}['226']
226: function setHookConfig(address newHookTarget, uint32 newHookedOps) public virtual override use(MODULE_GOVERNANCE) {}['228']
228: function setLTV(address collateral, uint16 borrowLTV, uint16 liquidationLTV, uint32 rampDuration) public virtual override use(MODULE_GOVERNANCE) {}['230']
230: function clearLTV(address collateral) public virtual override use(MODULE_GOVERNANCE) {}['232']
232: function setMaxLiquidationDiscount(uint16 newDiscount) public virtual override use(MODULE_GOVERNANCE) {}['234']
234: function setLiquidationCoolOffTime(uint16 newCoolOffTime) public virtual override use(MODULE_GOVERNANCE) {}['236']
236: function setInterestRateModel(address newModel) public virtual override use(MODULE_GOVERNANCE) {}['238']
238: function setConfigFlags(uint32 newConfigFlags) public virtual override use(MODULE_GOVERNANCE) {}['240']
240: function setCaps(uint16 supplyCap, uint16 borrowCap) public virtual override use(MODULE_GOVERNANCE) {}['242']
242: function setInterestFee(uint16 newFee) public virtual override use(MODULE_GOVERNANCE) {}In smart contract development, utilizing constants for known maximum or minimum values, rather than computing type(uint<n>).max or assuming 0 for .min, can significantly reduce gas costs. Constants require less runtime computation and storage, optimizing contract efficiency—a crucial strategy for developers aiming for cost-effective and performant code.
Num of instances: 17
Click to show findings
['21']
21: if (amountCap == 0) return type(uint256).max; // <= FOUND['119']
119: if (allowance != type(uint256).max) { // <= FOUND['98']
98: && !(vaultCache.supplyCap == type(uint256).max && vaultCache.borrowCap == type(uint256).max) // <= FOUND['68']
68: Assets assets = amount == type(uint256).max ? vaultCache.cash : amount.toAssets(); // <= FOUND['86']
86: Assets assets = (amount == type(uint256).max ? owed : amount).toAssets(); // <= FOUND['189']
189: if (amount == type(uint256).max) { // <= FOUND['136']
136: Assets assets = amount == type(uint256).max ? getCurrentOwed(vaultCache, from).toAssetsUp() : amount.toAssets(); // <= FOUND['9']
9: uint256 constant MAX_SANE_AMOUNT = type(uint112).max; // <= FOUND['60']
60: amount > type(uint128).max - minterCache.minted // <= FOUND['205']
205: uint256 maxGulp = type(uint168).max - esrSlotCache.interestLeft; // <= FOUND['197']
197: if (end == type(uint256).max) end = proxyList.length; // <= FOUND['56']
56: : uint32(borrows * type(uint32).max / totalAssets); // <= FOUND['39']
39: calculateLiquidation(loadVault(), liquidator, violator, collateral, type(uint256).max); // <= FOUND['103']
103: if (desiredRepay != type(uint256).max) { // <= FOUND['32']
32: if (value > type(uint160).max) { // <= FOUND['127']
127: Assets assets = amount == type(uint256).max ? vaultCache.asset.balanceOf(account).toAssets() : amount.toAssets(); // <= FOUND['170']
170: Shares shares = amount == type(uint256).max ? vaultStorage.users[owner].getBalance() : amount.toShares(); // <= FOUNDUsing type(uint<n>).min in Solidity is redundant because it always evaluates to 0 for any unsigned integer. Since uint<n> types cannot represent negative values, their minimum is universally zero. Directly using 0 is more straightforward and gas-efficient, aligning with best practices for clear and optimized smart contract code.
Num of instances: 1
The following functions does not write or read from storage as such can be defined as pure to save Gas.
Num of instances: 69
Click to show findings
['12']
12: function checkContract(address addr) internal view returns (address) { // <= FOUND
13: if (addr.code.length == 0) revert Errors.E_BadAddress();
14:
15: return addr;
16: }['121']
121: function callHookWithLock(Flags hookedOps, uint32 operation, address caller) internal virtual { // <= FOUND
122: if (hookedOps.isNotSet(operation)) return;
123:
124: invokeHookTargetWithLock(caller);
125: }['141']
141: function logVaultStatus(VaultCache memory a, uint256 interestRate) internal { // <= FOUND
142: emit VaultStatus(
143: a.totalShares.toUint(),
144: a.totalBorrows.toAssetsUp().toUint(),
145: a.accumulatedFees.toUint(),
146: a.cash.toUint(),
147: a.interestAccumulator,
148: interestRate,
149: block.timestamp
150: );
151: }['152']
152: function calculateDTokenAddress() internal view virtual returns (address dToken) { // <= FOUND
153:
154:
155: assembly ("memory-safe") {
156: mstore(0x14, address())
157:
158:
159: mstore(0x00, 0xd694)
160:
161: mstore8(0x34, 0x01)
162:
163: dToken := keccak256(0x1e, 0x17)
164: }
165: }['167']
167: function logBorrow(address account, Assets amount, Assets prevOwed, Assets owed) private { // <= FOUND
168: Assets interest = owed.subUnchecked(prevOwed).subUnchecked(amount);
169: if (!interest.isZero()) emit InterestAccrued(account, interest.toUint());
170: if (!amount.isZero()) emit Borrow(account, amount.toUint());
171: logDToken(account, prevOwed, owed);
172: }['174']
174: function logRepay(address account, Assets amount, Assets prevOwed, Assets owed) private { // <= FOUND
175: Assets interest = owed.addUnchecked(amount).subUnchecked(prevOwed);
176: if (!interest.isZero()) emit InterestAccrued(account, interest.toUint());
177: if (!amount.isZero()) emit Repay(account, amount.toUint());
178: logDToken(account, prevOwed, owed);
179: }['181']
181: function logDToken(address account, Assets prevOwed, Assets owed) private { // <= FOUND
182: address dTokenAddress = calculateDTokenAddress();
183:
184: if (owed > prevOwed) {
185: uint256 change = owed.subUnchecked(prevOwed).toUint();
186: DToken(dTokenAddress).emitTransfer(address(0), account, change);
187: } else if (prevOwed > owed) {
188: uint256 change = prevOwed.subUnchecked(owed).toUint();
189: DToken(dTokenAddress).emitTransfer(account, address(0), change);
190: }
191: }['25']
25: function totalBorrows() public view virtual nonReentrantView returns (uint256) { // <= FOUND
26: return loadVault().totalBorrows.toAssetsUp().toUint();
27: }['30']
30: function totalBorrowsExact() public view virtual nonReentrantView returns (uint256) { // <= FOUND
31: return loadVault().totalBorrows.toUint();
32: }['40']
40: function debtOf(address account) public view virtual nonReentrantView returns (uint256) { // <= FOUND
41: return getCurrentOwed(loadVault(), account).toAssetsUp().toUint();
42: }['45']
45: function debtOfExact(address account) public view virtual nonReentrantView returns (uint256) { // <= FOUND
46: return getCurrentOwed(loadVault(), account).toUint();
47: }['50']
50: function interestRate() public view virtual nonReentrantView returns (uint256) { // <= FOUND
51: return computeInterestRateView(loadVault());
52: }['55']
55: function interestAccumulator() public view virtual nonReentrantView returns (uint256) { // <= FOUND
56: return loadVault().interestAccumulator;
57: }['60']
60: function dToken() public view virtual reentrantOK returns (address) { // <= FOUND
61: return calculateDTokenAddress();
62: }['65']
65: function borrow(uint256 amount, address receiver) public virtual nonReentrant returns (uint256) { // <= FOUND
66: (VaultCache memory vaultCache, address account) = initOperation(OP_BORROW, CHECKACCOUNT_CALLER);
67:
68: Assets assets = amount == type(uint256).max ? vaultCache.cash : amount.toAssets();
69: if (assets.isZero()) return 0;
70:
71: if (assets > vaultCache.cash) revert E_InsufficientCash();
72:
73: increaseBorrow(vaultCache, account, assets);
74:
75: pushAssets(vaultCache, receiver, assets);
76:
77: return assets.toUint();
78: }['81']
81: function repay(uint256 amount, address receiver) public virtual nonReentrant returns (uint256) { // <= FOUND
82: (VaultCache memory vaultCache, address account) = initOperation(OP_REPAY, CHECKACCOUNT_NONE);
83:
84: uint256 owed = getCurrentOwed(vaultCache, receiver).toAssetsUp().toUint();
85:
86: Assets assets = (amount == type(uint256).max ? owed : amount).toAssets();
87: if (assets.isZero()) return 0;
88:
89: pullAssets(vaultCache, account, assets);
90:
91: decreaseBorrow(vaultCache, receiver, assets);
92:
93: return assets.toUint();
94: }['131']
131: function pullDebt(uint256 amount, address from) public virtual nonReentrant returns (uint256) { // <= FOUND
132: (VaultCache memory vaultCache, address account) = initOperation(OP_PULL_DEBT, CHECKACCOUNT_CALLER);
133:
134: if (from == account) revert E_SelfTransfer();
135:
136: Assets assets = amount == type(uint256).max ? getCurrentOwed(vaultCache, from).toAssetsUp() : amount.toAssets();
137:
138: if (assets.isZero()) return 0;
139: transferBorrow(vaultCache, from, account, assets);
140:
141: emit PullDebt(from, account, assets.toUint());
142:
143: return assets.toUint();
144: }['163']
163: function touch() public virtual nonReentrant { // <= FOUND
164: initOperation(OP_TOUCH, CHECKACCOUNT_NONE);
165: }['104']
104: function viewDelegate() external payable { // <= FOUND
105: if (msg.sender != address(this)) revert E_Unauthorized();
106:
107: assembly {
108: let size := sub(calldatasize(), 36)
109: calldatacopy(0, 36, size)
110: let result := delegatecall(gas(), calldataload(4), 0, size, 0, 0)
111: returndatacopy(0, 0, returndatasize())
112: switch result
113: case 0 { revert(0, returndatasize()) }
114: default { return(0, returndatasize()) }
115: }
116: }['118']
118: function delegateToModule(address module) private { // <= FOUND
119: assembly {
120: calldatacopy(0, 0, calldatasize())
121: let result := delegatecall(gas(), module, 0, calldatasize(), 0, 0)
122: returndatacopy(0, 0, returndatasize())
123: switch result
124: case 0 { revert(0, returndatasize()) }
125: default { return(0, returndatasize()) }
126: }
127: }['129']
129: function delegateToModuleView(address module) private view { // <= FOUND
130: assembly {
131:
132:
133:
134: mstore(0, 0x1fe8b95300000000000000000000000000000000000000000000000000000000)
135: let strippedCalldataSize := sub(calldatasize(), PROXY_METADATA_LENGTH)
136:
137:
138:
139: mstore(add(24, strippedCalldataSize), caller())
140: mstore(4, module)
141: calldatacopy(36, 0, strippedCalldataSize)
142:
143: let result := staticcall(gas(), address(), 0, add(strippedCalldataSize, 56), 0, 0)
144: returndatacopy(0, 0, returndatasize())
145: switch result
146: case 0 { revert(0, returndatasize()) }
147: default { return(0, returndatasize()) }
148: }
149: }['27']
27: function transfer(address to, uint256 amount) public virtual override nonReentrant returns (bool) { // <= FOUND
28: return super.transfer(to, amount);
29: }['37']
37: function transferFrom(address from, address to, uint256 amount) // <= FOUND
38: public
39: virtual
40: override
41: nonReentrant
42: returns (bool)
43: {
44: return super.transferFrom(from, to, amount);
45: }['71']
71: function _msgSender() internal view virtual override (EVCUtil, Context) returns (address) { // <= FOUND
72: return EVCUtil._msgSender();
73: }['116']
116: function deallocate(address vault, uint256 amount) external onlyOwner { // <= FOUND
117: IEVault(vault).withdraw(amount, address(this), address(this));
118: }['125']
125: function _msgSender() internal view virtual override (ERC20Collateral, Context) returns (address) { // <= FOUND
126: return ERC20Collateral._msgSender();
127: }['32']
32: function decimals() public view virtual override returns (uint8) { return super.decimals(); } // <= FOUND['36']
36: function balanceOf(address account) public view virtual override returns (uint256) { return super.balanceOf(account); } // <= FOUND['38']
38: function allowance(address holder, address spender) public view virtual override returns (uint256) { return super.allowance(holder, spender); } // <= FOUND['41']
41: function transfer(address to, uint256 amount) public virtual override callThroughEVC returns (bool) { return super.transfer(to, amount); } // <= FOUND['43']
43: function transferFrom(address from, address to, uint256 amount) public virtual override callThroughEVC returns (bool) { return super.transferFrom(from, to, amount); } // <= FOUND['45']
45: function approve(address spender, uint256 amount) public virtual override returns (bool) { return super.approve(spender, amount); } // <= FOUND['47']
47: function transferFromMax(address from, address to) public virtual override callThroughEVC returns (bool) { return super.transferFromMax(from, to); } // <= FOUND['55']
55: function asset() public view virtual override returns (address) { return super.asset(); } // <= FOUND['59']
59: function convertToAssets(uint256 shares) public view virtual override returns (uint256) { return super.convertToAssets(shares); } // <= FOUND['61']
61: function convertToShares(uint256 assets) public view virtual override returns (uint256) { return super.convertToShares(assets); } // <= FOUND['79']
79: function accumulatedFees() public view virtual override returns (uint256) { return super.accumulatedFees(); } // <= FOUND['81']
81: function accumulatedFeesAssets() public view virtual override returns (uint256) { return super.accumulatedFeesAssets(); } // <= FOUND['86']
86: function deposit(uint256 amount, address receiver) public virtual override callThroughEVC returns (uint256) { return super.deposit(amount, receiver); } // <= FOUND['108']
108: function debtOf(address account) public view virtual override returns (uint256) { return super.debtOf(account); } // <= FOUND['112']
112: function interestRate() public view virtual override returns (uint256) { return super.interestRate(); } // <= FOUND['154']
154: function checkAccountStatus(address account, address[] calldata collaterals) public virtual override returns (bytes4) { return super.checkAccountStatus(account, collaterals); } // <= FOUND['156']
156: function checkVaultStatus() public virtual override returns (bytes4) { return super.checkVaultStatus(); } // <= FOUND['183']
183: function interestFee() public view virtual override returns (uint16) { return super.interestFee(); } // <= FOUND['104']
104: function transfer(address to, uint256 amount) // <= FOUND
105: public
106: override (ERC20, IERC20)
107: nonReentrant
108: requireAccountStatusCheck(_msgSender())
109: returns (bool)
110: {
111: return super.transfer(to, amount);
112: }['119']
119: function transferFrom(address from, address to, uint256 amount) // <= FOUND
120: public
121: override (ERC20, IERC20)
122: nonReentrant
123: requireAccountStatusCheck(from)
124: returns (bool)
125: {
126: return super.transferFrom(from, to, amount);
127: }['133']
133: function deposit(uint256 assets, address receiver) public override nonReentrant returns (uint256) { // <= FOUND
134: return super.deposit(assets, receiver);
135: }['141']
141: function mint(uint256 shares, address receiver) public override nonReentrant returns (uint256) { // <= FOUND
142: return super.mint(shares, receiver);
143: }['264']
264: function _msgSender() internal view override (Context, EVCUtil) returns (address) { // <= FOUND
265: return EVCUtil._msgSender();
266: }['76']
76: function getTokenSymbol(address asset) private view returns (string memory) { // <= FOUND
77: (bool success, bytes memory data) = address(asset).staticcall(abi.encodeCall(IERC20.symbol, ()));
78: if (!success) return "UNDEFINED";
79: return data.length <= 32 ? string(data) : abi.decode(data, (string));
80: }['37']
37: function getLTV(LTVConfig memory self, bool liquidation) internal view returns (ConfigAmount) { // <= FOUND
38: if (!liquidation) {
39: return self.borrowLTV;
40: }
41:
42: if (block.timestamp >= self.targetTimestamp || self.liquidationLTV >= self.initialLiquidationLTV) {
43: return self.liquidationLTV;
44: }
45:
46: uint256 currentLiquidationLTV = self.initialLiquidationLTV.toUint16();
47:
48: unchecked {
49: uint256 targetLiquidationLTV = self.liquidationLTV.toUint16();
50: uint256 timeRemaining = self.targetTimestamp - block.timestamp;
51:
52:
53: currentLiquidationLTV = targetLiquidationLTV
54: + (currentLiquidationLTV - targetLiquidationLTV) * timeRemaining / self.rampDuration;
55: }
56:
57:
58: return ConfigAmount.wrap(uint16(currentLiquidationLTV));
59: }['75']
75: function clear(LTVConfig storage self) internal { // <= FOUND
76: self.borrowLTV = ConfigAmount.wrap(0);
77: self.liquidationLTV = ConfigAmount.wrap(0);
78: self.initialLiquidationLTV = ConfigAmount.wrap(0);
79: self.targetTimestamp = 0;
80: self.rampDuration = 0;
81: }['31']
31: function checkLiquidation(address liquidator, address violator, address collateral) // <= FOUND
32: public
33: view
34: virtual
35: nonReentrantView
36: returns (uint256 maxRepay, uint256 maxYield)
37: {
38: LiquidationCache memory liqCache =
39: calculateLiquidation(loadVault(), liquidator, violator, collateral, type(uint256).max);
40:
41: maxRepay = liqCache.repay.toUint();
42: maxYield = liqCache.yieldBalance;
43: }['46']
46: function liquidate(address violator, address collateral, uint256 repayAssets, uint256 minYieldBalance) // <= FOUND
47: public
48: virtual
49: nonReentrant
50: {
51: (VaultCache memory vaultCache, address liquidator) = initOperation(OP_LIQUIDATE, CHECKACCOUNT_CALLER);
52:
53: LiquidationCache memory liqCache =
54: calculateLiquidation(vaultCache, liquidator, violator, collateral, repayAssets);
55:
56: executeLiquidation(vaultCache, liqCache, minYieldBalance);
57: }['174']
174: function executeLiquidation(VaultCache memory vaultCache, LiquidationCache memory liqCache, uint256 minYieldBalance) // <= FOUND
175: private
176: {
177:
178:
179: if (minYieldBalance > liqCache.yieldBalance) revert E_MinYield();
180:
181:
182:
183: if (liqCache.repay.toUint() > 0) {
184: transferBorrow(vaultCache, liqCache.violator, liqCache.liquidator, liqCache.repay);
185: }
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201: if (liqCache.yieldBalance > 0) {
202: enforceCollateralTransfer(
203: liqCache.collateral, liqCache.yieldBalance, liqCache.violator, liqCache.liquidator
204: );
205:
206: forgiveAccountStatusCheck(liqCache.violator);
207: }
208:
209:
210:
211: if (
212: vaultCache.configFlags.isNotSet(CFG_DONT_SOCIALIZE_DEBT) && liqCache.liability > liqCache.repay
213: && checkNoCollateral(liqCache.violator, liqCache.collaterals)
214: ) {
215: Assets owedRemaining = liqCache.liability.subUnchecked(liqCache.repay);
216: decreaseBorrow(vaultCache, liqCache.violator, owedRemaining);
217:
218:
219:
220: emit Withdraw(liqCache.liquidator, address(0), address(0), owedRemaining.toUint(), 0);
221: emit DebtSocialized(liqCache.violator, owedRemaining.toUint());
222: }
223:
224: emit Liquidate(
225: liqCache.liquidator, liqCache.violator, liqCache.collateral, liqCache.repay.toUint(), liqCache.yieldBalance
226: );
227: }['61']
61: function checkNoCollateral(address account, address[] memory collaterals) internal view virtual returns (bool) { // <= FOUND
62: for (uint256 i; i < collaterals.length; ++i) {
63: address collateral = collaterals[i];
64:
65: if (!isRecognizedCollateral(collateral)) continue;
66:
67: if (IERC20(collateral).balanceOf(account) > 0) return false;
68: }
69:
70: return true;
71: }['19']
19: function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) // <= FOUND
20: internal
21: returns (bool, bytes memory)
22: {
23: (bool success, bytes memory data) = address(token).call(abi.encodeCall(IERC20.transferFrom, (from, to, value)));
24:
25: return isEmptyOrTrueReturn(success, data) ? (true, bytes("")) : (false, data);
26: }['45']
45: function safeTransfer(IERC20 token, address to, uint256 value) internal { // <= FOUND
46: (bool success, bytes memory data) = address(token).call(abi.encodeCall(IERC20.transfer, (to, value)));
47: if (!isEmptyOrTrueReturn(success, data)) RevertBytes.revertBytes(data);
48: }['33']
33: function reset(Snapshot storage self) internal { // <= FOUND
34: self.set(Assets.wrap(0), Assets.wrap(0));
35: }['37']
37: function totalSupply() public view virtual nonReentrantView returns (uint256) { // <= FOUND
38: return loadVault().totalShares.toUint();
39: }['29']
29: function totalAssets() public view virtual nonReentrantView returns (uint256) { // <= FOUND
30: VaultCache memory vaultCache = loadVault();
31: return totalAssetsInternal(vaultCache);
32: }['35']
35: function convertToAssets(uint256 shares) public view virtual nonReentrantView returns (uint256) { // <= FOUND
36: VaultCache memory vaultCache = loadVault();
37: return shares.toShares().toAssetsDown(vaultCache).toUint();
38: }['41']
41: function convertToShares(uint256 assets) public view virtual nonReentrantView returns (uint256) { // <= FOUND
42: VaultCache memory vaultCache = loadVault();
43: return assets.toAssets().toSharesDown(vaultCache).toUint();
44: }['59']
59: function previewDeposit(uint256 assets) public view virtual nonReentrantView returns (uint256) { // <= FOUND
60: return convertToShares(assets);
61: }['71']
71: function previewMint(uint256 shares) public view virtual nonReentrantView returns (uint256) { // <= FOUND
72: VaultCache memory vaultCache = loadVault();
73: return shares.toShares().toAssetsUp(vaultCache).toUint();
74: }['86']
86: function previewWithdraw(uint256 assets) public view virtual nonReentrantView returns (uint256) { // <= FOUND
87: VaultCache memory vaultCache = loadVault();
88: return assets.toAssets().toSharesUp(vaultCache).toUint();
89: }['102']
102: function previewRedeem(uint256 shares) public view virtual nonReentrantView returns (uint256) { // <= FOUND
103: return convertToAssets(shares);
104: }['107']
107: function accumulatedFees() public view virtual nonReentrantView returns (uint256) { // <= FOUND
108: return loadVault().accumulatedFees.toUint();
109: }['112']
112: function accumulatedFeesAssets() public view virtual nonReentrantView returns (uint256) { // <= FOUND
113: VaultCache memory vaultCache = loadVault();
114:
115: return vaultCache.accumulatedFees.toAssetsDown(vaultCache).toUint();
116: }