Number | Details | Instances |
---|---|---|
[Medium-1] | Privileged functions can create points of failure | 2 |
[Medium-2] | Non payable fallback function can break compatibility | 1 |
[Medium-3] | Arbitrary staking/deposit on a arbitrary token with an arbitrary amount has no checks to ensure token amount isn't type(uint256).max thus allowing wrong stake amount for certain tokens with custom logic such as cUSDCv3 | 3 |
Number | Details | Instances |
---|---|---|
[Low-1] | Gas grief possible on unsafe external calls | 5 |
[Low-2] | Code does not follow the best practice of check-effects-interaction | 7 |
[Low-3] | Solidity version 0.8.23 won't work on all chains due to MCOPY | 1 |
[Low-4] | Function call without checking or using it's return values | 6 |
[Low-5] | Some tokens may revert when zero value transfers are made | 12 |
[Low-6] | Withdraw/Redeem functions can fail due to blocked USDT/USDC accounts | 3 |
[Low-7] | Deposit mechanism can be DOS'd with dust deposits due to lack of minAmount checks against deposit amount | 2 |
[Low-8] | Int casting block.timestamp can reduce the lifespan of a contract | 5 |
[Low-9] | Empty receive functions can cause gas issues | 1 |
[Low-10] | The call abi.encodeWithSelector is not type safe | 4 |
[Low-11] | Using > when declaring solidity version without specifying an upperbound can cause future vulnerabilities | 2 |
[Low-12] | Call calls must have their return checked | 1 |
[Low-13] | Contracts are vulnerable to fee-on-transfer accounting-related issues | 7 |
[Low-14] | Use SafeCast to safely downcast variables | 18 |
[Low-15] | The nonReentrant modifier should be first in a function declaration | 4 |
[Low-16] | No limits when setting min/max amounts | 2 |
[Low-17] | Loss of precision | 8 |
[Low-18] | Missing zero address check in constructor | 2 |
[Low-19] | Off-by-one timestamp error | 12 |
[Low-20] | Avoid using msg.sig for validation | 1 |
[Low-21] | Function which returns data from a call has no validation for said data | 1 |
[Low-22] | Sending tokens in a for loop | 4 |
[Low-23] | Revert on Transfer to the Zero Address | 7 |
[Low-24] | No access control on receive/payable fallback | 1 |
[Low-25] | External call recipient may consume all transaction gas | 4 |
[Low-26] | Events may be emitted out of order due to code not follow the best practice of check-effects-interaction | 3 |
[Low-27] | Avoid mutating function parameters | 3 |
[Low-28] | Prefer skip over revert model in iteration | 4 |
[Low-29] | Missing contract-existence checks before low-level calls | 10 |
[Low-30] | Low Level Calls to Custom Addresses | 4 |
[Low-31] | No license selected for project | 1 |
[Low-32] | Inconsistent expiry logic using block global values | 1 |
[Low-33] | Constructors missing validation | 4 |
[Low-34] | Division in comparison | 1 |
[Low-35] | Contract contains payable functions but no withdraw/sweep function | 1 |
[Low-36] | Large transfers may not work with some ERC20 tokens | 15 |
[Low-37] | Functions calling contracts/addresses with transfer hooks are missing reentrancy guards | 22 |
[Low-38] | Bps variable not checked that they are below 1e4 | 2 |
[Low-39] | Missing events in functions that are either setters, privileged or voting related | 15 |
[Low-40] | Read only reentrancy risk detected | 16 |
[Low-41] | Common tokens such as WETH9 work differently on chains such a Blast which isn't taken into account during transfer calls. |
4 |
[Low-42] | Function designed to add elements to an array or toggle a isX mapping doesn't check if the element is already activated or the element already exists in the array |
1 |
[Low-43] | Transfers which take place within iteration do not compare the amounts[i] value against zero which can result in the transfer chain reverting |
4 |
[Low-44] | Consider a uptime feed on L2 deployments to prevent issues caused by downtime | 39 |
Number | Details | Instances |
---|---|---|
[NonCritical-1] | Local variable shadowing | 27 |
[NonCritical-2] | Using abi.encodePacked can result in hash collision when used in hashing functions | 4 |
[NonCritical-3] | Unchecked blocks with subtractions may underflow | 20 |
[NonCritical-4] | Getting a bool return value does not confirm the existence of a function in an external call | 6 |
[NonCritical-5] | Floating pragma should be avoided | 2 |
[NonCritical-6] | Interfaces should be declared in a separate file | 1 |
[NonCritical-7] | Events regarding state variable changes should emit the previous state variable value | 1 |
[NonCritical-8] | In functions which accept an address as a parameter, there should be a zero address check to prevent bugs | 85 |
[NonCritical-9] | Enum values should be used in place of constant array indexes | 2 |
[NonCritical-10] | Use safeTransferOwnership instead of transferOwnership | 2 |
[NonCritical-11] | Default int values are manually set | 5 |
[NonCritical-12] | Functions which are either private or internal should have a preceding _ in their name | 28 |
[NonCritical-13] | Private and internal state variables should have a preceding _ in their name unless they are constants | 2 |
[NonCritical-14] | Contract lines should not be longer than 120 characters for readability | 6 |
[NonCritical-15] | Old Solidity version | 2 |
[NonCritical-16] | Explicitly define visibility of functions to prevent misconceptions on what can access the function | 6 |
[NonCritical-17] | Contracts should have all public/external functions exposed by interfaces | 100 |
[NonCritical-18] | Functions within contracts are not ordered according to the solidity style guide | 1 |
[NonCritical-19] | Double type casts create complexity within the code | 2 |
[NonCritical-20] | Emits without msg.sender parameter | 7 |
[NonCritical-21] | Functions with array parameters should have length checks in place | 3 |
[NonCritical-22] | All interfaces used within a project should be imported | 5 |
[NonCritical-23] | A function which defines named returns in it's declaration doesn't need to use return | 24 |
[NonCritical-24] | Constants should be on the left side of the comparison | 8 |
[NonCritical-25] | Interface names should have an I as the first character | 2 |
[NonCritical-26] | Defined named returns not used within function | 7 |
[NonCritical-27] | Both immutable and constant state variables should be CONSTANT_CASE | 2 |
[NonCritical-28] | Overly complicated arithmetic | 1 |
[NonCritical-29] | Use of non-named numeric constants | 3 |
[NonCritical-30] | Utility contracts can be made into libraries | 3 |
[NonCritical-31] | Redundant else statement | 5 |
[NonCritical-32] | Unused structs present | 1 |
[NonCritical-33] | Empty bytes check is missing | 38 |
[NonCritical-34] | call bypasses function existence check, type checking and argument packing | 8 |
[NonCritical-35] | Cyclomatic complexity in functions | 4 |
[NonCritical-36] | Incorrect withdraw declaration | 1 |
[NonCritical-37] | Unused import | 1 |
[NonCritical-38] | Unchecked increments can overflow | 7 |
[NonCritical-39] | Unchecked decrements can underflow | 1 |
[NonCritical-40] | Ensure block.timestamp is only used in long time intervals | 3 |
[NonCritical-41] | Don't only depend on Solidity's arithmetic ordering. | 7 |
[NonCritical-42] | Contracts with only unimplemented functions can be labeled as abstract | 5 |
[NonCritical-43] | A event should be emitted if a non immutable state variable is set in a constructor | 1 |
[NonCritical-44] | Addition/multiplication in unchecked block is unsafe | 4 |
[NonCritical-45] | Numbers downcast to addresses may result in collisions | 4 |
[NonCritical-46] | Use transfer libraries instead of low level calls | 1 |
[NonCritical-47] | Function returns non-mutated named return variable | 1 |
[NonCritical-48] | Use 'using' keyword when using specific imports rather than calling the specific import directly | 1 |
[NonCritical-49] | Inconsistent checks of address params against address(0) | 4 |
[NonCritical-50] | Avoid declaring variables with the names of defined functions within the project | 9 |
[NonCritical-51] | Simplify complex require statements | 5 |
[NonCritical-52] | Constructors should emit an event | 11 |
[NonCritical-53] | Contract and Abstract files should have a fixed compiler version | 2 |
[NonCritical-54] | int/uint passed into abi.encodePacked without casting to a string. | 2 |
[NonCritical-55] | Avoid external calls in modifiers | 4 |
[NonCritical-56] | Errors should have parameters | 123 |
[NonCritical-57] | Consider using OpenZeppelins SafeCall library when making calls to arbitrary contracts | 4 |
[NonCritical-58] | Avoid arithmetic directly within array indices | 3 |
[NonCritical-59] | Modifier checks msg.sender against two addresses. Consider using multiple modifiers for more control | 2 |
[NonCritical-60] | Unnecessary struct attribute prefix | 6 |
[NonCritical-61] | ERC777 tokens can introduce reentrancy risks | 6 |
[NonCritical-62] | It is best practise to initialize a local variable when they are defined | 30 |
[NonCritical-63] | Avoid using nested struct mappings as in solidity versions prior to 5.0.0 produced incorrect values for these nested struct mappings |
1 |
Number | Details | Instances | Gas |
---|---|---|---|
[Gas-1] | The result of a function call should be cached rather than re-calling the function | 1 | 100 |
[Gas-2] | Avoid updating storage when the value hasn't changed | 3 | 7200 |
[Gas-3] | Public functions not used internally can be marked as external to save gas | 3 | 0.0 |
[Gas-4] | Usage of smaller uint/int types causes overhead | 86 | 406780 |
[Gas-5] | Use != 0 instead of > 0 | 10 | 300 |
[Gas-6] | Default bool values are manually reset | 11 | 0.0 |
[Gas-7] | Default int values are manually reset | 8 | 0.0 |
[Gas-8] | Function calls within for loops | 11 | 0.0 |
[Gas-9] | For loops in public or external functions should be avoided due to high gas costs and possible DOS | 8 | 0.0 |
[Gas-10] | Mappings used within a function more than once should be cached to save gas | 1 | 100 |
[Gas-11] | Use assembly to check for the zero address | 13 | 0.0 |
[Gas-12] | Some error strings are not descriptive | 77 | 0.0 |
[Gas-13] | Divisions which do not divide by -X cannot overflow or underflow so such operations can be unchecked to save gas | 2 | 0.0 |
[Gas-14] | Redundant state variable getters | 1 | 0.0 |
[Gas-15] | State variables which are not modified within functions should be set as constants or immutable for values set at deployment | 2 | 0.0 |
[Gas-16] | multiplications of powers of 2 can be replaced by a left shift operation to save gas | 1 | 0.0 |
[Gas-17] | Structs can be packed into fewer storage slots | 8 | 160000 |
[Gas-18] | Private functions used once can be inlined | 1 | 0.0 |
[Gas-19] | Consider using OZ EnumerateSet in place of nested mappings | 4 | 16000 |
[Gas-20] | Use assembly to emit events | 79 | 237158 |
[Gas-21] | Use solady library where possible to save gas | 2 | 4000 |
[Gas-22] | Use assembly in place of abi.decode to extract calldata values more efficiently | 5 | 0.0 |
[Gas-23] | Struct variables can be packed into fewer storage slots by truncating timestamp bytes | 6 | 90000 |
[Gas-24] | Using private rather than public for constants and immutables, saves gas | 1 | 0.0 |
[Gas-25] | Mark Functions That Revert For Normal Users As payable | 2 | 100 |
[Gas-26] | Lack of unchecked in loops | 18 | 23760 |
[Gas-27] | Where a value is casted more than once, consider caching the result to save gas | 7 | 0.0 |
[Gas-28] | Assembly let var only used on once | 4 | 0.0 |
[Gas-29] | Use assembly to validate msg.sender | 14 | 0.0 |
[Gas-30] | Simple checks for zero uint can be done using assembly to save gas | 29 | 5046 |
[Gas-31] | Using nested if to save gas | 7 | 294 |
[Gas-32] | Optimize Storage with Byte Truncation for Time Related State Variables | 1 | 2000 |
[Gas-33] | Using delete instead of setting struct to 0 saves gas | 1 | 5 |
[Gas-34] | Stack variable cost less than state variables while used in emiting event | 5 | 225 |
[Gas-35] | Low level call can be optimized with assembly | 19 | 89528 |
[Gas-36] | Inline modifiers used only once | 4 | 0.0 |
[Gas-37] | Use s.x = s.x + y instead of s.x += y for memory structs (same for -= etc) | 2 | 400 |
[Gas-38] | Solidity versions 0.8.19 and above are more gas efficient | 2 | 4000 |
[Gas-39] | Internal functions only used once can be inlined to save gas | 42 | 52920 |
[Gas-40] | Constructors can be marked as payable to save deployment gas | 18 | 0.0 |
[Gas-41] | Assigning to structs can be more efficient | 5 | 3250 |
[Gas-42] | Internal functions never used once can be removed | 9 | 0.0 |
[Gas-43] | Only emit event in setter function if the state variable was changed | 11 | 0.0 |
[Gas-44] | It is a waste of GAS to emit variable literals | 2 | 32 |
[Gas-45] | Short circuit before external calls | 1 | 0.0 |
[Gas-46] | Use OZ Array.unsafeAccess() to avoid repeated array length checks | 14 | 411600 |
[Gas-47] | Use uint256(1)/uint256(2) instead of true/false to save gas for changes | 18 | 5508000 |
[Gas-48] | Avoid emitting events in loops | 4 | 6000 |
[Gas-49] | Consider pre-calculating the address of address(this) to save gas | 22 | 0.0 |
[Gas-50] | Use 'storage' instead of 'memory' for struct/array state variables | 1 | 2100 |
[Gas-51] | Empty blocks should be removed or emit something | 5 | 0.0 |
[Gas-52] | Use constants instead of type(uint).max | 1 | 0.0 |
[Gas-53] | Using named returns for pure and view functions is cheaper than using regular returns | 88 | 201344 |
Ensure such accounts are protected and consider implementing multi sig to prevent a single point of failure
Num of instances: 2
Click to show findings
['52']
52: function transferOwnership(address newOwner) public virtual override onlyOwner // <= FOUND
['30']
30: function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) // <= FOUND
Reason: In Solidity, a defined fallback function that is not declared as payable can cause compatibility issues when value is transferred with incorrect data. If the contract is meant to receive Ether but the fallback function isn't payable, the contract will reject the transaction, resulting in a failed operation. This could lead to unintentional loss of funds or incompatible interactions with other contracts.
Resolution: To ensure broad compatibility and safe receipt of Ether, the fallback function should be declared as payable. This allows the contract to accept Ether transfers, regardless of the data sent along with the transaction. However, it's important to note that this should be complemented by proper checks and controls in the contract to avoid potential attacks or misuse. Always exercise caution when dealing with payable functions, and consider using the latest Solidity version to take advantage of the receive() function for explicit Ether transfers.
Num of instances: 1
Click to show findings
['62']
62: fallback(bytes calldata) external returns (bytes memory returnValue) {
63: (address caller, bytes4 selector, uint16 userDataOffset) = _getAllowedCallback();
64:
65: require(msg.sig == selector, Aera__UnauthorizedCallback());
66:
67: require(msg.sender == caller, Aera__UnauthorizedCallback());
68:
69: bytes32 root = _getAllowedMerkleRoot();
70:
71: require(root != bytes32(0), Aera__UnauthorizedCallback());
72:
73:
74: if (userDataOffset == NO_CALLBACK_DATA) return bytes("");
75:
76:
77: returnValue = _handleCallbackOperations(root, userDataOffset);
78: }
[Medium-3] Arbitrary staking/deposit on a arbitrary token with an arbitrary amount has no checks to ensure token amount isn't type(uint256).max thus allowing wrong stake amount for certain tokens with custom logic such as cUSDCv3
Certain tokens such as cUSDCv3 have transfer logic where when a transfer takes place their transfer functionality checks if the 'amount' to be transferred is type(uint256).max, if this is the case the balance of the sender is transferred. So if a user has a dust amount cUSDCv3 attempts to stake/deposit amount 'type(uint256).max' in this protocol, the actual transfer amount will be procesed as the user's total balance of that token, not type(uint256).max. Thus the staking function will register/queue the stake as type(uint256).max but in actuality only cUSDCv3.balanceOf(msg.sender) will have been transferred. This can cause serious discrepancies with the protocols intended logic. To remedy this, there can be a check to prevent users from passing type(uint256).max as the stake/deposit amount.
Num of instances: 3
Click to show findings
['180']
180: function requestDeposit( // <= FOUND
181: IERC20 token,
182: uint256 tokensIn,
183: uint256 minUnitsOut,
184: uint256 solverTip,
185: uint256 deadline,
186: uint256 maxPriceAge,
187: bool isFixedPrice
188: ) external anyoneButVault {
189:
190:
191: require(tokensIn != 0, Aera__TokensInZero());
192: require(minUnitsOut != 0, Aera__MinUnitsOutZero());
193: require(deadline > block.timestamp, Aera__DeadlineInPast());
194: unchecked {
195: require(deadline - block.timestamp <= MAX_SECONDS_TO_DEADLINE, Aera__DeadlineTooFarInFuture());
196: }
197: require(tokensDetails[token].asyncDepositEnabled, Aera__AsyncDepositDisabled());
198: require(!PRICE_FEE_CALCULATOR.isVaultPaused(MULTI_DEPOSITOR_VAULT), Aera__PriceAndFeeCalculatorVaultPaused());
199: require(solverTip == 0 || !isFixedPrice, Aera__FixedPriceSolverTipNotAllowed());
200:
201:
202: token.safeTransferFrom(msg.sender, address(this), tokensIn);
203:
204: RequestType requestType = isFixedPrice ? RequestType.DEPOSIT_FIXED_PRICE : RequestType.DEPOSIT_AUTO_PRICE;
205:
206: bytes32 depositHash = _getRequestHashParams(
207: token, msg.sender, requestType, tokensIn, minUnitsOut, solverTip, deadline, maxPriceAge
208: );
209:
210: require(!asyncDepositHashes[depositHash], Aera__HashCollision());
211:
212: asyncDepositHashes[depositHash] = true;
213:
214:
215: emit DepositRequested(
216: msg.sender, token, tokensIn, minUnitsOut, solverTip, deadline, maxPriceAge, isFixedPrice, depositHash
217: );
218: }
['65']
65: function requestSell( // <= FOUND
66: uint256 sellAmount,
67: IERC20 sellToken,
68: IERC20 receiveToken,
69: bytes32 appData,
70: address priceChecker,
71: bytes calldata priceCheckerData
72: ) external onlyVault nonReentrant {
73:
74: require(sellAmount > 0, AeraPeriphery__MilkmanRouter__SellAmountIsZero());
75:
76:
77: sellToken.safeTransferFrom(msg.sender, address(this), sellAmount);
78:
79:
80: sellToken.safeIncreaseAllowance(address(milkmanRoot), sellAmount);
81:
82:
83: milkmanRoot.requestSwapExactTokensForTokens(
84: sellAmount, sellToken, receiveToken, address(this), appData, priceChecker, priceCheckerData
85: );
86:
87:
88:
89: require(
90: sellToken.allowance(address(this), address(milkmanRoot)) == 0,
91: AeraPeriphery__MilkmanRequestSwapExactTokensForTokensFailed(sellToken)
92: );
93:
94:
95: emit SellRequested(sellAmount, sellToken, receiveToken, appData, priceChecker, priceCheckerData);
96: }
['221']
221: function requestRedeem( // <= FOUND
222: IERC20 token,
223: uint256 unitsIn,
224: uint256 minTokensOut,
225: uint256 solverTip,
226: uint256 deadline,
227: uint256 maxPriceAge,
228: bool isFixedPrice
229: ) external anyoneButVault {
230:
231:
232: require(unitsIn != 0, Aera__UnitsInZero());
233: require(minTokensOut != 0, Aera__MinTokenOutZero());
234: require(deadline > block.timestamp, Aera__DeadlineInPast());
235: unchecked {
236: require(deadline - block.timestamp <= MAX_SECONDS_TO_DEADLINE, Aera__DeadlineTooFarInFuture());
237: }
238: require(tokensDetails[token].asyncRedeemEnabled, Aera__AsyncRedeemDisabled());
239: require(!PRICE_FEE_CALCULATOR.isVaultPaused(MULTI_DEPOSITOR_VAULT), Aera__PriceAndFeeCalculatorVaultPaused());
240: require(solverTip == 0 || !isFixedPrice, Aera__FixedPriceSolverTipNotAllowed());
241:
242:
243: IERC20(MULTI_DEPOSITOR_VAULT).safeTransferFrom(msg.sender, address(this), unitsIn);
244:
245: RequestType requestType = isFixedPrice ? RequestType.REDEEM_FIXED_PRICE : RequestType.REDEEM_AUTO_PRICE;
246:
247: bytes32 redeemHash = _getRequestHashParams(
248: token, msg.sender, requestType, minTokensOut, unitsIn, solverTip, deadline, maxPriceAge
249: );
250:
251: require(!asyncRedeemHashes[redeemHash], Aera__HashCollision());
252:
253: asyncRedeemHashes[redeemHash] = true;
254:
255:
256: emit RedeemRequested(
257: msg.sender, token, minTokensOut, unitsIn, solverTip, deadline, maxPriceAge, isFixedPrice, redeemHash
258: );
259: }
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: 5
Click to show findings
['278']
278: function _beforeSubmitHooks(address hooks, bytes calldata data) internal { // <= FOUND
279: if (_hasBeforeHooks(hooks)) { // <= FOUND
280:
281: (bool success, bytes memory result) =
282: hooks.call(abi.encodeWithSelector(ISubmitHooks.beforeSubmit.selector, data, msg.sender));
283:
284: require(success, Aera__BeforeSubmitHooksFailed(result));
285: }
286: }
['291']
291: function _afterSubmitHooks(address hooks, bytes calldata data) internal { // <= FOUND
292: if (_hasAfterHooks(hooks)) { // <= FOUND
293:
294: (bool success, bytes memory result) =
295: hooks.call(abi.encodeWithSelector(ISubmitHooks.afterSubmit.selector, data, msg.sender));
296:
297: require(success, Aera__AfterSubmitHooksFailed(result));
298: }
299: }
['306']
306: function _beforeOperationHooks(address operationHooks, bytes memory data, uint256 i) // <= FOUND
307: internal
308: returns (bytes memory result)
309: {
310: if (_hasBeforeHooks(operationHooks)) {
311:
312: _setHookCallType(HookCallType.BEFORE);
313:
314:
315: (bool success, bytes memory returnValue) = operationHooks.call(data); // <= FOUND
316:
317: require(success, Aera__BeforeOperationHooksFailed(i, returnValue));
318:
319:
320: require(returnValue.length % WORD_SIZE == 0, Aera__InvalidBeforeOperationHooksReturnDataLength());
321:
322:
323: _setHookCallType(HookCallType.NONE);
324:
325:
326:
327: (result) = abi.decode(returnValue, (bytes));
328: }
329: }
['335']
335: function _afterOperationHooks(address operationHooks, bytes memory data, uint256 i) internal { // <= FOUND
336: if (_hasAfterHooks(operationHooks)) {
337:
338: _setHookCallType(HookCallType.AFTER);
339:
340:
341: (bool success, bytes memory result) = operationHooks.call(data); // <= FOUND
342:
343: require(success, Aera__AfterOperationHooksFailed(i, result));
344:
345:
346: _setHookCallType(HookCallType.NONE);
347: }
348: }
['34']
34: function execute(TargetCalldata[] calldata operations) external nonReentrant { // <= FOUND
35: address target;
36: bytes memory data;
37: bytes32 targetAndSelector;
38: mapping(bytes32 targetAndSelector => bool enabled) storage callerCapabilities = _canCall[msg.sender];
39:
40: uint256 length = operations.length;
41: for (uint256 i; i < length; ++i) {
42: target = operations[i].target;
43: data = operations[i].data;
44: targetAndSelector = _packTargetSig(target, bytes4(data));
45:
46:
47: require(
48: callerCapabilities[targetAndSelector], AeraPeriphery__Unauthorized(msg.sender, target, bytes4(data))
49: );
50:
51:
52: (bool success, bytes memory returnData) = target.call(data); // <= FOUND
53: Address.verifyCallResultFromTarget(target, success, returnData);
54: }
55:
56:
57: emit Executed(msg.sender, operations);
58: }
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: 7
Click to show findings
['514']
514: function _solveDepositVaultAutoPrice(
515: IERC20 token,
516: uint256 depositMultiplier,
517: Request calldata request,
518: uint256 priceAge,
519: uint256 index
520: ) internal returns (uint256 solverTip) {
521:
522: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
523:
524: bytes32 depositHash = _getRequestHash(token, request);
525:
526: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0;
527:
528: if (request.deadline >= block.timestamp) {
529: solverTip = request.solverTip;
530: uint256 tokens = request.tokens;
531:
532:
533: if (_guardInsufficientTokensForTip(tokens, solverTip, index)) return 0;
534:
535: uint256 tokensAfterTip;
536: unchecked {
537: tokensAfterTip = tokens - solverTip;
538: }
539:
540:
541: uint256 unitsOut = _tokensToUnitsFloorIfActive(token, tokensAfterTip, depositMultiplier);
542:
543: if (_guardAmountBound(unitsOut, request.units, index)) return 0;
544:
545: if (_guardDepositCapExceeded(unitsOut, index)) return 0;
546:
547:
548: asyncDepositHashes[depositHash] = false;
549:
550: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter( // <= FOUND
551: address(this), token, tokensAfterTip, unitsOut, request.user
552: );
553:
554:
555: emit DepositSolved(depositHash);
556: } else {
557:
558: asyncDepositHashes[depositHash] = false;
559:
560: token.safeTransfer(request.user, request.tokens);
561:
562: emit DepositRefunded(depositHash);
563: }
564: }
['583']
583: function _solveDepositVaultFixedPrice(
584: IERC20 token,
585: uint256 depositMultiplier,
586: Request calldata request,
587: uint256 priceAge,
588: uint256 index
589: ) internal returns (uint256 solverTip) {
590:
591: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
592:
593: bytes32 depositHash = _getRequestHash(token, request);
594:
595: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0;
596:
597: if (request.deadline >= block.timestamp) {
598:
599: uint256 tokensNeeded = _unitsToTokensCeilIfActive(token, request.units, depositMultiplier);
600:
601: if (_guardAmountBound(request.tokens, tokensNeeded, index)) return 0;
602:
603: if (_guardDepositCapExceeded(request.units, index)) return 0;
604:
605:
606: asyncDepositHashes[depositHash] = false;
607:
608: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter( // <= FOUND
609: address(this), token, tokensNeeded, request.units, request.user
610: );
611:
612: unchecked {
613: solverTip = request.tokens - tokensNeeded;
614: }
615:
616:
617: emit DepositSolved(depositHash);
618: } else {
619:
620: asyncDepositHashes[depositHash] = false;
621:
622: token.safeTransfer(request.user, request.tokens);
623:
624: emit DepositRefunded(depositHash);
625: }
626: }
['643']
643: function _solveRedeemVaultAutoPrice(
644: IERC20 token,
645: uint256 redeemMultiplier,
646: Request calldata request,
647: uint256 priceAge,
648: uint256 index
649: ) internal returns (uint256 solverTip) {
650:
651: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
652:
653: bytes32 redeemHash = _getRequestHash(token, request);
654:
655: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0;
656:
657: if (request.deadline >= block.timestamp) {
658: solverTip = request.solverTip;
659:
660:
661: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
662:
663: if (_guardInsufficientTokensForTip(tokenOut, solverTip, index)) return 0;
664:
665: uint256 tokenOutAfterTip;
666: unchecked {
667: tokenOutAfterTip = tokenOut - solverTip;
668: }
669:
670:
671: if (_guardAmountBound(tokenOutAfterTip, request.tokens, index)) return 0;
672:
673:
674: asyncRedeemHashes[redeemHash] = false;
675:
676: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit( // <= FOUND
677: address(this), token, tokenOut, request.units, address(this)
678: );
679:
680:
681: token.safeTransfer(request.user, tokenOutAfterTip);
682:
683:
684: emit RedeemSolved(redeemHash);
685: } else {
686:
687: asyncRedeemHashes[redeemHash] = false;
688:
689: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
690:
691: emit RedeemRefunded(redeemHash);
692: }
693: }
['710']
710: function _solveRedeemVaultFixedPrice(
711: IERC20 token,
712: uint256 redeemMultiplier,
713: Request calldata request,
714: uint256 priceAge,
715: uint256 index
716: ) internal returns (uint256 solverTip) {
717:
718: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
719:
720: bytes32 redeemHash = _getRequestHash(token, request);
721:
722: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0;
723:
724: if (request.deadline >= block.timestamp) {
725:
726: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
727:
728: if (_guardAmountBound(tokenOut, request.tokens, index)) return 0;
729:
730:
731: asyncRedeemHashes[redeemHash] = false;
732:
733: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit( // <= FOUND
734: address(this), token, tokenOut, request.units, address(this)
735: );
736:
737: token.safeTransfer(request.user, request.tokens);
738:
739: unchecked {
740: solverTip = tokenOut - request.tokens;
741: }
742:
743:
744: emit RedeemSolved(redeemHash);
745: } else {
746:
747: asyncRedeemHashes[redeemHash] = false;
748:
749: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
750:
751: emit RedeemRefunded(redeemHash);
752: }
753: }
['180']
180: function requestDeposit(
181: IERC20 token,
182: uint256 tokensIn,
183: uint256 minUnitsOut,
184: uint256 solverTip,
185: uint256 deadline,
186: uint256 maxPriceAge,
187: bool isFixedPrice
188: ) external anyoneButVault {
189:
190:
191: require(tokensIn != 0, Aera__TokensInZero());
192: require(minUnitsOut != 0, Aera__MinUnitsOutZero());
193: require(deadline > block.timestamp, Aera__DeadlineInPast());
194: unchecked {
195: require(deadline - block.timestamp <= MAX_SECONDS_TO_DEADLINE, Aera__DeadlineTooFarInFuture());
196: }
197: require(tokensDetails[token].asyncDepositEnabled, Aera__AsyncDepositDisabled());
198: require(!PRICE_FEE_CALCULATOR.isVaultPaused(MULTI_DEPOSITOR_VAULT), Aera__PriceAndFeeCalculatorVaultPaused()); // <= FOUND
199: require(solverTip == 0 || !isFixedPrice, Aera__FixedPriceSolverTipNotAllowed());
200:
201:
202: token.safeTransferFrom(msg.sender, address(this), tokensIn);
203:
204: RequestType requestType = isFixedPrice ? RequestType.DEPOSIT_FIXED_PRICE : RequestType.DEPOSIT_AUTO_PRICE;
205:
206: bytes32 depositHash = _getRequestHashParams(
207: token, msg.sender, requestType, tokensIn, minUnitsOut, solverTip, deadline, maxPriceAge
208: );
209:
210: require(!asyncDepositHashes[depositHash], Aera__HashCollision());
211:
212: asyncDepositHashes[depositHash] = true;
213:
214:
215: emit DepositRequested(
216: msg.sender, token, tokensIn, minUnitsOut, solverTip, deadline, maxPriceAge, isFixedPrice, depositHash
217: );
218: }
['221']
221: function requestRedeem(
222: IERC20 token,
223: uint256 unitsIn,
224: uint256 minTokensOut,
225: uint256 solverTip,
226: uint256 deadline,
227: uint256 maxPriceAge,
228: bool isFixedPrice
229: ) external anyoneButVault {
230:
231:
232: require(unitsIn != 0, Aera__UnitsInZero());
233: require(minTokensOut != 0, Aera__MinTokenOutZero());
234: require(deadline > block.timestamp, Aera__DeadlineInPast());
235: unchecked {
236: require(deadline - block.timestamp <= MAX_SECONDS_TO_DEADLINE, Aera__DeadlineTooFarInFuture());
237: }
238: require(tokensDetails[token].asyncRedeemEnabled, Aera__AsyncRedeemDisabled());
239: require(!PRICE_FEE_CALCULATOR.isVaultPaused(MULTI_DEPOSITOR_VAULT), Aera__PriceAndFeeCalculatorVaultPaused()); // <= FOUND
240: require(solverTip == 0 || !isFixedPrice, Aera__FixedPriceSolverTipNotAllowed());
241:
242:
243: IERC20(MULTI_DEPOSITOR_VAULT).safeTransferFrom(msg.sender, address(this), unitsIn);
244:
245: RequestType requestType = isFixedPrice ? RequestType.REDEEM_FIXED_PRICE : RequestType.REDEEM_AUTO_PRICE;
246:
247: bytes32 redeemHash = _getRequestHashParams(
248: token, msg.sender, requestType, minTokensOut, unitsIn, solverTip, deadline, maxPriceAge
249: );
250:
251: require(!asyncRedeemHashes[redeemHash], Aera__HashCollision());
252:
253: asyncRedeemHashes[redeemHash] = true;
254:
255:
256: emit RedeemRequested(
257: msg.sender, token, minTokensOut, unitsIn, solverTip, deadline, maxPriceAge, isFixedPrice, redeemHash
258: );
259: }
['38']
38: function setAuthority(Authority newAuthority) public virtual {
39:
40:
41: require(msg.sender == owner || authority.canCall(msg.sender, address(this), msg.sig)); // <= FOUND
42:
43: authority = newAuthority; // <= FOUND
44:
45: emit AuthorityUpdated(msg.sender, newAuthority);
46: }
Solidity version 0.8.23 introduces the MCOPY opcode, this may not be implemented on all chains and L2 thus reducing the portability and compatibility of the code. Consider using a earlier solidity version.
Num of instances: 1
Calling a function which has returns without capturing said returns can indicate poor validation as it is standard to check function returns to ensure proper operation of the function call, especially when a bool return is involved as this is a clear indication that a silent revert may have been missed i.e the function returns false rather than reverting
Num of instances: 6
Click to show findings
['61']
61: function submitSnapshot(address vault, uint160 averageValue, uint128 highestProfit, uint32 timestamp) // <= FOUND
62: external
63: onlyVaultAccountant(vault)
64: {
65:
66: require(timestamp <= block.timestamp, Aera__SnapshotInFuture());
67:
68: VaultSnapshot storage vaultSnapshot = _vaultSnapshots[vault];
69: VaultAccruals storage vaultAccruals = _vaultAccruals[vault];
70:
71: uint256 lastFeeAccrualCached = vaultSnapshot.lastFeeAccrual;
72: require(lastFeeAccrualCached != 0, Aera__VaultNotRegistered());
73:
74:
75: _accrueFees(vaultSnapshot, vaultAccruals, lastFeeAccrualCached); // <= FOUND
76:
77:
78: require(timestamp > vaultSnapshot.lastFeeAccrual, Aera__SnapshotTooOld());
79:
80: require(vaultSnapshot.lastHighestProfit <= highestProfit, Aera__HighestProfitDecreased());
81:
82:
83: vaultSnapshot.timestamp = timestamp;
84: unchecked {
85:
86: vaultSnapshot.finalizedAt = uint32(block.timestamp + DISPUTE_PERIOD);
87: }
88: vaultSnapshot.averageValue = averageValue;
89: vaultSnapshot.highestProfit = highestProfit;
90:
91:
92: emit SnapshotSubmitted(vault, averageValue, highestProfit, timestamp);
93: }
['147']
147: function _beforeClaimFees() internal override { // <= FOUND
148: VaultSnapshot storage vaultSnapshot = _vaultSnapshots[msg.sender];
149:
150: _accrueFees(vaultSnapshot, _vaultAccruals[msg.sender], vaultSnapshot.lastFeeAccrual); // <= FOUND
151: }
['154']
154: function _beforeClaimProtocolFees() internal override { // <= FOUND
155: VaultSnapshot storage vaultSnapshot = _vaultSnapshots[msg.sender];
156:
157: _accrueFees(vaultSnapshot, _vaultAccruals[msg.sender], vaultSnapshot.lastFeeAccrual); // <= FOUND
158: }
['156']
156: function setUnitPrice(address vault, uint128 price, uint32 timestamp) external onlyVaultAccountant(vault) { // <= FOUND
157: VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
158:
159:
160: _validatePriceUpdate(vaultPriceState, price, timestamp);
161:
162: if (!vaultPriceState.paused) {
163: if (_shouldPause(vaultPriceState, price, timestamp)) {
164:
165: _setVaultPaused(vaultPriceState, vault, true);
166:
167: unchecked {
168:
169: vaultPriceState.accrualLag = uint24(timestamp - vaultPriceState.timestamp);
170: }
171: } else {
172:
173: _accrueFees(vault, price, timestamp); // <= FOUND
174: }
175: } else {
176:
177: unchecked {
178:
179: vaultPriceState.accrualLag = uint24(timestamp - vaultPriceState.timestamp + vaultPriceState.accrualLag);
180: }
181: }
182:
183:
184: vaultPriceState.unitPrice = price;
185: vaultPriceState.timestamp = timestamp;
186:
187:
188: emit UnitPriceUpdated(vault, price, timestamp);
189: }
['203']
203: function unpauseVault(address vault, uint128 price, uint32 timestamp) external requiresVaultAuth(vault) { // <= FOUND
204: VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
205:
206:
207: require(vaultPriceState.paused, Aera__VaultNotPaused());
208: require(vaultPriceState.unitPrice == price, Aera__UnitPriceMismatch());
209: require(vaultPriceState.timestamp == timestamp, Aera__TimestampMismatch());
210:
211:
212: _accrueFees(vault, price, timestamp); // <= FOUND
213:
214:
215: _setVaultPaused(vaultPriceState, vault, false);
216: }
['294']
294: function solveRequestsVault(IERC20 token, Request[] calldata requests) external requiresAuth nonReentrant { // <= FOUND
295:
296: uint256 priceAge = PRICE_FEE_CALCULATOR.getVaultsPriceAge(MULTI_DEPOSITOR_VAULT);
297:
298: uint256 solverTip;
299: Request calldata request;
300:
301: uint256 length = requests.length;
302: TokenDetails memory tokenDetails = tokensDetails[token];
303: bool depositsExist;
304: for (uint256 i = 0; i < length; i++) {
305: request = requests[i];
306: if (_isRequestTypeDeposit(request.requestType)) {
307:
308: if (!tokenDetails.asyncDepositEnabled) {
309:
310: emit AsyncDepositDisabled(i);
311: continue;
312: }
313:
314: if (!depositsExist) {
315: depositsExist = true;
316: token.forceApprove(MULTI_DEPOSITOR_VAULT, type(uint256).max);
317: }
318:
319: if (_isRequestTypeAutoPrice(request.requestType)) {
320:
321: solverTip += // <= FOUND
322: _solveDepositVaultAutoPrice(token, tokenDetails.depositMultiplier, request, priceAge, i);
323: } else {
324:
325: solverTip += // <= FOUND
326: _solveDepositVaultFixedPrice(token, tokenDetails.depositMultiplier, request, priceAge, i);
327: }
328: } else {
329:
330: if (!tokenDetails.asyncRedeemEnabled) {
331:
332: emit AsyncRedeemDisabled(i);
333: continue;
334: }
335:
336: if (_isRequestTypeAutoPrice(request.requestType)) {
337:
338: solverTip += _solveRedeemVaultAutoPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i); // <= FOUND
339: } else {
340:
341: solverTip += _solveRedeemVaultFixedPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i); // <= FOUND
342: }
343: }
344: }
345:
346: if (solverTip != 0) {
347:
348: token.safeTransfer(msg.sender, solverTip);
349: }
350:
351: if (depositsExist) {
352:
353: token.forceApprove(MULTI_DEPOSITOR_VAULT, 0);
354: }
355: }
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: 12
Click to show findings
['262']
262: function refundRequest(IERC20 token, Request calldata request) external nonReentrant { // <= FOUND
263:
264: require(
265: request.deadline < block.timestamp || isAuthorized(msg.sender, msg.sig),
266: Aera__DeadlineInFutureAndUnauthorized()
267: );
268:
269: bytes32 requestHash = _getRequestHash(token, request);
270:
271: if (_isRequestTypeDeposit(request.requestType)) {
272:
273: require(asyncDepositHashes[requestHash], Aera__HashNotFound());
274:
275: asyncDepositHashes[requestHash] = false;
276:
277: token.safeTransfer(request.user, request.tokens); // <= FOUND
278:
279: emit DepositRefunded(requestHash);
280: } else {
281:
282: require(asyncRedeemHashes[requestHash], Aera__HashNotFound());
283:
284: asyncRedeemHashes[requestHash] = false;
285:
286: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
287:
288: emit RedeemRefunded(requestHash);
289: }
290: }
['294']
294: function solveRequestsVault(IERC20 token, Request[] calldata requests) external requiresAuth nonReentrant { // <= FOUND
295:
296: uint256 priceAge = PRICE_FEE_CALCULATOR.getVaultsPriceAge(MULTI_DEPOSITOR_VAULT);
297:
298: uint256 solverTip;
299: Request calldata request;
300:
301: uint256 length = requests.length;
302: TokenDetails memory tokenDetails = tokensDetails[token];
303: bool depositsExist;
304: for (uint256 i = 0; i < length; i++) {
305: request = requests[i];
306: if (_isRequestTypeDeposit(request.requestType)) {
307:
308: if (!tokenDetails.asyncDepositEnabled) {
309:
310: emit AsyncDepositDisabled(i);
311: continue;
312: }
313:
314: if (!depositsExist) {
315: depositsExist = true;
316: token.forceApprove(MULTI_DEPOSITOR_VAULT, type(uint256).max);
317: }
318:
319: if (_isRequestTypeAutoPrice(request.requestType)) {
320:
321: solverTip +=
322: _solveDepositVaultAutoPrice(token, tokenDetails.depositMultiplier, request, priceAge, i);
323: } else {
324:
325: solverTip +=
326: _solveDepositVaultFixedPrice(token, tokenDetails.depositMultiplier, request, priceAge, i);
327: }
328: } else {
329:
330: if (!tokenDetails.asyncRedeemEnabled) {
331:
332: emit AsyncRedeemDisabled(i);
333: continue;
334: }
335:
336: if (_isRequestTypeAutoPrice(request.requestType)) {
337:
338: solverTip += _solveRedeemVaultAutoPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i);
339: } else {
340:
341: solverTip += _solveRedeemVaultFixedPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i);
342: }
343: }
344: }
345:
346: if (solverTip != 0) {
347:
348: token.safeTransfer(msg.sender, solverTip); // <= FOUND
349: }
350:
351: if (depositsExist) {
352:
353: token.forceApprove(MULTI_DEPOSITOR_VAULT, 0);
354: }
355: }
['514']
514: function _solveDepositVaultAutoPrice( // <= FOUND
515: IERC20 token,
516: uint256 depositMultiplier,
517: Request calldata request,
518: uint256 priceAge,
519: uint256 index
520: ) internal returns (uint256 solverTip) {
521:
522: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
523:
524: bytes32 depositHash = _getRequestHash(token, request);
525:
526: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0;
527:
528: if (request.deadline >= block.timestamp) {
529: solverTip = request.solverTip;
530: uint256 tokens = request.tokens;
531:
532:
533: if (_guardInsufficientTokensForTip(tokens, solverTip, index)) return 0;
534:
535: uint256 tokensAfterTip;
536: unchecked {
537: tokensAfterTip = tokens - solverTip;
538: }
539:
540:
541: uint256 unitsOut = _tokensToUnitsFloorIfActive(token, tokensAfterTip, depositMultiplier);
542:
543: if (_guardAmountBound(unitsOut, request.units, index)) return 0;
544:
545: if (_guardDepositCapExceeded(unitsOut, index)) return 0;
546:
547:
548: asyncDepositHashes[depositHash] = false;
549:
550: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter(
551: address(this), token, tokensAfterTip, unitsOut, request.user
552: );
553:
554:
555: emit DepositSolved(depositHash);
556: } else {
557:
558: asyncDepositHashes[depositHash] = false;
559:
560: token.safeTransfer(request.user, request.tokens); // <= FOUND
561:
562: emit DepositRefunded(depositHash);
563: }
564: }
['583']
583: function _solveDepositVaultFixedPrice( // <= FOUND
584: IERC20 token,
585: uint256 depositMultiplier,
586: Request calldata request,
587: uint256 priceAge,
588: uint256 index
589: ) internal returns (uint256 solverTip) {
590:
591: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
592:
593: bytes32 depositHash = _getRequestHash(token, request);
594:
595: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0;
596:
597: if (request.deadline >= block.timestamp) {
598:
599: uint256 tokensNeeded = _unitsToTokensCeilIfActive(token, request.units, depositMultiplier);
600:
601: if (_guardAmountBound(request.tokens, tokensNeeded, index)) return 0;
602:
603: if (_guardDepositCapExceeded(request.units, index)) return 0;
604:
605:
606: asyncDepositHashes[depositHash] = false;
607:
608: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter(
609: address(this), token, tokensNeeded, request.units, request.user
610: );
611:
612: unchecked {
613: solverTip = request.tokens - tokensNeeded;
614: }
615:
616:
617: emit DepositSolved(depositHash);
618: } else {
619:
620: asyncDepositHashes[depositHash] = false;
621:
622: token.safeTransfer(request.user, request.tokens); // <= FOUND
623:
624: emit DepositRefunded(depositHash);
625: }
626: }
['643']
643: function _solveRedeemVaultAutoPrice( // <= FOUND
644: IERC20 token,
645: uint256 redeemMultiplier,
646: Request calldata request,
647: uint256 priceAge,
648: uint256 index
649: ) internal returns (uint256 solverTip) {
650:
651: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
652:
653: bytes32 redeemHash = _getRequestHash(token, request);
654:
655: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0;
656:
657: if (request.deadline >= block.timestamp) {
658: solverTip = request.solverTip;
659:
660:
661: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
662:
663: if (_guardInsufficientTokensForTip(tokenOut, solverTip, index)) return 0;
664:
665: uint256 tokenOutAfterTip;
666: unchecked {
667: tokenOutAfterTip = tokenOut - solverTip;
668: }
669:
670:
671: if (_guardAmountBound(tokenOutAfterTip, request.tokens, index)) return 0;
672:
673:
674: asyncRedeemHashes[redeemHash] = false;
675:
676: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit(
677: address(this), token, tokenOut, request.units, address(this)
678: );
679:
680:
681: token.safeTransfer(request.user, tokenOutAfterTip); // <= FOUND
682:
683:
684: emit RedeemSolved(redeemHash);
685: } else {
686:
687: asyncRedeemHashes[redeemHash] = false;
688:
689: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
690:
691: emit RedeemRefunded(redeemHash);
692: }
693: }
['710']
710: function _solveRedeemVaultFixedPrice( // <= FOUND
711: IERC20 token,
712: uint256 redeemMultiplier,
713: Request calldata request,
714: uint256 priceAge,
715: uint256 index
716: ) internal returns (uint256 solverTip) {
717:
718: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
719:
720: bytes32 redeemHash = _getRequestHash(token, request);
721:
722: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0;
723:
724: if (request.deadline >= block.timestamp) {
725:
726: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
727:
728: if (_guardAmountBound(tokenOut, request.tokens, index)) return 0;
729:
730:
731: asyncRedeemHashes[redeemHash] = false;
732:
733: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit(
734: address(this), token, tokenOut, request.units, address(this)
735: );
736:
737: token.safeTransfer(request.user, request.tokens); // <= FOUND
738:
739: unchecked {
740: solverTip = tokenOut - request.tokens;
741: }
742:
743:
744: emit RedeemSolved(redeemHash);
745: } else {
746:
747: asyncRedeemHashes[redeemHash] = false;
748:
749: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
750:
751: emit RedeemRefunded(redeemHash);
752: }
753: }
['764']
764: function _solveDepositDirect(IERC20 token, Request calldata request) internal { // <= FOUND
765: bytes32 depositHash = _getRequestHash(token, request);
766:
767: if (!asyncDepositHashes[depositHash]) {
768:
769: emit InvalidRequestHash(depositHash);
770: return;
771: }
772:
773:
774: asyncDepositHashes[depositHash] = false;
775:
776: if (request.deadline >= block.timestamp) {
777:
778: IERC20(MULTI_DEPOSITOR_VAULT).safeTransferFrom(msg.sender, request.user, request.units);
779:
780:
781: token.safeTransfer(msg.sender, request.tokens); // <= FOUND
782:
783:
784: emit DepositSolved(depositHash);
785: } else {
786:
787: token.safeTransfer(request.user, request.tokens);
788:
789:
790: emit DepositRefunded(depositHash);
791: }
792: }
['803']
803: function _solveRedeemDirect(IERC20 token, Request calldata request) internal { // <= FOUND
804: bytes32 redeemHash = _getRequestHash(token, request);
805:
806: if (!asyncRedeemHashes[redeemHash]) {
807:
808: emit InvalidRequestHash(redeemHash);
809: return;
810: }
811:
812:
813: asyncRedeemHashes[redeemHash] = false;
814:
815: if (request.deadline >= block.timestamp) {
816:
817: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(msg.sender, request.units); // <= FOUND
818:
819:
820: token.safeTransferFrom(msg.sender, request.user, request.tokens);
821:
822:
823: emit RedeemSolved(redeemHash);
824: } else {
825:
826: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
827:
828:
829: emit RedeemRefunded(redeemHash);
830: }
831: }
['27']
27: function deposit(TokenAmount[] calldata tokenAmounts) external requiresAuth { // <= FOUND
28: TokenAmount calldata tokenAmount;
29: uint256 length = tokenAmounts.length;
30: for (uint256 i = 0; i < length; ++i) {
31: tokenAmount = tokenAmounts[i];
32:
33: tokenAmount.token.safeTransferFrom(msg.sender, address(this), tokenAmount.amount); // <= FOUND
34:
35:
36: require(
37: tokenAmount.token.allowance(msg.sender, address(this)) == 0,
38: Aera__UnexpectedTokenAllowance(tokenAmount.token.allowance(msg.sender, address(this)))
39: );
40: }
41:
42:
43: emit Deposited(msg.sender, tokenAmounts);
44: }
['47']
47: function withdraw(TokenAmount[] calldata tokenAmounts) external requiresAuth { // <= FOUND
48: TokenAmount calldata tokenAmount;
49: uint256 length = tokenAmounts.length;
50: for (uint256 i = 0; i < length; ++i) {
51: tokenAmount = tokenAmounts[i];
52:
53: tokenAmount.token.safeTransfer(msg.sender, tokenAmount.amount); // <= FOUND
54: }
55:
56:
57: emit Withdrawn(msg.sender, tokenAmounts);
58: }
['24']
24: function sweep(address token, uint256 amount) external requiresAuth { // <= FOUND
25: if (token == address(0)) {
26:
27: (bool success,) = msg.sender.call{ value: amount }("");
28:
29: require(success, Aera__FailedToSendNativeToken());
30: } else {
31:
32: IERC20(token).safeTransfer(msg.sender, amount); // <= FOUND
33: }
34:
35:
36: emit Sweep(token, amount);
37: }
['99']
99: function cancelSell( // <= FOUND
100: address milkmanOrderContract,
101: uint256 sellAmount,
102: IERC20 sellToken,
103: IERC20 receiveToken,
104: bytes32 appData,
105: address priceChecker,
106: bytes calldata priceCheckerData
107: ) external onlyVault nonReentrant {
108:
109: IMilkman(milkmanOrderContract).cancelSwap(
110: sellAmount, sellToken, receiveToken, address(this), appData, priceChecker, priceCheckerData
111: );
112:
113:
114: sellToken.safeTransfer(vault, sellAmount); // <= FOUND
115:
116:
117: emit SellCancelled(
118: milkmanOrderContract, sellAmount, sellToken, receiveToken, appData, priceChecker, priceCheckerData
119: );
120: }
Withdraw and redeem functions in smart contracts can fail if they involve transferring USDT, USDC, or other centralized stablecoins from blocked or blacklisted accounts. Both Tether (USDT) and USD Coin (USDC) are issued by centralized entities with the authority to freeze or blacklist addresses. When an account is blacklisted, any attempts to transfer these tokens from the blacklisted address will be blocked, causing the transaction to fail.
This issue can significantly impact decentralized finance (DeFi) protocols, which rely on seamless token transfers for operations like withdrawals and redemptions. If a user's address or the contract itself gets blacklisted, users may be unable to withdraw their funds, leading to a loss of trust and potentially severe financial consequences.
A potential way to fix this issue is to allow users to specify an alternative address for the transfer to go to. However, the contract must still validate that the withdrawal amount is correctly debited from the msg.sender's balance to prevent any unauthorized transfers. This ensures that users cannot steal other users' assets by specifying different addresses.
Num of instances: 3
Click to show findings
['24']
24: function sweep(address token, uint256 amount) external requiresAuth {
25: if (token == address(0)) {
26:
27: (bool success,) = msg.sender.call{ value: amount }("");
28:
29: require(success, Aera__FailedToSendNativeToken());
30: } else {
31:
32: IERC20(token).safeTransfer(msg.sender, amount); // <= FOUND
33: }
34:
35:
36: emit Sweep(token, amount);
37: }
['105']
105: function claimFees() external onlyFeeRecipient returns (uint256 feeRecipientFees, uint256 protocolFees) {
106: address protocolFeeRecipient;
107:
108:
109: (feeRecipientFees, protocolFees, protocolFeeRecipient) =
110: feeCalculator.claimFees(FEE_TOKEN.balanceOf(address(this)));
111:
112:
113: require(feeRecipientFees != 0, Aera__NoFeesToClaim());
114:
115:
116: FEE_TOKEN.safeTransfer(msg.sender, feeRecipientFees); // <= FOUND
117:
118: emit FeesClaimed(msg.sender, feeRecipientFees);
119:
120: if (protocolFees != 0) {
121:
122: FEE_TOKEN.safeTransfer(protocolFeeRecipient, protocolFees); // <= FOUND
123:
124: emit ProtocolFeesClaimed(protocolFeeRecipient, protocolFees);
125: }
126: }
['47']
47: function withdraw(TokenAmount[] calldata tokenAmounts) external requiresAuth {
48: TokenAmount calldata tokenAmount;
49: uint256 length = tokenAmounts.length;
50: for (uint256 i = 0; i < length; ++i) {
51: tokenAmount = tokenAmounts[i];
52:
53: tokenAmount.token.safeTransfer(msg.sender, tokenAmount.amount); // <= FOUND
54: }
55:
56:
57: emit Withdrawn(msg.sender, tokenAmounts);
58: }
[Low-7] Deposit mechanism can be DOS'd with dust deposits due to lack of minAmount checks against deposit amount
A contract's deposit mechanism can be vulnerable to a Denial-of-Service (DoS) attack if malicious users spam it with "dust deposits" (very small amounts of tokens or ETH). This attack exploits the fact that deposit mechanisms often update internal accounting, emit events, or execute additional logic for every deposit. By making numerous small deposits, a malicious actor can congest the contract, cause storage bloat, or inflate gas costs, making it prohibitively expensive or inefficient for legitimate users to interact with the contract. For example, if a contract tracks user balances in a mapping, each tiny deposit might unnecessarily create or update entries. This attack could flood on-chain logs with events, increasing monitoring difficulties for off-chain systems. It could also fragment balances, leading to operational inefficiencies and rounding issues during future withdrawals. To prevent this, introducing a minimum deposit threshold is common, such as: require(amount >= MIN_DEPOSIT, "Deposit amount too small"); However, this safeguard can unintentionally prevent legitimate deposits if a user’s intended deposit is below the threshold. For instance, smaller investors or users testing the contract with small amounts may find themselves excluded. To address this, implement reasonable thresholds while considering user accessibility. Additionally, periodic aggregation or batching mechanisms can mitigate spam without deterring legitimate participation, balancing security with inclusivity.
Num of instances: 2
Click to show findings
['108']
108: function deposit(IERC20 token, uint256 tokensIn, uint256 minUnitsOut) // <= FOUND
109: external
110: anyoneButVault
111: returns (uint256 unitsOut)
112: {
113:
114: require(tokensIn != 0, Aera__TokensInZero());
115: require(minUnitsOut != 0, Aera__MinUnitsOutZero());
116:
117:
118: TokenDetails storage tokenDetails = _requireSyncDepositsEnabled(token);
119:
120:
121: unitsOut = _tokensToUnitsFloorIfActive(token, tokensIn, tokenDetails.depositMultiplier);
122:
123: require(unitsOut >= minUnitsOut, Aera__MinUnitsOutNotMet());
124:
125: _requireDepositCapNotExceeded(unitsOut);
126:
127:
128: _syncDeposit(token, tokensIn, unitsOut);
129: }
['27']
27: function deposit(TokenAmount[] calldata tokenAmounts) external requiresAuth { // <= FOUND
28: TokenAmount calldata tokenAmount;
29: uint256 length = tokenAmounts.length;
30: for (uint256 i = 0; i < length; ++i) {
31: tokenAmount = tokenAmounts[i];
32:
33: tokenAmount.token.safeTransferFrom(msg.sender, address(this), tokenAmount.amount);
34:
35:
36: require(
37: tokenAmount.token.allowance(msg.sender, address(this)) == 0,
38: Aera__UnexpectedTokenAllowance(tokenAmount.token.allowance(msg.sender, address(this)))
39: );
40: }
41:
42:
43: emit Deposited(msg.sender, tokenAmounts);
44: }
Casting block.timestamp
(a Unix epoch time in seconds) to an integer type with a smaller size in Solidity can potentially reduce the lifespan of a smart contract. The block.timestamp
returns a value that increases over time and can eventually exceed the maximum value storable in smaller integer types. For example, casting block.timestamp
to an int32
could lead to an overflow by the year 2038 (the year when Unix time exceeds the maximum value storable in a 32-bit integer). This overflow would result in incorrect timestamp values, potentially disrupting the contract's logic or rendering it unusable. Therefore, it's important to use an integer type that can accommodate the range of expected timestamp values throughout the contract's intended lifespan.
Num of instances: 5
Click to show findings
['54']
54:
55: vaultSnapshot.lastFeeAccrual = uint32(block.timestamp); // <= FOUND
['86']
86:
87: vaultSnapshot.finalizedAt = uint32(block.timestamp + DISPUTE_PERIOD); // <= FOUND
['87']
87:
88:
89: vaultPriceState.timestamp = uint32(block.timestamp); // <= FOUND
['108']
108: uint32 timestampU32 = uint32(block.timestamp); // <= FOUND
['76']
76: uint32 day = uint32(block.timestamp / 1 days); // <= FOUND
In Solidity, functions that receive Ether without corresponding functionality to utilize or withdraw these funds can inadvertently lead to a permanent loss of value. This is because if someone sends Ether to the contract, they may be unable to retrieve it. To avoid this, functions receiving Ether should be accompanied by additional methods that process or allow the withdrawal of these funds. If the intent is to use the received Ether, it should trigger a separate function; if not, it should revert the transaction (for instance, via require(msg.sender == address(weth))
). Access control checks can also prevent unintended Ether transfers, despite the slight gas cost they entail. If concerns over gas costs persist, at minimum, include a rescue function to recover unused Ether. Missteps in handling Ether in smart contracts can lead to irreversible financial losses, hence these precautions are crucial.
Num of instances: 1
In Solidity, abi.encodeWithSelector
is a function used for encoding data along with a function selector, but it is not type-safe. This means it does not enforce type checking at compile time, potentially leading to errors if arguments do not match the expected types. Starting from version 0.8.13, Solidity introduced abi.encodeCall
, which offers a safer alternative. abi.encodeCall
ensures type safety by performing a full type check, aligning the types of the arguments with the function signature. This reduces the risk of bugs caused by typographical errors or mismatched types. Using abi.encodeCall
enhances the reliability and security of the code by ensuring that the encoded data strictly conforms to the specified types, making it a preferable choice in Solidity versions 0.8.13 and above.
Num of instances: 4
Click to show findings
['281']
281:
282: (bool success, bytes memory result) =
283: hooks.call(abi.encodeWithSelector(ISubmitHooks.beforeSubmit.selector, data, msg.sender)); // <= FOUND
['294']
294:
295: (bool success, bytes memory result) =
296: hooks.call(abi.encodeWithSelector(ISubmitHooks.afterSubmit.selector, data, msg.sender)); // <= FOUND
['281']
281:
282: (bool success, bytes memory result) =
283: hooks.call(abi.encodeWithSelector(ISubmitHooks.beforeSubmit.selector, data, msg.sender)); // <= FOUND
['294']
294:
295: (bool success, bytes memory result) =
296: hooks.call(abi.encodeWithSelector(ISubmitHooks.afterSubmit.selector, data, msg.sender)); // <= FOUND
[Low-11] 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: 2
External calls in Solidity should have their return values checked to ensure the expected behavior, enhance error handling, and maintain the overall security and integrity of the contract. Unchecked return values can lead to silent failures, allowing the contract to continue executing despite an error in the external call.
Num of instances: 1
Click to show findings
['34']
34: function execute(TargetCalldata[] calldata operations) external nonReentrant {
35: address target;
36: bytes memory data;
37: bytes32 targetAndSelector;
38: mapping(bytes32 targetAndSelector => bool enabled) storage callerCapabilities = _canCall[msg.sender];
39:
40: uint256 length = operations.length;
41: for (uint256 i; i < length; ++i) {
42: target = operations[i].target;
43: data = operations[i].data;
44: targetAndSelector = _packTargetSig(target, bytes4(data));
45:
46:
47: require(
48: callerCapabilities[targetAndSelector], AeraPeriphery__Unauthorized(msg.sender, target, bytes4(data))
49: );
50:
51:
52: (bool success, bytes memory returnData) = target.call(data); // <= FOUND
53: Address.verifyCallResultFromTarget(target, success, returnData);
54: }
55:
56:
57: emit Executed(msg.sender, operations);
58: }
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: 7
Click to show findings
['62']
62: function enter(address sender, IERC20 token, uint256 tokenAmount, uint256 unitsAmount, address recipient)
63: external
64: whenNotPaused
65: onlyProvisioner
66: {
67:
68: if (tokenAmount > 0) token.safeTransferFrom(sender, address(this), tokenAmount); // <= FOUND
69:
70:
71: _mint(recipient, unitsAmount);
72:
73:
74: emit Enter(sender, recipient, token, tokenAmount, unitsAmount);
75: }
['180']
180: function requestDeposit(
181: IERC20 token,
182: uint256 tokensIn,
183: uint256 minUnitsOut,
184: uint256 solverTip,
185: uint256 deadline,
186: uint256 maxPriceAge,
187: bool isFixedPrice
188: ) external anyoneButVault {
189:
190:
191: require(tokensIn != 0, Aera__TokensInZero());
192: require(minUnitsOut != 0, Aera__MinUnitsOutZero());
193: require(deadline > block.timestamp, Aera__DeadlineInPast());
194: unchecked {
195: require(deadline - block.timestamp <= MAX_SECONDS_TO_DEADLINE, Aera__DeadlineTooFarInFuture());
196: }
197: require(tokensDetails[token].asyncDepositEnabled, Aera__AsyncDepositDisabled());
198: require(!PRICE_FEE_CALCULATOR.isVaultPaused(MULTI_DEPOSITOR_VAULT), Aera__PriceAndFeeCalculatorVaultPaused());
199: require(solverTip == 0 || !isFixedPrice, Aera__FixedPriceSolverTipNotAllowed());
200:
201:
202: token.safeTransferFrom(msg.sender, address(this), tokensIn); // <= FOUND
203:
204: RequestType requestType = isFixedPrice ? RequestType.DEPOSIT_FIXED_PRICE : RequestType.DEPOSIT_AUTO_PRICE;
205:
206: bytes32 depositHash = _getRequestHashParams(
207: token, msg.sender, requestType, tokensIn, minUnitsOut, solverTip, deadline, maxPriceAge
208: );
209:
210: require(!asyncDepositHashes[depositHash], Aera__HashCollision());
211:
212: asyncDepositHashes[depositHash] = true;
213:
214:
215: emit DepositRequested(
216: msg.sender, token, tokensIn, minUnitsOut, solverTip, deadline, maxPriceAge, isFixedPrice, depositHash
217: );
218: }
['221']
221: function requestRedeem(
222: IERC20 token,
223: uint256 unitsIn,
224: uint256 minTokensOut,
225: uint256 solverTip,
226: uint256 deadline,
227: uint256 maxPriceAge,
228: bool isFixedPrice
229: ) external anyoneButVault {
230:
231:
232: require(unitsIn != 0, Aera__UnitsInZero());
233: require(minTokensOut != 0, Aera__MinTokenOutZero());
234: require(deadline > block.timestamp, Aera__DeadlineInPast());
235: unchecked {
236: require(deadline - block.timestamp <= MAX_SECONDS_TO_DEADLINE, Aera__DeadlineTooFarInFuture());
237: }
238: require(tokensDetails[token].asyncRedeemEnabled, Aera__AsyncRedeemDisabled());
239: require(!PRICE_FEE_CALCULATOR.isVaultPaused(MULTI_DEPOSITOR_VAULT), Aera__PriceAndFeeCalculatorVaultPaused());
240: require(solverTip == 0 || !isFixedPrice, Aera__FixedPriceSolverTipNotAllowed());
241:
242:
243: IERC20(MULTI_DEPOSITOR_VAULT).safeTransferFrom(msg.sender, address(this), unitsIn); // <= FOUND
244:
245: RequestType requestType = isFixedPrice ? RequestType.REDEEM_FIXED_PRICE : RequestType.REDEEM_AUTO_PRICE;
246:
247: bytes32 redeemHash = _getRequestHashParams(
248: token, msg.sender, requestType, minTokensOut, unitsIn, solverTip, deadline, maxPriceAge
249: );
250:
251: require(!asyncRedeemHashes[redeemHash], Aera__HashCollision());
252:
253: asyncRedeemHashes[redeemHash] = true;
254:
255:
256: emit RedeemRequested(
257: msg.sender, token, minTokensOut, unitsIn, solverTip, deadline, maxPriceAge, isFixedPrice, redeemHash
258: );
259: }
['764']
764: function _solveDepositDirect(IERC20 token, Request calldata request) internal {
765: bytes32 depositHash = _getRequestHash(token, request);
766:
767: if (!asyncDepositHashes[depositHash]) {
768:
769: emit InvalidRequestHash(depositHash);
770: return;
771: }
772:
773:
774: asyncDepositHashes[depositHash] = false;
775:
776: if (request.deadline >= block.timestamp) {
777:
778: IERC20(MULTI_DEPOSITOR_VAULT).safeTransferFrom(msg.sender, request.user, request.units); // <= FOUND
779:
780:
781: token.safeTransfer(msg.sender, request.tokens);
782:
783:
784: emit DepositSolved(depositHash);
785: } else {
786:
787: token.safeTransfer(request.user, request.tokens);
788:
789:
790: emit DepositRefunded(depositHash);
791: }
792: }
['803']
803: function _solveRedeemDirect(IERC20 token, Request calldata request) internal {
804: bytes32 redeemHash = _getRequestHash(token, request);
805:
806: if (!asyncRedeemHashes[redeemHash]) {
807:
808: emit InvalidRequestHash(redeemHash);
809: return;
810: }
811:
812:
813: asyncRedeemHashes[redeemHash] = false;
814:
815: if (request.deadline >= block.timestamp) {
816:
817: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(msg.sender, request.units);
818:
819:
820: token.safeTransferFrom(msg.sender, request.user, request.tokens); // <= FOUND
821:
822:
823: emit RedeemSolved(redeemHash);
824: } else {
825:
826: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
827:
828:
829: emit RedeemRefunded(redeemHash);
830: }
831: }
['27']
27: function deposit(TokenAmount[] calldata tokenAmounts) external requiresAuth {
28: TokenAmount calldata tokenAmount;
29: uint256 length = tokenAmounts.length;
30: for (uint256 i = 0; i < length; ++i) {
31: tokenAmount = tokenAmounts[i];
32:
33: tokenAmount.token.safeTransferFrom(msg.sender, address(this), tokenAmount.amount); // <= FOUND
34:
35:
36: require(
37: tokenAmount.token.allowance(msg.sender, address(this)) == 0,
38: Aera__UnexpectedTokenAllowance(tokenAmount.token.allowance(msg.sender, address(this)))
39: );
40: }
41:
42:
43: emit Deposited(msg.sender, tokenAmounts);
44: }
['65']
65: function requestSell(
66: uint256 sellAmount,
67: IERC20 sellToken,
68: IERC20 receiveToken,
69: bytes32 appData,
70: address priceChecker,
71: bytes calldata priceCheckerData
72: ) external onlyVault nonReentrant {
73:
74: require(sellAmount > 0, AeraPeriphery__MilkmanRouter__SellAmountIsZero());
75:
76:
77: sellToken.safeTransferFrom(msg.sender, address(this), sellAmount); // <= FOUND
78:
79:
80: sellToken.safeIncreaseAllowance(address(milkmanRoot), sellAmount);
81:
82:
83: milkmanRoot.requestSwapExactTokensForTokens(
84: sellAmount, sellToken, receiveToken, address(this), appData, priceChecker, priceCheckerData
85: );
86:
87:
88:
89: require(
90: sellToken.allowance(address(this), address(milkmanRoot)) == 0,
91: AeraPeriphery__MilkmanRequestSwapExactTokensForTokensFailed(sellToken)
92: );
93:
94:
95: emit SellRequested(sellAmount, sellToken, receiveToken, appData, priceChecker, priceCheckerData);
96: }
Downcasting int/uints in Solidity can be unsafe due to the potential for data loss and unintended behavior. When downcasting a larger integer type to a smaller one (e.g., uint256 to uint128), the value may exceed the range of the target type, leading to truncation and loss of significant digits. This data loss can result in unexpected state changes, incorrect calculations, or other contract vulnerabilities, ultimately compromising the contracts functionality and reliability. To prevent these risks, developers should carefully consider the range of values their variables may hold and ensure that proper checks are in place to prevent out-of-range values before performing downcasting. Also consider using OZ SafeCast functionality.
Num of instances: 18
Click to show findings
['102']
102: function claimFees(uint256 feeTokenBalance) external virtual returns (uint256, uint256, address) { // <= FOUND
103:
104: _beforeClaimFees();
105:
106: VaultAccruals storage vaultAccruals = _vaultAccruals[msg.sender];
107:
108: uint256 vaultEarnedFees = vaultAccruals.accruedFees;
109: uint256 protocolEarnedFees = vaultAccruals.accruedProtocolFees;
110: uint256 claimableProtocolFee = Math.min(feeTokenBalance, protocolEarnedFees);
111: uint256 claimableVaultFee;
112: unchecked {
113: claimableVaultFee = Math.min(feeTokenBalance - claimableProtocolFee, vaultEarnedFees);
114: }
115:
116:
117: unchecked {
118: vaultAccruals.accruedProtocolFees = uint112(protocolEarnedFees - claimableProtocolFee); // <= FOUND
119: vaultAccruals.accruedFees = uint112(vaultEarnedFees - claimableVaultFee);
120: }
121:
122: return (claimableVaultFee, claimableProtocolFee, protocolFeeRecipient);
123: }
['126']
126: function claimProtocolFees(uint256 feeTokenBalance) external virtual returns (uint256, address) { // <= FOUND
127:
128: _beforeClaimProtocolFees();
129:
130: VaultAccruals storage vaultAccruals = _vaultAccruals[msg.sender];
131: uint256 accruedFees = vaultAccruals.accruedProtocolFees;
132: uint256 claimableProtocolFee = Math.min(feeTokenBalance, accruedFees);
133:
134:
135: unchecked {
136: vaultAccruals.accruedProtocolFees = uint112(accruedFees - claimableProtocolFee); // <= FOUND
137: }
138:
139: return (claimableProtocolFee, protocolFeeRecipient);
140: }
['540']
540: function _setHookCallType(HookCallType hookCallType) internal { // <= FOUND
541:
542: HOOK_CALL_TYPE_SLOT.asUint256().tstore(uint8(hookCallType)); // <= FOUND
543: }
['641']
641: function _hasBeforeHooks(address hooks) internal pure returns (bool) { // <= FOUND
642:
643: return uint160(hooks) & BEFORE_HOOK_MASK != 0; // <= FOUND
644: }
['649']
649: function _hasAfterHooks(address hooks) internal pure returns (bool) { // <= FOUND
650:
651: return uint160(hooks) & AFTER_HOOK_MASK != 0; // <= FOUND
652: }
['122']
122: function _storeCallbackApprovals(Approval[] memory approvals, uint256 length) internal { // <= FOUND
123: if (length == 0) return;
124:
125: uint256 existingApproval = APPROVALS_SLOT.asUint256().tload();
126: uint256 existingLength = existingApproval >> ADDRESS_SIZE_BITS;
127:
128: uint256 i;
129: uint256 currentSlot = uint256(APPROVALS_SLOT);
130: Approval memory approval;
131: if (existingLength == 0) {
132: approval = approvals[0];
133: unchecked {
134:
135:
136: bytes32(currentSlot).asUint256().tstore(_packLengthAndToken(length, approval.token));
137: bytes32(++currentSlot).asAddress().tstore(approval.spender);
138: }
139:
140: i = 1;
141: } else {
142: unchecked {
143: uint256 newLength = existingLength + length;
144:
145:
146: bytes32(currentSlot).asUint256().tstore(
147: _packLengthAndToken(newLength, address(uint160(existingApproval))) // <= FOUND
148: );
149:
150: currentSlot += existingLength * 2 - 1;
151: }
152: }
153:
154: for (; i < length; ++i) {
155: approval = approvals[i];
156: unchecked {
157:
158: bytes32(++currentSlot).asAddress().tstore(approval.token);
159: bytes32(++currentSlot).asAddress().tstore(approval.spender);
160: }
161: }
162: }
['194']
194: function _getCallbackApprovals() internal returns (Approval[] memory approvals) { // <= FOUND
195: uint256 lengthWithToken = APPROVALS_SLOT.asUint256().tload();
196: uint256 length = lengthWithToken >> ADDRESS_SIZE_BITS;
197: if (length == 0) return approvals;
198:
199:
200: APPROVALS_SLOT.asUint256().tstore(0);
201:
202: approvals = new Approval[](length);
203:
204: address token = address(uint160(lengthWithToken)); // <= FOUND
205:
206: uint256 slotUint256 = uint256(APPROVALS_SLOT);
207: address spender;
208: unchecked {
209: spender = bytes32(++slotUint256).asAddress().tload();
210: }
211:
212: approvals[0] = Approval({ token: token, spender: spender });
213:
214: for (uint256 i = 1; i < length; ++i) {
215: unchecked {
216: token = bytes32(++slotUint256).asAddress().tload();
217: spender = bytes32(++slotUint256).asAddress().tload();
218: }
219: approvals[i] = Approval({ token: token, spender: spender });
220: }
221: }
['235']
235: function _unpackCallbackData(uint256 packed) // <= FOUND
236: private
237: pure
238: returns (address target, bytes4 selector, uint16 dataOffset)
239: {
240: target = address(uint160(packed));
241: selector = bytes4(bytes32(packed << SELECTOR_OFFSET));
242: dataOffset = uint16(packed >> CALLBACK_DATA_OFFSET); // <= FOUND
243: }
['251']
251: function _packLengthAndToken(uint256 length, address token) private pure returns (uint256) { // <= FOUND
252: return uint160(token) | (length << ADDRESS_SIZE_BITS); // <= FOUND
253: }
['168']
168: function _accrueFees( // <= FOUND
169: VaultSnapshot storage vaultSnapshot,
170: VaultAccruals storage vaultAccruals,
171: uint256 lastFeeAccrualCached
172: ) internal returns (uint256 protocolFeesEarned, uint256 vaultFeesEarned) {
173: uint256 snapshotTimestamp = vaultSnapshot.timestamp;
174: if (lastFeeAccrualCached >= snapshotTimestamp || vaultSnapshot.finalizedAt > block.timestamp) {
175:
176: return (0, 0);
177: }
178:
179:
180: (uint256 vaultPerformanceFeeEarned, uint256 protocolPerformanceFeeEarned) = _calculatePerformanceFees(
181: vaultAccruals.fees.performance, vaultSnapshot.highestProfit, vaultSnapshot.lastHighestProfit
182: );
183:
184: (uint256 vaultTvlFeeEarned, uint256 protocolTvlFeeEarned) = _calculateTvlFees(
185: vaultAccruals.fees.tvl, vaultSnapshot.averageValue, snapshotTimestamp, lastFeeAccrualCached
186: );
187:
188:
189: vaultSnapshot.lastHighestProfit = vaultSnapshot.highestProfit;
190: vaultSnapshot.lastFeeAccrual = uint32(snapshotTimestamp); // <= FOUND
191: vaultAccruals.accruedFees += (vaultPerformanceFeeEarned + vaultTvlFeeEarned).toUint112();
192: vaultAccruals.accruedProtocolFees += (protocolPerformanceFeeEarned + protocolTvlFeeEarned).toUint112();
193:
194:
195: vaultSnapshot.averageValue = 0;
196: vaultSnapshot.highestProfit = 0;
197: vaultSnapshot.timestamp = 0;
198: vaultSnapshot.finalizedAt = 0;
199:
200: protocolFeesEarned = protocolPerformanceFeeEarned + protocolTvlFeeEarned;
201: vaultFeesEarned = vaultPerformanceFeeEarned + vaultTvlFeeEarned;
202: }
['156']
156: function setUnitPrice(address vault, uint128 price, uint32 timestamp) external onlyVaultAccountant(vault) { // <= FOUND
157: VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
158:
159:
160: _validatePriceUpdate(vaultPriceState, price, timestamp);
161:
162: if (!vaultPriceState.paused) {
163: if (_shouldPause(vaultPriceState, price, timestamp)) {
164:
165: _setVaultPaused(vaultPriceState, vault, true);
166:
167: unchecked {
168:
169: vaultPriceState.accrualLag = uint24(timestamp - vaultPriceState.timestamp); // <= FOUND
170: }
171: } else {
172:
173: _accrueFees(vault, price, timestamp);
174: }
175: } else {
176:
177: unchecked {
178:
179: vaultPriceState.accrualLag = uint24(timestamp - vaultPriceState.timestamp + vaultPriceState.accrualLag);
180: }
181: }
182:
183:
184: vaultPriceState.unitPrice = price;
185: vaultPriceState.timestamp = timestamp;
186:
187:
188: emit UnitPriceUpdated(vault, price, timestamp);
189: }
['334']
334: function _accrueFees(address vault, uint256 price, uint256 timestamp) internal { // <= FOUND
335: VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
336:
337: uint256 timeDelta;
338: unchecked {
339: timeDelta = timestamp - vaultPriceState.timestamp + vaultPriceState.accrualLag;
340: }
341:
342:
343: uint256 currentTotalSupply = IERC20(vault).totalSupply();
344: uint256 minTotalSupply = Math.min(currentTotalSupply, uint256(vaultPriceState.lastTotalSupply));
345: uint256 minUnitPrice = Math.min(price, uint256(vaultPriceState.unitPrice));
346:
347: uint256 tvl = minUnitPrice * minTotalSupply / UNIT_PRICE_PRECISION;
348:
349: VaultAccruals storage vaultAccruals = _vaultAccruals[vault];
350: uint256 vaultFeesEarned = _calculateTvlFee(tvl, vaultAccruals.fees.tvl, timeDelta);
351:
352: uint256 protocolFeesEarned = _calculateTvlFee(tvl, protocolFees.tvl, timeDelta);
353:
354: if (price > vaultPriceState.highestPrice) {
355: uint256 profit = (price - vaultPriceState.highestPrice) * minTotalSupply / UNIT_PRICE_PRECISION;
356: vaultFeesEarned += _calculatePerformanceFee(profit, vaultAccruals.fees.performance);
357: protocolFeesEarned += _calculatePerformanceFee(profit, protocolFees.performance);
358:
359:
360: vaultPriceState.highestPrice = uint128(price); // <= FOUND
361: }
362:
363:
364: vaultAccruals.accruedFees += vaultFeesEarned.toUint112();
365: vaultAccruals.accruedProtocolFees += protocolFeesEarned.toUint112();
366:
367:
368: vaultPriceState.lastTotalSupply = currentTotalSupply.toUint128();
369: vaultPriceState.accrualLag = 0;
370: }
['1039']
1039: function _isRequestTypeDeposit(RequestType requestType) internal pure returns (bool) { // <= FOUND
1040: return uint8(requestType) & DEPOSIT_REDEEM_FLAG == 0; // <= FOUND
1041: }
['1046']
1046: function _isRequestTypeAutoPrice(RequestType requestType) internal pure returns (bool) { // <= FOUND
1047: return uint8(requestType) & AUTO_PRICE_FIXED_PRICE_FLAG == 0; // <= FOUND
1048: }
['96']
96: function _packTargetSig(address target, bytes4 sig) internal pure returns (bytes32) { // <= FOUND
97: return bytes32(uint256(bytes32(sig)) | uint256(uint160(target))); // <= FOUND
98: }
['175']
175: function _enforceSlippageLimitAndDailyLoss(uint256 valueBefore, uint256 valueAfter) // <= FOUND
176: internal
177: returns (uint128 cumulativeDailyLossInNumeraire)
178: {
179: State storage state = _vaultStates[msg.sender];
180:
181: uint256 loss;
182: unchecked {
183: loss = valueBefore - valueAfter;
184: }
185:
186:
187: _enforceSlippageLimit(state, loss, valueBefore);
188:
189:
190: cumulativeDailyLossInNumeraire = uint128(_enforceDailyLoss(state, loss)); // <= FOUND
191: state.cumulativeDailyLossInNumeraire = cumulativeDailyLossInNumeraire;
192: }
['16']
16: function depositForBurn( // <= FOUND
17: uint256 amount,
18: uint32 destinationDomain,
19: bytes32 mintRecipient,
20: address burnToken,
21: bytes32 destinationCaller,
22: uint256 maxFee,
23: uint32
24: ) external returns (bytes memory returnData) {
25:
26: require(destinationCaller == bytes32(0), AeraPeriphery__DestinationCallerNotZero());
27:
28: if (maxFee > 0) {
29:
30: uint256 sourceAmountInNumeraire = _convertToNumeraire(amount, burnToken);
31:
32:
33: uint256 destinationAmountInNumeraire = _convertToNumeraire(amount - maxFee, burnToken);
34:
35:
36: _enforceSlippageLimitAndDailyLossLog(
37: msg.sender, burnToken, burnToken, sourceAmountInNumeraire, destinationAmountInNumeraire
38: );
39: }
40:
41: returnData = abi.encode(destinationDomain, address(uint160(uint256(mintRecipient))), burnToken); // <= FOUND
42: }
['72']
72: function _handleMilkmanBeforeHook(address sellToken, uint256 sellAmount, address vault) internal { // <= FOUND
73: State storage state = _vaultStates[vault];
74:
75:
76: uint256 loss = _convertToNumeraire(sellAmount * state.maxSlippagePerTrade / MAX_BPS, sellToken);
77:
78:
79: state.cumulativeDailyLossInNumeraire = uint128(_enforceDailyLoss(state, loss)); // <= FOUND
80: }
In Solidity, the nonReentrant
modifier is essential for securing smart contracts against reentrancy attacks. It should be positioned first in a function declaration. This placement is critical because it ensures that the modifier's protection is applied before any other code or modifiers in the function. If placed later, other modifiers could potentially be executed in a reentrant manner before nonReentrant
has a chance to lock the function, leaving the contract vulnerable. Thus, prioritizing nonReentrant
as the first modifier is a best practice for robust smart contract security, effectively guarding against unintended reentries and related exploits.
Num of instances: 4
Click to show findings
['294']
294: function solveRequestsVault(IERC20 token, Request[] calldata requests) external requiresAuth nonReentrant { // <= FOUND
295:
296: uint256 priceAge = PRICE_FEE_CALCULATOR.getVaultsPriceAge(MULTI_DEPOSITOR_VAULT);
297:
298: uint256 solverTip;
299: Request calldata request;
300:
301: uint256 length = requests.length;
302: TokenDetails memory tokenDetails = tokensDetails[token];
303: bool depositsExist;
304: for (uint256 i = 0; i < length; i++) {
305: request = requests[i];
306: if (_isRequestTypeDeposit(request.requestType)) {
307:
308: if (!tokenDetails.asyncDepositEnabled) {
309:
310: emit AsyncDepositDisabled(i);
311: continue;
312: }
313:
314: if (!depositsExist) {
315: depositsExist = true;
316: token.forceApprove(MULTI_DEPOSITOR_VAULT, type(uint256).max);
317: }
318:
319: if (_isRequestTypeAutoPrice(request.requestType)) {
320:
321: solverTip +=
322: _solveDepositVaultAutoPrice(token, tokenDetails.depositMultiplier, request, priceAge, i);
323: } else {
324:
325: solverTip +=
326: _solveDepositVaultFixedPrice(token, tokenDetails.depositMultiplier, request, priceAge, i);
327: }
328: } else {
329:
330: if (!tokenDetails.asyncRedeemEnabled) {
331:
332: emit AsyncRedeemDisabled(i);
333: continue;
334: }
335:
336: if (_isRequestTypeAutoPrice(request.requestType)) {
337:
338: solverTip += _solveRedeemVaultAutoPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i);
339: } else {
340:
341: solverTip += _solveRedeemVaultFixedPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i);
342: }
343: }
344: }
345:
346: if (solverTip != 0) {
347:
348: token.safeTransfer(msg.sender, solverTip);
349: }
350:
351: if (depositsExist) {
352:
353: token.forceApprove(MULTI_DEPOSITOR_VAULT, 0);
354: }
355: }
['65']
65: function requestSell(
66: uint256 sellAmount,
67: IERC20 sellToken,
68: IERC20 receiveToken,
69: bytes32 appData,
70: address priceChecker,
71: bytes calldata priceCheckerData
72: ) external onlyVault nonReentrant { // <= FOUND
73:
74: require(sellAmount > 0, AeraPeriphery__MilkmanRouter__SellAmountIsZero());
75:
76:
77: sellToken.safeTransferFrom(msg.sender, address(this), sellAmount);
78:
79:
80: sellToken.safeIncreaseAllowance(address(milkmanRoot), sellAmount);
81:
82:
83: milkmanRoot.requestSwapExactTokensForTokens(
84: sellAmount, sellToken, receiveToken, address(this), appData, priceChecker, priceCheckerData
85: );
86:
87:
88:
89: require(
90: sellToken.allowance(address(this), address(milkmanRoot)) == 0,
91: AeraPeriphery__MilkmanRequestSwapExactTokensForTokensFailed(sellToken)
92: );
93:
94:
95: emit SellRequested(sellAmount, sellToken, receiveToken, appData, priceChecker, priceCheckerData);
96: }
['99']
99: function cancelSell(
100: address milkmanOrderContract,
101: uint256 sellAmount,
102: IERC20 sellToken,
103: IERC20 receiveToken,
104: bytes32 appData,
105: address priceChecker,
106: bytes calldata priceCheckerData
107: ) external onlyVault nonReentrant { // <= FOUND
108:
109: IMilkman(milkmanOrderContract).cancelSwap(
110: sellAmount, sellToken, receiveToken, address(this), appData, priceChecker, priceCheckerData
111: );
112:
113:
114: sellToken.safeTransfer(vault, sellAmount);
115:
116:
117: emit SellCancelled(
118: milkmanOrderContract, sellAmount, sellToken, receiveToken, appData, priceChecker, priceCheckerData
119: );
120: }
['123']
123: function claim(IERC20 token) external onlyVault nonReentrant { // <= FOUND
124: uint256 balance = token.balanceOf(address(this));
125:
126:
127:
128: if (balance == 0) return;
129:
130:
131: token.safeTransfer(vault, balance);
132:
133:
134: emit Claimed(token, balance);
135: }
When settings min/max state variables, ensure there a require checks in place to prevent incorrect values from being set
Num of instances: 2
Click to show findings
['122']
122: function setThresholds( // <= FOUND
123: address vault,
124: uint16 minPriceToleranceRatio,
125: uint16 maxPriceToleranceRatio,
126: uint16 minUpdateIntervalMinutes, // <= FOUND
127: uint8 maxPriceAge,
128: uint8 maxUpdateDelayDays
129: ) external requiresVaultAuth(vault) {
130:
131: require(minPriceToleranceRatio <= ONE_IN_BPS, Aera__InvalidMinPriceToleranceRatio());
132:
133: require(maxPriceToleranceRatio >= ONE_IN_BPS, Aera__InvalidMaxPriceToleranceRatio());
134:
135: require(maxPriceAge > 0, Aera__InvalidMaxPriceAge());
136:
137: require(maxUpdateDelayDays > 0, Aera__InvalidMaxUpdateDelayDays());
138:
139: VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
140:
141:
142: require(vaultPriceState.timestamp != 0, Aera__VaultNotRegistered());
143:
144:
145: vaultPriceState.minPriceToleranceRatio = minPriceToleranceRatio;
146: vaultPriceState.maxPriceToleranceRatio = maxPriceToleranceRatio;
147: vaultPriceState.minUpdateIntervalMinutes = minUpdateIntervalMinutes; // <= FOUND
148: vaultPriceState.maxPriceAge = maxPriceAge;
149: vaultPriceState.maxUpdateDelayDays = maxUpdateDelayDays;
150:
151:
152: emit ThresholdsSet(vault, minPriceToleranceRatio, maxPriceToleranceRatio, minUpdateIntervalMinutes, maxPriceAge); // <= FOUND
153: }
['40']
40: function setMaxDailyLoss(address vault, uint128 maxLoss) external requiresVaultAuth(vault) { // <= FOUND
41:
42: _vaultStates[vault].maxDailyLossInNumeraire = maxLoss; // <= FOUND
43:
44:
45: emit UpdateMaxDailyLoss(vault, maxLoss); // <= FOUND
46: }
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: 8
Click to show findings
['166']
166: function _calculateTvlFee(uint256 averageValue, uint256 tvlFee, uint256 timeDelta) // <= FOUND
167: internal
168: pure
169: returns (uint256)
170: {
171: unchecked {
172:
173: return averageValue * tvlFee * timeDelta / ONE_IN_BPS / SECONDS_PER_YEAR; // <= FOUND
174: }
175: }
['181']
181: function _calculatePerformanceFee(uint256 profit, uint256 feeRate) internal pure returns (uint256) { // <= FOUND
182: unchecked {
183:
184: return profit * feeRate / ONE_IN_BPS; // <= FOUND
185: }
186: }
['260']
260: function convertUnitsToNumeraire(address vault, uint256 unitsAmount) external view returns (uint256) { // <= FOUND
261: VaultPriceState storage vaultState = _vaultPriceStates[vault];
262:
263:
264: require(!vaultState.paused, Aera__VaultPaused());
265:
266: return unitsAmount * vaultState.unitPrice / UNIT_PRICE_PRECISION; // <= FOUND
267: }
['334']
334: function _accrueFees(address vault, uint256 price, uint256 timestamp) internal { // <= FOUND
335: VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
336:
337: uint256 timeDelta;
338: unchecked {
339: timeDelta = timestamp - vaultPriceState.timestamp + vaultPriceState.accrualLag;
340: }
341:
342:
343: uint256 currentTotalSupply = IERC20(vault).totalSupply();
344: uint256 minTotalSupply = Math.min(currentTotalSupply, uint256(vaultPriceState.lastTotalSupply));
345: uint256 minUnitPrice = Math.min(price, uint256(vaultPriceState.unitPrice));
346:
347: uint256 tvl = minUnitPrice * minTotalSupply / UNIT_PRICE_PRECISION; // <= FOUND
348:
349: VaultAccruals storage vaultAccruals = _vaultAccruals[vault];
350: uint256 vaultFeesEarned = _calculateTvlFee(tvl, vaultAccruals.fees.tvl, timeDelta);
351:
352: uint256 protocolFeesEarned = _calculateTvlFee(tvl, protocolFees.tvl, timeDelta);
353:
354: if (price > vaultPriceState.highestPrice) {
355: uint256 profit = (price - vaultPriceState.highestPrice) * minTotalSupply / UNIT_PRICE_PRECISION;
356: vaultFeesEarned += _calculatePerformanceFee(profit, vaultAccruals.fees.performance);
357: protocolFeesEarned += _calculatePerformanceFee(profit, protocolFees.performance);
358:
359:
360: vaultPriceState.highestPrice = uint128(price);
361: }
362:
363:
364: vaultAccruals.accruedFees += vaultFeesEarned.toUint112();
365: vaultAccruals.accruedProtocolFees += protocolFeesEarned.toUint112();
366:
367:
368: vaultPriceState.lastTotalSupply = currentTotalSupply.toUint128();
369: vaultPriceState.accrualLag = 0;
370: }
['932']
932: function _tokensToUnitsFloorIfActive(IERC20 token, uint256 tokens, uint256 multiplier) // <= FOUND
933: internal
934: view
935: returns (uint256)
936: {
937: uint256 tokensAdjusted = tokens * multiplier / ONE_IN_BPS; // <= FOUND
938:
939: return PRICE_FEE_CALCULATOR.convertTokenToUnitsIfActive(
940: MULTI_DEPOSITOR_VAULT, token, tokensAdjusted, Math.Rounding.Floor
941: );
942: }
['949']
949: function _unitsToTokensFloorIfActive(IERC20 token, uint256 units, uint256 multiplier) // <= FOUND
950: internal
951: view
952: returns (uint256)
953: {
954:
955: uint256 tokensAmount =
956: PRICE_FEE_CALCULATOR.convertUnitsToTokenIfActive(MULTI_DEPOSITOR_VAULT, token, units, Math.Rounding.Floor);
957: return tokensAmount * multiplier / ONE_IN_BPS; // <= FOUND
958: }
['44']
44: function checkPrice( // <= FOUND
45: uint256 amountIn,
46: address fromToken,
47: address toToken,
48: uint256,
49: uint256 minOut,
50: bytes calldata data
51: ) external view returns (bool) {
52: address vault = abi.decode(data, (address));
53: State storage state = _vaultStates[vault];
54:
55:
56: uint256 expectedOut = state.oracleRegistry.getQuoteForUser(amountIn, fromToken, toToken, vault);
57: uint256 slippageMultiplier;
58: unchecked {
59: slippageMultiplier = MAX_BPS - state.maxSlippagePerTrade;
60: }
61: return minOut > (expectedOut * slippageMultiplier / MAX_BPS); // <= FOUND
62: }
['72']
72: function _handleMilkmanBeforeHook(address sellToken, uint256 sellAmount, address vault) internal { // <= FOUND
73: State storage state = _vaultStates[vault];
74:
75:
76: uint256 loss = _convertToNumeraire(sellAmount * state.maxSlippagePerTrade / MAX_BPS, sellToken); // <= FOUND
77:
78:
79: state.cumulativeDailyLossInNumeraire = uint128(_enforceDailyLoss(state, loss));
80: }
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
['35']
35: constructor(address owner_, Authority authority_, uint256 disputePeriod) BaseFeeCalculator(owner_, authority_) { // <= FOUND
36:
37: require(disputePeriod <= MAX_DISPUTE_PERIOD, Aera__DisputePeriodTooLong());
38:
39:
40: DISPUTE_PERIOD = disputePeriod;
41: }
['16']
16: constructor(address _owner, Authority _authority) { // <= FOUND
17: owner = _owner;
18: authority = _authority;
19:
20: emit OwnershipTransferred(msg.sender, _owner);
21: emit AuthorityUpdated(msg.sender, _authority);
22: }
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: 12
Click to show findings
['61']
61: function submitSnapshot(address vault, uint160 averageValue, uint128 highestProfit, uint32 timestamp) // <= FOUND
62: external
63: onlyVaultAccountant(vault)
64: {
65:
66: require(timestamp <= block.timestamp, Aera__SnapshotInFuture());
67:
68: VaultSnapshot storage vaultSnapshot = _vaultSnapshots[vault];
69: VaultAccruals storage vaultAccruals = _vaultAccruals[vault];
70:
71: uint256 lastFeeAccrualCached = vaultSnapshot.lastFeeAccrual;
72: require(lastFeeAccrualCached != 0, Aera__VaultNotRegistered());
73:
74:
75: _accrueFees(vaultSnapshot, vaultAccruals, lastFeeAccrualCached);
76:
77:
78: require(timestamp > vaultSnapshot.lastFeeAccrual, Aera__SnapshotTooOld());
79:
80: require(vaultSnapshot.lastHighestProfit <= highestProfit, Aera__HighestProfitDecreased());
81:
82:
83: vaultSnapshot.timestamp = timestamp;
84: unchecked {
85:
86: vaultSnapshot.finalizedAt = uint32(block.timestamp + DISPUTE_PERIOD);
87: }
88: vaultSnapshot.averageValue = averageValue;
89: vaultSnapshot.highestProfit = highestProfit;
90:
91:
92: emit SnapshotSubmitted(vault, averageValue, highestProfit, timestamp);
93: }
['105']
105: function previewFees(address vault, uint256 feeTokenBalance) external view override returns (uint256, uint256) { // <= FOUND
106: VaultSnapshot storage vaultSnapshot = _vaultSnapshots[vault];
107: VaultAccruals storage vaultAccruals = _vaultAccruals[vault];
108:
109: uint256 claimableProtocolFee = vaultAccruals.accruedProtocolFees;
110: uint256 claimableVaultFee = vaultAccruals.accruedFees;
111:
112: if (vaultSnapshot.lastFeeAccrual < vaultSnapshot.timestamp && vaultSnapshot.finalizedAt <= block.timestamp) { // <= FOUND
113:
114: (uint256 vaultPerformanceFeeEarned, uint256 protocolPerformanceFeeEarned) = _calculatePerformanceFees(
115: vaultAccruals.fees.performance, vaultSnapshot.highestProfit, vaultSnapshot.lastHighestProfit
116: );
117:
118: (uint256 vaultTvlFeeEarned, uint256 protocolTvlFeeEarned) = _calculateTvlFees(
119: vaultAccruals.fees.tvl,
120: vaultSnapshot.averageValue,
121: vaultSnapshot.timestamp,
122: vaultSnapshot.lastFeeAccrual
123: );
124:
125: claimableProtocolFee += protocolPerformanceFeeEarned + protocolTvlFeeEarned;
126: claimableVaultFee += vaultPerformanceFeeEarned + vaultTvlFeeEarned;
127: }
128:
129: claimableProtocolFee = Math.min(feeTokenBalance, claimableProtocolFee);
130: claimableVaultFee;
131: unchecked {
132: claimableVaultFee = Math.min(feeTokenBalance - claimableProtocolFee, claimableVaultFee);
133: }
134:
135: return (claimableVaultFee, claimableProtocolFee);
136: }
['168']
168: function _accrueFees( // <= FOUND
169: VaultSnapshot storage vaultSnapshot,
170: VaultAccruals storage vaultAccruals,
171: uint256 lastFeeAccrualCached
172: ) internal returns (uint256 protocolFeesEarned, uint256 vaultFeesEarned) {
173: uint256 snapshotTimestamp = vaultSnapshot.timestamp;
174: if (lastFeeAccrualCached >= snapshotTimestamp || vaultSnapshot.finalizedAt > block.timestamp) { // <= FOUND
175:
176: return (0, 0);
177: }
178:
179:
180: (uint256 vaultPerformanceFeeEarned, uint256 protocolPerformanceFeeEarned) = _calculatePerformanceFees(
181: vaultAccruals.fees.performance, vaultSnapshot.highestProfit, vaultSnapshot.lastHighestProfit
182: );
183:
184: (uint256 vaultTvlFeeEarned, uint256 protocolTvlFeeEarned) = _calculateTvlFees(
185: vaultAccruals.fees.tvl, vaultSnapshot.averageValue, snapshotTimestamp, lastFeeAccrualCached
186: );
187:
188:
189: vaultSnapshot.lastHighestProfit = vaultSnapshot.highestProfit;
190: vaultSnapshot.lastFeeAccrual = uint32(snapshotTimestamp);
191: vaultAccruals.accruedFees += (vaultPerformanceFeeEarned + vaultTvlFeeEarned).toUint112();
192: vaultAccruals.accruedProtocolFees += (protocolPerformanceFeeEarned + protocolTvlFeeEarned).toUint112();
193:
194:
195: vaultSnapshot.averageValue = 0;
196: vaultSnapshot.highestProfit = 0;
197: vaultSnapshot.timestamp = 0;
198: vaultSnapshot.finalizedAt = 0;
199:
200: protocolFeesEarned = protocolPerformanceFeeEarned + protocolTvlFeeEarned;
201: vaultFeesEarned = vaultPerformanceFeeEarned + vaultTvlFeeEarned;
202: }
['431']
431: function _validatePriceUpdate(VaultPriceState storage vaultPriceState, uint256 price, uint256 timestamp) // <= FOUND
432: internal
433: view
434: {
435:
436: require(price != 0, Aera__InvalidPrice());
437:
438: require(timestamp > vaultPriceState.timestamp, Aera__TimestampMustBeAfterLastUpdate());
439:
440: require(block.timestamp >= timestamp, Aera__TimestampCantBeInFuture()); // <= FOUND
441:
442: uint256 maxPriceAge = vaultPriceState.maxPriceAge;
443:
444: require(maxPriceAge != 0, Aera__ThresholdNotSet());
445:
446: require(maxPriceAge + timestamp >= block.timestamp, Aera__StalePrice());
447: }
['156']
156: function refundDeposit( // <= FOUND
157: address sender,
158: IERC20 token,
159: uint256 tokenAmount,
160: uint256 unitsAmount,
161: uint256 refundableUntil
162: ) external requiresAuth {
163:
164: require(refundableUntil >= block.timestamp, Aera__RefundPeriodExpired());
165:
166: bytes32 depositHash = _getDepositHash(sender, token, tokenAmount, unitsAmount, refundableUntil);
167:
168: require(syncDepositHashes[depositHash], Aera__DepositHashNotFound());
169:
170: syncDepositHashes[depositHash] = false;
171:
172:
173: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit(sender, token, tokenAmount, unitsAmount, sender);
174:
175:
176: emit DirectDepositRefunded(depositHash);
177: }
['514']
514: function _solveDepositVaultAutoPrice( // <= FOUND
515: IERC20 token,
516: uint256 depositMultiplier,
517: Request calldata request,
518: uint256 priceAge,
519: uint256 index
520: ) internal returns (uint256 solverTip) {
521:
522: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
523:
524: bytes32 depositHash = _getRequestHash(token, request);
525:
526: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0;
527:
528: if (request.deadline >= block.timestamp) { // <= FOUND
529: solverTip = request.solverTip;
530: uint256 tokens = request.tokens;
531:
532:
533: if (_guardInsufficientTokensForTip(tokens, solverTip, index)) return 0;
534:
535: uint256 tokensAfterTip;
536: unchecked {
537: tokensAfterTip = tokens - solverTip;
538: }
539:
540:
541: uint256 unitsOut = _tokensToUnitsFloorIfActive(token, tokensAfterTip, depositMultiplier);
542:
543: if (_guardAmountBound(unitsOut, request.units, index)) return 0;
544:
545: if (_guardDepositCapExceeded(unitsOut, index)) return 0;
546:
547:
548: asyncDepositHashes[depositHash] = false;
549:
550: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter(
551: address(this), token, tokensAfterTip, unitsOut, request.user
552: );
553:
554:
555: emit DepositSolved(depositHash);
556: } else {
557:
558: asyncDepositHashes[depositHash] = false;
559:
560: token.safeTransfer(request.user, request.tokens);
561:
562: emit DepositRefunded(depositHash);
563: }
564: }
['583']
583: function _solveDepositVaultFixedPrice( // <= FOUND
584: IERC20 token,
585: uint256 depositMultiplier,
586: Request calldata request,
587: uint256 priceAge,
588: uint256 index
589: ) internal returns (uint256 solverTip) {
590:
591: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
592:
593: bytes32 depositHash = _getRequestHash(token, request);
594:
595: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0;
596:
597: if (request.deadline >= block.timestamp) { // <= FOUND
598:
599: uint256 tokensNeeded = _unitsToTokensCeilIfActive(token, request.units, depositMultiplier);
600:
601: if (_guardAmountBound(request.tokens, tokensNeeded, index)) return 0;
602:
603: if (_guardDepositCapExceeded(request.units, index)) return 0;
604:
605:
606: asyncDepositHashes[depositHash] = false;
607:
608: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter(
609: address(this), token, tokensNeeded, request.units, request.user
610: );
611:
612: unchecked {
613: solverTip = request.tokens - tokensNeeded;
614: }
615:
616:
617: emit DepositSolved(depositHash);
618: } else {
619:
620: asyncDepositHashes[depositHash] = false;
621:
622: token.safeTransfer(request.user, request.tokens);
623:
624: emit DepositRefunded(depositHash);
625: }
626: }
['643']
643: function _solveRedeemVaultAutoPrice( // <= FOUND
644: IERC20 token,
645: uint256 redeemMultiplier,
646: Request calldata request,
647: uint256 priceAge,
648: uint256 index
649: ) internal returns (uint256 solverTip) {
650:
651: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
652:
653: bytes32 redeemHash = _getRequestHash(token, request);
654:
655: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0;
656:
657: if (request.deadline >= block.timestamp) { // <= FOUND
658: solverTip = request.solverTip;
659:
660:
661: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
662:
663: if (_guardInsufficientTokensForTip(tokenOut, solverTip, index)) return 0;
664:
665: uint256 tokenOutAfterTip;
666: unchecked {
667: tokenOutAfterTip = tokenOut - solverTip;
668: }
669:
670:
671: if (_guardAmountBound(tokenOutAfterTip, request.tokens, index)) return 0;
672:
673:
674: asyncRedeemHashes[redeemHash] = false;
675:
676: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit(
677: address(this), token, tokenOut, request.units, address(this)
678: );
679:
680:
681: token.safeTransfer(request.user, tokenOutAfterTip);
682:
683:
684: emit RedeemSolved(redeemHash);
685: } else {
686:
687: asyncRedeemHashes[redeemHash] = false;
688:
689: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
690:
691: emit RedeemRefunded(redeemHash);
692: }
693: }
['710']
710: function _solveRedeemVaultFixedPrice( // <= FOUND
711: IERC20 token,
712: uint256 redeemMultiplier,
713: Request calldata request,
714: uint256 priceAge,
715: uint256 index
716: ) internal returns (uint256 solverTip) {
717:
718: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
719:
720: bytes32 redeemHash = _getRequestHash(token, request);
721:
722: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0;
723:
724: if (request.deadline >= block.timestamp) { // <= FOUND
725:
726: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
727:
728: if (_guardAmountBound(tokenOut, request.tokens, index)) return 0;
729:
730:
731: asyncRedeemHashes[redeemHash] = false;
732:
733: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit(
734: address(this), token, tokenOut, request.units, address(this)
735: );
736:
737: token.safeTransfer(request.user, request.tokens);
738:
739: unchecked {
740: solverTip = tokenOut - request.tokens;
741: }
742:
743:
744: emit RedeemSolved(redeemHash);
745: } else {
746:
747: asyncRedeemHashes[redeemHash] = false;
748:
749: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
750:
751: emit RedeemRefunded(redeemHash);
752: }
753: }
['764']
764: function _solveDepositDirect(IERC20 token, Request calldata request) internal { // <= FOUND
765: bytes32 depositHash = _getRequestHash(token, request);
766:
767: if (!asyncDepositHashes[depositHash]) {
768:
769: emit InvalidRequestHash(depositHash);
770: return;
771: }
772:
773:
774: asyncDepositHashes[depositHash] = false;
775:
776: if (request.deadline >= block.timestamp) { // <= FOUND
777:
778: IERC20(MULTI_DEPOSITOR_VAULT).safeTransferFrom(msg.sender, request.user, request.units);
779:
780:
781: token.safeTransfer(msg.sender, request.tokens);
782:
783:
784: emit DepositSolved(depositHash);
785: } else {
786:
787: token.safeTransfer(request.user, request.tokens);
788:
789:
790: emit DepositRefunded(depositHash);
791: }
792: }
['803']
803: function _solveRedeemDirect(IERC20 token, Request calldata request) internal { // <= FOUND
804: bytes32 redeemHash = _getRequestHash(token, request);
805:
806: if (!asyncRedeemHashes[redeemHash]) {
807:
808: emit InvalidRequestHash(redeemHash);
809: return;
810: }
811:
812:
813: asyncRedeemHashes[redeemHash] = false;
814:
815: if (request.deadline >= block.timestamp) { // <= FOUND
816:
817: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(msg.sender, request.units);
818:
819:
820: token.safeTransferFrom(msg.sender, request.user, request.tokens);
821:
822:
823: emit RedeemSolved(redeemHash);
824: } else {
825:
826: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
827:
828:
829: emit RedeemRefunded(redeemHash);
830: }
831: }
['114']
114: function commitOracleUpdate(address base, address quote) external { // <= FOUND
115: OracleData storage oracleData = _oracles[base][quote];
116:
117: IOracle pendingOracle = oracleData.pendingOracle;
118:
119:
120: require(pendingOracle != IOracle(address(0)), AeraPeriphery__NoPendingOracleUpdate());
121:
122: require(oracleData.commitTimestamp <= block.timestamp, AeraPeriphery__CommitTimestampNotReached()); // <= FOUND
123:
124:
125: oracleData.commitTimestamp = 0;
126: oracleData.pendingOracle = IOracle(address(0));
127: oracleData.oracle = pendingOracle;
128: oracleData.isScheduledForUpdate = false;
129: oracleData.isDisabled = false;
130:
131:
132: _validateOracle(pendingOracle, base, quote);
133:
134:
135: emit OracleSet(base, quote, oracleData.oracle);
136: }
Utilizing msg.sig
for validation in if
or require
statements can be precarious in Solidity because msg.sig
only includes the first four bytes of the hash of the function signature, and not the entire signature. This means an attacker can feasibly brute force to find another function signature that generates the same first four bytes of the hash, thereby bypassing the intended security mechanism. This is known as a hash collision and is possible due to the limited size of msg.sig
. A safer alternative is to rely on role-based access control or specific address-based permissions, which are far less susceptible to such vulnerabilities.
Num of instances: 1
Click to show findings
['65']
65:
66: require(msg.sig == selector, Aera__UnauthorizedCallback()); // <= FOUND
In Solidity, when call
or delegatecall
is used to interact with another contract, it's possible that the called contract function doesn't return any data. This could be due to various reasons, such as the function is not implemented, or the function encountered an error. If the calling contract then tries to directly access elements in the returned data without first checking the length of the returned data, it could lead to a runtime error, or in some cases, incorrect logic execution.
Therefore, it's advisable to have a length check and revert the transaction if results.length
is 0. This ensures the function doesn't proceed with potentially unsafe or invalid data. It also provides a clear error message to the caller, indicating that the called function did not return any data, which assists in troubleshooting.
Num of instances: 1
Click to show findings
['359']
359: function _executeSubmit(bytes32 root, CalldataReader reader, bool isCalledFromCallback) // <= FOUND
360: internal
361: returns (Approval[] memory approvals, uint256 approvalsLength, bytes[] memory results, CalldataReader newReader)
362: {
363: uint256 operationsLength;
364: (reader, operationsLength) = reader.readU8();
365:
366: results = new bytes[](operationsLength);
367:
368:
369: approvals = new Approval[](operationsLength);
370:
371:
372:
373: OperationContext memory ctx;
374: for (uint256 i = 0; i < operationsLength; ++i) {
375: (reader, ctx.target) = reader.readAddr();
376:
377: bytes memory callData;
378: (reader, callData) = reader.readBytesToMemory();
379:
380: reader = callData.pipe(reader, results);
381:
382: bool isStaticCall;
383: (reader, isStaticCall) = reader.readBool();
384: if (isStaticCall) {
385:
386: (bool success, bytes memory result) = ctx.target.staticcall(callData);
387:
388: require(success, Aera__SubmissionFailed(i, result));
389:
390: results[i] = result;
391: } else {
392: ctx.selector = bytes4(callData);
393: if (_isAllowanceSelector(ctx.selector)) {
394: unchecked {
395: approvals[approvalsLength++] =
396: Approval({ token: ctx.target, spender: _extractApprovalSpender(callData) });
397: }
398: }
399:
400: (reader, ctx.callbackData) = _processExpectedCallback(reader, root);
401:
402: bytes memory extractedData;
403:
404: (reader, extractedData, ctx.configurableOperationHooks, ctx.operationHooks) =
405: _processBeforeOperationHooks(reader, callData, i);
406:
407: bytes32[] memory proof;
408: (reader, proof) = reader.readBytes32Array();
409:
410: (reader, ctx.value) = reader.readOptionalU256();
411:
412:
413: _verifyOperation(proof, root, _createMerkleLeaf(ctx, extractedData));
414:
415:
416: (bool success, bytes memory result) = ctx.target.call{ value: ctx.value }(callData);
417:
418: require(success, Aera__SubmissionFailed(i, result));
419:
420: if (ctx.callbackData != 0) {
421:
422: require(_hasCallbackBeenCalled(), Aera__ExpectedCallbackNotReceived());
423:
424: if (!isCalledFromCallback) {
425:
426: Approval[] memory callbackApprovals = _getCallbackApprovals();
427:
428:
429: _noPendingApprovalsInvariant(callbackApprovals, callbackApprovals.length);
430: }
431: }
432:
433:
434: _afterOperationHooks(ctx.operationHooks, callData, i);
435:
436: results[i] = result;
437: }
438: }
439:
440: return (approvals, approvalsLength, results, reader); // <= FOUND
441: }
Performing token transfers in a loop in a Solidity contract is generally not recommended due to various reasons. One of these reasons is the "Fail-Silently" issue.
In a Solidity loop, if one transfer operation fails, it causes the entire transaction to fail. This issue can be particularly troublesome when you're dealing with multiple transfers in one transaction. For instance, if you're looping through an array of recipients to distribute dividends or rewards, a single failed transfer will prevent all the subsequent recipients from receiving their transfers. This could be due to the recipient contract throwing an exception or due to other issues like a gas limit being exceeded.
Moreover, such a design could also inadvertently lead to a situation where a malicious contract intentionally causes a failure when receiving Ether to prevent other participants from getting their rightful transfers. This could open up avenues for griefing attacks in the system.
Resolution: To mitigate this problem, it's typically recommended to follow the "withdraw pattern" in your contracts instead of pushing payments. In this model, each recipient would be responsible for triggering their own payment. This separates each transfer operation, so a failure in one doesn't impact the others. Additionally, it greatly reduces the chance of malicious interference as the control over fund withdrawal lies with the intended recipient and not with an external loop operation.
Num of instances: 4
Click to show findings
['50']
50: for (uint256 i = 0; i < length; ++i) {
51: tokenAmount = tokenAmounts[i];
52:
53: tokenAmount.token.safeTransfer(msg.sender, tokenAmount.amount); // <= FOUND
54: }
['50']
50: for (uint256 i = 0; i < length; ++i) {
51: tokenAmount = tokenAmounts[i];
52:
53: tokenAmount.token.safeTransfer(msg.sender, tokenAmount.amount); // <= FOUND
54: }
['30']
30: for (uint256 i = 0; i < length; ++i) {
31: tokenAmount = tokenAmounts[i];
32:
33: tokenAmount.token.safeTransferFrom(msg.sender, address(this), tokenAmount.amount); // <= FOUND
34:
35:
36: require(
37: tokenAmount.token.allowance(msg.sender, address(this)) == 0,
38: Aera__UnexpectedTokenAllowance(tokenAmount.token.allowance(msg.sender, address(this)))
39: );
40: }
['30']
30: for (uint256 i = 0; i < length; ++i) {
31: tokenAmount = tokenAmounts[i];
32:
33: tokenAmount.token.safeTransferFrom(msg.sender, address(this), tokenAmount.amount); // <= FOUND
34:
35:
36: require(
37: tokenAmount.token.allowance(msg.sender, address(this)) == 0,
38: Aera__UnexpectedTokenAllowance(tokenAmount.token.allowance(msg.sender, address(this)))
39: );
40: }
Many 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: 7
Click to show findings
['105']
105: function claimFees() external onlyFeeRecipient returns (uint256 feeRecipientFees, uint256 protocolFees) { // <= FOUND
106: address protocolFeeRecipient;
107:
108:
109: (feeRecipientFees, protocolFees, protocolFeeRecipient) =
110: feeCalculator.claimFees(FEE_TOKEN.balanceOf(address(this)));
111:
112:
113: require(feeRecipientFees != 0, Aera__NoFeesToClaim());
114:
115:
116: FEE_TOKEN.safeTransfer(msg.sender, feeRecipientFees);
117:
118: emit FeesClaimed(msg.sender, feeRecipientFees);
119:
120: if (protocolFees != 0) { // <= FOUND
121:
122: FEE_TOKEN.safeTransfer(protocolFeeRecipient, protocolFees);
123:
124: emit ProtocolFeesClaimed(protocolFeeRecipient, protocolFees);
125: }
126: }
['129']
129: function claimProtocolFees() external returns (uint256 protocolFees) { // <= FOUND
130: address protocolFeeRecipient;
131:
132:
133: (protocolFees, protocolFeeRecipient) = feeCalculator.claimProtocolFees(FEE_TOKEN.balanceOf(address(this)));
134:
135:
136: require(msg.sender == protocolFeeRecipient, Aera__CallerIsNotProtocolFeeRecipient());
137:
138:
139: require(protocolFees != 0, Aera__NoFeesToClaim());
140:
141:
142: FEE_TOKEN.safeTransfer(protocolFeeRecipient, protocolFees); // <= FOUND
143:
144: emit ProtocolFeesClaimed(protocolFeeRecipient, protocolFees);
145: }
['78']
78: function exit(address sender, IERC20 token, uint256 tokenAmount, uint256 unitsAmount, address recipient) // <= FOUND
79: external
80: whenNotPaused
81: onlyProvisioner
82: {
83:
84: _burn(sender, unitsAmount);
85:
86:
87: if (tokenAmount > 0) token.safeTransfer(recipient, tokenAmount); // <= FOUND
88:
89:
90: emit Exit(sender, recipient, token, tokenAmount, unitsAmount);
91: }
['514']
514: function _solveDepositVaultAutoPrice( // <= FOUND
515: IERC20 token,
516: uint256 depositMultiplier,
517: Request calldata request,
518: uint256 priceAge,
519: uint256 index
520: ) internal returns (uint256 solverTip) {
521:
522: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
523:
524: bytes32 depositHash = _getRequestHash(token, request);
525:
526: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0;
527:
528: if (request.deadline >= block.timestamp) {
529: solverTip = request.solverTip;
530: uint256 tokens = request.tokens;
531:
532:
533: if (_guardInsufficientTokensForTip(tokens, solverTip, index)) return 0;
534:
535: uint256 tokensAfterTip;
536: unchecked {
537: tokensAfterTip = tokens - solverTip;
538: }
539:
540:
541: uint256 unitsOut = _tokensToUnitsFloorIfActive(token, tokensAfterTip, depositMultiplier);
542:
543: if (_guardAmountBound(unitsOut, request.units, index)) return 0;
544:
545: if (_guardDepositCapExceeded(unitsOut, index)) return 0;
546:
547:
548: asyncDepositHashes[depositHash] = false;
549:
550: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter(
551: address(this), token, tokensAfterTip, unitsOut, request.user
552: );
553:
554:
555: emit DepositSolved(depositHash);
556: } else {
557:
558: asyncDepositHashes[depositHash] = false;
559:
560: token.safeTransfer(request.user, request.tokens); // <= FOUND
561:
562: emit DepositRefunded(depositHash);
563: }
564: }
['583']
583: function _solveDepositVaultFixedPrice( // <= FOUND
584: IERC20 token,
585: uint256 depositMultiplier,
586: Request calldata request,
587: uint256 priceAge,
588: uint256 index
589: ) internal returns (uint256 solverTip) {
590:
591: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
592:
593: bytes32 depositHash = _getRequestHash(token, request);
594:
595: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0;
596:
597: if (request.deadline >= block.timestamp) {
598:
599: uint256 tokensNeeded = _unitsToTokensCeilIfActive(token, request.units, depositMultiplier);
600:
601: if (_guardAmountBound(request.tokens, tokensNeeded, index)) return 0;
602:
603: if (_guardDepositCapExceeded(request.units, index)) return 0;
604:
605:
606: asyncDepositHashes[depositHash] = false;
607:
608: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter(
609: address(this), token, tokensNeeded, request.units, request.user
610: );
611:
612: unchecked {
613: solverTip = request.tokens - tokensNeeded;
614: }
615:
616:
617: emit DepositSolved(depositHash);
618: } else {
619:
620: asyncDepositHashes[depositHash] = false;
621:
622: token.safeTransfer(request.user, request.tokens); // <= FOUND
623:
624: emit DepositRefunded(depositHash);
625: }
626: }
['643']
643: function _solveRedeemVaultAutoPrice( // <= FOUND
644: IERC20 token,
645: uint256 redeemMultiplier,
646: Request calldata request,
647: uint256 priceAge,
648: uint256 index
649: ) internal returns (uint256 solverTip) {
650:
651: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
652:
653: bytes32 redeemHash = _getRequestHash(token, request);
654:
655: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0;
656:
657: if (request.deadline >= block.timestamp) {
658: solverTip = request.solverTip;
659:
660:
661: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
662:
663: if (_guardInsufficientTokensForTip(tokenOut, solverTip, index)) return 0;
664:
665: uint256 tokenOutAfterTip;
666: unchecked {
667: tokenOutAfterTip = tokenOut - solverTip;
668: }
669:
670:
671: if (_guardAmountBound(tokenOutAfterTip, request.tokens, index)) return 0;
672:
673:
674: asyncRedeemHashes[redeemHash] = false;
675:
676: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit(
677: address(this), token, tokenOut, request.units, address(this)
678: );
679:
680:
681: token.safeTransfer(request.user, tokenOutAfterTip); // <= FOUND
682:
683:
684: emit RedeemSolved(redeemHash);
685: } else {
686:
687: asyncRedeemHashes[redeemHash] = false;
688:
689: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
690:
691: emit RedeemRefunded(redeemHash);
692: }
693: }
['710']
710: function _solveRedeemVaultFixedPrice( // <= FOUND
711: IERC20 token,
712: uint256 redeemMultiplier,
713: Request calldata request,
714: uint256 priceAge,
715: uint256 index
716: ) internal returns (uint256 solverTip) {
717:
718: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
719:
720: bytes32 redeemHash = _getRequestHash(token, request);
721:
722: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0;
723:
724: if (request.deadline >= block.timestamp) {
725:
726: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
727:
728: if (_guardAmountBound(tokenOut, request.tokens, index)) return 0;
729:
730:
731: asyncRedeemHashes[redeemHash] = false;
732:
733: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit(
734: address(this), token, tokenOut, request.units, address(this)
735: );
736:
737: token.safeTransfer(request.user, request.tokens); // <= FOUND
738:
739: unchecked {
740: solverTip = tokenOut - request.tokens;
741: }
742:
743:
744: emit RedeemSolved(redeemHash);
745: } else {
746:
747: asyncRedeemHashes[redeemHash] = false;
748:
749: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
750:
751: emit RedeemRefunded(redeemHash);
752: }
753: }
Without access control on receive/payable fallback functions in a contract, anyone can send Ether (ETH) to the contract's address. If there's no way to withdraw those funds defined within the contract, any Ether sent, whether intentionally or by mistake, will be permanently stuck. This could lead to unintended loss of funds. Implementing proper access control ensures that only authorized addresses can interact with these functions. Resolution could involve adding access control mechanisms, like Ownable or specific permission requirements, or creating a withdrawal function accessible only to the contract's owner, thus preventing unintentional loss of funds.
Num of instances: 1
When making external calls, the called contract can intentionally or unintentionally consume all provided gas, leading to unintended transaction reversion. To mitigate this risk, it's crucial to specify a gas limit when making the call. By using addr.call{gas: <amount>}("")
, you allocate a specific amount of gas to the external call, ensuring the parent transaction has gas left for post-call operations. This approach safeguards against malevolent contracts aiming to exhaust gas and provides greater control over transaction execution.
Num of instances: 4
Click to show findings
['416']
416:
417: (bool success, bytes memory result) = ctx.target.call{ value: ctx.value }(callData); // <= FOUND
['71']
71:
72:
73: (success, result) = operation.target.call{ value: operation.value }(operation.data); // <= FOUND
['27']
27:
28: (bool success,) = msg.sender.call{ value: amount }(""); // <= FOUND
['43']
43:
44:
45: (bool success, bytes memory result) = operation.target.call{ value: operation.value }(operation.data); // <= FOUND
[Low-26] 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: 3
Click to show findings
['94']
94: function setInitialPrice(address vault, uint128 price, uint32 timestamp) external requiresVaultAuth(vault) { // <= FOUND
95: require(price != 0, Aera__InvalidPrice());
96:
97: VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
98:
99:
100:
101: require(vaultPriceState.maxPriceAge != 0, Aera__ThresholdNotSet());
102: require(vaultPriceState.unitPrice == 0, Aera__VaultAlreadyInitialized());
103:
104:
105:
106: require(block.timestamp - timestamp <= vaultPriceState.maxPriceAge, Aera__StalePrice());
107:
108: uint32 timestampU32 = uint32(block.timestamp);
109:
110:
111: vaultPriceState.unitPrice = price;
112: vaultPriceState.highestPrice = price;
113: vaultPriceState.timestamp = timestampU32;
114: vaultPriceState.accrualLag = 0;
115: vaultPriceState.lastTotalSupply = IERC20(vault).totalSupply().toUint128(); // <= FOUND
116:
117:
118: emit UnitPriceUpdated(vault, price, timestampU32); // <= FOUND
119: }
['24']
24: function sweep(address token, uint256 amount) external requiresAuth { // <= FOUND
25: if (token == address(0)) {
26:
27: (bool success,) = msg.sender.call{ value: amount }("");
28:
29: require(success, Aera__FailedToSendNativeToken());
30: } else {
31:
32: IERC20(token).safeTransfer(msg.sender, amount); // <= FOUND
33: }
34:
35:
36: emit Sweep(token, amount); // <= FOUND
37: }
['99']
99: function cancelSell( // <= FOUND
100: address milkmanOrderContract,
101: uint256 sellAmount,
102: IERC20 sellToken,
103: IERC20 receiveToken,
104: bytes32 appData,
105: address priceChecker,
106: bytes calldata priceCheckerData
107: ) external onlyVault nonReentrant {
108:
109: IMilkman(milkmanOrderContract).cancelSwap( // <= FOUND
110: sellAmount, sellToken, receiveToken, address(this), appData, priceChecker, priceCheckerData
111: );
112:
113:
114: sellToken.safeTransfer(vault, sellAmount);
115:
116:
117: emit SellCancelled( // <= FOUND
118: milkmanOrderContract, sellAmount, sellToken, receiveToken, appData, priceChecker, priceCheckerData
119: );
120: }
Function 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
['359']
359: function _executeSubmit(bytes32 root, CalldataReader reader, bool isCalledFromCallback) // <= FOUND
360: internal
361: returns (Approval[] memory approvals, uint256 approvalsLength, bytes[] memory results, CalldataReader newReader)
362: {
363: uint256 operationsLength;
364: (reader, operationsLength) = reader.readU8();
365:
366: results = new bytes[](operationsLength);
367:
368:
369: approvals = new Approval[](operationsLength);
370:
371:
372:
373: OperationContext memory ctx;
374: for (uint256 i = 0; i < operationsLength; ++i) {
375: (reader, ctx.target) = reader.readAddr();
376:
377: bytes memory callData;
378: (reader, callData) = reader.readBytesToMemory();
379:
380: reader = callData.pipe(reader, results); // <= FOUND
381:
382: bool isStaticCall;
383: (reader, isStaticCall) = reader.readBool();
384: if (isStaticCall) {
385:
386: (bool success, bytes memory result) = ctx.target.staticcall(callData);
387:
388: require(success, Aera__SubmissionFailed(i, result));
389:
390: results[i] = result;
391: } else {
392: ctx.selector = bytes4(callData);
393: if (_isAllowanceSelector(ctx.selector)) {
394: unchecked {
395: approvals[approvalsLength++] =
396: Approval({ token: ctx.target, spender: _extractApprovalSpender(callData) });
397: }
398: }
399:
400: (reader, ctx.callbackData) = _processExpectedCallback(reader, root);
401:
402: bytes memory extractedData;
403:
404: (reader, extractedData, ctx.configurableOperationHooks, ctx.operationHooks) =
405: _processBeforeOperationHooks(reader, callData, i);
406:
407: bytes32[] memory proof;
408: (reader, proof) = reader.readBytes32Array();
409:
410: (reader, ctx.value) = reader.readOptionalU256();
411:
412:
413: _verifyOperation(proof, root, _createMerkleLeaf(ctx, extractedData));
414:
415:
416: (bool success, bytes memory result) = ctx.target.call{ value: ctx.value }(callData);
417:
418: require(success, Aera__SubmissionFailed(i, result));
419:
420: if (ctx.callbackData != 0) {
421:
422: require(_hasCallbackBeenCalled(), Aera__ExpectedCallbackNotReceived());
423:
424: if (!isCalledFromCallback) {
425:
426: Approval[] memory callbackApprovals = _getCallbackApprovals();
427:
428:
429: _noPendingApprovalsInvariant(callbackApprovals, callbackApprovals.length);
430: }
431: }
432:
433:
434: _afterOperationHooks(ctx.operationHooks, callData, i);
435:
436: results[i] = result;
437: }
438: }
439:
440: return (approvals, approvalsLength, results, reader);
441: }
['38']
38: function extract(bytes memory callData, uint256 calldataOffsetsPacked, uint256 calldataOffsetsCount) // <= FOUND
39: internal
40: pure
41: returns (bytes memory)
42: {
43: unchecked {
44:
45: require(calldataOffsetsCount < MAX_EXTRACT_OFFSETS_EXCLUSIVE, Aera__ExtractionNumberTooLarge());
46:
47: bytes memory result = new bytes(calldataOffsetsCount * WORD_SIZE);
48:
49: uint256 resultPtr;
50: assembly ("memory-safe") {
51: resultPtr := result
52: }
53:
54: resultPtr += WORD_SIZE;
55:
56: uint256 callDataLength = callData.length;
57:
58: require(callDataLength >= MINIMUM_CALLDATA_LENGTH, Aera__CalldataTooShort());
59:
60:
61: uint256 maxValidOffset = callDataLength - WORD_SIZE;
62:
63: uint256 calldataPointer;
64: assembly ("memory-safe") {
65: calldataPointer := callData
66: }
67:
68: calldataPointer += WORD_SIZE;
69:
70: uint256 resultWriteOffset;
71: for (uint256 i = 0; i < calldataOffsetsCount; ++i) {
72: uint256 extractionOffset = (calldataOffsetsPacked >> EXTRACTION_OFFSET_SHIFT_BITS) + SELECTOR_SIZE;
73:
74:
75: require(extractionOffset <= maxValidOffset, Aera__OffsetOutOfBounds());
76:
77: uint256 calldataOffsetPointer = calldataPointer + extractionOffset;
78:
79:
80:
81: bytes32 extracted;
82: assembly ("memory-safe") {
83: extracted := mload(calldataOffsetPointer)
84: }
85:
86:
87:
88: uint256 resultOffsetPointer = resultPtr + resultWriteOffset;
89: assembly ("memory-safe") {
90: mstore(resultOffsetPointer, extracted)
91: }
92:
93: resultWriteOffset += WORD_SIZE;
94: calldataOffsetsPacked = calldataOffsetsPacked << EXTRACT_OFFSET_SIZE_BITS; // <= FOUND
95: }
96:
97: return result;
98: }
99: }
['390']
390: function _convertTokenToUnits( // <= FOUND
391: address vault,
392: IERC20 token,
393: uint256 tokenAmount,
394: uint256 unitPrice,
395: Math.Rounding rounding
396: ) internal view returns (uint256 unitsAmount) {
397: if (address(token) != NUMERAIRE) {
398: tokenAmount = ORACLE_REGISTRY.getQuoteForUser(tokenAmount, address(token), NUMERAIRE, vault); // <= FOUND
399: }
400:
401: return Math.mulDiv(tokenAmount, UNIT_PRICE_PRECISION, unitPrice, rounding);
402: }
It is preferable to skip operations on an array index when a condition is not met rather than reverting the whole transaction as reverting can introduce the possiblity of malicous actors purposefully introducing array objects which fail conditional checks within for/while loops so group operations fail. As such it is recommended to simply skip such array indices over reverting unless there is a valid security or logic reason behind not doing so.
Num of instances: 4
Click to show findings
['364']
364: for (uint256 i = 0; i < length; i++) { // <= FOUND
365: if (_isRequestTypeDeposit(requests[i].requestType)) {
366:
367: require(tokenDetails.asyncDepositEnabled, Aera__AsyncDepositDisabled());
368:
369: require(!_isRequestTypeAutoPrice(requests[i].requestType), Aera__AutoPriceSolveNotAllowed()); // <= FOUND
370:
371: _solveDepositDirect(token, requests[i]);
372: } else {
373:
374: require(tokenDetails.asyncRedeemEnabled, Aera__AsyncRedeemDisabled());
375:
376: require(!_isRequestTypeAutoPrice(requests[i].requestType), Aera__AutoPriceSolveNotAllowed()); // <= FOUND
377:
378: _solveRedeemDirect(token, requests[i]);
379: }
380: }
['364']
364: for (uint256 i = 0; i < length; i++) { // <= FOUND
365: if (_isRequestTypeDeposit(requests[i].requestType)) {
366:
367: require(tokenDetails.asyncDepositEnabled, Aera__AsyncDepositDisabled());
368:
369: require(!_isRequestTypeAutoPrice(requests[i].requestType), Aera__AutoPriceSolveNotAllowed()); // <= FOUND
370:
371: _solveDepositDirect(token, requests[i]);
372: } else {
373:
374: require(tokenDetails.asyncRedeemEnabled, Aera__AsyncRedeemDisabled());
375:
376: require(!_isRequestTypeAutoPrice(requests[i].requestType), Aera__AutoPriceSolveNotAllowed()); // <= FOUND
377:
378: _solveRedeemDirect(token, requests[i]);
379: }
380: }
['41']
41: for (uint256 i; i < length; ++i) { // <= FOUND
42: target = operations[i].target;
43: data = operations[i].data;
44: targetAndSelector = _packTargetSig(target, bytes4(data));
45:
46:
47: require( // <= FOUND
48: callerCapabilities[targetAndSelector], AeraPeriphery__Unauthorized(msg.sender, target, bytes4(data))
49: );
50:
51:
52: (bool success, bytes memory returnData) = target.call(data);
53: Address.verifyCallResultFromTarget(target, success, returnData);
54: }
['41']
41: for (uint256 i; i < length; ++i) { // <= FOUND
42: target = operations[i].target;
43: data = operations[i].data;
44: targetAndSelector = _packTargetSig(target, bytes4(data));
45:
46:
47: require( // <= FOUND
48: callerCapabilities[targetAndSelector], AeraPeriphery__Unauthorized(msg.sender, target, bytes4(data))
49: );
50:
51:
52: (bool success, bytes memory returnData) = target.call(data);
53: Address.verifyCallResultFromTarget(target, success, returnData);
54: }
Low-level calls in Solidity, when made to addresses without contract code, don't fail but return a successful status. This behavior can be misleading, leading to unintended consequences in dApps. Ignoring this can potentially mean acting on false positive results. To address this, apart from the conventional zero-address check, developers should verify the existence of contract code at the target address by ensuring that the code length at the specified address (<address>.code.length
) is greater than zero. By doing so, it provides a more robust validation before executing low-level calls, safeguarding against unintentional interactions with empty addresses.
Num of instances: 10
Click to show findings
['34']
34: function execute(TargetCalldata[] calldata operations) external nonReentrant { // <= FOUND
35: address target;
36: bytes memory data;
37: bytes32 targetAndSelector;
38: mapping(bytes32 targetAndSelector => bool enabled) storage callerCapabilities = _canCall[msg.sender];
39:
40: uint256 length = operations.length;
41: for (uint256 i; i < length; ++i) {
42: target = operations[i].target;
43: data = operations[i].data;
44: targetAndSelector = _packTargetSig(target, bytes4(data));
45:
46:
47: require(
48: callerCapabilities[targetAndSelector], AeraPeriphery__Unauthorized(msg.sender, target, bytes4(data))
49: );
50:
51:
52: (bool success, bytes memory returnData) = target.call(data); // <= FOUND
53: Address.verifyCallResultFromTarget(target, success, returnData);
54: }
55:
56:
57: emit Executed(msg.sender, operations);
58: }
['278']
278: function _beforeSubmitHooks(address hooks, bytes calldata data) internal {
279: if (_hasBeforeHooks(hooks)) {
280:
281: (bool success, bytes memory result) =
282: hooks.call(abi.encodeWithSelector(ISubmitHooks.beforeSubmit.selector, data, msg.sender)); // <= FOUND
283:
284: require(success, Aera__BeforeSubmitHooksFailed(result));
285: }
286: }
['291']
291: function _afterSubmitHooks(address hooks, bytes calldata data) internal {
292: if (_hasAfterHooks(hooks)) {
293:
294: (bool success, bytes memory result) =
295: hooks.call(abi.encodeWithSelector(ISubmitHooks.afterSubmit.selector, data, msg.sender)); // <= FOUND
296:
297: require(success, Aera__AfterSubmitHooksFailed(result));
298: }
299: }
['306']
306: function _beforeOperationHooks(address operationHooks, bytes memory data, uint256 i)
307: internal
308: returns (bytes memory result)
309: {
310: if (_hasBeforeHooks(operationHooks)) {
311:
312: _setHookCallType(HookCallType.BEFORE);
313:
314:
315: (bool success, bytes memory returnValue) = operationHooks.call(data); // <= FOUND
316:
317: require(success, Aera__BeforeOperationHooksFailed(i, returnValue));
318:
319:
320: require(returnValue.length % WORD_SIZE == 0, Aera__InvalidBeforeOperationHooksReturnDataLength());
321:
322:
323: _setHookCallType(HookCallType.NONE);
324:
325:
326:
327: (result) = abi.decode(returnValue, (bytes));
328: }
329: }
['335']
335: function _afterOperationHooks(address operationHooks, bytes memory data, uint256 i) internal {
336: if (_hasAfterHooks(operationHooks)) {
337:
338: _setHookCallType(HookCallType.AFTER);
339:
340:
341: (bool success, bytes memory result) = operationHooks.call(data); // <= FOUND
342:
343: require(success, Aera__AfterOperationHooksFailed(i, result));
344:
345:
346: _setHookCallType(HookCallType.NONE);
347: }
348: }
['34']
34: function execute(TargetCalldata[] calldata operations) external nonReentrant {
35: address target;
36: bytes memory data;
37: bytes32 targetAndSelector;
38: mapping(bytes32 targetAndSelector => bool enabled) storage callerCapabilities = _canCall[msg.sender];
39:
40: uint256 length = operations.length;
41: for (uint256 i; i < length; ++i) {
42: target = operations[i].target;
43: data = operations[i].data;
44: targetAndSelector = _packTargetSig(target, bytes4(data));
45:
46:
47: require(
48: callerCapabilities[targetAndSelector], AeraPeriphery__Unauthorized(msg.sender, target, bytes4(data))
49: );
50:
51:
52: (bool success, bytes memory returnData) = target.call(data); // <= FOUND
53: Address.verifyCallResultFromTarget(target, success, returnData);
54: }
55:
56:
57: emit Executed(msg.sender, operations);
58: }
['359']
359: function _executeSubmit(bytes32 root, CalldataReader reader, bool isCalledFromCallback)
360: internal
361: returns (Approval[] memory approvals, uint256 approvalsLength, bytes[] memory results, CalldataReader newReader)
362: {
363: uint256 operationsLength;
364: (reader, operationsLength) = reader.readU8();
365:
366: results = new bytes[](operationsLength);
367:
368:
369: approvals = new Approval[](operationsLength);
370:
371:
372:
373: OperationContext memory ctx;
374: for (uint256 i = 0; i < operationsLength; ++i) {
375: (reader, ctx.target) = reader.readAddr();
376:
377: bytes memory callData;
378: (reader, callData) = reader.readBytesToMemory();
379:
380: reader = callData.pipe(reader, results);
381:
382: bool isStaticCall;
383: (reader, isStaticCall) = reader.readBool();
384: if (isStaticCall) {
385:
386: (bool success, bytes memory result) = ctx.target.staticcall(callData);
387:
388: require(success, Aera__SubmissionFailed(i, result));
389:
390: results[i] = result;
391: } else {
392: ctx.selector = bytes4(callData);
393: if (_isAllowanceSelector(ctx.selector)) {
394: unchecked {
395: approvals[approvalsLength++] =
396: Approval({ token: ctx.target, spender: _extractApprovalSpender(callData) });
397: }
398: }
399:
400: (reader, ctx.callbackData) = _processExpectedCallback(reader, root);
401:
402: bytes memory extractedData;
403:
404: (reader, extractedData, ctx.configurableOperationHooks, ctx.operationHooks) =
405: _processBeforeOperationHooks(reader, callData, i);
406:
407: bytes32[] memory proof;
408: (reader, proof) = reader.readBytes32Array();
409:
410: (reader, ctx.value) = reader.readOptionalU256();
411:
412:
413: _verifyOperation(proof, root, _createMerkleLeaf(ctx, extractedData));
414:
415:
416: (bool success, bytes memory result) = ctx.target.call{ value: ctx.value }(callData); // <= FOUND
417:
418: require(success, Aera__SubmissionFailed(i, result));
419:
420: if (ctx.callbackData != 0) {
421:
422: require(_hasCallbackBeenCalled(), Aera__ExpectedCallbackNotReceived());
423:
424: if (!isCalledFromCallback) {
425:
426: Approval[] memory callbackApprovals = _getCallbackApprovals();
427:
428:
429: _noPendingApprovalsInvariant(callbackApprovals, callbackApprovals.length);
430: }
431: }
432:
433:
434: _afterOperationHooks(ctx.operationHooks, callData, i);
435:
436: results[i] = result;
437: }
438: }
439:
440: return (approvals, approvalsLength, results, reader);
441: }
['61']
61: function execute(OperationPayable[] calldata operations) external requiresAuth {
62: bool success;
63: bytes memory result;
64: OperationPayable calldata operation;
65: uint256 length = operations.length;
66: for (uint256 i = 0; i < length; ++i) {
67: operation = operations[i];
68:
69:
70:
71: (success, result) = operation.target.call{ value: operation.value }(operation.data); // <= FOUND
72:
73:
74: require(success, Aera__ExecutionFailed(i, result));
75: }
76:
77:
78: emit Executed(msg.sender, operations);
79: }
['24']
24: function sweep(address token, uint256 amount) external requiresAuth {
25: if (token == address(0)) {
26:
27: (bool success,) = msg.sender.call{ value: amount }(""); // <= FOUND
28:
29: require(success, Aera__FailedToSendNativeToken());
30: } else {
31:
32: IERC20(token).safeTransfer(msg.sender, amount);
33: }
34:
35:
36: emit Sweep(token, amount);
37: }
['37']
37: function _executeOperation(OperationPayable calldata operation) internal virtual {
38:
39: _checkOperation(operation);
40:
41:
42:
43: (bool success, bytes memory result) = operation.target.call{ value: operation.value }(operation.data); // <= FOUND
44:
45:
46:
47:
48: require(success, AeraPeriphery__ExecutionFailed(result));
49:
50:
51: emit Executed(msg.sender, operation);
52: }
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: 4
Click to show findings
['278']
278: function _beforeSubmitHooks(address hooks, bytes calldata data) internal { // <= FOUND
279: if (_hasBeforeHooks(hooks)) {
280:
281: (bool success, bytes memory result) =
282: hooks.call(abi.encodeWithSelector(ISubmitHooks.beforeSubmit.selector, data, msg.sender)); // <= FOUND
283:
284: require(success, Aera__BeforeSubmitHooksFailed(result));
285: }
286: }
['291']
291: function _afterSubmitHooks(address hooks, bytes calldata data) internal { // <= FOUND
292: if (_hasAfterHooks(hooks)) {
293:
294: (bool success, bytes memory result) =
295: hooks.call(abi.encodeWithSelector(ISubmitHooks.afterSubmit.selector, data, msg.sender)); // <= FOUND
296:
297: require(success, Aera__AfterSubmitHooksFailed(result));
298: }
299: }
['306']
306: function _beforeOperationHooks(address operationHooks, bytes memory data, uint256 i) // <= FOUND
307: internal
308: returns (bytes memory result)
309: {
310: if (_hasBeforeHooks(operationHooks)) {
311:
312: _setHookCallType(HookCallType.BEFORE);
313:
314:
315: (bool success, bytes memory returnValue) = operationHooks.call(data); // <= FOUND
316:
317: require(success, Aera__BeforeOperationHooksFailed(i, returnValue));
318:
319:
320: require(returnValue.length % WORD_SIZE == 0, Aera__InvalidBeforeOperationHooksReturnDataLength());
321:
322:
323: _setHookCallType(HookCallType.NONE);
324:
325:
326:
327: (result) = abi.decode(returnValue, (bytes));
328: }
329: }
['335']
335: function _afterOperationHooks(address operationHooks, bytes memory data, uint256 i) internal { // <= FOUND
336: if (_hasAfterHooks(operationHooks)) {
337:
338: _setHookCallType(HookCallType.AFTER);
339:
340:
341: (bool success, bytes memory result) = operationHooks.call(data); // <= FOUND
342:
343: require(success, Aera__AfterOperationHooksFailed(i, result));
344:
345:
346: _setHookCallType(HookCallType.NONE);
347: }
348: }
Not selecting a license for a software project, particularly in the context of blockchain and smart contracts, poses significant risks and uncertainties. A license explicitly defines how others can use, modify, distribute, and contribute to your code. Without it, the legal status of a project remains unclear, potentially deterring usage and contributions, and raising concerns about intellectual property rights. To mitigate these risks, it is advisable to choose an appropriate open-source license. This fosters a clear understanding of how your project can be utilized and encourages collaboration and adoption within the community, aligning with the open and collaborative nature of blockchain technology.
Num of instances: 1
Inconsistent usage of block global values in expiry logic, marked by a mix of '<' and '<=' comparisons, can lead to unpredictable and unreliable contract behavior. This inconsistency may arise in conditions where block number or timestamp is used to determine the expiration of a transaction or a contract state. Using both '<' and '<=' for similar checks can create ambiguity and errors in contract execution, potentially leading to unexpected outcomes. To ensure reliability and clarity, it's essential to standardize the comparison operators in expiry logic. Consistently using either '<' or '<=' across the contract helps maintain predictable behavior and avoids potential off-by-one errors, enhancing the contract's overall robustness and user trust.
Num of instances: 1
Click to show findings
['35']
35: contract Provisioner is IProvisioner, Auth2Step, ReentrancyGuardTransient {
36: using SafeERC20 for IERC20;
37: using BitMaps for BitMaps.BitMap;
164: require(refundableUntil >= block.timestamp, Aera__RefundPeriodExpired()); // <= FOUND
193: require(deadline > block.timestamp, Aera__DeadlineInPast()); // <= FOUND
234: require(deadline > block.timestamp, Aera__DeadlineInPast()); // <= FOUND
451: return userUnitsRefundableUntil[user] >= block.timestamp; // <= FOUND
528: if (request.deadline >= block.timestamp) { // <= FOUND
597: if (request.deadline >= block.timestamp) { // <= FOUND
657: if (request.deadline >= block.timestamp) { // <= FOUND
724: if (request.deadline >= block.timestamp) { // <= FOUND
776: if (request.deadline >= block.timestamp) { // <= FOUND
815: if (request.deadline >= block.timestamp) { // <= FOUND
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: 4
Click to show findings
['64']
64: constructor(IERC20 numeraire, IOracleRegistry oracleRegistry, address owner_, Authority authority_)
65: BaseFeeCalculator(owner_, authority_)
66: HasNumeraire(address(numeraire))
67: {
68:
69: require(address(oracleRegistry) != address(0), Aera__ZeroAddressOracleRegistry());
70:
71:
72: ORACLE_REGISTRY = oracleRegistry; // <= FOUND
73: }
['88']
88: constructor(
89: IPriceAndFeeCalculator priceAndFeeCalculator,
90: address multiDepositorVault,
91: address owner_,
92: Authority authority_
93: ) Auth2Step(owner_, authority_) {
94:
95: require(address(priceAndFeeCalculator) != address(0), Aera__ZeroAddressPriceAndFeeCalculator());
96: require(multiDepositorVault != address(0), Aera__ZeroAddressMultiDepositorVault());
97:
98:
99: PRICE_FEE_CALCULATOR = priceAndFeeCalculator; // <= FOUND
100: MULTI_DEPOSITOR_VAULT = multiDepositorVault;
101: }
['16']
16: constructor(address _owner, Authority _authority) {
17: owner = _owner; // <= FOUND
18: authority = _authority;
19:
20: emit OwnershipTransferred(msg.sender, _owner);
21: emit AuthorityUpdated(msg.sender, _authority);
22: }
['21']
21: constructor(IChainalysisSanctionsOracle oracle_) {
22:
23: require(address(oracle_) != address(0), AeraPeriphery__ZeroAddressBlacklistOracle());
24:
25:
26: BLACKLIST_ORACLE = oracle_; // <= FOUND
27: }
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: 1
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
Click to show findings
['50']
50: contract BaseVault is IBaseVault, Pausable, CallbackHandler, ReentrancyGuardTransient, Auth2Step, IERC721Receiver
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: 15
Click to show findings
['105']
105: function claimFees() external onlyFeeRecipient returns (uint256 feeRecipientFees, uint256 protocolFees) { // <= FOUND
106: address protocolFeeRecipient;
107:
108:
109: (feeRecipientFees, protocolFees, protocolFeeRecipient) =
110: feeCalculator.claimFees(FEE_TOKEN.balanceOf(address(this)));
111:
112:
113: require(feeRecipientFees != 0, Aera__NoFeesToClaim());
114:
115:
116: FEE_TOKEN.safeTransfer(msg.sender, feeRecipientFees); // <= FOUND
117:
118: emit FeesClaimed(msg.sender, feeRecipientFees);
119:
120: if (protocolFees != 0) {
121:
122: FEE_TOKEN.safeTransfer(protocolFeeRecipient, protocolFees);
123:
124: emit ProtocolFeesClaimed(protocolFeeRecipient, protocolFees);
125: }
126: }
['129']
129: function claimProtocolFees() external returns (uint256 protocolFees) { // <= FOUND
130: address protocolFeeRecipient;
131:
132:
133: (protocolFees, protocolFeeRecipient) = feeCalculator.claimProtocolFees(FEE_TOKEN.balanceOf(address(this)));
134:
135:
136: require(msg.sender == protocolFeeRecipient, Aera__CallerIsNotProtocolFeeRecipient());
137:
138:
139: require(protocolFees != 0, Aera__NoFeesToClaim());
140:
141:
142: FEE_TOKEN.safeTransfer(protocolFeeRecipient, protocolFees); // <= FOUND
143:
144: emit ProtocolFeesClaimed(protocolFeeRecipient, protocolFees);
145: }
['62']
62: function enter(address sender, IERC20 token, uint256 tokenAmount, uint256 unitsAmount, address recipient) // <= FOUND
63: external
64: whenNotPaused
65: onlyProvisioner
66: {
67:
68: if (tokenAmount > 0) token.safeTransferFrom(sender, address(this), tokenAmount); // <= FOUND
69:
70:
71: _mint(recipient, unitsAmount);
72:
73:
74: emit Enter(sender, recipient, token, tokenAmount, unitsAmount);
75: }
['78']
78: function exit(address sender, IERC20 token, uint256 tokenAmount, uint256 unitsAmount, address recipient) // <= FOUND
79: external
80: whenNotPaused
81: onlyProvisioner
82: {
83:
84: _burn(sender, unitsAmount);
85:
86:
87: if (tokenAmount > 0) token.safeTransfer(recipient, tokenAmount); // <= FOUND
88:
89:
90: emit Exit(sender, recipient, token, tokenAmount, unitsAmount);
91: }
['180']
180: function requestDeposit( // <= FOUND
181: IERC20 token,
182: uint256 tokensIn,
183: uint256 minUnitsOut,
184: uint256 solverTip,
185: uint256 deadline,
186: uint256 maxPriceAge,
187: bool isFixedPrice
188: ) external anyoneButVault {
189:
190:
191: require(tokensIn != 0, Aera__TokensInZero());
192: require(minUnitsOut != 0, Aera__MinUnitsOutZero());
193: require(deadline > block.timestamp, Aera__DeadlineInPast());
194: unchecked {
195: require(deadline - block.timestamp <= MAX_SECONDS_TO_DEADLINE, Aera__DeadlineTooFarInFuture());
196: }
197: require(tokensDetails[token].asyncDepositEnabled, Aera__AsyncDepositDisabled());
198: require(!PRICE_FEE_CALCULATOR.isVaultPaused(MULTI_DEPOSITOR_VAULT), Aera__PriceAndFeeCalculatorVaultPaused());
199: require(solverTip == 0 || !isFixedPrice, Aera__FixedPriceSolverTipNotAllowed());
200:
201:
202: token.safeTransferFrom(msg.sender, address(this), tokensIn); // <= FOUND
203:
204: RequestType requestType = isFixedPrice ? RequestType.DEPOSIT_FIXED_PRICE : RequestType.DEPOSIT_AUTO_PRICE;
205:
206: bytes32 depositHash = _getRequestHashParams(
207: token, msg.sender, requestType, tokensIn, minUnitsOut, solverTip, deadline, maxPriceAge
208: );
209:
210: require(!asyncDepositHashes[depositHash], Aera__HashCollision());
211:
212: asyncDepositHashes[depositHash] = true;
213:
214:
215: emit DepositRequested(
216: msg.sender, token, tokensIn, minUnitsOut, solverTip, deadline, maxPriceAge, isFixedPrice, depositHash
217: );
218: }
['221']
221: function requestRedeem( // <= FOUND
222: IERC20 token,
223: uint256 unitsIn,
224: uint256 minTokensOut,
225: uint256 solverTip,
226: uint256 deadline,
227: uint256 maxPriceAge,
228: bool isFixedPrice
229: ) external anyoneButVault {
230:
231:
232: require(unitsIn != 0, Aera__UnitsInZero());
233: require(minTokensOut != 0, Aera__MinTokenOutZero());
234: require(deadline > block.timestamp, Aera__DeadlineInPast());
235: unchecked {
236: require(deadline - block.timestamp <= MAX_SECONDS_TO_DEADLINE, Aera__DeadlineTooFarInFuture());
237: }
238: require(tokensDetails[token].asyncRedeemEnabled, Aera__AsyncRedeemDisabled());
239: require(!PRICE_FEE_CALCULATOR.isVaultPaused(MULTI_DEPOSITOR_VAULT), Aera__PriceAndFeeCalculatorVaultPaused());
240: require(solverTip == 0 || !isFixedPrice, Aera__FixedPriceSolverTipNotAllowed());
241:
242:
243: IERC20(MULTI_DEPOSITOR_VAULT).safeTransferFrom(msg.sender, address(this), unitsIn); // <= FOUND
244:
245: RequestType requestType = isFixedPrice ? RequestType.REDEEM_FIXED_PRICE : RequestType.REDEEM_AUTO_PRICE;
246:
247: bytes32 redeemHash = _getRequestHashParams(
248: token, msg.sender, requestType, minTokensOut, unitsIn, solverTip, deadline, maxPriceAge
249: );
250:
251: require(!asyncRedeemHashes[redeemHash], Aera__HashCollision());
252:
253: asyncRedeemHashes[redeemHash] = true;
254:
255:
256: emit RedeemRequested(
257: msg.sender, token, minTokensOut, unitsIn, solverTip, deadline, maxPriceAge, isFixedPrice, redeemHash
258: );
259: }
['262']
262: function refundRequest(IERC20 token, Request calldata request) external nonReentrant { // <= FOUND
263:
264: require(
265: request.deadline < block.timestamp || isAuthorized(msg.sender, msg.sig),
266: Aera__DeadlineInFutureAndUnauthorized()
267: );
268:
269: bytes32 requestHash = _getRequestHash(token, request);
270:
271: if (_isRequestTypeDeposit(request.requestType)) {
272:
273: require(asyncDepositHashes[requestHash], Aera__HashNotFound());
274:
275: asyncDepositHashes[requestHash] = false;
276:
277: token.safeTransfer(request.user, request.tokens); // <= FOUND
278:
279: emit DepositRefunded(requestHash);
280: } else {
281:
282: require(asyncRedeemHashes[requestHash], Aera__HashNotFound());
283:
284: asyncRedeemHashes[requestHash] = false;
285:
286: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
287:
288: emit RedeemRefunded(requestHash);
289: }
290: }
['764']
764: function _solveDepositDirect(IERC20 token, Request calldata request) internal { // <= FOUND
765: bytes32 depositHash = _getRequestHash(token, request);
766:
767: if (!asyncDepositHashes[depositHash]) {
768:
769: emit InvalidRequestHash(depositHash);
770: return;
771: }
772:
773:
774: asyncDepositHashes[depositHash] = false;
775:
776: if (request.deadline >= block.timestamp) {
777:
778: IERC20(MULTI_DEPOSITOR_VAULT).safeTransferFrom(msg.sender, request.user, request.units);
779:
780:
781: token.safeTransfer(msg.sender, request.tokens); // <= FOUND
782:
783:
784: emit DepositSolved(depositHash);
785: } else {
786:
787: token.safeTransfer(request.user, request.tokens);
788:
789:
790: emit DepositRefunded(depositHash);
791: }
792: }
['803']
803: function _solveRedeemDirect(IERC20 token, Request calldata request) internal { // <= FOUND
804: bytes32 redeemHash = _getRequestHash(token, request);
805:
806: if (!asyncRedeemHashes[redeemHash]) {
807:
808: emit InvalidRequestHash(redeemHash);
809: return;
810: }
811:
812:
813: asyncRedeemHashes[redeemHash] = false;
814:
815: if (request.deadline >= block.timestamp) {
816:
817: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(msg.sender, request.units); // <= FOUND
818:
819:
820: token.safeTransferFrom(msg.sender, request.user, request.tokens);
821:
822:
823: emit RedeemSolved(redeemHash);
824: } else {
825:
826: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
827:
828:
829: emit RedeemRefunded(redeemHash);
830: }
831: }
['27']
27: function deposit(TokenAmount[] calldata tokenAmounts) external requiresAuth { // <= FOUND
28: TokenAmount calldata tokenAmount;
29: uint256 length = tokenAmounts.length;
30: for (uint256 i = 0; i < length; ++i) {
31: tokenAmount = tokenAmounts[i];
32:
33: tokenAmount.token.safeTransferFrom(msg.sender, address(this), tokenAmount.amount); // <= FOUND
34:
35:
36: require(
37: tokenAmount.token.allowance(msg.sender, address(this)) == 0,
38: Aera__UnexpectedTokenAllowance(tokenAmount.token.allowance(msg.sender, address(this)))
39: );
40: }
41:
42:
43: emit Deposited(msg.sender, tokenAmounts);
44: }
['47']
47: function withdraw(TokenAmount[] calldata tokenAmounts) external requiresAuth { // <= FOUND
48: TokenAmount calldata tokenAmount;
49: uint256 length = tokenAmounts.length;
50: for (uint256 i = 0; i < length; ++i) {
51: tokenAmount = tokenAmounts[i];
52:
53: tokenAmount.token.safeTransfer(msg.sender, tokenAmount.amount); // <= FOUND
54: }
55:
56:
57: emit Withdrawn(msg.sender, tokenAmounts);
58: }
['24']
24: function sweep(address token, uint256 amount) external requiresAuth { // <= FOUND
25: if (token == address(0)) {
26:
27: (bool success,) = msg.sender.call{ value: amount }("");
28:
29: require(success, Aera__FailedToSendNativeToken());
30: } else {
31:
32: IERC20(token).safeTransfer(msg.sender, amount); // <= FOUND
33: }
34:
35:
36: emit Sweep(token, amount);
37: }
['65']
65: function requestSell( // <= FOUND
66: uint256 sellAmount,
67: IERC20 sellToken,
68: IERC20 receiveToken,
69: bytes32 appData,
70: address priceChecker,
71: bytes calldata priceCheckerData
72: ) external onlyVault nonReentrant {
73:
74: require(sellAmount > 0, AeraPeriphery__MilkmanRouter__SellAmountIsZero());
75:
76:
77: sellToken.safeTransferFrom(msg.sender, address(this), sellAmount); // <= FOUND
78:
79:
80: sellToken.safeIncreaseAllowance(address(milkmanRoot), sellAmount);
81:
82:
83: milkmanRoot.requestSwapExactTokensForTokens(
84: sellAmount, sellToken, receiveToken, address(this), appData, priceChecker, priceCheckerData
85: );
86:
87:
88:
89: require(
90: sellToken.allowance(address(this), address(milkmanRoot)) == 0,
91: AeraPeriphery__MilkmanRequestSwapExactTokensForTokensFailed(sellToken)
92: );
93:
94:
95: emit SellRequested(sellAmount, sellToken, receiveToken, appData, priceChecker, priceCheckerData);
96: }
['99']
99: function cancelSell( // <= FOUND
100: address milkmanOrderContract,
101: uint256 sellAmount,
102: IERC20 sellToken,
103: IERC20 receiveToken,
104: bytes32 appData,
105: address priceChecker,
106: bytes calldata priceCheckerData
107: ) external onlyVault nonReentrant {
108:
109: IMilkman(milkmanOrderContract).cancelSwap(
110: sellAmount, sellToken, receiveToken, address(this), appData, priceChecker, priceCheckerData
111: );
112:
113:
114: sellToken.safeTransfer(vault, sellAmount); // <= FOUND
115:
116:
117: emit SellCancelled(
118: milkmanOrderContract, sellAmount, sellToken, receiveToken, appData, priceChecker, priceCheckerData
119: );
120: }
['123']
123: function claim(IERC20 token) external onlyVault nonReentrant { // <= FOUND
124: uint256 balance = token.balanceOf(address(this));
125:
126:
127:
128: if (balance == 0) return;
129:
130:
131: token.safeTransfer(vault, balance); // <= FOUND
132:
133:
134: emit Claimed(token, balance);
135: }
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: 22
Click to show findings
['105']
105: function claimFees() external onlyFeeRecipient returns (uint256 feeRecipientFees, uint256 protocolFees) { // <= FOUND
106: address protocolFeeRecipient;
107:
108:
109: (feeRecipientFees, protocolFees, protocolFeeRecipient) =
110: feeCalculator.claimFees(FEE_TOKEN.balanceOf(address(this)));
111:
112:
113: require(feeRecipientFees != 0, Aera__NoFeesToClaim());
114:
115:
116: FEE_TOKEN.safeTransfer(msg.sender, feeRecipientFees); // <= FOUND
117:
118: emit FeesClaimed(msg.sender, feeRecipientFees);
119:
120: if (protocolFees != 0) {
121:
122: FEE_TOKEN.safeTransfer(protocolFeeRecipient, protocolFees);
123:
124: emit ProtocolFeesClaimed(protocolFeeRecipient, protocolFees);
125: }
126: }
['129']
129: function claimProtocolFees() external returns (uint256 protocolFees) { // <= FOUND
130: address protocolFeeRecipient;
131:
132:
133: (protocolFees, protocolFeeRecipient) = feeCalculator.claimProtocolFees(FEE_TOKEN.balanceOf(address(this)));
134:
135:
136: require(msg.sender == protocolFeeRecipient, Aera__CallerIsNotProtocolFeeRecipient());
137:
138:
139: require(protocolFees != 0, Aera__NoFeesToClaim());
140:
141:
142: FEE_TOKEN.safeTransfer(protocolFeeRecipient, protocolFees); // <= FOUND
143:
144: emit ProtocolFeesClaimed(protocolFeeRecipient, protocolFees);
145: }
['78']
78: function exit(address sender, IERC20 token, uint256 tokenAmount, uint256 unitsAmount, address recipient) // <= FOUND
79: external
80: whenNotPaused
81: onlyProvisioner
82: {
83:
84: _burn(sender, unitsAmount);
85:
86:
87: if (tokenAmount > 0) token.safeTransfer(recipient, tokenAmount); // <= FOUND
88:
89:
90: emit Exit(sender, recipient, token, tokenAmount, unitsAmount);
91: }
['514']
514: function _solveDepositVaultAutoPrice( // <= FOUND
515: IERC20 token,
516: uint256 depositMultiplier,
517: Request calldata request,
518: uint256 priceAge,
519: uint256 index
520: ) internal returns (uint256 solverTip) {
521:
522: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
523:
524: bytes32 depositHash = _getRequestHash(token, request);
525:
526: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0;
527:
528: if (request.deadline >= block.timestamp) {
529: solverTip = request.solverTip;
530: uint256 tokens = request.tokens;
531:
532:
533: if (_guardInsufficientTokensForTip(tokens, solverTip, index)) return 0;
534:
535: uint256 tokensAfterTip;
536: unchecked {
537: tokensAfterTip = tokens - solverTip;
538: }
539:
540:
541: uint256 unitsOut = _tokensToUnitsFloorIfActive(token, tokensAfterTip, depositMultiplier);
542:
543: if (_guardAmountBound(unitsOut, request.units, index)) return 0;
544:
545: if (_guardDepositCapExceeded(unitsOut, index)) return 0;
546:
547:
548: asyncDepositHashes[depositHash] = false;
549:
550: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter(
551: address(this), token, tokensAfterTip, unitsOut, request.user
552: );
553:
554:
555: emit DepositSolved(depositHash);
556: } else {
557:
558: asyncDepositHashes[depositHash] = false;
559:
560: token.safeTransfer(request.user, request.tokens); // <= FOUND
561:
562: emit DepositRefunded(depositHash);
563: }
564: }
['583']
583: function _solveDepositVaultFixedPrice( // <= FOUND
584: IERC20 token,
585: uint256 depositMultiplier,
586: Request calldata request,
587: uint256 priceAge,
588: uint256 index
589: ) internal returns (uint256 solverTip) {
590:
591: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
592:
593: bytes32 depositHash = _getRequestHash(token, request);
594:
595: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0;
596:
597: if (request.deadline >= block.timestamp) {
598:
599: uint256 tokensNeeded = _unitsToTokensCeilIfActive(token, request.units, depositMultiplier);
600:
601: if (_guardAmountBound(request.tokens, tokensNeeded, index)) return 0;
602:
603: if (_guardDepositCapExceeded(request.units, index)) return 0;
604:
605:
606: asyncDepositHashes[depositHash] = false;
607:
608: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter(
609: address(this), token, tokensNeeded, request.units, request.user
610: );
611:
612: unchecked {
613: solverTip = request.tokens - tokensNeeded;
614: }
615:
616:
617: emit DepositSolved(depositHash);
618: } else {
619:
620: asyncDepositHashes[depositHash] = false;
621:
622: token.safeTransfer(request.user, request.tokens); // <= FOUND
623:
624: emit DepositRefunded(depositHash);
625: }
626: }
['643']
643: function _solveRedeemVaultAutoPrice( // <= FOUND
644: IERC20 token,
645: uint256 redeemMultiplier,
646: Request calldata request,
647: uint256 priceAge,
648: uint256 index
649: ) internal returns (uint256 solverTip) {
650:
651: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
652:
653: bytes32 redeemHash = _getRequestHash(token, request);
654:
655: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0;
656:
657: if (request.deadline >= block.timestamp) {
658: solverTip = request.solverTip;
659:
660:
661: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
662:
663: if (_guardInsufficientTokensForTip(tokenOut, solverTip, index)) return 0;
664:
665: uint256 tokenOutAfterTip;
666: unchecked {
667: tokenOutAfterTip = tokenOut - solverTip;
668: }
669:
670:
671: if (_guardAmountBound(tokenOutAfterTip, request.tokens, index)) return 0;
672:
673:
674: asyncRedeemHashes[redeemHash] = false;
675:
676: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit(
677: address(this), token, tokenOut, request.units, address(this)
678: );
679:
680:
681: token.safeTransfer(request.user, tokenOutAfterTip); // <= FOUND
682:
683:
684: emit RedeemSolved(redeemHash);
685: } else {
686:
687: asyncRedeemHashes[redeemHash] = false;
688:
689: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
690:
691: emit RedeemRefunded(redeemHash);
692: }
693: }
['710']
710: function _solveRedeemVaultFixedPrice( // <= FOUND
711: IERC20 token,
712: uint256 redeemMultiplier,
713: Request calldata request,
714: uint256 priceAge,
715: uint256 index
716: ) internal returns (uint256 solverTip) {
717:
718: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
719:
720: bytes32 redeemHash = _getRequestHash(token, request);
721:
722: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0;
723:
724: if (request.deadline >= block.timestamp) {
725:
726: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
727:
728: if (_guardAmountBound(tokenOut, request.tokens, index)) return 0;
729:
730:
731: asyncRedeemHashes[redeemHash] = false;
732:
733: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit(
734: address(this), token, tokenOut, request.units, address(this)
735: );
736:
737: token.safeTransfer(request.user, request.tokens); // <= FOUND
738:
739: unchecked {
740: solverTip = tokenOut - request.tokens;
741: }
742:
743:
744: emit RedeemSolved(redeemHash);
745: } else {
746:
747: asyncRedeemHashes[redeemHash] = false;
748:
749: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
750:
751: emit RedeemRefunded(redeemHash);
752: }
753: }
['764']
764: function _solveDepositDirect(IERC20 token, Request calldata request) internal { // <= FOUND
765: bytes32 depositHash = _getRequestHash(token, request);
766:
767: if (!asyncDepositHashes[depositHash]) {
768:
769: emit InvalidRequestHash(depositHash);
770: return;
771: }
772:
773:
774: asyncDepositHashes[depositHash] = false;
775:
776: if (request.deadline >= block.timestamp) {
777:
778: IERC20(MULTI_DEPOSITOR_VAULT).safeTransferFrom(msg.sender, request.user, request.units);
779:
780:
781: token.safeTransfer(msg.sender, request.tokens); // <= FOUND
782:
783:
784: emit DepositSolved(depositHash);
785: } else {
786:
787: token.safeTransfer(request.user, request.tokens);
788:
789:
790: emit DepositRefunded(depositHash);
791: }
792: }
['803']
803: function _solveRedeemDirect(IERC20 token, Request calldata request) internal { // <= FOUND
804: bytes32 redeemHash = _getRequestHash(token, request);
805:
806: if (!asyncRedeemHashes[redeemHash]) {
807:
808: emit InvalidRequestHash(redeemHash);
809: return;
810: }
811:
812:
813: asyncRedeemHashes[redeemHash] = false;
814:
815: if (request.deadline >= block.timestamp) { // <= FOUND
816:
817: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(msg.sender, request.units);
818:
819:
820: token.safeTransferFrom(msg.sender, request.user, request.tokens);
821:
822:
823: emit RedeemSolved(redeemHash);
824: } else {
825:
826: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
827:
828:
829: emit RedeemRefunded(redeemHash);
830: }
831: }
['47']
47: function withdraw(TokenAmount[] calldata tokenAmounts) external requiresAuth { // <= FOUND
48: TokenAmount calldata tokenAmount;
49: uint256 length = tokenAmounts.length;
50: for (uint256 i = 0; i < length; ++i) {
51: tokenAmount = tokenAmounts[i];
52:
53: tokenAmount.token.safeTransfer(msg.sender, tokenAmount.amount); // <= FOUND
54: }
55:
56:
57: emit Withdrawn(msg.sender, tokenAmounts);
58: }
['24']
24: function sweep(address token, uint256 amount) external requiresAuth { // <= FOUND
25: if (token == address(0)) {
26:
27: (bool success,) = msg.sender.call{ value: amount }("");
28:
29: require(success, Aera__FailedToSendNativeToken());
30: } else { // <= FOUND
31:
32: IERC20(token).safeTransfer(msg.sender, amount);
33: }
34:
35:
36: emit Sweep(token, amount);
37: }
['105']
105: function claimFees() external onlyFeeRecipient returns (uint256 feeRecipientFees, uint256 protocolFees) {
106: address protocolFeeRecipient;
107:
108:
109: (feeRecipientFees, protocolFees, protocolFeeRecipient) =
110: feeCalculator.claimFees(FEE_TOKEN.balanceOf(address(this)));
111:
112:
113: require(feeRecipientFees != 0, Aera__NoFeesToClaim());
114:
115:
116: FEE_TOKEN.safeTransfer(msg.sender, feeRecipientFees); // <= FOUND
117:
118: emit FeesClaimed(msg.sender, feeRecipientFees);
119:
120: if (protocolFees != 0) {
121:
122: FEE_TOKEN.safeTransfer(protocolFeeRecipient, protocolFees); // <= FOUND
123:
124: emit ProtocolFeesClaimed(protocolFeeRecipient, protocolFees);
125: }
126: }
['129']
129: function claimProtocolFees() external returns (uint256 protocolFees) {
130: address protocolFeeRecipient;
131:
132:
133: (protocolFees, protocolFeeRecipient) = feeCalculator.claimProtocolFees(FEE_TOKEN.balanceOf(address(this)));
134:
135:
136: require(msg.sender == protocolFeeRecipient, Aera__CallerIsNotProtocolFeeRecipient());
137:
138:
139: require(protocolFees != 0, Aera__NoFeesToClaim());
140:
141:
142: FEE_TOKEN.safeTransfer(protocolFeeRecipient, protocolFees); // <= FOUND
143:
144: emit ProtocolFeesClaimed(protocolFeeRecipient, protocolFees);
145: }
['78']
78: function exit(address sender, IERC20 token, uint256 tokenAmount, uint256 unitsAmount, address recipient)
79: external
80: whenNotPaused
81: onlyProvisioner
82: {
83:
84: _burn(sender, unitsAmount);
85:
86:
87: if (tokenAmount > 0) token.safeTransfer(recipient, tokenAmount); // <= FOUND
88:
89:
90: emit Exit(sender, recipient, token, tokenAmount, unitsAmount);
91: }
['514']
514: function _solveDepositVaultAutoPrice(
515: IERC20 token,
516: uint256 depositMultiplier,
517: Request calldata request,
518: uint256 priceAge,
519: uint256 index
520: ) internal returns (uint256 solverTip) {
521:
522: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
523:
524: bytes32 depositHash = _getRequestHash(token, request);
525:
526: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0;
527:
528: if (request.deadline >= block.timestamp) {
529: solverTip = request.solverTip;
530: uint256 tokens = request.tokens;
531:
532:
533: if (_guardInsufficientTokensForTip(tokens, solverTip, index)) return 0;
534:
535: uint256 tokensAfterTip;
536: unchecked {
537: tokensAfterTip = tokens - solverTip;
538: }
539:
540:
541: uint256 unitsOut = _tokensToUnitsFloorIfActive(token, tokensAfterTip, depositMultiplier);
542:
543: if (_guardAmountBound(unitsOut, request.units, index)) return 0;
544:
545: if (_guardDepositCapExceeded(unitsOut, index)) return 0;
546:
547:
548: asyncDepositHashes[depositHash] = false;
549:
550: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter(
551: address(this), token, tokensAfterTip, unitsOut, request.user
552: );
553:
554:
555: emit DepositSolved(depositHash);
556: } else {
557:
558: asyncDepositHashes[depositHash] = false;
559:
560: token.safeTransfer(request.user, request.tokens); // <= FOUND
561:
562: emit DepositRefunded(depositHash);
563: }
564: }
['583']
583: function _solveDepositVaultFixedPrice(
584: IERC20 token,
585: uint256 depositMultiplier,
586: Request calldata request,
587: uint256 priceAge,
588: uint256 index
589: ) internal returns (uint256 solverTip) {
590:
591: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
592:
593: bytes32 depositHash = _getRequestHash(token, request);
594:
595: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0;
596:
597: if (request.deadline >= block.timestamp) {
598:
599: uint256 tokensNeeded = _unitsToTokensCeilIfActive(token, request.units, depositMultiplier);
600:
601: if (_guardAmountBound(request.tokens, tokensNeeded, index)) return 0;
602:
603: if (_guardDepositCapExceeded(request.units, index)) return 0;
604:
605:
606: asyncDepositHashes[depositHash] = false;
607:
608: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter(
609: address(this), token, tokensNeeded, request.units, request.user
610: );
611:
612: unchecked {
613: solverTip = request.tokens - tokensNeeded;
614: }
615:
616:
617: emit DepositSolved(depositHash);
618: } else {
619:
620: asyncDepositHashes[depositHash] = false;
621:
622: token.safeTransfer(request.user, request.tokens); // <= FOUND
623:
624: emit DepositRefunded(depositHash);
625: }
626: }
['643']
643: function _solveRedeemVaultAutoPrice(
644: IERC20 token,
645: uint256 redeemMultiplier,
646: Request calldata request,
647: uint256 priceAge,
648: uint256 index
649: ) internal returns (uint256 solverTip) {
650:
651: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
652:
653: bytes32 redeemHash = _getRequestHash(token, request);
654:
655: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0;
656:
657: if (request.deadline >= block.timestamp) {
658: solverTip = request.solverTip;
659:
660:
661: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
662:
663: if (_guardInsufficientTokensForTip(tokenOut, solverTip, index)) return 0;
664:
665: uint256 tokenOutAfterTip;
666: unchecked {
667: tokenOutAfterTip = tokenOut - solverTip;
668: }
669:
670:
671: if (_guardAmountBound(tokenOutAfterTip, request.tokens, index)) return 0;
672:
673:
674: asyncRedeemHashes[redeemHash] = false;
675:
676: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit(
677: address(this), token, tokenOut, request.units, address(this)
678: );
679:
680:
681: token.safeTransfer(request.user, tokenOutAfterTip); // <= FOUND
682:
683:
684: emit RedeemSolved(redeemHash);
685: } else {
686:
687: asyncRedeemHashes[redeemHash] = false;
688:
689: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units); // <= FOUND
690:
691: emit RedeemRefunded(redeemHash);
692: }
693: }
['710']
710: function _solveRedeemVaultFixedPrice(
711: IERC20 token,
712: uint256 redeemMultiplier,
713: Request calldata request,
714: uint256 priceAge,
715: uint256 index
716: ) internal returns (uint256 solverTip) {
717:
718: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
719:
720: bytes32 redeemHash = _getRequestHash(token, request);
721:
722: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0;
723:
724: if (request.deadline >= block.timestamp) {
725:
726: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
727:
728: if (_guardAmountBound(tokenOut, request.tokens, index)) return 0;
729:
730:
731: asyncRedeemHashes[redeemHash] = false;
732:
733: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit(
734: address(this), token, tokenOut, request.units, address(this)
735: );
736:
737: token.safeTransfer(request.user, request.tokens); // <= FOUND
738:
739: unchecked {
740: solverTip = tokenOut - request.tokens;
741: }
742:
743:
744: emit RedeemSolved(redeemHash);
745: } else {
746:
747: asyncRedeemHashes[redeemHash] = false;
748:
749: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units); // <= FOUND
750:
751: emit RedeemRefunded(redeemHash);
752: }
753: }
['764']
764: function _solveDepositDirect(IERC20 token, Request calldata request) internal {
765: bytes32 depositHash = _getRequestHash(token, request);
766:
767: if (!asyncDepositHashes[depositHash]) {
768:
769: emit InvalidRequestHash(depositHash);
770: return;
771: }
772:
773:
774: asyncDepositHashes[depositHash] = false;
775:
776: if (request.deadline >= block.timestamp) {
777:
778: IERC20(MULTI_DEPOSITOR_VAULT).safeTransferFrom(msg.sender, request.user, request.units);
779:
780:
781: token.safeTransfer(msg.sender, request.tokens); // <= FOUND
782:
783:
784: emit DepositSolved(depositHash);
785: } else {
786:
787: token.safeTransfer(request.user, request.tokens); // <= FOUND
788:
789:
790: emit DepositRefunded(depositHash);
791: }
792: }
['803']
803: function _solveRedeemDirect(IERC20 token, Request calldata request) internal {
804: bytes32 redeemHash = _getRequestHash(token, request);
805:
806: if (!asyncRedeemHashes[redeemHash]) {
807:
808: emit InvalidRequestHash(redeemHash);
809: return;
810: }
811:
812:
813: asyncRedeemHashes[redeemHash] = false;
814:
815: if (request.deadline >= block.timestamp) {
816:
817: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(msg.sender, request.units); // <= FOUND
818:
819:
820: token.safeTransferFrom(msg.sender, request.user, request.tokens);
821:
822:
823: emit RedeemSolved(redeemHash);
824: } else {
825:
826: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units); // <= FOUND
827:
828:
829: emit RedeemRefunded(redeemHash);
830: }
831: }
['47']
47: function withdraw(TokenAmount[] calldata tokenAmounts) external requiresAuth {
48: TokenAmount calldata tokenAmount;
49: uint256 length = tokenAmounts.length;
50: for (uint256 i = 0; i < length; ++i) {
51: tokenAmount = tokenAmounts[i];
52:
53: tokenAmount.token.safeTransfer(msg.sender, tokenAmount.amount); // <= FOUND
54: }
55:
56:
57: emit Withdrawn(msg.sender, tokenAmounts);
58: }
['24']
24: function sweep(address token, uint256 amount) external requiresAuth {
25: if (token == address(0)) {
26:
27: (bool success,) = msg.sender.call{ value: amount }("");
28:
29: require(success, Aera__FailedToSendNativeToken());
30: } else {
31:
32: IERC20(token).safeTransfer(msg.sender, amount); // <= FOUND
33: }
34:
35:
36: emit Sweep(token, amount);
37: }
When handling basis points (bps) in smart contracts, it's essential to validate that their values do not exceed 10,000, as 1 bps equals 0.01%, and 10,000 bps equate to 100%. Failing to check that bps values stay within this range can lead to calculations that mistakenly exceed intended limits, causing issues such as excessive fees or incorrect interest rates. Implementing a simple validation check to ensure bps do not surpass 10,000 will safeguard against such errors, maintaining the integrity of financial computations and preventing potential overcharges or contract misbehaviors.
Num of instances: 2
Click to show findings
['932']
932: function _tokensToUnitsFloorIfActive(IERC20 token, uint256 tokens, uint256 multiplier) // <= FOUND
933: internal
934: view
935: returns (uint256)
936: {
937: uint256 tokensAdjusted = tokens * multiplier / ONE_IN_BPS; // <= FOUND
938:
939: return PRICE_FEE_CALCULATOR.convertTokenToUnitsIfActive(
940: MULTI_DEPOSITOR_VAULT, token, tokensAdjusted, Math.Rounding.Floor // <= FOUND
941: );
942: }
['72']
72: function _handleMilkmanBeforeHook(address sellToken, uint256 sellAmount, address vault) internal { // <= FOUND
73: State storage state = _vaultStates[vault];
74:
75:
76: uint256 loss = _convertToNumeraire(sellAmount * state.maxSlippagePerTrade / MAX_BPS, sellToken); // <= FOUND
77:
78:
79: state.cumulativeDailyLossInNumeraire = uint128(_enforceDailyLoss(state, loss)); // <= FOUND
80: }
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: 15
Click to show findings
['154']
154: function setGuardianRoot(address guardian, bytes32 root) external virtual requiresAuth
['183']
183: function setSubmitHooks(ISubmitHooks newSubmitHooks) external virtual requiresAuth
['93']
93: function setProvisioner(address provisioner_) external requiresAuth
['99']
99: function setBeforeTransferHook(IBeforeTransferHook hook) external requiresAuth
['24']
24: function setIsVaultUnitsTransferable(address vault, bool isTransferable) external requiresVaultAuth(vault)
['540']
540: function _setHookCallType(HookCallType hookCallType) internal
['109']
109: function _update(address from, address to, uint256 amount) internal override
['30']
30: function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool)
['154']
154: function setGuardianRoot(address guardian, bytes32 root) external virtual requiresAuth { // <= FOUND
155:
156: _setGuardianRoot(guardian, root);
157: }
['183']
183: function setSubmitHooks(ISubmitHooks newSubmitHooks) external virtual requiresAuth { // <= FOUND
184:
185: _setSubmitHooks(newSubmitHooks);
186: }
['93']
93: function setProvisioner(address provisioner_) external requiresAuth { // <= FOUND
94:
95: _setProvisioner(provisioner_);
96: }
['99']
99: function setBeforeTransferHook(IBeforeTransferHook hook) external requiresAuth { // <= FOUND
100:
101: _setBeforeTransferHook(hook);
102: }
['24']
24: function setIsVaultUnitsTransferable(address vault, bool isTransferable) external requiresVaultAuth(vault) { // <= FOUND
25: _setIsVaultUnitsTransferable(vault, isTransferable);
26: }
['540']
540: function _setHookCallType(HookCallType hookCallType) internal { // <= FOUND
541:
542: HOOK_CALL_TYPE_SLOT.asUint256().tstore(uint8(hookCallType));
543: }
['109']
109: function _update(address from, address to, uint256 amount) internal override { // <= FOUND
110: IBeforeTransferHook hook = beforeTransferHook;
111: if (address(hook) != address(0)) {
112:
113: hook.beforeTransfer(from, to, provisioner);
114: }
115:
116:
117:
118:
119: require(
120: from == address(0) || to == address(0) || !IProvisioner(provisioner).areUserUnitsLocked(from),
121: Aera__UnitsLocked()
122: );
123:
124:
125: return super._update(from, to, amount);
126: }
A read-only reentrancy vulnerability arises when a contract's view
or pure
function is re-entered during its execution, potentially leading to inconsistent state readings or unintended behaviors. Although these functions don't modify the contract's state, they can still be exploited if they depend on external calls that may re-enter the contract. This can result in the function returning outdated or manipulated data, which, when relied upon by other contracts or systems, can cause incorrect operations or financial discrepancies.
To mitigate this risk, it's essential to implement reentrancy guards even in read-only functions, especially when they involve external calls. Additionally, adhering to the checks-effects-interactions pattern and minimizing external calls within view functions can further reduce the potential for such vulnerabilities. Regular security audits and thorough testing are also crucial to identify and address any reentrancy issues before deployment.
Num of instances: 16
Click to show findings
['180']
180: function requestDeposit(
181: IERC20 token,
182: uint256 tokensIn,
183: uint256 minUnitsOut,
184: uint256 solverTip,
185: uint256 deadline,
186: uint256 maxPriceAge,
187: bool isFixedPrice
188: ) external anyoneButVault {
189:
190:
191: require(tokensIn != 0, Aera__TokensInZero());
192: require(minUnitsOut != 0, Aera__MinUnitsOutZero());
193: require(deadline > block.timestamp, Aera__DeadlineInPast());
194: unchecked {
195: require(deadline - block.timestamp <= MAX_SECONDS_TO_DEADLINE, Aera__DeadlineTooFarInFuture());
196: }
197: require(tokensDetails[token].asyncDepositEnabled, Aera__AsyncDepositDisabled());
198: require(!PRICE_FEE_CALCULATOR.isVaultPaused(MULTI_DEPOSITOR_VAULT), Aera__PriceAndFeeCalculatorVaultPaused()); // <= FOUND
199: require(solverTip == 0 || !isFixedPrice, Aera__FixedPriceSolverTipNotAllowed());
200:
201:
202: token.safeTransferFrom(msg.sender, address(this), tokensIn);
203:
204: RequestType requestType = isFixedPrice ? RequestType.DEPOSIT_FIXED_PRICE : RequestType.DEPOSIT_AUTO_PRICE;
205:
206: bytes32 depositHash = _getRequestHashParams(
207: token, msg.sender, requestType, tokensIn, minUnitsOut, solverTip, deadline, maxPriceAge
208: );
209:
210: require(!asyncDepositHashes[depositHash], Aera__HashCollision());
211:
212: asyncDepositHashes[depositHash] = true;
213:
214:
215: emit DepositRequested(
216: msg.sender, token, tokensIn, minUnitsOut, solverTip, deadline, maxPriceAge, isFixedPrice, depositHash
217: );
218: }
['221']
221: function requestRedeem(
222: IERC20 token,
223: uint256 unitsIn,
224: uint256 minTokensOut,
225: uint256 solverTip,
226: uint256 deadline,
227: uint256 maxPriceAge,
228: bool isFixedPrice
229: ) external anyoneButVault {
230:
231:
232: require(unitsIn != 0, Aera__UnitsInZero());
233: require(minTokensOut != 0, Aera__MinTokenOutZero());
234: require(deadline > block.timestamp, Aera__DeadlineInPast());
235: unchecked {
236: require(deadline - block.timestamp <= MAX_SECONDS_TO_DEADLINE, Aera__DeadlineTooFarInFuture());
237: }
238: require(tokensDetails[token].asyncRedeemEnabled, Aera__AsyncRedeemDisabled());
239: require(!PRICE_FEE_CALCULATOR.isVaultPaused(MULTI_DEPOSITOR_VAULT), Aera__PriceAndFeeCalculatorVaultPaused()); // <= FOUND
240: require(solverTip == 0 || !isFixedPrice, Aera__FixedPriceSolverTipNotAllowed());
241:
242:
243: IERC20(MULTI_DEPOSITOR_VAULT).safeTransferFrom(msg.sender, address(this), unitsIn);
244:
245: RequestType requestType = isFixedPrice ? RequestType.REDEEM_FIXED_PRICE : RequestType.REDEEM_AUTO_PRICE;
246:
247: bytes32 redeemHash = _getRequestHashParams(
248: token, msg.sender, requestType, minTokensOut, unitsIn, solverTip, deadline, maxPriceAge
249: );
250:
251: require(!asyncRedeemHashes[redeemHash], Aera__HashCollision());
252:
253: asyncRedeemHashes[redeemHash] = true;
254:
255:
256: emit RedeemRequested(
257: msg.sender, token, minTokensOut, unitsIn, solverTip, deadline, maxPriceAge, isFixedPrice, redeemHash
258: );
259: }
['169']
169: function checkGuardianWhitelist(address guardian) external returns (bool isRemoved) {
170:
171: if (!WHITELIST.isWhitelisted(guardian)) { // <= FOUND
172:
173: guardianRoots.remove(guardian);
174:
175: isRemoved = true;
176:
177:
178: emit GuardianRootSet(guardian, bytes32(0));
179: }
180: }
['38']
38: function setAuthority(Authority newAuthority) public virtual {
39:
40:
41: require(msg.sender == owner || authority.canCall(msg.sender, address(this), msg.sig)); // <= FOUND
42:
43: authority = newAuthority;
44:
45: emit AuthorityUpdated(msg.sender, newAuthority);
46: }
['44']
44: function checkPrice(
45: uint256 amountIn,
46: address fromToken,
47: address toToken,
48: uint256,
49: uint256 minOut,
50: bytes calldata data
51: ) external view returns (bool) {
52: address vault = abi.decode(data, (address));
53: State storage state = _vaultStates[vault];
54:
55:
56: uint256 expectedOut = state.oracleRegistry.getQuoteForUser(amountIn, fromToken, toToken, vault); // <= FOUND
57: uint256 slippageMultiplier;
58: unchecked {
59: slippageMultiplier = MAX_BPS - state.maxSlippagePerTrade;
60: }
61: return minOut > (expectedOut * slippageMultiplier / MAX_BPS);
62: }
['455']
455: function _processBeforeOperationHooks(CalldataReader reader, bytes memory callData, uint256 i)
456: internal
457: returns (CalldataReader, bytes memory, uint256, address)
458: {
459: uint8 hooksConfigFlag;
460: (reader, hooksConfigFlag) = reader.readU8(); // <= FOUND
461:
462: if (hooksConfigFlag == 0) {
463: return (reader, "", 0, address(0));
464: }
465:
466: uint256 calldataOffsetsCount = hooksConfigFlag & CONFIGURABLE_HOOKS_LENGTH_MASK;
467:
468:
469: if (hooksConfigFlag & HOOKS_FLAG_MASK == 0) {
470: uint256 calldataOffsetsPacked;
471: (reader, calldataOffsetsPacked) = reader.readU256();
472:
473: return (
474: reader, callData.extract(calldataOffsetsPacked, calldataOffsetsCount), calldataOffsetsPacked, address(0) // <= FOUND
475: );
476: }
477:
478: address operationHooks;
479:
480: if (calldataOffsetsCount != 0) {
481: uint256 calldataOffsetsPacked;
482: (reader, calldataOffsetsPacked) = reader.readU256();
483:
484: (reader, operationHooks) = reader.readAddr();
485:
486: require(!_hasBeforeHooks(operationHooks), Aera__BeforeOperationHooksWithConfigurableHooks());
487:
488: return (
489: reader,
490: callData.extract(calldataOffsetsPacked, calldataOffsetsCount), // <= FOUND
491: calldataOffsetsPacked,
492: operationHooks
493: );
494: }
495:
496:
497: (reader, operationHooks) = reader.readAddr();
498:
499: return (
500: reader,
501:
502: _beforeOperationHooks(operationHooks, callData, i),
503: 0,
504: operationHooks
505: );
506: }
['258']
258: function _processExpectedCallback(CalldataReader reader, bytes32 root) internal returns (CalldataReader, uint208) {
259: bool hasCallback;
260: (reader, hasCallback) = reader.readBool(); // <= FOUND
261:
262: if (!hasCallback) {
263: return (reader, 0);
264: }
265:
266: uint208 packedCallbackData;
267: (reader, packedCallbackData) = reader.readU208();
268:
269:
270: _allowCallback(root, packedCallbackData);
271:
272: return (reader, packedCallbackData);
273: }
['359']
359: function _executeSubmit(bytes32 root, CalldataReader reader, bool isCalledFromCallback)
360: internal
361: returns (Approval[] memory approvals, uint256 approvalsLength, bytes[] memory results, CalldataReader newReader)
362: {
363: uint256 operationsLength;
364: (reader, operationsLength) = reader.readU8(); // <= FOUND
365:
366: results = new bytes[](operationsLength);
367:
368:
369: approvals = new Approval[](operationsLength);
370:
371:
372:
373: OperationContext memory ctx;
374: for (uint256 i = 0; i < operationsLength; ++i) {
375: (reader, ctx.target) = reader.readAddr();
376:
377: bytes memory callData;
378: (reader, callData) = reader.readBytesToMemory();
379:
380: reader = callData.pipe(reader, results);
381:
382: bool isStaticCall;
383: (reader, isStaticCall) = reader.readBool(); // <= FOUND
384: if (isStaticCall) {
385:
386: (bool success, bytes memory result) = ctx.target.staticcall(callData);
387:
388: require(success, Aera__SubmissionFailed(i, result));
389:
390: results[i] = result;
391: } else {
392: ctx.selector = bytes4(callData);
393: if (_isAllowanceSelector(ctx.selector)) {
394: unchecked {
395: approvals[approvalsLength++] =
396: Approval({ token: ctx.target, spender: _extractApprovalSpender(callData) });
397: }
398: }
399:
400: (reader, ctx.callbackData) = _processExpectedCallback(reader, root);
401:
402: bytes memory extractedData;
403:
404: (reader, extractedData, ctx.configurableOperationHooks, ctx.operationHooks) =
405: _processBeforeOperationHooks(reader, callData, i);
406:
407: bytes32[] memory proof;
408: (reader, proof) = reader.readBytes32Array();
409:
410: (reader, ctx.value) = reader.readOptionalU256();
411:
412:
413: _verifyOperation(proof, root, _createMerkleLeaf(ctx, extractedData));
414:
415:
416: (bool success, bytes memory result) = ctx.target.call{ value: ctx.value }(callData);
417:
418: require(success, Aera__SubmissionFailed(i, result));
419:
420: if (ctx.callbackData != 0) {
421:
422: require(_hasCallbackBeenCalled(), Aera__ExpectedCallbackNotReceived());
423:
424: if (!isCalledFromCallback) {
425:
426: Approval[] memory callbackApprovals = _getCallbackApprovals();
427:
428:
429: _noPendingApprovalsInvariant(callbackApprovals, callbackApprovals.length);
430: }
431: }
432:
433:
434: _afterOperationHooks(ctx.operationHooks, callData, i);
435:
436: results[i] = result;
437: }
438: }
439:
440: return (approvals, approvalsLength, results, reader);
441: }
['184']
184: function readOptionalU256(CalldataReader reader) internal pure returns (CalldataReader, uint256 u256) {
185: bool hasU256;
186: (reader, hasU256) = reader.readBool(); // <= FOUND
187: if (hasU256) {
188: (reader, u256) = reader.readU256();
189: }
190: return (reader, u256);
191: }
['574']
574: function _getReturnValue(CalldataReader reader, bytes[] memory results)
575: internal
576: pure
577: returns (CalldataReader newReader, bytes memory returnValue)
578: {
579: uint8 returnTypeFlag;
580: (reader, returnTypeFlag) = reader.readU8(); // <= FOUND
581:
582: if (returnTypeFlag == uint8(ReturnValueType.STATIC_RETURN)) {
583: (reader, returnValue) = reader.readBytesToMemory();
584: } else if (returnTypeFlag == uint8(ReturnValueType.DYNAMIC_RETURN)) {
585: uint256 length = results.length;
586: require(length > 0, Aera__NoResults());
587:
588: unchecked {
589: returnValue = results[length - 1];
590: }
591: }
592:
593: return (reader, returnValue);
594: }
['35']
35: function pipe(bytes memory data, CalldataReader reader, bytes[] memory results)
36: internal
37: pure
38: returns (CalldataReader)
39: {
40: uint256 clipboardCount;
41: (reader, clipboardCount) = reader.readU8(); // <= FOUND
42:
43: unchecked {
44: for (; clipboardCount != 0; --clipboardCount) {
45: uint256 clipboard;
46: (reader, clipboard) = reader.readU32();
47:
48: uint256 resultIndex = clipboard >> RESULTS_INDEX_OFFSET;
49:
50:
51: bytes memory result = results[resultIndex];
52:
53: uint256 copyOffset = (clipboard >> COPY_WORD_OFFSET & MASK_8_BIT) * WORD_SIZE;
54:
55: require(copyOffset + WORD_SIZE <= result.length, Aera__CopyOffsetOutOfBounds());
56:
57: uint256 pasteOffset = clipboard & MASK_16_BIT;
58:
59: require(pasteOffset + WORD_SIZE <= data.length, Aera__PasteOffsetOutOfBounds());
60:
61: uint256 operationCalldataPointer;
62: uint256 resultPointer;
63: assembly ("memory-safe") {
64:
65:
66: operationCalldataPointer := data
67: resultPointer := result
68: }
69:
70: uint256 pastePointer = operationCalldataPointer + pasteOffset + CALLDATA_OFFSET;
71: uint256 copyPointer = resultPointer + WORD_SIZE + copyOffset;
72:
73: assembly ("memory-safe") {
74: mcopy(pastePointer, copyPointer, WORD_SIZE)
75: }
76: }
77: }
78:
79: return reader;
80: }
['226']
226: function _handleCallbackOperations(bytes32 root, uint256 cursor)
227: internal
228: virtual
229: override
230: returns (bytes memory returnValue)
231: {
232: CalldataReader reader = CalldataReader.wrap(cursor);
233: CalldataReader end = reader.readBytesEnd(); // <= FOUND
234:
235: Approval[] memory approvals;
236: uint256 approvalsLength;
237: bytes[] memory results;
238:
239: (approvals, approvalsLength, results, reader) = _executeSubmit(root, reader, true);
240:
241:
242: _storeCallbackApprovals(approvals, approvalsLength);
243:
244: (reader, returnValue) = _getReturnValue(reader, results);
245:
246:
247: reader.requireAtEndOf(end);
248:
249: return returnValue;
250: }
['514']
514: function _solveDepositVaultAutoPrice(
515: IERC20 token,
516: uint256 depositMultiplier,
517: Request calldata request,
518: uint256 priceAge,
519: uint256 index
520: ) internal returns (uint256 solverTip) {
521:
522: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
523:
524: bytes32 depositHash = _getRequestHash(token, request);
525:
526: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0;
527:
528: if (request.deadline >= block.timestamp) {
529: solverTip = request.solverTip;
530: uint256 tokens = request.tokens;
531:
532:
533: if (_guardInsufficientTokensForTip(tokens, solverTip, index)) return 0;
534:
535: uint256 tokensAfterTip;
536: unchecked {
537: tokensAfterTip = tokens - solverTip;
538: }
539:
540:
541: uint256 unitsOut = _tokensToUnitsFloorIfActive(token, tokensAfterTip, depositMultiplier);
542:
543: if (_guardAmountBound(unitsOut, request.units, index)) return 0;
544:
545: if (_guardDepositCapExceeded(unitsOut, index)) return 0;
546:
547:
548: asyncDepositHashes[depositHash] = false;
549:
550: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter( // <= FOUND
551: address(this), token, tokensAfterTip, unitsOut, request.user
552: );
553:
554:
555: emit DepositSolved(depositHash);
556: } else {
557:
558: asyncDepositHashes[depositHash] = false;
559:
560: token.safeTransfer(request.user, request.tokens);
561:
562: emit DepositRefunded(depositHash);
563: }
564: }
['583']
583: function _solveDepositVaultFixedPrice(
584: IERC20 token,
585: uint256 depositMultiplier,
586: Request calldata request,
587: uint256 priceAge,
588: uint256 index
589: ) internal returns (uint256 solverTip) {
590:
591: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
592:
593: bytes32 depositHash = _getRequestHash(token, request);
594:
595: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0;
596:
597: if (request.deadline >= block.timestamp) {
598:
599: uint256 tokensNeeded = _unitsToTokensCeilIfActive(token, request.units, depositMultiplier);
600:
601: if (_guardAmountBound(request.tokens, tokensNeeded, index)) return 0;
602:
603: if (_guardDepositCapExceeded(request.units, index)) return 0;
604:
605:
606: asyncDepositHashes[depositHash] = false;
607:
608: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter( // <= FOUND
609: address(this), token, tokensNeeded, request.units, request.user
610: );
611:
612: unchecked {
613: solverTip = request.tokens - tokensNeeded;
614: }
615:
616:
617: emit DepositSolved(depositHash);
618: } else {
619:
620: asyncDepositHashes[depositHash] = false;
621:
622: token.safeTransfer(request.user, request.tokens);
623:
624: emit DepositRefunded(depositHash);
625: }
626: }
['643']
643: function _solveRedeemVaultAutoPrice(
644: IERC20 token,
645: uint256 redeemMultiplier,
646: Request calldata request,
647: uint256 priceAge,
648: uint256 index
649: ) internal returns (uint256 solverTip) {
650:
651: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
652:
653: bytes32 redeemHash = _getRequestHash(token, request);
654:
655: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0;
656:
657: if (request.deadline >= block.timestamp) {
658: solverTip = request.solverTip;
659:
660:
661: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
662:
663: if (_guardInsufficientTokensForTip(tokenOut, solverTip, index)) return 0;
664:
665: uint256 tokenOutAfterTip;
666: unchecked {
667: tokenOutAfterTip = tokenOut - solverTip;
668: }
669:
670:
671: if (_guardAmountBound(tokenOutAfterTip, request.tokens, index)) return 0;
672:
673:
674: asyncRedeemHashes[redeemHash] = false;
675:
676: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit( // <= FOUND
677: address(this), token, tokenOut, request.units, address(this)
678: );
679:
680:
681: token.safeTransfer(request.user, tokenOutAfterTip);
682:
683:
684: emit RedeemSolved(redeemHash);
685: } else {
686:
687: asyncRedeemHashes[redeemHash] = false;
688:
689: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
690:
691: emit RedeemRefunded(redeemHash);
692: }
693: }
['710']
710: function _solveRedeemVaultFixedPrice(
711: IERC20 token,
712: uint256 redeemMultiplier,
713: Request calldata request,
714: uint256 priceAge,
715: uint256 index
716: ) internal returns (uint256 solverTip) {
717:
718: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
719:
720: bytes32 redeemHash = _getRequestHash(token, request);
721:
722: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0;
723:
724: if (request.deadline >= block.timestamp) {
725:
726: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
727:
728: if (_guardAmountBound(tokenOut, request.tokens, index)) return 0;
729:
730:
731: asyncRedeemHashes[redeemHash] = false;
732:
733: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit( // <= FOUND
734: address(this), token, tokenOut, request.units, address(this)
735: );
736:
737: token.safeTransfer(request.user, request.tokens);
738:
739: unchecked {
740: solverTip = tokenOut - request.tokens;
741: }
742:
743:
744: emit RedeemSolved(redeemHash);
745: } else {
746:
747: asyncRedeemHashes[redeemHash] = false;
748:
749: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
750:
751: emit RedeemRefunded(redeemHash);
752: }
753: }
[Low-41] Common tokens such as WETH9
work differently on chains such a Blast
which isn't taken into account during transfer calls.
There is a difference on chains such as Blast on how WETH9 is implemented. On most chains the WETH9 contract contains handling for the case where src == msg.sender, however on chains such as Blast, Arbitrum and Fantom. This isn’t the case. Failing to take this discrepancy into account can results in the protocol not functioning as intended on these chains which can have drastic results. Particularly in cases where the contract interacts with it’s own WETH allowance through transferFrom calls, in such cases these can fail if the contract fails to approve itself to use it’s WETH balance which normally isn't done as most WETH contracts do not require approval for instances where src is msg.sender.
Num of instances: 4
Click to show findings
['62']
62: function enter(address sender, IERC20 token, uint256 tokenAmount, uint256 unitsAmount, address recipient) // <= FOUND
63: external
64: whenNotPaused
65: onlyProvisioner
66: {
67:
68: if (tokenAmount > 0) token.safeTransferFrom(sender, address(this), tokenAmount); // <= FOUND
69:
70:
71: _mint(recipient, unitsAmount);
72:
73:
74: emit Enter(sender, recipient, token, tokenAmount, unitsAmount);
75: }
['180']
180: function requestDeposit( // <= FOUND
181: IERC20 token,
182: uint256 tokensIn,
183: uint256 minUnitsOut,
184: uint256 solverTip,
185: uint256 deadline,
186: uint256 maxPriceAge,
187: bool isFixedPrice
188: ) external anyoneButVault {
189:
190:
191: require(tokensIn != 0, Aera__TokensInZero());
192: require(minUnitsOut != 0, Aera__MinUnitsOutZero());
193: require(deadline > block.timestamp, Aera__DeadlineInPast());
194: unchecked {
195: require(deadline - block.timestamp <= MAX_SECONDS_TO_DEADLINE, Aera__DeadlineTooFarInFuture());
196: }
197: require(tokensDetails[token].asyncDepositEnabled, Aera__AsyncDepositDisabled());
198: require(!PRICE_FEE_CALCULATOR.isVaultPaused(MULTI_DEPOSITOR_VAULT), Aera__PriceAndFeeCalculatorVaultPaused());
199: require(solverTip == 0 || !isFixedPrice, Aera__FixedPriceSolverTipNotAllowed());
200:
201:
202: token.safeTransferFrom(msg.sender, address(this), tokensIn); // <= FOUND
203:
204: RequestType requestType = isFixedPrice ? RequestType.DEPOSIT_FIXED_PRICE : RequestType.DEPOSIT_AUTO_PRICE;
205:
206: bytes32 depositHash = _getRequestHashParams(
207: token, msg.sender, requestType, tokensIn, minUnitsOut, solverTip, deadline, maxPriceAge
208: );
209:
210: require(!asyncDepositHashes[depositHash], Aera__HashCollision());
211:
212: asyncDepositHashes[depositHash] = true;
213:
214:
215: emit DepositRequested(
216: msg.sender, token, tokensIn, minUnitsOut, solverTip, deadline, maxPriceAge, isFixedPrice, depositHash
217: );
218: }
['803']
803: function _solveRedeemDirect(IERC20 token, Request calldata request) internal { // <= FOUND
804: bytes32 redeemHash = _getRequestHash(token, request);
805:
806: if (!asyncRedeemHashes[redeemHash]) {
807:
808: emit InvalidRequestHash(redeemHash);
809: return;
810: }
811:
812:
813: asyncRedeemHashes[redeemHash] = false;
814:
815: if (request.deadline >= block.timestamp) {
816:
817: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(msg.sender, request.units);
818:
819:
820: token.safeTransferFrom(msg.sender, request.user, request.tokens); // <= FOUND
821:
822:
823: emit RedeemSolved(redeemHash);
824: } else {
825:
826: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
827:
828:
829: emit RedeemRefunded(redeemHash);
830: }
831: }
['65']
65: function requestSell( // <= FOUND
66: uint256 sellAmount,
67: IERC20 sellToken,
68: IERC20 receiveToken,
69: bytes32 appData,
70: address priceChecker,
71: bytes calldata priceCheckerData
72: ) external onlyVault nonReentrant {
73:
74: require(sellAmount > 0, AeraPeriphery__MilkmanRouter__SellAmountIsZero());
75:
76:
77: sellToken.safeTransferFrom(msg.sender, address(this), sellAmount); // <= FOUND
78:
79:
80: sellToken.safeIncreaseAllowance(address(milkmanRoot), sellAmount);
81:
82:
83: milkmanRoot.requestSwapExactTokensForTokens(
84: sellAmount, sellToken, receiveToken, address(this), appData, priceChecker, priceCheckerData
85: );
86:
87:
88:
89: require(
90: sellToken.allowance(address(this), address(milkmanRoot)) == 0,
91: AeraPeriphery__MilkmanRequestSwapExactTokensForTokensFailed(sellToken)
92: );
93:
94:
95: emit SellRequested(sellAmount, sellToken, receiveToken, appData, priceChecker, priceCheckerData);
96: }
[Low-42] Function designed to add
elements to an array or toggle a isX
mapping doesn't check if the element is already activated or the element already exists in the array
Num of instances: 1
Click to show findings
['61']
61: function addCallerCapability(address caller, address target, bytes4 sig) external requiresAuth {
62: bytes32 targetAndSelector = _packTargetSig(target, sig);
63:
64:
65: _canCall[caller][targetAndSelector] = true; // <= FOUND
66:
67:
68: emit CallerCapabilityAdded(caller, target, sig);
69: }
[Low-43] Transfers which take place within iteration do not compare the amounts[i]
value against zero which can result in the transfer chain reverting
A downside of performing multiple transfers using iteration is that if one of these transfers fails, they all do, one way this can happen is if 0 amount is used within a transfer as many tokens revert on zero transfer. As such it is advisable to check all amounts values against zero prior to performing the transfer loop, ideally when the amounts value is pushed to the amounts array.
Num of instances: 4
Click to show findings
['30']
30: for (uint256 i = 0; i < length; ++i) { // <= FOUND
31: tokenAmount = tokenAmounts[i];
32:
33: tokenAmount.token.safeTransferFrom(msg.sender, address(this), tokenAmount.amount); // <= FOUND
34:
35:
36: require(
37: tokenAmount.token.allowance(msg.sender, address(this)) == 0,
38: Aera__UnexpectedTokenAllowance(tokenAmount.token.allowance(msg.sender, address(this)))
39: );
40: }
['30']
30: for (uint256 i = 0; i < length; ++i) { // <= FOUND
31: tokenAmount = tokenAmounts[i];
32:
33: tokenAmount.token.safeTransferFrom(msg.sender, address(this), tokenAmount.amount); // <= FOUND
34:
35:
36: require(
37: tokenAmount.token.allowance(msg.sender, address(this)) == 0,
38: Aera__UnexpectedTokenAllowance(tokenAmount.token.allowance(msg.sender, address(this)))
39: );
40: }
['50']
50: for (uint256 i = 0; i < length; ++i) { // <= FOUND
51: tokenAmount = tokenAmounts[i];
52:
53: tokenAmount.token.safeTransfer(msg.sender, tokenAmount.amount); // <= FOUND
54: }
['50']
50: for (uint256 i = 0; i < length; ++i) { // <= FOUND
51: tokenAmount = tokenAmounts[i];
52:
53: tokenAmount.token.safeTransfer(msg.sender, tokenAmount.amount); // <= FOUND
54: }
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: 39
Click to show findings
['9']
9: contract Auth2Step is IAuth2Step, Auth // <= FOUND
['15']
15: contract BaseVaultFactory is IBaseVaultFactory, BaseVaultDeployer, Sweepable // <= FOUND
['50']
50: contract BaseVault is IBaseVault, Pausable, CallbackHandler, ReentrancyGuardTransient, Auth2Step, IERC721Receiver // <= FOUND
['18']
18: contract DelayedFeeCalculator is IDelayedFeeCalculator, BaseFeeCalculator // <= FOUND
['11']
11: contract MultiDepositorVaultDeployDelegate is IVaultDeployDelegate // <= FOUND
['20']
20: contract MultiDepositorVaultFactory is IMultiDepositorVaultFactory, FeeVaultDeployer, Sweepable // <= FOUND
['18']
18: contract MultiDepositorVault is IMultiDepositorVault, ERC20, FeeVault // <= FOUND
['32']
32: contract PriceAndFeeCalculator is IPriceAndFeeCalculator, BaseFeeCalculator, HasNumeraire // <= FOUND
['35']
35: contract Provisioner is IProvisioner, Auth2Step, ReentrancyGuardTransient // <= FOUND
['11']
11: contract SingleDepositorVaultDeployDelegate is IVaultDeployDelegate // <= FOUND
['16']
16: contract SingleDepositorVaultFactory is ISingleDepositorVaultFactory, FeeVaultDeployer, Sweepable // <= FOUND
['17']
17: contract SingleDepositorVault is ISingleDepositorVault, FeeVault // <= FOUND
['11']
11: contract Whitelist is IWhitelist, Auth2Step // <= FOUND
['10']
10: contract ComputeBaseVaultAddressLens // <= FOUND
['10']
10: contract ComputeMultiDepositorVaultAddressLens // <= FOUND
['10']
10: contract ComputeSingleDepositorVaultAddressLens // <= FOUND
['13']
13: contract Forwarder is IForwarder, Auth, ReentrancyGuard // <= FOUND
['11']
11: contract StablecoinStrategyHooks is UniswapV3DexHooks, OdosV2DexHooks, KyberSwapDexHooks, CCTPHooks // <= FOUND
['13']
13: contract TransferBlacklistHook is AbstractTransferHook, ITransferBlacklistHook // <= FOUND
['10']
10: contract TransferWhitelistHook is AbstractTransferHook, ITransferWhitelistHook // <= FOUND
['16']
16: contract MilkmanRouter is IMilkmanRouter, Executor, VaultAuth // <= FOUND
['21']
21: contract OracleRegistry is IOracleRegistry, Auth2Step, ERC165 // <= FOUND
['17']
17: abstract contract BaseFeeCalculator is IBaseFeeCalculator, IFeeCalculator, Auth2Step, VaultAuth // <= FOUND
['14']
14: abstract contract BaseVaultDeployer is IBaseVaultDeployer // <= FOUND
['30']
30: abstract contract CallbackHandler is ICallbackHandler // <= FOUND
['16']
16: abstract contract FeeVaultDeployer is IFeeVaultDeployer, BaseVaultDeployer // <= FOUND
['19']
19: abstract contract FeeVault is IFeeVault, BaseVault // <= FOUND
['8']
8: abstract contract HasNumeraire is IHasNumeraire // <= FOUND
['14']
14: abstract contract Sweepable is ISweepable, Auth2Step // <= FOUND
['10']
10: abstract contract VaultAuth // <= FOUND
['7']
7: abstract contract Auth // <= FOUND
['11']
11: abstract contract Executor is IExecutor, ReentrancyGuard // <= FOUND
['21']
21: abstract contract BaseSlippageHooks is IBaseSlippageHooks, HasNumeraire, VaultAuth // <= FOUND
['10']
10: abstract contract CCTPHooks is ICCTPHooks, BaseSlippageHooks // <= FOUND
['13']
13: abstract contract KyberSwapDexHooks is IKyberSwapDexHooks, BaseSlippageHooks // <= FOUND
['10']
10: abstract contract MilkmanHooks is IMilkmanHooks, IMilkmanPriceChecker, BaseSlippageHooks // <= FOUND
['12']
12: abstract contract OdosV2DexHooks is IOdosV2DexHooks, BaseSlippageHooks // <= FOUND
['13']
13: abstract contract UniswapV3DexHooks is IUniswapV3DexHooks, BaseSlippageHooks // <= FOUND
['11']
11: abstract contract AbstractTransferHook is IBeforeTransferHook, VaultAuth // <= FOUND
Local variable shadowing in Solidity creates confusion by allowing a local variable within a function to share the same name as a state variable or another local variable in an outer scope. This confusion can lead to errors in code interpretation and execution. It's especially problematic when maintaining or modifying the code, as it may inadvertently introduce bugs. To resolve this issue, developers should avoid variable shadowing by using unique and descriptive names for variables. Leveraging development tools and linters that warn about shadowing can also be helpful. By paying careful attention to variable naming and being aware of shadowing, developers can create more readable and robust code.
Num of instances: 27
Click to show findings
['16']
16: function requestSell(
17: uint256 sellAmount,
18: IERC20 sellToken,
19: IERC20 receiveToken,
20: bytes32,
21: address priceChecker,
22: bytes calldata priceCheckerData
23: ) external returns (bytes memory returnData) {
24:
25: require(priceChecker == address(this), AeraPeriphery__InvalidPriceChecker(address(this), priceChecker));
26:
27:
28: address vault = abi.decode(priceCheckerData, (address)); // <= FOUND
29: require(msg.sender == vault, AeraPeriphery__InvalidVaultInPriceCheckerData(msg.sender, vault));
30:
31:
32: _handleMilkmanBeforeHook(address(sellToken), sellAmount, msg.sender);
33:
34:
35: returnData = abi.encode(address(sellToken), address(receiveToken));
36: }
['44']
44: function checkPrice(
45: uint256 amountIn,
46: address fromToken,
47: address toToken,
48: uint256,
49: uint256 minOut,
50: bytes calldata data
51: ) external view returns (bool) {
52: address vault = abi.decode(data, (address)); // <= FOUND
53: State storage state = _vaultStates[vault];
54:
55:
56: uint256 expectedOut = state.oracleRegistry.getQuoteForUser(amountIn, fromToken, toToken, vault);
57: uint256 slippageMultiplier;
58: unchecked {
59: slippageMultiplier = MAX_BPS - state.maxSlippagePerTrade;
60: }
61: return minOut > (expectedOut * slippageMultiplier / MAX_BPS);
62: }
['75']
75: function setVaultAccountant(address vault, address accountant) external requiresVaultAuth(vault) { // <= FOUND
76:
77: vaultAccountant[vault] = accountant;
78:
79:
80: emit VaultAccountantSet(vault, accountant);
81: }
['88']
88: function setVaultFees(address vault, uint16 tvl, uint16 performance) external requiresVaultAuth(vault) { // <= FOUND
89:
90: require(tvl <= MAX_TVL_FEE, Aera__TvlFeeTooHigh());
91: require(performance <= MAX_PERFORMANCE_FEE, Aera__PerformanceFeeTooHigh());
92:
93:
94: VaultAccruals storage vaultAccruals = _vaultAccruals[vault];
95: vaultAccruals.fees = Fee({ tvl: tvl, performance: performance });
96:
97:
98: emit VaultFeesSet(vault, tvl, performance);
99: }
['61']
61: function submitSnapshot(address vault, uint160 averageValue, uint128 highestProfit, uint32 timestamp) // <= FOUND
62: external
63: onlyVaultAccountant(vault)
64: {
65:
66: require(timestamp <= block.timestamp, Aera__SnapshotInFuture());
67:
68: VaultSnapshot storage vaultSnapshot = _vaultSnapshots[vault];
69: VaultAccruals storage vaultAccruals = _vaultAccruals[vault];
70:
71: uint256 lastFeeAccrualCached = vaultSnapshot.lastFeeAccrual;
72: require(lastFeeAccrualCached != 0, Aera__VaultNotRegistered());
73:
74:
75: _accrueFees(vaultSnapshot, vaultAccruals, lastFeeAccrualCached);
76:
77:
78: require(timestamp > vaultSnapshot.lastFeeAccrual, Aera__SnapshotTooOld());
79:
80: require(vaultSnapshot.lastHighestProfit <= highestProfit, Aera__HighestProfitDecreased());
81:
82:
83: vaultSnapshot.timestamp = timestamp;
84: unchecked {
85:
86: vaultSnapshot.finalizedAt = uint32(block.timestamp + DISPUTE_PERIOD);
87: }
88: vaultSnapshot.averageValue = averageValue;
89: vaultSnapshot.highestProfit = highestProfit;
90:
91:
92: emit SnapshotSubmitted(vault, averageValue, highestProfit, timestamp);
93: }
['105']
105: function previewFees(address vault, uint256 feeTokenBalance) external view override returns (uint256, uint256) { // <= FOUND
106: VaultSnapshot storage vaultSnapshot = _vaultSnapshots[vault];
107: VaultAccruals storage vaultAccruals = _vaultAccruals[vault];
108:
109: uint256 claimableProtocolFee = vaultAccruals.accruedProtocolFees;
110: uint256 claimableVaultFee = vaultAccruals.accruedFees;
111:
112: if (vaultSnapshot.lastFeeAccrual < vaultSnapshot.timestamp && vaultSnapshot.finalizedAt <= block.timestamp) {
113:
114: (uint256 vaultPerformanceFeeEarned, uint256 protocolPerformanceFeeEarned) = _calculatePerformanceFees(
115: vaultAccruals.fees.performance, vaultSnapshot.highestProfit, vaultSnapshot.lastHighestProfit
116: );
117:
118: (uint256 vaultTvlFeeEarned, uint256 protocolTvlFeeEarned) = _calculateTvlFees(
119: vaultAccruals.fees.tvl,
120: vaultSnapshot.averageValue,
121: vaultSnapshot.timestamp,
122: vaultSnapshot.lastFeeAccrual
123: );
124:
125: claimableProtocolFee += protocolPerformanceFeeEarned + protocolTvlFeeEarned;
126: claimableVaultFee += vaultPerformanceFeeEarned + vaultTvlFeeEarned;
127: }
128:
129: claimableProtocolFee = Math.min(feeTokenBalance, claimableProtocolFee);
130: claimableVaultFee;
131: unchecked {
132: claimableVaultFee = Math.min(feeTokenBalance - claimableProtocolFee, claimableVaultFee);
133: }
134:
135: return (claimableVaultFee, claimableProtocolFee);
136: }
['94']
94: function setInitialPrice(address vault, uint128 price, uint32 timestamp) external requiresVaultAuth(vault) { // <= FOUND
95: require(price != 0, Aera__InvalidPrice());
96:
97: VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
98:
99:
100:
101: require(vaultPriceState.maxPriceAge != 0, Aera__ThresholdNotSet());
102: require(vaultPriceState.unitPrice == 0, Aera__VaultAlreadyInitialized());
103:
104:
105:
106: require(block.timestamp - timestamp <= vaultPriceState.maxPriceAge, Aera__StalePrice());
107:
108: uint32 timestampU32 = uint32(block.timestamp);
109:
110:
111: vaultPriceState.unitPrice = price;
112: vaultPriceState.highestPrice = price;
113: vaultPriceState.timestamp = timestampU32;
114: vaultPriceState.accrualLag = 0;
115: vaultPriceState.lastTotalSupply = IERC20(vault).totalSupply().toUint128();
116:
117:
118: emit UnitPriceUpdated(vault, price, timestampU32);
119: }
['122']
122: function setThresholds(
123: address vault, // <= FOUND
124: uint16 minPriceToleranceRatio,
125: uint16 maxPriceToleranceRatio,
126: uint16 minUpdateIntervalMinutes,
127: uint8 maxPriceAge,
128: uint8 maxUpdateDelayDays
129: ) external requiresVaultAuth(vault) {
130:
131: require(minPriceToleranceRatio <= ONE_IN_BPS, Aera__InvalidMinPriceToleranceRatio());
132:
133: require(maxPriceToleranceRatio >= ONE_IN_BPS, Aera__InvalidMaxPriceToleranceRatio());
134:
135: require(maxPriceAge > 0, Aera__InvalidMaxPriceAge());
136:
137: require(maxUpdateDelayDays > 0, Aera__InvalidMaxUpdateDelayDays());
138:
139: VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
140:
141:
142: require(vaultPriceState.timestamp != 0, Aera__VaultNotRegistered());
143:
144:
145: vaultPriceState.minPriceToleranceRatio = minPriceToleranceRatio;
146: vaultPriceState.maxPriceToleranceRatio = maxPriceToleranceRatio;
147: vaultPriceState.minUpdateIntervalMinutes = minUpdateIntervalMinutes;
148: vaultPriceState.maxPriceAge = maxPriceAge;
149: vaultPriceState.maxUpdateDelayDays = maxUpdateDelayDays;
150:
151:
152: emit ThresholdsSet(vault, minPriceToleranceRatio, maxPriceToleranceRatio, minUpdateIntervalMinutes, maxPriceAge);
153: }
['156']
156: function setUnitPrice(address vault, uint128 price, uint32 timestamp) external onlyVaultAccountant(vault) { // <= FOUND
157: VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
158:
159:
160: _validatePriceUpdate(vaultPriceState, price, timestamp);
161:
162: if (!vaultPriceState.paused) {
163: if (_shouldPause(vaultPriceState, price, timestamp)) {
164:
165: _setVaultPaused(vaultPriceState, vault, true);
166:
167: unchecked {
168:
169: vaultPriceState.accrualLag = uint24(timestamp - vaultPriceState.timestamp);
170: }
171: } else {
172:
173: _accrueFees(vault, price, timestamp);
174: }
175: } else {
176:
177: unchecked {
178:
179: vaultPriceState.accrualLag = uint24(timestamp - vaultPriceState.timestamp + vaultPriceState.accrualLag);
180: }
181: }
182:
183:
184: vaultPriceState.unitPrice = price;
185: vaultPriceState.timestamp = timestamp;
186:
187:
188: emit UnitPriceUpdated(vault, price, timestamp);
189: }
['203']
203: function unpauseVault(address vault, uint128 price, uint32 timestamp) external requiresVaultAuth(vault) { // <= FOUND
204: VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
205:
206:
207: require(vaultPriceState.paused, Aera__VaultNotPaused());
208: require(vaultPriceState.unitPrice == price, Aera__UnitPriceMismatch());
209: require(vaultPriceState.timestamp == timestamp, Aera__TimestampMismatch());
210:
211:
212: _accrueFees(vault, price, timestamp);
213:
214:
215: _setVaultPaused(vaultPriceState, vault, false);
216: }
['237']
237: function convertUnitsToToken(address vault, IERC20 token, uint256 unitsAmount) // <= FOUND
238: external
239: view
240: returns (uint256 tokenAmount)
241: {
242: return _convertUnitsToToken(vault, token, unitsAmount, _vaultPriceStates[vault].unitPrice, Math.Rounding.Floor);
243: }
['246']
246: function convertUnitsToTokenIfActive(address vault, IERC20 token, uint256 unitsAmount, Math.Rounding rounding) // <= FOUND
247: external
248: view
249: returns (uint256 tokenAmount)
250: {
251: VaultPriceState storage vaultState = _vaultPriceStates[vault];
252:
253:
254: require(!vaultState.paused, Aera__VaultPaused());
255:
256: return _convertUnitsToToken(vault, token, unitsAmount, vaultState.unitPrice, rounding);
257: }
['260']
260: function convertUnitsToNumeraire(address vault, uint256 unitsAmount) external view returns (uint256) { // <= FOUND
261: VaultPriceState storage vaultState = _vaultPriceStates[vault];
262:
263:
264: require(!vaultState.paused, Aera__VaultPaused());
265:
266: return unitsAmount * vaultState.unitPrice / UNIT_PRICE_PRECISION;
267: }
['270']
270: function convertTokenToUnits(address vault, IERC20 token, uint256 tokenAmount) // <= FOUND
271: external
272: view
273: returns (uint256 unitsAmount)
274: {
275: return _convertTokenToUnits(vault, token, tokenAmount, _vaultPriceStates[vault].unitPrice, Math.Rounding.Floor);
276: }
['279']
279: function convertTokenToUnitsIfActive(address vault, IERC20 token, uint256 tokenAmount, Math.Rounding rounding) // <= FOUND
280: external
281: view
282: returns (uint256 unitsAmount)
283: {
284: VaultPriceState storage vaultState = _vaultPriceStates[vault];
285:
286:
287: require(!vaultState.paused, Aera__VaultPaused());
288:
289: return _convertTokenToUnits(vault, token, tokenAmount, vaultState.unitPrice, rounding);
290: }
['311']
311: function previewFees(address vault, uint256 feeTokenBalance) external view override returns (uint256, uint256) { // <= FOUND
312: VaultAccruals storage vaultAccruals = _vaultAccruals[vault];
313:
314: uint256 claimableProtocolFee = Math.min(feeTokenBalance, vaultAccruals.accruedProtocolFees);
315: uint256 claimableVaultFee;
316: unchecked {
317: claimableVaultFee = Math.min(feeTokenBalance - claimableProtocolFee, vaultAccruals.accruedFees);
318: }
319:
320: return (claimableVaultFee, claimableProtocolFee);
321: }
['334']
334: function _accrueFees(address vault, uint256 price, uint256 timestamp) internal { // <= FOUND
335: VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
336:
337: uint256 timeDelta;
338: unchecked {
339: timeDelta = timestamp - vaultPriceState.timestamp + vaultPriceState.accrualLag;
340: }
341:
342:
343: uint256 currentTotalSupply = IERC20(vault).totalSupply();
344: uint256 minTotalSupply = Math.min(currentTotalSupply, uint256(vaultPriceState.lastTotalSupply));
345: uint256 minUnitPrice = Math.min(price, uint256(vaultPriceState.unitPrice));
346:
347: uint256 tvl = minUnitPrice * minTotalSupply / UNIT_PRICE_PRECISION;
348:
349: VaultAccruals storage vaultAccruals = _vaultAccruals[vault];
350: uint256 vaultFeesEarned = _calculateTvlFee(tvl, vaultAccruals.fees.tvl, timeDelta);
351:
352: uint256 protocolFeesEarned = _calculateTvlFee(tvl, protocolFees.tvl, timeDelta);
353:
354: if (price > vaultPriceState.highestPrice) {
355: uint256 profit = (price - vaultPriceState.highestPrice) * minTotalSupply / UNIT_PRICE_PRECISION;
356: vaultFeesEarned += _calculatePerformanceFee(profit, vaultAccruals.fees.performance);
357: protocolFeesEarned += _calculatePerformanceFee(profit, protocolFees.performance);
358:
359:
360: vaultPriceState.highestPrice = uint128(price);
361: }
362:
363:
364: vaultAccruals.accruedFees += vaultFeesEarned.toUint112();
365: vaultAccruals.accruedProtocolFees += protocolFeesEarned.toUint112();
366:
367:
368: vaultPriceState.lastTotalSupply = currentTotalSupply.toUint128();
369: vaultPriceState.accrualLag = 0;
370: }
['376']
376: function _setVaultPaused(VaultPriceState storage vaultPriceState, address vault, bool paused) internal { // <= FOUND
377:
378: vaultPriceState.paused = paused;
379:
380:
381: emit VaultPausedChanged(vault, paused);
382: }
['390']
390: function _convertTokenToUnits(
391: address vault, // <= FOUND
392: IERC20 token,
393: uint256 tokenAmount,
394: uint256 unitPrice,
395: Math.Rounding rounding
396: ) internal view returns (uint256 unitsAmount) {
397: if (address(token) != NUMERAIRE) {
398: tokenAmount = ORACLE_REGISTRY.getQuoteForUser(tokenAmount, address(token), NUMERAIRE, vault);
399: }
400:
401: return Math.mulDiv(tokenAmount, UNIT_PRICE_PRECISION, unitPrice, rounding);
402: }
['410']
410: function _convertUnitsToToken(
411: address vault, // <= FOUND
412: IERC20 token,
413: uint256 unitsAmount,
414: uint256 unitPrice,
415: Math.Rounding rounding
416: ) internal view returns (uint256 tokenAmount) {
417: uint256 numeraireAmount = Math.mulDiv(unitsAmount, unitPrice, UNIT_PRICE_PRECISION, rounding);
418:
419: if (address(token) == NUMERAIRE) {
420: return numeraireAmount;
421: }
422:
423: return ORACLE_REGISTRY.getQuoteForUser(numeraireAmount, NUMERAIRE, address(token), vault);
424: }
['40']
40: function setMaxDailyLoss(address vault, uint128 maxLoss) external requiresVaultAuth(vault) { // <= FOUND
41:
42: _vaultStates[vault].maxDailyLossInNumeraire = maxLoss;
43:
44:
45: emit UpdateMaxDailyLoss(vault, maxLoss);
46: }
['49']
49: function setMaxSlippagePerTrade(address vault, uint16 newMaxSlippage) external requiresVaultAuth(vault) { // <= FOUND
50:
51: require(newMaxSlippage < MAX_BPS, AeraPeriphery__MaxSlippagePerTradeTooHigh(newMaxSlippage));
52:
53:
54: _vaultStates[vault].maxSlippagePerTrade = newMaxSlippage;
55:
56:
57: emit UpdateMaxSlippage(vault, newMaxSlippage);
58: }
['61']
61: function setOracleRegistry(address vault, address oracleRegistry) external requiresVaultAuth(vault) { // <= FOUND
62:
63: require(oracleRegistry != address(0), AeraPeriphery__ZeroAddressOracleRegistry());
64:
65:
66: _vaultStates[vault].oracleRegistry = IOracleRegistry(oracleRegistry);
67:
68:
69: emit UpdateOracleRegistry(vault, oracleRegistry);
70: }
['151']
151: function _enforceSlippageLimitAndDailyLossLog(
152: address vault, // <= FOUND
153: address tokenIn,
154: address tokenOut,
155: uint256 valueBefore,
156: uint256 valueAfter
157: ) internal {
158:
159:
160: if (valueBefore <= valueAfter) {
161: return;
162: }
163:
164:
165: uint128 cumulativeDailyLossNumeraire = _enforceSlippageLimitAndDailyLoss(valueBefore, valueAfter);
166:
167:
168: emit TradeSlippageChecked(vault, tokenIn, tokenOut, valueBefore, valueAfter, cumulativeDailyLossNumeraire);
169: }
['24']
24: function setIsVaultUnitsTransferable(address vault, bool isTransferable) external requiresVaultAuth(vault) { // <= FOUND
25: _setIsVaultUnitsTransferable(vault, isTransferable);
26: }
['43']
43: function _setIsVaultUnitsTransferable(address vault, bool isTransferable) internal { // <= FOUND
44:
45: isVaultUnitTransferable[vault] = isTransferable;
46:
47:
48: emit VaultUnitTransferableSet(vault, isTransferable);
49: }
['23']
23: function updateWhitelist(address vault, address[] calldata addresses, bool isWhitelisted) // <= FOUND
24: external
25: requiresVaultAuth(vault)
26: {
27: uint256 length = addresses.length;
28: address addr;
29:
30: for (uint256 i; i < length; i++) {
31: addr = addresses[i];
32:
33:
34: whitelist[vault][addr] = isWhitelisted;
35: }
36:
37:
38: emit VaultWhitelistUpdated(vault, addresses, isWhitelisted);
39: }
Consider using abi.encode as this pads data to 32 byte segments
Num of instances: 4
Click to show findings
['613']
613: return keccak256(
614: abi.encodePacked(
615: ctx.target,
616: ctx.selector,
617: ctx.value > 0,
618: ctx.operationHooks,
619: ctx.configurableOperationHooks,
620: ctx.callbackData,
621: extractedData
622: )
623: );
['992']
992: return keccak256(abi.encodePacked(user, token, tokenAmount, unitsAmount, refundableUntil));
['1015']
1015: return keccak256(abi.encodePacked(token, user, requestType, tokens, units, solverTip, deadline, maxPriceAge));
['1022']
1022: return keccak256(
1023: abi.encodePacked(
1024: token,
1025: request.user,
1026: request.requestType,
1027: request.tokens,
1028: request.units,
1029: request.solverTip,
1030: request.deadline,
1031: request.maxPriceAge
1032: )
1033: );
Within unchecked blocks in Solidity, arithmetic operations bypass overflow and underflow checks. When subtractions occur without proper bounds validation, they may underflow. An underflow in an unsigned integer subtraction can wrap the value around to its maximum, leading to unintended contract behavior or potential vulnerabilities. To prevent such scenarios, developers should either avoid unchecked blocks for subtraction operations or manually implement checks to ensure operands' validity before subtraction.
Num of instances: 20
Click to show findings
['112']
112: unchecked {
113: claimableVaultFee = Math.min(feeTokenBalance - claimableProtocolFee, vaultEarnedFees);
114: }
['117']
117: unchecked {
118: vaultAccruals.accruedProtocolFees = uint112(protocolEarnedFees - claimableProtocolFee);
119: vaultAccruals.accruedFees = uint112(vaultEarnedFees - claimableVaultFee);
120: }
['135']
135: unchecked {
136: vaultAccruals.accruedProtocolFees = uint112(accruedFees - claimableProtocolFee);
137: }
['588']
588: unchecked {
589: returnValue = results[length - 1];
590: }
['142']
142: unchecked {
143: uint256 newLength = existingLength + length;
144:
145:
146: bytes32(currentSlot).asUint256().tstore(
147: _packLengthAndToken(newLength, address(uint160(existingApproval)))
148: );
149:
150: currentSlot += existingLength * 2 - 1;
151: }
['131']
131: unchecked {
132: claimableVaultFee = Math.min(feeTokenBalance - claimableProtocolFee, claimableVaultFee);
133: }
['221']
221: unchecked {
222: profit = newHighestProfit - oldHighestProfit;
223: }
['245']
245: unchecked {
246: totalDuration = snapshotTimestamp - lastFeeAccrual;
247: }
['177']
177: unchecked {
178:
179: vaultPriceState.accrualLag = uint24(timestamp - vaultPriceState.timestamp + vaultPriceState.accrualLag);
180: }
['299']
299: unchecked {
300:
301: return block.timestamp - _vaultPriceStates[vault].timestamp;
302: }
['316']
316: unchecked {
317: claimableVaultFee = Math.min(feeTokenBalance - claimableProtocolFee, vaultAccruals.accruedFees);
318: }
['338']
338: unchecked {
339: timeDelta = timestamp - vaultPriceState.timestamp + vaultPriceState.accrualLag;
340: }
['182']
182: unchecked {
183: loss = valueBefore - valueAfter;
184: }
['58']
58: unchecked {
59: slippageMultiplier = MAX_BPS - state.maxSlippagePerTrade;
60: }
['36']
36: unchecked {
37: tokenIn = address(bytes20(params.path[:20]));
38: tokenOut = address(bytes20(params.path[pathLength - 20:]));
39: }
['536']
536: unchecked {
537: tokensAfterTip = tokens - solverTip;
538: }
['612']
612: unchecked {
613: solverTip = request.tokens - tokensNeeded;
614: }
['666']
666: unchecked {
667: tokenOutAfterTip = tokenOut - solverTip;
668: }
['739']
739: unchecked {
740: solverTip = tokenOut - request.tokens;
741: }
['167']
167: unchecked {
168:
169: vaultPriceState.accrualLag = uint24(timestamp - vaultPriceState.timestamp);
170: }
[NonCritical-4] 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: 6
Click to show findings
['278']
278: function _beforeSubmitHooks(address hooks, bytes calldata data) internal { // <= FOUND
279: if (_hasBeforeHooks(hooks)) {
280:
281: (bool success, bytes memory result) = // <= FOUND
282: hooks.call(abi.encodeWithSelector(ISubmitHooks.beforeSubmit.selector, data, msg.sender));
283:
284: require(success, Aera__BeforeSubmitHooksFailed(result));
285: }
286: }
['291']
291: function _afterSubmitHooks(address hooks, bytes calldata data) internal { // <= FOUND
292: if (_hasAfterHooks(hooks)) {
293:
294: (bool success, bytes memory result) = // <= FOUND
295: hooks.call(abi.encodeWithSelector(ISubmitHooks.afterSubmit.selector, data, msg.sender));
296:
297: require(success, Aera__AfterSubmitHooksFailed(result));
298: }
299: }
['335']
335: function _afterOperationHooks(address operationHooks, bytes memory data, uint256 i) internal { // <= FOUND
336: if (_hasAfterHooks(operationHooks)) {
337:
338: _setHookCallType(HookCallType.AFTER);
339:
340:
341: (bool success, bytes memory result) = operationHooks.call(data); // <= FOUND
342:
343: require(success, Aera__AfterOperationHooksFailed(i, result));
344:
345:
346: _setHookCallType(HookCallType.NONE);
347: }
348: }
['359']
359: function _executeSubmit(bytes32 root, CalldataReader reader, bool isCalledFromCallback) // <= FOUND
360: internal
361: returns (Approval[] memory approvals, uint256 approvalsLength, bytes[] memory results, CalldataReader newReader)
362: {
363: uint256 operationsLength;
364: (reader, operationsLength) = reader.readU8();
365:
366: results = new bytes[](operationsLength);
367:
368:
369: approvals = new Approval[](operationsLength);
370:
371:
372:
373: OperationContext memory ctx;
374: for (uint256 i = 0; i < operationsLength; ++i) {
375: (reader, ctx.target) = reader.readAddr();
376:
377: bytes memory callData;
378: (reader, callData) = reader.readBytesToMemory();
379:
380: reader = callData.pipe(reader, results);
381:
382: bool isStaticCall;
383: (reader, isStaticCall) = reader.readBool();
384: if (isStaticCall) {
385:
386: (bool success, bytes memory result) = ctx.target.staticcall(callData); // <= FOUND
387:
388: require(success, Aera__SubmissionFailed(i, result));
389:
390: results[i] = result;
391: } else {
392: ctx.selector = bytes4(callData);
393: if (_isAllowanceSelector(ctx.selector)) {
394: unchecked {
395: approvals[approvalsLength++] =
396: Approval({ token: ctx.target, spender: _extractApprovalSpender(callData) });
397: }
398: }
399:
400: (reader, ctx.callbackData) = _processExpectedCallback(reader, root);
401:
402: bytes memory extractedData;
403:
404: (reader, extractedData, ctx.configurableOperationHooks, ctx.operationHooks) =
405: _processBeforeOperationHooks(reader, callData, i);
406:
407: bytes32[] memory proof;
408: (reader, proof) = reader.readBytes32Array();
409:
410: (reader, ctx.value) = reader.readOptionalU256();
411:
412:
413: _verifyOperation(proof, root, _createMerkleLeaf(ctx, extractedData));
414:
415:
416: (bool success, bytes memory result) = ctx.target.call{ value: ctx.value }(callData);
417:
418: require(success, Aera__SubmissionFailed(i, result));
419:
420: if (ctx.callbackData != 0) {
421:
422: require(_hasCallbackBeenCalled(), Aera__ExpectedCallbackNotReceived());
423:
424: if (!isCalledFromCallback) {
425:
426: Approval[] memory callbackApprovals = _getCallbackApprovals();
427:
428:
429: _noPendingApprovalsInvariant(callbackApprovals, callbackApprovals.length);
430: }
431: }
432:
433:
434: _afterOperationHooks(ctx.operationHooks, callData, i);
435:
436: results[i] = result;
437: }
438: }
439:
440: return (approvals, approvalsLength, results, reader);
441: }
['37']
37: function _executeOperation(OperationPayable calldata operation) internal virtual { // <= FOUND
38:
39: _checkOperation(operation);
40:
41:
42:
43: (bool success, bytes memory result) = operation.target.call{ value: operation.value }(operation.data); // <= FOUND
44:
45:
46:
47:
48: require(success, AeraPeriphery__ExecutionFailed(result));
49:
50:
51: emit Executed(msg.sender, operation);
52: }
['34']
34: function execute(TargetCalldata[] calldata operations) external nonReentrant { // <= FOUND
35: address target;
36: bytes memory data;
37: bytes32 targetAndSelector;
38: mapping(bytes32 targetAndSelector => bool enabled) storage callerCapabilities = _canCall[msg.sender];
39:
40: uint256 length = operations.length;
41: for (uint256 i; i < length; ++i) {
42: target = operations[i].target;
43: data = operations[i].data;
44: targetAndSelector = _packTargetSig(target, bytes4(data));
45:
46:
47: require(
48: callerCapabilities[targetAndSelector], AeraPeriphery__Unauthorized(msg.sender, target, bytes4(data))
49: );
50:
51:
52: (bool success, bytes memory returnData) = target.call(data); // <= FOUND
53: Address.verifyCallResultFromTarget(target, success, returnData);
54: }
55:
56:
57: emit Executed(msg.sender, operations);
58: }
Num of instances: 2
Click to show findings
['2']
2: pragma solidity >=0.5.0; // <= FOUND
['2']
2: pragma solidity >=0.5.0 >=0.7.5; // <= FOUND
It is general standard to declare interfaces on files separate from regular contract declarations
Num of instances: 1
[NonCritical-7] 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: 1
Click to show findings
['10']
10: event AuthorityUpdated(address indexed user, Authority indexed newAuthority);
[NonCritical-8] 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: 85
Click to show findings
['52']
52: function transferOwnership(address newOwner) public virtual override onlyOwner
['75']
75: function setVaultAccountant(address vault, address accountant) external requiresVaultAuth(vault)
['88']
88: function setVaultFees(address vault, uint16 tvl, uint16 performance) external requiresVaultAuth(vault)
['23']
23: function create(
24: bytes32 salt,
25: string calldata description,
26: BaseVaultParameters calldata baseVaultParams,
27: address expectedVaultAddress
28: ) external override requiresAuth returns (address deployedVault)
['160']
160: function removeGuardian(address guardian) external virtual requiresAuth
['169']
169: function checkGuardianWhitelist(address guardian) external returns (bool isRemoved)
['206']
206: function getGuardianRoot(address guardian) external view returns (bytes32)
['217']
217: function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4)
['278']
278: function _beforeSubmitHooks(address hooks, bytes calldata data) internal
['291']
291: function _afterSubmitHooks(address hooks, bytes calldata data) internal
['306']
306: function _beforeOperationHooks(address operationHooks, bytes memory data, uint256 i)
307: internal
308: returns (bytes memory result)
309:
['335']
335: function _afterOperationHooks(address operationHooks, bytes memory data, uint256 i) internal
['641']
641: function _hasBeforeHooks(address hooks) internal pure returns (bool)
['649']
649: function _hasAfterHooks(address hooks) internal pure returns (bool)
['251']
251: function _packLengthAndToken(uint256 length, address token) private pure returns (uint256)
['61']
61: function submitSnapshot(address vault, uint160 averageValue, uint128 highestProfit, uint32 timestamp)
62: external
63: onlyVaultAccountant(vault)
64:
['96']
96: function accrueFees(address vault) external returns (uint256 protocolFeesEarned, uint256 vaultFeesEarned)
['105']
105: function previewFees(address vault, uint256 feeTokenBalance) external view override returns (uint256, uint256)
['138']
138: function vaultFeeState(address vault) external view returns (VaultSnapshot memory, VaultAccruals memory)
['65']
65: function create(
66: bytes32 salt,
67: string calldata description,
68: ERC20Parameters calldata erc20Params,
69: BaseVaultParameters calldata baseVaultParams,
70: FeeVaultParameters calldata feeVaultParams,
71: IBeforeTransferHook beforeTransferHook,
72: address expectedVaultAddress
73: ) external override requiresAuth returns (address deployedVault)
['62']
62: function enter(address sender, IERC20 token, uint256 tokenAmount, uint256 unitsAmount, address recipient)
63: external
64: whenNotPaused
65: onlyProvisioner
66:
['78']
78: function exit(address sender, IERC20 token, uint256 tokenAmount, uint256 unitsAmount, address recipient)
79: external
80: whenNotPaused
81: onlyProvisioner
82:
['94']
94: function setInitialPrice(address vault, uint128 price, uint32 timestamp) external requiresVaultAuth(vault)
['122']
122: function setThresholds(
123: address vault,
124: uint16 minPriceToleranceRatio,
125: uint16 maxPriceToleranceRatio,
126: uint16 minUpdateIntervalMinutes,
127: uint8 maxPriceAge,
128: uint8 maxUpdateDelayDays
129: ) external requiresVaultAuth(vault)
['156']
156: function setUnitPrice(address vault, uint128 price, uint32 timestamp) external onlyVaultAccountant(vault)
['192']
192: function pauseVault(address vault) external requiresVaultAuthOrAccountant(vault)
['203']
203: function unpauseVault(address vault, uint128 price, uint32 timestamp) external requiresVaultAuth(vault)
['219']
219: function resetHighestPrice(address vault) external requiresVaultAuth(vault)
['119']
119: function convertUnitsToToken(address vault, IERC20 token, uint256 unitsAmount)
120: external
121: view
122: returns (uint256 tokenAmount)
123:
['131']
131: function convertUnitsToTokenIfActive(address vault, IERC20 token, uint256 unitsAmount, Math.Rounding rounding)
132: external
133: view
134: returns (uint256 tokenAmount)
135:
['260']
260: function convertUnitsToNumeraire(address vault, uint256 unitsAmount) external view returns (uint256)
['141']
141: function convertTokenToUnits(address vault, IERC20 token, uint256 tokenAmount)
142: external
143: view
144: returns (uint256 unitsAmount)
145:
['153']
153: function convertTokenToUnitsIfActive(address vault, IERC20 token, uint256 tokenAmount, Math.Rounding rounding)
154: external
155: view
156: returns (uint256 unitsAmount)
157:
['293']
293: function getVaultState(address vault) external view returns (VaultPriceState memory, VaultAccruals memory)
['298']
298: function getVaultsPriceAge(address vault) external view returns (uint256)
['306']
306: function isVaultPaused(address vault) external view returns (bool)
['105']
105: function previewFees(address vault, uint256 feeTokenBalance) external view override returns (uint256, uint256)
['334']
334: function _accrueFees(address vault, uint256 price, uint256 timestamp) internal
['376']
376: function _setVaultPaused(VaultPriceState storage vaultPriceState, address vault, bool paused) internal
['390']
390: function _convertTokenToUnits(
391: address vault,
392: IERC20 token,
393: uint256 tokenAmount,
394: uint256 unitPrice,
395: Math.Rounding rounding
396: ) internal view returns (uint256 unitsAmount)
['410']
410: function _convertUnitsToToken(
411: address vault,
412: IERC20 token,
413: uint256 unitsAmount,
414: uint256 unitPrice,
415: Math.Rounding rounding
416: ) internal view returns (uint256 tokenAmount)
['156']
156: function refundDeposit(
157: address sender,
158: IERC20 token,
159: uint256 tokenAmount,
160: uint256 unitsAmount,
161: uint256 refundableUntil
162: ) external requiresAuth
['450']
450: function areUserUnitsLocked(address user) external view returns (bool)
['455']
455: function getDepositHash(
456: address user,
457: IERC20 token,
458: uint256 tokenAmount,
459: uint256 unitsAmount,
460: uint256 refundableUntil
461: ) external pure returns (bytes32)
['985']
985: function _getDepositHash(
986: address user,
987: IERC20 token,
988: uint256 tokenAmount,
989: uint256 unitsAmount,
990: uint256 refundableUntil
991: ) internal pure returns (bytes32)
['1005']
1005: function _getRequestHashParams(
1006: IERC20 token,
1007: address user,
1008: RequestType requestType,
1009: uint256 tokens,
1010: uint256 units,
1011: uint256 solverTip,
1012: uint256 deadline,
1013: uint256 maxPriceAge
1014: ) internal pure returns (bytes32)
['41']
41: function create(
42: bytes32 salt,
43: string calldata description,
44: BaseVaultParameters calldata baseVaultParams,
45: FeeVaultParameters calldata singleDepositorVaultParams,
46: address expectedVaultAddress
47: ) external override requiresAuth returns (address deployedVault)
['28']
28: function setWhitelisted(address addr, bool isAddressWhitelisted) external requiresAuth
['41']
41: function isWhitelisted(address addr) external view returns (bool)
['48']
48: function transferOwnership(address newOwner) public virtual requiresAuth
['61']
61: function addCallerCapability(address caller, address target, bytes4 sig) external requiresAuth
['72']
72: function removeCallerCapability(address caller, address target, bytes4 sig) external requiresAuth
['83']
83: function canCall(address caller, address target, bytes4 sig) external view returns (bool)
['96']
96: function _packTargetSig(address target, bytes4 sig) internal pure returns (bytes32)
['40']
40: function setMaxDailyLoss(address vault, uint128 maxLoss) external requiresVaultAuth(vault)
['49']
49: function setMaxSlippagePerTrade(address vault, uint16 newMaxSlippage) external requiresVaultAuth(vault)
['73']
73: function vaultStates(address vault) external view returns (State memory state)
['94']
94: function _handleBeforeExactInputSingle(
95: address tokenIn,
96: address tokenOut,
97: address recipient,
98: uint256 amountIn,
99: uint256 amountOutMinimum
100: ) internal returns (address, address, address)
['123']
123: function _handleBeforeExactOutputSingle(
124: address tokenIn,
125: address tokenOut,
126: address recipient,
127: uint256 amountOut,
128: uint256 amountInMaximum
129: ) internal returns (address, address, address)
['151']
151: function _enforceSlippageLimitAndDailyLossLog(
152: address vault,
153: address tokenIn,
154: address tokenOut,
155: uint256 valueBefore,
156: uint256 valueAfter
157: ) internal
['231']
231: function _convertToNumeraire(uint256 amount, address token) internal view returns (uint256)
['16']
16: function depositForBurn(
17: uint256 amount,
18: uint32 destinationDomain,
19: bytes32 mintRecipient,
20: address burnToken,
21: bytes32 destinationCaller,
22: uint256 maxFee,
23: uint32
24: ) external returns (bytes memory returnData)
['41']
41: function _processSwapHooks(IMetaAggregationRouterV2.SwapDescriptionV2 calldata desc, address executor)
42: internal
43: returns (bytes memory returnData)
44:
['16']
16: function requestSell(
17: uint256 sellAmount,
18: IERC20 sellToken,
19: IERC20 receiveToken,
20: bytes32,
21: address priceChecker,
22: bytes calldata priceCheckerData
23: ) external returns (bytes memory returnData)
['44']
44: function checkPrice(
45: uint256 amountIn,
46: address fromToken,
47: address toToken,
48: uint256,
49: uint256 minOut,
50: bytes calldata data
51: ) external view returns (bool)
['72']
72: function _handleMilkmanBeforeHook(address sellToken, uint256 sellAmount, address vault) internal
['18']
18: function swap(
19: IOdosRouterV2.SwapTokenInfo calldata tokenInfo,
20: bytes calldata,
21: address,
22: uint32
23: ) external returns (bytes memory returnData)
['24']
24: function setIsVaultUnitsTransferable(address vault, bool isTransferable) external requiresVaultAuth(vault)
['43']
43: function _setIsVaultUnitsTransferable(address vault, bool isTransferable) internal
['23']
23: function updateWhitelist(address vault, address[] calldata addresses, bool isWhitelisted)
24: external
25: requiresVaultAuth(vault)
26:
['65']
65: function requestSell(
66: uint256 sellAmount,
67: IERC20 sellToken,
68: IERC20 receiveToken,
69: bytes32 appData,
70: address priceChecker,
71: bytes calldata priceCheckerData
72: ) external onlyVault nonReentrant
['99']
99: function cancelSell(
100: address milkmanOrderContract,
101: uint256 sellAmount,
102: IERC20 sellToken,
103: IERC20 receiveToken,
104: bytes32 appData,
105: address priceChecker,
106: bytes calldata priceCheckerData
107: ) external onlyVault nonReentrant
['70']
70: function addOracle(address base, address quote, IOracle oracle) external requiresAuth
['91']
91: function scheduleOracleUpdate(address base, address quote, IOracle oracle) external requiresAuth
['114']
114: function commitOracleUpdate(address base, address quote) external
['139']
139: function cancelScheduledOracleUpdate(address base, address quote) external requiresAuth
['155']
155: function disableOracle(address base, address quote, IOracle oracle) external requiresAuth
['173']
173: function acceptPendingOracle(address base, address quote, address user, IOracle oracle)
174: external
175: requiresUserAuth(user)
176:
['190']
190: function removeOracleOverride(address base, address quote, address user) external requiresUserAuth(user)
['199']
199: function getQuote(uint256 baseAmount, address base, address quote) external view virtual returns (uint256)
['206']
206: function getQuoteForUser(uint256 baseAmount, address base, address quote, address user)
207: external
208: view
209: virtual
210: returns (uint256)
211:
['218']
218: function getOracleData(address base, address quote) external view virtual returns (OracleData memory)
['235']
235: function _getOracleForVault(address user, address base, address quote) internal view returns (IOracle)
['255']
255: function _validateOracle(IOracle oracle, address base, address quote) internal view
['266']
266: function _getDecimals(address asset) internal view returns (uint8)
Create a commented enum value to use in place of constant array indexes, this makes the code far easier to understand
Num of instances: 2
Click to show findings
['132']
132: approval = approvals[0]; // <= FOUND
['212']
212: approvals[0] = Approval({ token: token, spender: spender }); // <= FOUND
SafeTransferOwnership should be used in place of transferOwner in Solidity contracts to enhance security and error handling. Unlike the basic transferOwner function, SafeTransferOwnership incorporates checks to validate the new owner's address and ensures that the transfer is executed only after receiving the new owner's confirmation. This additional layer of protection prevents accidental ownership transfers and mitigates the risk of locking a contract due to an invalid or unintended address assignment.
Num of instances: 2
Click to show findings
['52']
52: function transferOwnership(address newOwner) public virtual override onlyOwner // <= FOUND
['48']
48: function transferOwnership(address newOwner) public virtual requiresAuth // <= FOUND
In instances where a new variable is defined, there is no need to set it to it's default value.
Num of instances: 5
Click to show findings
['374']
374: for (uint256 i = 0; i < operationsLength; ++i) { // <= FOUND
['71']
71: for (uint256 i = 0; i < calldataOffsetsCount; ++i) { // <= FOUND
['304']
304: for (uint256 i = 0; i < length; i++) { // <= FOUND
['30']
30: for (uint256 i = 0; i < length; ++i) { // <= FOUND
['26']
26: for (uint256 i = 0; i < numOperations; ++i) { // <= FOUND
[NonCritical-12] Functions which are either private or internal should have a preceding _ in their name
Add a preceding underscore to the function name, take care to refactor where there functions are called
Num of instances: 28
Click to show findings
['38']
38: function extract(bytes memory callData, uint256 calldataOffsetsPacked, uint256 calldataOffsetsCount)
39: internal
40: pure
41: returns (bytes memory)
42:
['51']
51: function from(bytes calldata data) internal pure returns (CalldataReader reader)
['57']
57: function requireAtEndOf(CalldataReader self, bytes calldata data) internal pure
['67']
67: function requireAtEndOf(CalldataReader self, CalldataReader end) internal pure
['71']
71: function offset(CalldataReader self) internal pure returns (uint256)
['75']
75: function readBool(CalldataReader self) internal pure returns (CalldataReader, bool value)
['83']
83: function readU8(CalldataReader self) internal pure returns (CalldataReader, uint8 value)
['91']
91: function readU16(CalldataReader self) internal pure returns (CalldataReader, uint16 value)
['99']
99: function readU32(CalldataReader self) internal pure returns (CalldataReader, uint32 value)
['107']
107: function readI24(CalldataReader self) internal pure returns (CalldataReader, int24 value)
['115']
115: function readU40(CalldataReader self) internal pure returns (CalldataReader, uint40 value)
['123']
123: function readU64(CalldataReader self) internal pure returns (CalldataReader, uint64 value)
['131']
131: function readU128(CalldataReader self) internal pure returns (CalldataReader, uint128 value)
['139']
139: function readAddr(CalldataReader self) internal pure returns (CalldataReader, address addr)
['147']
147: function readU256(CalldataReader self) internal pure returns (CalldataReader, uint256 value)
['155']
155: function readU24End(CalldataReader self) internal pure returns (CalldataReader, CalldataReader end)
['164']
164: function readBytes(CalldataReader self) internal pure returns (CalldataReader, bytes calldata slice)
['176']
176: function readU208(CalldataReader self) internal pure returns (CalldataReader, uint208 value)
['184']
184: function readOptionalU256(CalldataReader reader) internal pure returns (CalldataReader, uint256 u256)
['193']
193: function readBytes32Array(CalldataReader self) internal pure returns (CalldataReader, bytes32[] memory array)
['204']
204: function readBytesEnd(CalldataReader self) internal pure returns (CalldataReader end)
['211']
211: function readBytesEnd(CalldataReader self, bytes calldata data) internal pure returns (CalldataReader end)
['217']
217: function readBytesToMemory(CalldataReader self) internal pure returns (CalldataReader, bytes memory data)
['223']
223: function readBytesToMemory(CalldataReader self, uint256 length)
224: internal
225: pure
226: returns (CalldataReader, bytes memory data)
227:
['35']
35: function pipe(bytes memory data, CalldataReader reader, bytes[] memory results)
36: internal
37: pure
38: returns (CalldataReader)
39:
['30']
30: function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool)
['16']
16: function isBeforeHook() internal view returns (bool)
['22']
22: function isAfterHook() internal view returns (bool)
[NonCritical-13] 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: 2
Click to show findings
['79']
79: EnumerableMap.AddressToBytes32Map internal guardianRoots; // <= FOUND
['19']
19: EnumerableMap.AddressToUintMap internal whitelist; // <= FOUND
Consider spreading these lines over multiple lines to aid in readability and the support of VIM users everywhere.
Num of instances: 6
Click to show findings
['4']
4: // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/63851f8de5a6e560e9774832d1a31c43645b73d2/contracts/token/ERC20/ERC20.sol // <= FOUND
['143']
143: function _checkOperations(OperationPayable[] calldata operations) internal view override requiresVaultAuth(vault) { } // <= FOUND
['54']
54: require(to == address(0) || to == transferAgent || whitelist[msg.sender][to], AeraPeriphery__NotWhitelisted(to)); // <= FOUND
['341']
341: solverTip += _solveRedeemVaultFixedPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i); // <= FOUND
['43']
43: _handleBeforeExactInputSingle(tokenIn, tokenOut, params.recipient, params.amountIn, params.amountOutMinimum); // <= FOUND
['152']
152: emit ThresholdsSet(vault, minPriceToleranceRatio, maxPriceToleranceRatio, minUpdateIntervalMinutes, maxPriceAge); // <= FOUND
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
[NonCritical-16] 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: 6
Click to show findings
['12']
12: function neq(CalldataReader a, CalldataReader b) pure returns (bool)
['16']
16: function eq(CalldataReader a, CalldataReader b) pure returns (bool)
['20']
20: function gt(CalldataReader a, CalldataReader b) pure returns (bool)
['24']
24: function lt(CalldataReader a, CalldataReader b) pure returns (bool)
['28']
28: function ge(CalldataReader a, CalldataReader b) pure returns (bool)
['32']
32: function le(CalldataReader a, CalldataReader 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: 100
Click to show findings
['49']
49: function setProtocolFeeRecipient(address feeRecipient) external requiresAuth
['61']
61: function setProtocolFees(uint16 tvl, uint16 performance) external requiresAuth
['75']
75: function setVaultAccountant(address vault, address accountant) external requiresVaultAuth(vault)
['88']
88: function setVaultFees(address vault, uint16 tvl, uint16 performance) external requiresVaultAuth(vault)
['32']
32: function baseVaultParameters() external view returns (BaseVaultParameters memory params)
['126']
126: function submit(bytes calldata data) external whenNotPaused nonReentrant
['169']
169: function checkGuardianWhitelist(address guardian) external returns (bool isRemoved)
['189']
189: function pause() external onlyAuthOrGuardian
['195']
195: function unpause() external requiresAuth
['201']
201: function getActiveGuardians() external view returns (address[] memory)
['206']
206: function getGuardianRoot(address guardian) external view returns (bytes32)
['212']
212: function getCurrentHookCallType() external view returns (HookCallType)
['217']
217: function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4)
['61']
61: function submitSnapshot(address vault, uint160 averageValue, uint128 highestProfit, uint32 timestamp)
62: external
63: onlyVaultAccountant(vault)
64:
['96']
96: function accrueFees(address vault) external returns (uint256 protocolFeesEarned, uint256 vaultFeesEarned)
['138']
138: function vaultFeeState(address vault) external view returns (VaultSnapshot memory, VaultAccruals memory)
['34']
34: function feeVaultParameters() external view returns (FeeVaultParameters memory params)
['81']
81: function setFeeCalculator(IFeeCalculator newFeeCalculator) external requiresAuth
['94']
94: function setFeeRecipient(address newFeeRecipient) external requiresAuth
['105']
105: function claimFees() external onlyFeeRecipient returns (uint256 feeRecipientFees, uint256 protocolFees)
['129']
129: function claimProtocolFees() external returns (uint256 protocolFees)
['17']
17: function createVault(bytes32 salt) external returns (address)
['86']
86: function getERC20Name() external view returns (string memory name)
['91']
91: function getERC20Symbol() external view returns (string memory symbol)
['96']
96: function multiDepositorVaultParameters() external view returns (IBeforeTransferHook beforeTransferHook)
['62']
62: function enter(address sender, IERC20 token, uint256 tokenAmount, uint256 unitsAmount, address recipient)
63: external
64: whenNotPaused
65: onlyProvisioner
66:
['78']
78: function exit(address sender, IERC20 token, uint256 tokenAmount, uint256 unitsAmount, address recipient)
79: external
80: whenNotPaused
81: onlyProvisioner
82:
['93']
93: function setProvisioner(address provisioner_) external requiresAuth
['99']
99: function setBeforeTransferHook(IBeforeTransferHook hook) external requiresAuth
['94']
94: function setInitialPrice(address vault, uint128 price, uint32 timestamp) external requiresVaultAuth(vault)
['122']
122: function setThresholds(
123: address vault,
124: uint16 minPriceToleranceRatio,
125: uint16 maxPriceToleranceRatio,
126: uint16 minUpdateIntervalMinutes,
127: uint8 maxPriceAge,
128: uint8 maxUpdateDelayDays
129: ) external requiresVaultAuth(vault)
['156']
156: function setUnitPrice(address vault, uint128 price, uint32 timestamp) external onlyVaultAccountant(vault)
['192']
192: function pauseVault(address vault) external requiresVaultAuthOrAccountant(vault)
['203']
203: function unpauseVault(address vault, uint128 price, uint32 timestamp) external requiresVaultAuth(vault)
['219']
219: function resetHighestPrice(address vault) external requiresVaultAuth(vault)
['119']
119: function convertUnitsToToken(address vault, IERC20 token, uint256 unitsAmount)
120: external
121: view
122: returns (uint256 tokenAmount)
123:
['131']
131: function convertUnitsToTokenIfActive(address vault, IERC20 token, uint256 unitsAmount, Math.Rounding rounding)
132: external
133: view
134: returns (uint256 tokenAmount)
135:
['260']
260: function convertUnitsToNumeraire(address vault, uint256 unitsAmount) external view returns (uint256)
['141']
141: function convertTokenToUnits(address vault, IERC20 token, uint256 tokenAmount)
142: external
143: view
144: returns (uint256 unitsAmount)
145:
['153']
153: function convertTokenToUnitsIfActive(address vault, IERC20 token, uint256 tokenAmount, Math.Rounding rounding)
154: external
155: view
156: returns (uint256 unitsAmount)
157:
['293']
293: function getVaultState(address vault) external view returns (VaultPriceState memory, VaultAccruals memory)
['298']
298: function getVaultsPriceAge(address vault) external view returns (uint256)
['306']
306: function isVaultPaused(address vault) external view returns (bool)
['108']
108: function deposit(IERC20 token, uint256 tokensIn, uint256 minUnitsOut)
109: external
110: anyoneButVault
111: returns (uint256 unitsOut)
112:
['132']
132: function mint(IERC20 token, uint256 unitsOut, uint256 maxTokensIn)
133: external
134: anyoneButVault
135: returns (uint256 tokensIn)
136:
['156']
156: function refundDeposit(
157: address sender,
158: IERC20 token,
159: uint256 tokenAmount,
160: uint256 unitsAmount,
161: uint256 refundableUntil
162: ) external requiresAuth
['180']
180: function requestDeposit(
181: IERC20 token,
182: uint256 tokensIn,
183: uint256 minUnitsOut,
184: uint256 solverTip,
185: uint256 deadline,
186: uint256 maxPriceAge,
187: bool isFixedPrice
188: ) external anyoneButVault
['221']
221: function requestRedeem(
222: IERC20 token,
223: uint256 unitsIn,
224: uint256 minTokensOut,
225: uint256 solverTip,
226: uint256 deadline,
227: uint256 maxPriceAge,
228: bool isFixedPrice
229: ) external anyoneButVault
['262']
262: function refundRequest(IERC20 token, Request calldata request) external nonReentrant
['294']
294: function solveRequestsVault(IERC20 token, Request[] calldata requests) external requiresAuth nonReentrant
['358']
358: function solveRequestsDirect(IERC20 token, Request[] calldata requests) external nonReentrant
['384']
384: function setDepositDetails(uint256 depositCap_, uint256 depositRefundTimeout_) external requiresAuth
['399']
399: function setTokenDetails(IERC20 token, TokenDetails calldata details) external requiresAuth
['430']
430: function removeToken(IERC20 token) external requiresAuth
['439']
439: function maxDeposit() external view returns (uint256)
['450']
450: function areUserUnitsLocked(address user) external view returns (bool)
['455']
455: function getDepositHash(
456: address user,
457: IERC20 token,
458: uint256 tokenAmount,
459: uint256 unitsAmount,
460: uint256 refundableUntil
461: ) external pure returns (bytes32)
['466']
466: function getRequestHash(IERC20 token, Request calldata request) external pure returns (bytes32)
['17']
17: function createVault(bytes32 salt) external returns (address)
['27']
27: function deposit(TokenAmount[] calldata tokenAmounts) external requiresAuth
['47']
47: function withdraw(TokenAmount[] calldata tokenAmounts) external requiresAuth
['61']
61: function execute(OperationPayable[] calldata operations) external requiresAuth
['24']
24: function sweep(address token, uint256 amount) external requiresAuth
['28']
28: function setWhitelisted(address addr, bool isAddressWhitelisted) external requiresAuth
['41']
41: function isWhitelisted(address addr) external view returns (bool)
['47']
47: function getAllWhitelisted() external view returns (address[] memory)
['17']
17: function execute(OperationPayable[] calldata operations) external nonReentrant
['34']
34: function execute(TargetCalldata[] calldata operations) external nonReentrant
['61']
61: function addCallerCapability(address caller, address target, bytes4 sig) external requiresAuth
['72']
72: function removeCallerCapability(address caller, address target, bytes4 sig) external requiresAuth
['83']
83: function canCall(address caller, address target, bytes4 sig) external view returns (bool)
['40']
40: function setMaxDailyLoss(address vault, uint128 maxLoss) external requiresVaultAuth(vault)
['49']
49: function setMaxSlippagePerTrade(address vault, uint16 newMaxSlippage) external requiresVaultAuth(vault)
['61']
61: function setOracleRegistry(address vault, address oracleRegistry) external requiresVaultAuth(vault)
['73']
73: function vaultStates(address vault) external view returns (State memory state)
['16']
16: function depositForBurn(
17: uint256 amount,
18: uint32 destinationDomain,
19: bytes32 mintRecipient,
20: address burnToken,
21: bytes32 destinationCaller,
22: uint256 maxFee,
23: uint32
24: ) external returns (bytes memory returnData)
['19']
19: function swap(IMetaAggregationRouterV2.SwapExecutionParams calldata execution) external returns (bytes memory)
['24']
24: function swapSimpleMode(
25: IAggregationExecutor caller,
26: IMetaAggregationRouterV2.SwapDescriptionV2 calldata desc,
27: bytes calldata,
28: bytes calldata
29: ) external returns (bytes memory)
['16']
16: function requestSell(
17: uint256 sellAmount,
18: IERC20 sellToken,
19: IERC20 receiveToken,
20: bytes32,
21: address priceChecker,
22: bytes calldata priceCheckerData
23: ) external returns (bytes memory returnData)
['44']
44: function checkPrice(
45: uint256 amountIn,
46: address fromToken,
47: address toToken,
48: uint256,
49: uint256 minOut,
50: bytes calldata data
51: ) external view returns (bool)
['18']
18: function swap(
19: IOdosRouterV2.SwapTokenInfo calldata tokenInfo,
20: bytes calldata,
21: address,
22: uint32
23: ) external returns (bytes memory returnData)
['19']
19: function exactInputSingle(ISwapRouter.ExactInputSingleParams calldata params) external returns (bytes memory)
['28']
28: function exactInput(ISwapRouter.ExactInputParams calldata params) external returns (bytes memory)
['48']
48: function exactOutputSingle(ISwapRouter.ExactOutputSingleParams calldata params) external returns (bytes memory)
['57']
57: function exactOutput(ISwapRouter.ExactOutputParams calldata params) external returns (bytes memory)
['24']
24: function setIsVaultUnitsTransferable(address vault, bool isTransferable) external requiresVaultAuth(vault)
['23']
23: function updateWhitelist(address vault, address[] calldata addresses, bool isWhitelisted)
24: external
25: requiresVaultAuth(vault)
26:
['65']
65: function requestSell(
66: uint256 sellAmount,
67: IERC20 sellToken,
68: IERC20 receiveToken,
69: bytes32 appData,
70: address priceChecker,
71: bytes calldata priceCheckerData
72: ) external onlyVault nonReentrant
['99']
99: function cancelSell(
100: address milkmanOrderContract,
101: uint256 sellAmount,
102: IERC20 sellToken,
103: IERC20 receiveToken,
104: bytes32 appData,
105: address priceChecker,
106: bytes calldata priceCheckerData
107: ) external onlyVault nonReentrant
['123']
123: function claim(IERC20 token) external onlyVault nonReentrant
['70']
70: function addOracle(address base, address quote, IOracle oracle) external requiresAuth
['91']
91: function scheduleOracleUpdate(address base, address quote, IOracle oracle) external requiresAuth
['114']
114: function commitOracleUpdate(address base, address quote) external
['139']
139: function cancelScheduledOracleUpdate(address base, address quote) external requiresAuth
['155']
155: function disableOracle(address base, address quote, IOracle oracle) external requiresAuth
['173']
173: function acceptPendingOracle(address base, address quote, address user, IOracle oracle)
174: external
175: requiresUserAuth(user)
176:
['190']
190: function removeOracleOverride(address base, address quote, address user) external requiresUserAuth(user)
['15']
15: function computeBaseVaultAddress(BaseVaultFactory baseVaultFactory, bytes32 salt) public pure returns (address)
['15']
15: function computeMultiDepositorVaultAddress(MultiDepositorVaultFactory multiDepositorVaultFactory, bytes32 salt)
16: public
17: pure
18: returns (address)
19:
['15']
15: function computeSingleDepositorVaultAddress(SingleDepositorVaultFactory singleDepositorVaultFactory, bytes32 salt)
16: public
17: pure
18: returns (address)
19:
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: 1
Double type casting should be avoided in Solidity contracts to prevent unintended consequences and ensure accurate data representation. Performing multiple type casts in succession can lead to unexpected truncation, rounding errors, or loss of precision, potentially compromising the contract's functionality and reliability. Furthermore, double type casting can make the code less readable and harder to maintain, increasing the likelihood of errors and misunderstandings during development and debugging. To ensure precise and consistent data handling, developers should use appropriate data types and avoid unnecessary or excessive type casting, promoting a more robust and dependable contract execution.
Num of instances: 2
Click to show findings
['241']
241: selector = bytes4(bytes32(packed << SELECTOR_OFFSET)); // <= FOUND
['97']
97: return bytes32(uint256(bytes32(sig)) | uint256(uint160(target))); // <= FOUND
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: 7
Click to show findings
['105']
105: function claimFees() external onlyFeeRecipient returns (uint256 feeRecipientFees, uint256 protocolFees) {
106: address protocolFeeRecipient;
107:
108:
109: (feeRecipientFees, protocolFees, protocolFeeRecipient) =
110: feeCalculator.claimFees(FEE_TOKEN.balanceOf(address(this)));
111:
112:
113: require(feeRecipientFees != 0, Aera__NoFeesToClaim());
114:
115:
116: FEE_TOKEN.safeTransfer(msg.sender, feeRecipientFees); // <= FOUND
117:
118: emit FeesClaimed(msg.sender, feeRecipientFees); // <= FOUND
119:
120: if (protocolFees != 0) {
121:
122: FEE_TOKEN.safeTransfer(protocolFeeRecipient, protocolFees);
123:
124: emit ProtocolFeesClaimed(protocolFeeRecipient, protocolFees);
125: }
126: }
['129']
129: function claimProtocolFees() external returns (uint256 protocolFees) {
130: address protocolFeeRecipient;
131:
132:
133: (protocolFees, protocolFeeRecipient) = feeCalculator.claimProtocolFees(FEE_TOKEN.balanceOf(address(this)));
134:
135:
136: require(msg.sender == protocolFeeRecipient, Aera__CallerIsNotProtocolFeeRecipient()); // <= FOUND
137:
138:
139: require(protocolFees != 0, Aera__NoFeesToClaim());
140:
141:
142: FEE_TOKEN.safeTransfer(protocolFeeRecipient, protocolFees);
143:
144: emit ProtocolFeesClaimed(protocolFeeRecipient, protocolFees);
145: }
['262']
262: function refundRequest(IERC20 token, Request calldata request) external nonReentrant {
263:
264: require(
265: request.deadline < block.timestamp || isAuthorized(msg.sender, msg.sig), // <= FOUND
266: Aera__DeadlineInFutureAndUnauthorized()
267: );
268:
269: bytes32 requestHash = _getRequestHash(token, request);
270:
271: if (_isRequestTypeDeposit(request.requestType)) {
272:
273: require(asyncDepositHashes[requestHash], Aera__HashNotFound());
274:
275: asyncDepositHashes[requestHash] = false;
276:
277: token.safeTransfer(request.user, request.tokens);
278:
279: emit DepositRefunded(requestHash);
280: } else {
281:
282: require(asyncRedeemHashes[requestHash], Aera__HashNotFound());
283:
284: asyncRedeemHashes[requestHash] = false;
285:
286: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
287:
288: emit RedeemRefunded(requestHash);
289: }
290: }
['764']
764: function _solveDepositDirect(IERC20 token, Request calldata request) internal {
765: bytes32 depositHash = _getRequestHash(token, request);
766:
767: if (!asyncDepositHashes[depositHash]) {
768:
769: emit InvalidRequestHash(depositHash);
770: return;
771: }
772:
773:
774: asyncDepositHashes[depositHash] = false;
775:
776: if (request.deadline >= block.timestamp) {
777:
778: IERC20(MULTI_DEPOSITOR_VAULT).safeTransferFrom(msg.sender, request.user, request.units); // <= FOUND
779:
780:
781: token.safeTransfer(msg.sender, request.tokens); // <= FOUND
782:
783:
784: emit DepositSolved(depositHash);
785: } else {
786:
787: token.safeTransfer(request.user, request.tokens);
788:
789:
790: emit DepositRefunded(depositHash);
791: }
792: }
['803']
803: function _solveRedeemDirect(IERC20 token, Request calldata request) internal {
804: bytes32 redeemHash = _getRequestHash(token, request);
805:
806: if (!asyncRedeemHashes[redeemHash]) {
807:
808: emit InvalidRequestHash(redeemHash);
809: return;
810: }
811:
812:
813: asyncRedeemHashes[redeemHash] = false;
814:
815: if (request.deadline >= block.timestamp) {
816:
817: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(msg.sender, request.units); // <= FOUND
818:
819:
820: token.safeTransferFrom(msg.sender, request.user, request.tokens); // <= FOUND
821:
822:
823: emit RedeemSolved(redeemHash);
824: } else {
825:
826: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
827:
828:
829: emit RedeemRefunded(redeemHash);
830: }
831: }
['24']
24: function sweep(address token, uint256 amount) external requiresAuth {
25: if (token == address(0)) {
26:
27: (bool success,) = msg.sender.call{ value: amount }(""); // <= FOUND
28:
29: require(success, Aera__FailedToSendNativeToken());
30: } else {
31:
32: IERC20(token).safeTransfer(msg.sender, amount); // <= FOUND
33: }
34:
35:
36: emit Sweep(token, amount);
37: }
['65']
65: function requestSell(
66: uint256 sellAmount,
67: IERC20 sellToken,
68: IERC20 receiveToken,
69: bytes32 appData,
70: address priceChecker,
71: bytes calldata priceCheckerData
72: ) external onlyVault nonReentrant {
73:
74: require(sellAmount > 0, AeraPeriphery__MilkmanRouter__SellAmountIsZero());
75:
76:
77: sellToken.safeTransferFrom(msg.sender, address(this), sellAmount); // <= FOUND
78:
79:
80: sellToken.safeIncreaseAllowance(address(milkmanRoot), sellAmount);
81:
82:
83: milkmanRoot.requestSwapExactTokensForTokens(
84: sellAmount, sellToken, receiveToken, address(this), appData, priceChecker, priceCheckerData
85: );
86:
87:
88:
89: require(
90: sellToken.allowance(address(this), address(milkmanRoot)) == 0,
91: AeraPeriphery__MilkmanRequestSwapExactTokensForTokensFailed(sellToken)
92: );
93:
94:
95: emit SellRequested(sellAmount, sellToken, receiveToken, appData, priceChecker, priceCheckerData);
96: }
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: 3
Click to show findings
['551']
551: function _noPendingApprovalsInvariant(Approval[] memory approvals, uint256 approvalsLength) internal view {
552: Approval memory approval;
553: while (approvalsLength != 0) {
554: unchecked {
555: --approvalsLength;
556: }
557:
558: approval = approvals[approvalsLength];
559:
560:
561: require(
562:
563: IERC20(approval.token).allowance(address(this), approval.spender) == 0,
564: Aera__AllowanceIsNotZero(approval.token, approval.spender)
565: );
566: }
567: }
['600']
600: function _verifyOperation(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure {
601: require(MerkleProof.verify(proof, root, leaf), Aera__ProofVerificationFailed());
602: }
['122']
122: function _storeCallbackApprovals(Approval[] memory approvals, uint256 length) internal {
123: if (length == 0) return;
124:
125: uint256 existingApproval = APPROVALS_SLOT.asUint256().tload();
126: uint256 existingLength = existingApproval >> ADDRESS_SIZE_BITS;
127:
128: uint256 i;
129: uint256 currentSlot = uint256(APPROVALS_SLOT);
130: Approval memory approval;
131: if (existingLength == 0) {
132: approval = approvals[0];
133: unchecked {
134:
135:
136: bytes32(currentSlot).asUint256().tstore(_packLengthAndToken(length, approval.token));
137: bytes32(++currentSlot).asAddress().tstore(approval.spender);
138: }
139:
140: i = 1;
141: } else {
142: unchecked {
143: uint256 newLength = existingLength + length;
144:
145:
146: bytes32(currentSlot).asUint256().tstore(
147: _packLengthAndToken(newLength, address(uint160(existingApproval)))
148: );
149:
150: currentSlot += existingLength * 2 - 1;
151: }
152: }
153:
154: for (; i < length; ++i) {
155: approval = approvals[i];
156: unchecked {
157:
158: bytes32(++currentSlot).asAddress().tstore(approval.token);
159: bytes32(++currentSlot).asAddress().tstore(approval.spender);
160: }
161: }
162: }
Num of instances: 5
Click to show findings
['4']
4: interface IWithdrawalManagerLike // <= FOUND
['5']
5: interface IUniswapV3SwapCallback // <= FOUND
['5']
5: interface IUniswapV3SwapCallback // <= FOUND
['7']
7: interface ITokenMessengerV2 // <= FOUND
['7']
7: interface IMaplePool is IERC20, IERC4626 // <= FOUND
[NonCritical-23] 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: 24
Click to show findings
['168']
168: function _accrueFees(
169: VaultSnapshot storage vaultSnapshot,
170: VaultAccruals storage vaultAccruals,
171: uint256 lastFeeAccrualCached
172: ) internal returns (uint256 protocolFeesEarned, uint256 vaultFeesEarned) {
173: uint256 snapshotTimestamp = vaultSnapshot.timestamp;
174: if (lastFeeAccrualCached >= snapshotTimestamp || vaultSnapshot.finalizedAt > block.timestamp) {
175:
176: return (0, 0); // <= FOUND
177: }
178:
179:
180: (uint256 vaultPerformanceFeeEarned, uint256 protocolPerformanceFeeEarned) = _calculatePerformanceFees(
181: vaultAccruals.fees.performance, vaultSnapshot.highestProfit, vaultSnapshot.lastHighestProfit
182: );
183:
184: (uint256 vaultTvlFeeEarned, uint256 protocolTvlFeeEarned) = _calculateTvlFees(
185: vaultAccruals.fees.tvl, vaultSnapshot.averageValue, snapshotTimestamp, lastFeeAccrualCached
186: );
187:
188:
189: vaultSnapshot.lastHighestProfit = vaultSnapshot.highestProfit;
190: vaultSnapshot.lastFeeAccrual = uint32(snapshotTimestamp);
191: vaultAccruals.accruedFees += (vaultPerformanceFeeEarned + vaultTvlFeeEarned).toUint112();
192: vaultAccruals.accruedProtocolFees += (protocolPerformanceFeeEarned + protocolTvlFeeEarned).toUint112();
193:
194:
195: vaultSnapshot.averageValue = 0;
196: vaultSnapshot.highestProfit = 0;
197: vaultSnapshot.timestamp = 0;
198: vaultSnapshot.finalizedAt = 0;
199:
200: protocolFeesEarned = protocolPerformanceFeeEarned + protocolTvlFeeEarned;
201: vaultFeesEarned = vaultPerformanceFeeEarned + vaultTvlFeeEarned;
202: }
['75']
75: function readBool(CalldataReader self) internal pure returns (CalldataReader, bool value) {
76: assembly ("memory-safe") {
77: value := gt(byte(0, calldataload(self)), 0)
78: self := add(self, 1)
79: }
80: return (self, value); // <= FOUND
81: }
['83']
83: function readU8(CalldataReader self) internal pure returns (CalldataReader, uint8 value) {
84: assembly ("memory-safe") {
85: value := byte(0, calldataload(self))
86: self := add(self, 1)
87: }
88: return (self, value); // <= FOUND
89: }
['91']
91: function readU16(CalldataReader self) internal pure returns (CalldataReader, uint16 value) {
92: assembly ("memory-safe") {
93: value := shr(240, calldataload(self))
94: self := add(self, 2)
95: }
96: return (self, value); // <= FOUND
97: }
['99']
99: function readU32(CalldataReader self) internal pure returns (CalldataReader, uint32 value) {
100: assembly ("memory-safe") {
101: value := shr(224, calldataload(self))
102: self := add(self, 4)
103: }
104: return (self, value); // <= FOUND
105: }
['107']
107: function readI24(CalldataReader self) internal pure returns (CalldataReader, int24 value) {
108: assembly ("memory-safe") {
109: value := sar(232, calldataload(self))
110: self := add(self, 3)
111: }
112: return (self, value); // <= FOUND
113: }
['115']
115: function readU40(CalldataReader self) internal pure returns (CalldataReader, uint40 value) {
116: assembly ("memory-safe") {
117: value := shr(216, calldataload(self))
118: self := add(self, 5)
119: }
120: return (self, value); // <= FOUND
121: }
['123']
123: function readU64(CalldataReader self) internal pure returns (CalldataReader, uint64 value) {
124: assembly ("memory-safe") {
125: value := shr(192, calldataload(self))
126: self := add(self, 8)
127: }
128: return (self, value); // <= FOUND
129: }
['131']
131: function readU128(CalldataReader self) internal pure returns (CalldataReader, uint128 value) {
132: assembly ("memory-safe") {
133: value := shr(128, calldataload(self))
134: self := add(self, 16)
135: }
136: return (self, value); // <= FOUND
137: }
['139']
139: function readAddr(CalldataReader self) internal pure returns (CalldataReader, address addr) {
140: assembly ("memory-safe") {
141: addr := shr(96, calldataload(self))
142: self := add(self, 20)
143: }
144: return (self, addr); // <= FOUND
145: }
['147']
147: function readU256(CalldataReader self) internal pure returns (CalldataReader, uint256 value) {
148: assembly ("memory-safe") {
149: value := calldataload(self)
150: self := add(self, 32)
151: }
152: return (self, value); // <= FOUND
153: }
['155']
155: function readU24End(CalldataReader self) internal pure returns (CalldataReader, CalldataReader end) {
156: assembly ("memory-safe") {
157: let len := shr(232, calldataload(self))
158: self := add(self, 3)
159: end := add(self, len)
160: }
161: return (self, end); // <= FOUND
162: }
['176']
176: function readU208(CalldataReader self) internal pure returns (CalldataReader, uint208 value) {
177: assembly ("memory-safe") {
178: value := shr(48, calldataload(self))
179: self := add(self, 26)
180: }
181: return (self, value); // <= FOUND
182: }
['184']
184: function readOptionalU256(CalldataReader reader) internal pure returns (CalldataReader, uint256 u256) {
185: bool hasU256;
186: (reader, hasU256) = reader.readBool();
187: if (hasU256) {
188: (reader, u256) = reader.readU256();
189: }
190: return (reader, u256); // <= FOUND
191: }
['237']
237: function convertUnitsToToken(address vault, IERC20 token, uint256 unitsAmount)
238: external
239: view
240: returns (uint256 tokenAmount)
241: {
242: return _convertUnitsToToken(vault, token, unitsAmount, _vaultPriceStates[vault].unitPrice, Math.Rounding.Floor); // <= FOUND
243: }
['246']
246: function convertUnitsToTokenIfActive(address vault, IERC20 token, uint256 unitsAmount, Math.Rounding rounding)
247: external
248: view
249: returns (uint256 tokenAmount)
250: {
251: VaultPriceState storage vaultState = _vaultPriceStates[vault];
252:
253:
254: require(!vaultState.paused, Aera__VaultPaused());
255:
256: return _convertUnitsToToken(vault, token, unitsAmount, vaultState.unitPrice, rounding); // <= FOUND
257: }
['270']
270: function convertTokenToUnits(address vault, IERC20 token, uint256 tokenAmount)
271: external
272: view
273: returns (uint256 unitsAmount)
274: {
275: return _convertTokenToUnits(vault, token, tokenAmount, _vaultPriceStates[vault].unitPrice, Math.Rounding.Floor); // <= FOUND
276: }
['279']
279: function convertTokenToUnitsIfActive(address vault, IERC20 token, uint256 tokenAmount, Math.Rounding rounding)
280: external
281: view
282: returns (uint256 unitsAmount)
283: {
284: VaultPriceState storage vaultState = _vaultPriceStates[vault];
285:
286:
287: require(!vaultState.paused, Aera__VaultPaused());
288:
289: return _convertTokenToUnits(vault, token, tokenAmount, vaultState.unitPrice, rounding); // <= FOUND
290: }
['390']
390: function _convertTokenToUnits(
391: address vault,
392: IERC20 token,
393: uint256 tokenAmount,
394: uint256 unitPrice,
395: Math.Rounding rounding
396: ) internal view returns (uint256 unitsAmount) {
397: if (address(token) != NUMERAIRE) {
398: tokenAmount = ORACLE_REGISTRY.getQuoteForUser(tokenAmount, address(token), NUMERAIRE, vault);
399: }
400:
401: return Math.mulDiv(tokenAmount, UNIT_PRICE_PRECISION, unitPrice, rounding); // <= FOUND
402: }
['410']
410: function _convertUnitsToToken(
411: address vault,
412: IERC20 token,
413: uint256 unitsAmount,
414: uint256 unitPrice,
415: Math.Rounding rounding
416: ) internal view returns (uint256 tokenAmount) {
417: uint256 numeraireAmount = Math.mulDiv(unitsAmount, unitPrice, UNIT_PRICE_PRECISION, rounding);
418:
419: if (address(token) == NUMERAIRE) {
420: return numeraireAmount; // <= FOUND
421: }
422:
423: return ORACLE_REGISTRY.getQuoteForUser(numeraireAmount, NUMERAIRE, address(token), vault); // <= FOUND
424: }
['514']
514: function _solveDepositVaultAutoPrice(
515: IERC20 token,
516: uint256 depositMultiplier,
517: Request calldata request,
518: uint256 priceAge,
519: uint256 index
520: ) internal returns (uint256 solverTip) {
521:
522: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0; // <= FOUND
523:
524: bytes32 depositHash = _getRequestHash(token, request);
525:
526: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0; // <= FOUND
527:
528: if (request.deadline >= block.timestamp) {
529: solverTip = request.solverTip;
530: uint256 tokens = request.tokens;
531:
532:
533: if (_guardInsufficientTokensForTip(tokens, solverTip, index)) return 0; // <= FOUND
534:
535: uint256 tokensAfterTip;
536: unchecked {
537: tokensAfterTip = tokens - solverTip;
538: }
539:
540:
541: uint256 unitsOut = _tokensToUnitsFloorIfActive(token, tokensAfterTip, depositMultiplier);
542:
543: if (_guardAmountBound(unitsOut, request.units, index)) return 0; // <= FOUND
544:
545: if (_guardDepositCapExceeded(unitsOut, index)) return 0; // <= FOUND
546:
547:
548: asyncDepositHashes[depositHash] = false;
549:
550: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter(
551: address(this), token, tokensAfterTip, unitsOut, request.user
552: );
553:
554:
555: emit DepositSolved(depositHash);
556: } else {
557:
558: asyncDepositHashes[depositHash] = false;
559:
560: token.safeTransfer(request.user, request.tokens);
561:
562: emit DepositRefunded(depositHash);
563: }
564: }
['583']
583: function _solveDepositVaultFixedPrice(
584: IERC20 token,
585: uint256 depositMultiplier,
586: Request calldata request,
587: uint256 priceAge,
588: uint256 index
589: ) internal returns (uint256 solverTip) {
590:
591: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0; // <= FOUND
592:
593: bytes32 depositHash = _getRequestHash(token, request);
594:
595: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0; // <= FOUND
596:
597: if (request.deadline >= block.timestamp) {
598:
599: uint256 tokensNeeded = _unitsToTokensCeilIfActive(token, request.units, depositMultiplier);
600:
601: if (_guardAmountBound(request.tokens, tokensNeeded, index)) return 0; // <= FOUND
602:
603: if (_guardDepositCapExceeded(request.units, index)) return 0; // <= FOUND
604:
605:
606: asyncDepositHashes[depositHash] = false;
607:
608: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter(
609: address(this), token, tokensNeeded, request.units, request.user
610: );
611:
612: unchecked {
613: solverTip = request.tokens - tokensNeeded;
614: }
615:
616:
617: emit DepositSolved(depositHash);
618: } else {
619:
620: asyncDepositHashes[depositHash] = false;
621:
622: token.safeTransfer(request.user, request.tokens);
623:
624: emit DepositRefunded(depositHash);
625: }
626: }
['643']
643: function _solveRedeemVaultAutoPrice(
644: IERC20 token,
645: uint256 redeemMultiplier,
646: Request calldata request,
647: uint256 priceAge,
648: uint256 index
649: ) internal returns (uint256 solverTip) {
650:
651: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0; // <= FOUND
652:
653: bytes32 redeemHash = _getRequestHash(token, request);
654:
655: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0; // <= FOUND
656:
657: if (request.deadline >= block.timestamp) {
658: solverTip = request.solverTip;
659:
660:
661: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
662:
663: if (_guardInsufficientTokensForTip(tokenOut, solverTip, index)) return 0; // <= FOUND
664:
665: uint256 tokenOutAfterTip;
666: unchecked {
667: tokenOutAfterTip = tokenOut - solverTip;
668: }
669:
670:
671: if (_guardAmountBound(tokenOutAfterTip, request.tokens, index)) return 0; // <= FOUND
672:
673:
674: asyncRedeemHashes[redeemHash] = false;
675:
676: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit(
677: address(this), token, tokenOut, request.units, address(this)
678: );
679:
680:
681: token.safeTransfer(request.user, tokenOutAfterTip);
682:
683:
684: emit RedeemSolved(redeemHash);
685: } else {
686:
687: asyncRedeemHashes[redeemHash] = false;
688:
689: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
690:
691: emit RedeemRefunded(redeemHash);
692: }
693: }
['710']
710: function _solveRedeemVaultFixedPrice(
711: IERC20 token,
712: uint256 redeemMultiplier,
713: Request calldata request,
714: uint256 priceAge,
715: uint256 index
716: ) internal returns (uint256 solverTip) {
717:
718: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0; // <= FOUND
719:
720: bytes32 redeemHash = _getRequestHash(token, request);
721:
722: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0; // <= FOUND
723:
724: if (request.deadline >= block.timestamp) {
725:
726: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
727:
728: if (_guardAmountBound(tokenOut, request.tokens, index)) return 0; // <= FOUND
729:
730:
731: asyncRedeemHashes[redeemHash] = false;
732:
733: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit(
734: address(this), token, tokenOut, request.units, address(this)
735: );
736:
737: token.safeTransfer(request.user, request.tokens);
738:
739: unchecked {
740: solverTip = tokenOut - request.tokens;
741: }
742:
743:
744: emit RedeemSolved(redeemHash);
745: } else {
746:
747: asyncRedeemHashes[redeemHash] = false;
748:
749: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
750:
751: emit RedeemRefunded(redeemHash);
752: }
753: }
Putting 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: 8
Click to show findings
['462']
462: if (hooksConfigFlag == 0) // <= FOUND
['469']
469: if (hooksConfigFlag & HOOKS_FLAG_MASK == 0) // <= FOUND
['480']
480: if (calldataOffsetsCount != 0) // <= FOUND
['131']
131: if (existingLength == 0) // <= FOUND
['120']
120: if (protocolFees != 0) // <= FOUND
['346']
346: if (solverTip != 0) // <= FOUND
['420']
420: if (ctx.callbackData != 0) // <= FOUND
['28']
28: if (maxFee > 0) // <= FOUND
Modify such instances to include a capital I as the first character in the name to signify it is an interface. This improved readability during in
Num of instances: 2
Such instances can be replaced with unnamed returns
Num of instances: 7
Click to show findings
['155']
155: function readU24End(CalldataReader self) internal pure returns (CalldataReader, CalldataReader end) { // <= FOUND
156: assembly ("memory-safe") {
157: let len := shr(232, calldataload(self))
158: self := add(self, 3)
159: end := add(self, len)
160: }
161: return (self, end);
162: }
['237']
237: function convertUnitsToToken(address vault, IERC20 token, uint256 unitsAmount) // <= FOUND
238: external
239: view
240: returns (uint256 tokenAmount) // <= FOUND
241: {
242: return _convertUnitsToToken(vault, token, unitsAmount, _vaultPriceStates[vault].unitPrice, Math.Rounding.Floor);
243: }
['246']
246: function convertUnitsToTokenIfActive(address vault, IERC20 token, uint256 unitsAmount, Math.Rounding rounding) // <= FOUND
247: external
248: view
249: returns (uint256 tokenAmount) // <= FOUND
250: {
251: VaultPriceState storage vaultState = _vaultPriceStates[vault];
252:
253:
254: require(!vaultState.paused, Aera__VaultPaused());
255:
256: return _convertUnitsToToken(vault, token, unitsAmount, vaultState.unitPrice, rounding);
257: }
['270']
270: function convertTokenToUnits(address vault, IERC20 token, uint256 tokenAmount) // <= FOUND
271: external
272: view
273: returns (uint256 unitsAmount) // <= FOUND
274: {
275: return _convertTokenToUnits(vault, token, tokenAmount, _vaultPriceStates[vault].unitPrice, Math.Rounding.Floor);
276: }
['279']
279: function convertTokenToUnitsIfActive(address vault, IERC20 token, uint256 tokenAmount, Math.Rounding rounding) // <= FOUND
280: external
281: view
282: returns (uint256 unitsAmount) // <= FOUND
283: {
284: VaultPriceState storage vaultState = _vaultPriceStates[vault];
285:
286:
287: require(!vaultState.paused, Aera__VaultPaused());
288:
289: return _convertTokenToUnits(vault, token, tokenAmount, vaultState.unitPrice, rounding);
290: }
['390']
390: function _convertTokenToUnits( // <= FOUND
391: address vault,
392: IERC20 token,
393: uint256 tokenAmount,
394: uint256 unitPrice,
395: Math.Rounding rounding
396: ) internal view returns (uint256 unitsAmount) { // <= FOUND
397: if (address(token) != NUMERAIRE) {
398: tokenAmount = ORACLE_REGISTRY.getQuoteForUser(tokenAmount, address(token), NUMERAIRE, vault);
399: }
400:
401: return Math.mulDiv(tokenAmount, UNIT_PRICE_PRECISION, unitPrice, rounding);
402: }
['410']
410: function _convertUnitsToToken( // <= FOUND
411: address vault,
412: IERC20 token,
413: uint256 unitsAmount,
414: uint256 unitPrice,
415: Math.Rounding rounding
416: ) internal view returns (uint256 tokenAmount) { // <= FOUND
417: uint256 numeraireAmount = Math.mulDiv(unitsAmount, unitPrice, UNIT_PRICE_PRECISION, rounding);
418:
419: if (address(token) == NUMERAIRE) {
420: return numeraireAmount;
421: }
422:
423: return ORACLE_REGISTRY.getQuoteForUser(numeraireAmount, NUMERAIRE, address(token), vault);
424: }
Make found instants CAPITAL_CASE
Num of instances: 2
Click to show findings
['25']
25: address public immutable vault; // <= FOUND
['29']
29: IMilkman public immutable milkmanRoot; // <= FOUND
To 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: 1
Click to show findings
['173']
173:
174: return averageValue * tvlFee * timeDelta / ONE_IN_BPS / SECONDS_PER_YEAR; // <= FOUND
Magic 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: 3
Click to show findings
['268']
268: return success && data.length == 32 ? abi.decode(data, (uint8)) : 18;
['150']
150:
151: currentSlot += existingLength * 2 - 1;
['38']
38: tokenOut = address(bytes20(params.path[pathLength - 20:]));
Num of instances: 3
Click to show findings
['10']
10: contract ComputeBaseVaultAddressLens // <= FOUND
['10']
10: contract ComputeMultiDepositorVaultAddressLens // <= FOUND
['10']
10: contract ComputeSingleDepositorVaultAddressLens // <= FOUND
Num of instances: 5
Click to show findings
[]
456: function _shouldPause(VaultPriceState storage state, uint256 price, uint32 timestamp)
457: internal
458: view
459: returns (bool)
460: {
461: unchecked {
462: uint256 lastUpdateTime = state.timestamp;
463:
464: if (timestamp < state.minUpdateIntervalMinutes * ONE_MINUTE + lastUpdateTime) {
465: return true;
466: }
467:
468:
469: if (timestamp - lastUpdateTime > state.maxUpdateDelayDays * ONE_DAY) {
470: return true;
471: }
472:
473: uint256 currentPrice = state.unitPrice;
474:
475: if (price > currentPrice) {
476:
477: return price * ONE_IN_BPS > currentPrice * state.maxPriceToleranceRatio;
478: } else {
479:
480: return price * ONE_IN_BPS < currentPrice * state.minPriceToleranceRatio;
481: }
482: }
483: }
[]
514: function _solveDepositVaultAutoPrice(
515: IERC20 token,
516: uint256 depositMultiplier,
517: Request calldata request,
518: uint256 priceAge,
519: uint256 index
520: ) internal returns (uint256 solverTip) {
521:
522: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
523:
524: bytes32 depositHash = _getRequestHash(token, request);
525:
526: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0;
527:
528: if (request.deadline >= block.timestamp) {
529: solverTip = request.solverTip;
530: uint256 tokens = request.tokens;
531:
532:
533: if (_guardInsufficientTokensForTip(tokens, solverTip, index)) return 0;
534:
535: uint256 tokensAfterTip;
536: unchecked {
537: tokensAfterTip = tokens - solverTip;
538: }
539:
540:
541: uint256 unitsOut = _tokensToUnitsFloorIfActive(token, tokensAfterTip, depositMultiplier);
542:
543: if (_guardAmountBound(unitsOut, request.units, index)) return 0;
544:
545: if (_guardDepositCapExceeded(unitsOut, index)) return 0;
546:
547:
548: asyncDepositHashes[depositHash] = false;
549:
550: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter(
551: address(this), token, tokensAfterTip, unitsOut, request.user
552: );
553:
554:
555: emit DepositSolved(depositHash);
556: } else {
557:
558: asyncDepositHashes[depositHash] = false;
559:
560: token.safeTransfer(request.user, request.tokens);
561:
562: emit DepositRefunded(depositHash);
563: }
564: }
[]
583: function _solveDepositVaultFixedPrice(
584: IERC20 token,
585: uint256 depositMultiplier,
586: Request calldata request,
587: uint256 priceAge,
588: uint256 index
589: ) internal returns (uint256 solverTip) {
590:
591: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
592:
593: bytes32 depositHash = _getRequestHash(token, request);
594:
595: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0;
596:
597: if (request.deadline >= block.timestamp) {
598:
599: uint256 tokensNeeded = _unitsToTokensCeilIfActive(token, request.units, depositMultiplier);
600:
601: if (_guardAmountBound(request.tokens, tokensNeeded, index)) return 0;
602:
603: if (_guardDepositCapExceeded(request.units, index)) return 0;
604:
605:
606: asyncDepositHashes[depositHash] = false;
607:
608: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter(
609: address(this), token, tokensNeeded, request.units, request.user
610: );
611:
612: unchecked {
613: solverTip = request.tokens - tokensNeeded;
614: }
615:
616:
617: emit DepositSolved(depositHash);
618: } else {
619:
620: asyncDepositHashes[depositHash] = false;
621:
622: token.safeTransfer(request.user, request.tokens);
623:
624: emit DepositRefunded(depositHash);
625: }
626: }
[]
643: function _solveRedeemVaultAutoPrice(
644: IERC20 token,
645: uint256 redeemMultiplier,
646: Request calldata request,
647: uint256 priceAge,
648: uint256 index
649: ) internal returns (uint256 solverTip) {
650:
651: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
652:
653: bytes32 redeemHash = _getRequestHash(token, request);
654:
655: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0;
656:
657: if (request.deadline >= block.timestamp) {
658: solverTip = request.solverTip;
659:
660:
661: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
662:
663: if (_guardInsufficientTokensForTip(tokenOut, solverTip, index)) return 0;
664:
665: uint256 tokenOutAfterTip;
666: unchecked {
667: tokenOutAfterTip = tokenOut - solverTip;
668: }
669:
670:
671: if (_guardAmountBound(tokenOutAfterTip, request.tokens, index)) return 0;
672:
673:
674: asyncRedeemHashes[redeemHash] = false;
675:
676: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit(
677: address(this), token, tokenOut, request.units, address(this)
678: );
679:
680:
681: token.safeTransfer(request.user, tokenOutAfterTip);
682:
683:
684: emit RedeemSolved(redeemHash);
685: } else {
686:
687: asyncRedeemHashes[redeemHash] = false;
688:
689: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
690:
691: emit RedeemRefunded(redeemHash);
692: }
693: }
[]
710: function _solveRedeemVaultFixedPrice(
711: IERC20 token,
712: uint256 redeemMultiplier,
713: Request calldata request,
714: uint256 priceAge,
715: uint256 index
716: ) internal returns (uint256 solverTip) {
717:
718: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
719:
720: bytes32 redeemHash = _getRequestHash(token, request);
721:
722: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0;
723:
724: if (request.deadline >= block.timestamp) {
725:
726: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
727:
728: if (_guardAmountBound(tokenOut, request.tokens, index)) return 0;
729:
730:
731: asyncRedeemHashes[redeemHash] = false;
732:
733: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit(
734: address(this), token, tokenOut, request.units, address(this)
735: );
736:
737: token.safeTransfer(request.user, request.tokens);
738:
739: unchecked {
740: solverTip = tokenOut - request.tokens;
741: }
742:
743:
744: emit RedeemSolved(redeemHash);
745: } else {
746:
747: asyncRedeemHashes[redeemHash] = false;
748:
749: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
750:
751: emit RedeemRefunded(redeemHash);
752: }
753: }
If these serve no purpose, they should be safely removed
Num of instances: 1
Click to show findings
['30']
30: struct SimpleSwapData { // <= FOUND
31: address[] firstPools;
32: uint256[] firstSwapAmounts;
33: bytes[] swapDatas;
34: uint256 deadline;
35: bytes destTokenFeeData;
36: }
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: 38
Click to show findings
['48']
48: function _deployVault(bytes32 salt, string calldata description, BaseVaultParameters calldata baseVaultParams)
49: internal
50: returns (address deployed)
51: {
52:
53: _storeBaseVaultParameters(baseVaultParams);
54:
55:
56: deployed = address(new BaseVault{ salt: salt }());
57:
58:
59: emit VaultCreated(deployed, baseVaultParams.owner, address(baseVaultParams.submitHooks), description);
60: }
['126']
126: function submit(bytes calldata data) external whenNotPaused nonReentrant {
127: (bool success, bytes32 root) = guardianRoots.tryGet(msg.sender);
128:
129: require(success, Aera__CallerIsNotGuardian());
130:
131: address submitHooks_ = address(submitHooks);
132:
133: _beforeSubmitHooks(submitHooks_, data);
134:
135: CalldataReader reader = CalldataReaderLib.from(data);
136: CalldataReader end = reader.readBytesEnd(data);
137:
138: Approval[] memory approvals;
139: uint256 approvalsLength;
140:
141: (approvals, approvalsLength,, reader) = _executeSubmit(root, reader, false);
142:
143:
144: _afterSubmitHooks(submitHooks_, data);
145:
146:
147: _noPendingApprovalsInvariant(approvals, approvalsLength);
148:
149:
150: reader.requireAtEndOf(end);
151: }
['154']
154: function setGuardianRoot(address guardian, bytes32 root) external virtual requiresAuth {
155:
156: _setGuardianRoot(guardian, root);
157: }
['217']
217: function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
218: return IERC721Receiver.onERC721Received.selector;
219: }
['226']
226: function _handleCallbackOperations(bytes32 root, uint256 cursor)
227: internal
228: virtual
229: override
230: returns (bytes memory returnValue)
231: {
232: CalldataReader reader = CalldataReader.wrap(cursor);
233: CalldataReader end = reader.readBytesEnd();
234:
235: Approval[] memory approvals;
236: uint256 approvalsLength;
237: bytes[] memory results;
238:
239: (approvals, approvalsLength, results, reader) = _executeSubmit(root, reader, true);
240:
241:
242: _storeCallbackApprovals(approvals, approvalsLength);
243:
244: (reader, returnValue) = _getReturnValue(reader, results);
245:
246:
247: reader.requireAtEndOf(end);
248:
249: return returnValue;
250: }
['258']
258: function _processExpectedCallback(CalldataReader reader, bytes32 root) internal returns (CalldataReader, uint208) {
259: bool hasCallback;
260: (reader, hasCallback) = reader.readBool();
261:
262: if (!hasCallback) {
263: return (reader, 0);
264: }
265:
266: uint208 packedCallbackData;
267: (reader, packedCallbackData) = reader.readU208();
268:
269:
270: _allowCallback(root, packedCallbackData);
271:
272: return (reader, packedCallbackData);
273: }
['278']
278: function _beforeSubmitHooks(address hooks, bytes calldata data) internal {
279: if (_hasBeforeHooks(hooks)) {
280:
281: (bool success, bytes memory result) =
282: hooks.call(abi.encodeWithSelector(ISubmitHooks.beforeSubmit.selector, data, msg.sender));
283:
284: require(success, Aera__BeforeSubmitHooksFailed(result));
285: }
286: }
['291']
291: function _afterSubmitHooks(address hooks, bytes calldata data) internal {
292: if (_hasAfterHooks(hooks)) {
293:
294: (bool success, bytes memory result) =
295: hooks.call(abi.encodeWithSelector(ISubmitHooks.afterSubmit.selector, data, msg.sender));
296:
297: require(success, Aera__AfterSubmitHooksFailed(result));
298: }
299: }
['335']
335: function _afterOperationHooks(address operationHooks, bytes memory data, uint256 i) internal {
336: if (_hasAfterHooks(operationHooks)) {
337:
338: _setHookCallType(HookCallType.AFTER);
339:
340:
341: (bool success, bytes memory result) = operationHooks.call(data);
342:
343: require(success, Aera__AfterOperationHooksFailed(i, result));
344:
345:
346: _setHookCallType(HookCallType.NONE);
347: }
348: }
['455']
455: function _processBeforeOperationHooks(CalldataReader reader, bytes memory callData, uint256 i)
456: internal
457: returns (CalldataReader, bytes memory, uint256, address)
458: {
459: uint8 hooksConfigFlag;
460: (reader, hooksConfigFlag) = reader.readU8();
461:
462: if (hooksConfigFlag == 0) {
463: return (reader, "", 0, address(0));
464: }
465:
466: uint256 calldataOffsetsCount = hooksConfigFlag & CONFIGURABLE_HOOKS_LENGTH_MASK;
467:
468:
469: if (hooksConfigFlag & HOOKS_FLAG_MASK == 0) {
470: uint256 calldataOffsetsPacked;
471: (reader, calldataOffsetsPacked) = reader.readU256();
472:
473: return (
474: reader, callData.extract(calldataOffsetsPacked, calldataOffsetsCount), calldataOffsetsPacked, address(0)
475: );
476: }
477:
478: address operationHooks;
479:
480: if (calldataOffsetsCount != 0) {
481: uint256 calldataOffsetsPacked;
482: (reader, calldataOffsetsPacked) = reader.readU256();
483:
484: (reader, operationHooks) = reader.readAddr();
485:
486: require(!_hasBeforeHooks(operationHooks), Aera__BeforeOperationHooksWithConfigurableHooks());
487:
488: return (
489: reader,
490: callData.extract(calldataOffsetsPacked, calldataOffsetsCount),
491: calldataOffsetsPacked,
492: operationHooks
493: );
494: }
495:
496:
497: (reader, operationHooks) = reader.readAddr();
498:
499: return (
500: reader,
501:
502: _beforeOperationHooks(operationHooks, callData, i),
503: 0,
504: operationHooks
505: );
506: }
['521']
521: function _setGuardianRoot(address guardian, bytes32 root) internal virtual {
522:
523: require(guardian != address(0), Aera__ZeroAddressGuardian());
524:
525:
526: require(WHITELIST.isWhitelisted(guardian), Aera__GuardianNotWhitelisted());
527:
528:
529: require(root != bytes32(0), Aera__ZeroAddressMerkleRoot());
530:
531:
532: guardianRoots.set(guardian, root);
533:
534:
535: emit GuardianRootSet(guardian, root);
536: }
['600']
600: function _verifyOperation(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure {
601: require(MerkleProof.verify(proof, root, leaf), Aera__ProofVerificationFailed());
602: }
['608']
608: function _createMerkleLeaf(OperationContext memory ctx, bytes memory extractedData)
609: internal
610: pure
611: returns (bytes32)
612: {
613: return keccak256(
614: abi.encodePacked(
615: ctx.target,
616: ctx.selector,
617: ctx.value > 0,
618: ctx.operationHooks,
619: ctx.configurableOperationHooks,
620: ctx.callbackData,
621: extractedData
622: )
623: );
624: }
['657']
657: function _isAllowanceSelector(bytes4 selector) internal pure returns (bool) {
658: return selector == IERC20.approve.selector || selector == IERC20WithAllowance.increaseAllowance.selector;
659: }
['107']
107: function _allowCallback(bytes32 root, uint256 packedCallbackData) internal {
108:
109: CALLBACK_CALL_SLOT.asUint256().tstore(packedCallbackData);
110:
111:
112: CALLBACK_MERKLE_ROOT_SLOT.asBytes32().tstore(root);
113: }
['17']
17: function createVault(bytes32 salt) external returns (address) {
18:
19: return address(new MultiDepositorVault{ salt: salt }());
20: }
['112']
112: function _deployVault(
113: bytes32 salt,
114: string calldata description,
115: ERC20Parameters calldata erc20Params,
116: BaseVaultParameters calldata baseVaultParams,
117: FeeVaultParameters calldata feeVaultParams,
118: IBeforeTransferHook beforeTransferHook
119: ) internal returns (address deployed) {
120:
121: _storeBaseVaultParameters(baseVaultParams);
122: _storeFeeVaultParameters(feeVaultParams);
123: _storeERC20Parameters(erc20Params);
124: _storeMultiDepositorVaultParameters(beforeTransferHook);
125:
126:
127: deployed = _createVault(salt);
128:
129:
130: emit VaultCreated(
131: deployed,
132: baseVaultParams.owner,
133: address(baseVaultParams.submitHooks),
134: erc20Params,
135: feeVaultParams,
136: beforeTransferHook,
137: description
138: );
139: }
['163']
163: function _createVault(bytes32 salt) internal returns (address deployed) {
164:
165: bytes memory data =
166: Address.functionDelegateCall(_DEPLOY_DELEGATE, abi.encodeCall(IVaultDeployDelegate.createVault, (salt)));
167: deployed = abi.decode(data, (address));
168: }
['850']
850: function _guardInvalidRequestHash(bool hashExists, bytes32 requestHash) internal returns (bool) {
851: if (!hashExists) {
852:
853: emit InvalidRequestHash(requestHash);
854: return true;
855: }
856: return false;
857: }
['17']
17: function createVault(bytes32 salt) external returns (address) {
18:
19: return address(new SingleDepositorVault{ salt: salt }());
20: }
['68']
68: function _deployVault(
69: bytes32 salt,
70: string calldata description,
71: BaseVaultParameters calldata baseVaultParams,
72: FeeVaultParameters calldata singleDepositorVaultParams
73: ) internal returns (address deployed) {
74:
75: _storeBaseVaultParameters(baseVaultParams);
76: _storeFeeVaultParameters(singleDepositorVaultParams);
77:
78:
79: deployed = _createVault(salt);
80:
81:
82: emit VaultCreated(
83: deployed,
84: baseVaultParams.owner,
85: address(baseVaultParams.submitHooks),
86: singleDepositorVaultParams.feeToken,
87: singleDepositorVaultParams.feeCalculator,
88: singleDepositorVaultParams.feeRecipient,
89: description
90: );
91: }
['96']
96: function _createVault(bytes32 salt) internal returns (address deployed) {
97:
98: bytes memory data =
99: Address.functionDelegateCall(_DEPLOY_DELEGATE, abi.encodeCall(IVaultDeployDelegate.createVault, (salt)));
100:
101: deployed = abi.decode(data, (address));
102: }
['30']
30: function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) {
31: Authority auth = authority;
32:
33:
34:
35: return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig)) || user == owner;
36: }
['15']
15: function computeBaseVaultAddress(BaseVaultFactory baseVaultFactory, bytes32 salt) public pure returns (address) {
16: bytes32 creationCodeHash = keccak256(type(BaseVault).creationCode);
17:
18: return Create2.computeAddress(salt, creationCodeHash, address(baseVaultFactory));
19: }
['15']
15: function computeMultiDepositorVaultAddress(MultiDepositorVaultFactory multiDepositorVaultFactory, bytes32 salt)
16: public
17: pure
18: returns (address)
19: {
20: bytes32 creationCodeHash = keccak256(type(MultiDepositorVault).creationCode);
21:
22: return Create2.computeAddress(salt, creationCodeHash, address(multiDepositorVaultFactory));
23: }
['15']
15: function computeSingleDepositorVaultAddress(SingleDepositorVaultFactory singleDepositorVaultFactory, bytes32 salt)
16: public
17: pure
18: returns (address)
19: {
20: bytes32 creationCodeHash = keccak256(type(SingleDepositorVault).creationCode);
21:
22: return Create2.computeAddress(salt, creationCodeHash, address(singleDepositorVaultFactory));
23: }
['61']
61: function addCallerCapability(address caller, address target, bytes4 sig) external requiresAuth {
62: bytes32 targetAndSelector = _packTargetSig(target, sig);
63:
64:
65: _canCall[caller][targetAndSelector] = true;
66:
67:
68: emit CallerCapabilityAdded(caller, target, sig);
69: }
['72']
72: function removeCallerCapability(address caller, address target, bytes4 sig) external requiresAuth {
73: bytes32 targetAndSelector = _packTargetSig(target, sig);
74:
75:
76: _canCall[caller][targetAndSelector] = false;
77:
78:
79: emit CallerCapabilityRemoved(caller, target, sig);
80: }
['83']
83: function canCall(address caller, address target, bytes4 sig) external view returns (bool) {
84: bytes32 targetAndSelector = _packTargetSig(target, sig);
85: return _canCall[caller][targetAndSelector];
86: }
['96']
96: function _packTargetSig(address target, bytes4 sig) internal pure returns (bytes32) {
97: return bytes32(uint256(bytes32(sig)) | uint256(uint160(target)));
98: }
['16']
16: function depositForBurn(
17: uint256 amount,
18: uint32 destinationDomain,
19: bytes32 mintRecipient,
20: address burnToken,
21: bytes32 destinationCaller,
22: uint256 maxFee,
23: uint32
24: ) external returns (bytes memory returnData) {
25:
26: require(destinationCaller == bytes32(0), AeraPeriphery__DestinationCallerNotZero());
27:
28: if (maxFee > 0) {
29:
30: uint256 sourceAmountInNumeraire = _convertToNumeraire(amount, burnToken);
31:
32:
33: uint256 destinationAmountInNumeraire = _convertToNumeraire(amount - maxFee, burnToken);
34:
35:
36: _enforceSlippageLimitAndDailyLossLog(
37: msg.sender, burnToken, burnToken, sourceAmountInNumeraire, destinationAmountInNumeraire
38: );
39: }
40:
41: returnData = abi.encode(destinationDomain, address(uint160(uint256(mintRecipient))), burnToken);
42: }
['24']
24: function swapSimpleMode(
25: IAggregationExecutor caller,
26: IMetaAggregationRouterV2.SwapDescriptionV2 calldata desc,
27: bytes calldata,
28: bytes calldata
29: ) external returns (bytes memory) {
30: return _processSwapHooks(desc, address(caller));
31: }
['16']
16: function requestSell(
17: uint256 sellAmount,
18: IERC20 sellToken,
19: IERC20 receiveToken,
20: bytes32,
21: address priceChecker,
22: bytes calldata priceCheckerData
23: ) external returns (bytes memory returnData) {
24:
25: require(priceChecker == address(this), AeraPeriphery__InvalidPriceChecker(address(this), priceChecker));
26:
27:
28: address vault = abi.decode(priceCheckerData, (address));
29: require(msg.sender == vault, AeraPeriphery__InvalidVaultInPriceCheckerData(msg.sender, vault));
30:
31:
32: _handleMilkmanBeforeHook(address(sellToken), sellAmount, msg.sender);
33:
34:
35: returnData = abi.encode(address(sellToken), address(receiveToken));
36: }
['44']
44: function checkPrice(
45: uint256 amountIn,
46: address fromToken,
47: address toToken,
48: uint256,
49: uint256 minOut,
50: bytes calldata data
51: ) external view returns (bool) {
52: address vault = abi.decode(data, (address));
53: State storage state = _vaultStates[vault];
54:
55:
56: uint256 expectedOut = state.oracleRegistry.getQuoteForUser(amountIn, fromToken, toToken, vault);
57: uint256 slippageMultiplier;
58: unchecked {
59: slippageMultiplier = MAX_BPS - state.maxSlippagePerTrade;
60: }
61: return minOut > (expectedOut * slippageMultiplier / MAX_BPS);
62: }
['18']
18: function swap(
19: IOdosRouterV2.SwapTokenInfo calldata tokenInfo,
20: bytes calldata,
21: address,
22: uint32
23: ) external returns (bytes memory returnData) {
24:
25: require(tokenInfo.inputAmount != 0, AeraPeriphery__InputAmountIsZero());
26:
27:
28: require(tokenInfo.inputToken != ODOS_ROUTER_V2_ETH_ADDRESS, AeraPeriphery__InputTokenIsETH());
29:
30:
31: require(tokenInfo.outputToken != ODOS_ROUTER_V2_ETH_ADDRESS, AeraPeriphery__OutputTokenIsETH());
32:
33:
34: (address tokenIn, address tokenOut, address receiver) = _handleBeforeExactInputSingle(
35: tokenInfo.inputToken,
36: tokenInfo.outputToken,
37: tokenInfo.outputReceiver,
38: tokenInfo.inputAmount,
39: tokenInfo.outputMin
40: );
41: return abi.encode(tokenIn, tokenOut, receiver);
42: }
['65']
65: function requestSell(
66: uint256 sellAmount,
67: IERC20 sellToken,
68: IERC20 receiveToken,
69: bytes32 appData,
70: address priceChecker,
71: bytes calldata priceCheckerData
72: ) external onlyVault nonReentrant {
73:
74: require(sellAmount > 0, AeraPeriphery__MilkmanRouter__SellAmountIsZero());
75:
76:
77: sellToken.safeTransferFrom(msg.sender, address(this), sellAmount);
78:
79:
80: sellToken.safeIncreaseAllowance(address(milkmanRoot), sellAmount);
81:
82:
83: milkmanRoot.requestSwapExactTokensForTokens(
84: sellAmount, sellToken, receiveToken, address(this), appData, priceChecker, priceCheckerData
85: );
86:
87:
88:
89: require(
90: sellToken.allowance(address(this), address(milkmanRoot)) == 0,
91: AeraPeriphery__MilkmanRequestSwapExactTokensForTokensFailed(sellToken)
92: );
93:
94:
95: emit SellRequested(sellAmount, sellToken, receiveToken, appData, priceChecker, priceCheckerData);
96: }
['99']
99: function cancelSell(
100: address milkmanOrderContract,
101: uint256 sellAmount,
102: IERC20 sellToken,
103: IERC20 receiveToken,
104: bytes32 appData,
105: address priceChecker,
106: bytes calldata priceCheckerData
107: ) external onlyVault nonReentrant {
108:
109: IMilkman(milkmanOrderContract).cancelSwap(
110: sellAmount, sellToken, receiveToken, address(this), appData, priceChecker, priceCheckerData
111: );
112:
113:
114: sellToken.safeTransfer(vault, sellAmount);
115:
116:
117: emit SellCancelled(
118: milkmanOrderContract, sellAmount, sellToken, receiveToken, appData, priceChecker, priceCheckerData
119: );
120: }
['223']
223: function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
224: return interfaceId == type(IOracleRegistry).interfaceId || interfaceId == type(IOracle).interfaceId
225: || super.supportsInterface(interfaceId);
226: }
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: 8
Click to show findings
['281']
281:
282: (bool success, bytes memory result) =
283: hooks.call(abi.encodeWithSelector(ISubmitHooks.beforeSubmit.selector, data, msg.sender)); // <= FOUND
['294']
294:
295: (bool success, bytes memory result) =
296: hooks.call(abi.encodeWithSelector(ISubmitHooks.afterSubmit.selector, data, msg.sender)); // <= FOUND
['315']
315:
316: (bool success, bytes memory returnValue) = operationHooks.call(data); // <= FOUND
['341']
341:
342: (bool success, bytes memory result) = operationHooks.call(data); // <= FOUND
['52']
52:
53: (bool success, bytes memory returnData) = target.call(data); // <= FOUND
['416']
416:
417: (bool success, bytes memory result) = ctx.target.call{ value: ctx.value }(callData); // <= FOUND
['71']
71:
72:
73: (success, result) = operation.target.call{ value: operation.value }(operation.data); // <= FOUND
['43']
43:
44:
45: (bool success, bytes memory result) = operation.target.call{ value: operation.value }(operation.data); // <= FOUND
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: 4
Click to show findings
[]
359: function _executeSubmit(bytes32 root, CalldataReader reader, bool isCalledFromCallback)
360: internal
361: returns (Approval[] memory approvals, uint256 approvalsLength, bytes[] memory results, CalldataReader newReader)
362: {
363: uint256 operationsLength;
364: (reader, operationsLength) = reader.readU8();
365:
366: results = new bytes[](operationsLength);
367:
368:
369: approvals = new Approval[](operationsLength);
370:
371:
372:
373: OperationContext memory ctx;
374: for (uint256 i = 0; i < operationsLength; ++i) {
375: (reader, ctx.target) = reader.readAddr();
376:
377: bytes memory callData;
378: (reader, callData) = reader.readBytesToMemory();
379:
380: reader = callData.pipe(reader, results);
381:
382: bool isStaticCall;
383: (reader, isStaticCall) = reader.readBool();
384: if (isStaticCall) {
385:
386: (bool success, bytes memory result) = ctx.target.staticcall(callData);
387:
388: require(success, Aera__SubmissionFailed(i, result));
389:
390: results[i] = result;
391: } else {
392: ctx.selector = bytes4(callData);
393: if (_isAllowanceSelector(ctx.selector)) {
394: unchecked {
395: approvals[approvalsLength++] =
396: Approval({ token: ctx.target, spender: _extractApprovalSpender(callData) });
397: }
398: }
399:
400: (reader, ctx.callbackData) = _processExpectedCallback(reader, root);
401:
402: bytes memory extractedData;
403:
404: (reader, extractedData, ctx.configurableOperationHooks, ctx.operationHooks) =
405: _processBeforeOperationHooks(reader, callData, i);
406:
407: bytes32[] memory proof;
408: (reader, proof) = reader.readBytes32Array();
409:
410: (reader, ctx.value) = reader.readOptionalU256();
411:
412:
413: _verifyOperation(proof, root, _createMerkleLeaf(ctx, extractedData));
414:
415:
416: (bool success, bytes memory result) = ctx.target.call{ value: ctx.value }(callData);
417:
418: require(success, Aera__SubmissionFailed(i, result));
419:
420: if (ctx.callbackData != 0) {
421:
422: require(_hasCallbackBeenCalled(), Aera__ExpectedCallbackNotReceived());
423:
424: if (!isCalledFromCallback) {
425:
426: Approval[] memory callbackApprovals = _getCallbackApprovals();
427:
428:
429: _noPendingApprovalsInvariant(callbackApprovals, callbackApprovals.length);
430: }
431: }
432:
433:
434: _afterOperationHooks(ctx.operationHooks, callData, i);
435:
436: results[i] = result;
437: }
438: }
439:
440: return (approvals, approvalsLength, results, reader);
441: }
[]
455: function _processBeforeOperationHooks(CalldataReader reader, bytes memory callData, uint256 i)
456: internal
457: returns (CalldataReader, bytes memory, uint256, address)
458: {
459: uint8 hooksConfigFlag;
460: (reader, hooksConfigFlag) = reader.readU8();
461:
462: if (hooksConfigFlag == 0) {
463: return (reader, "", 0, address(0));
464: }
465:
466: uint256 calldataOffsetsCount = hooksConfigFlag & CONFIGURABLE_HOOKS_LENGTH_MASK;
467:
468:
469: if (hooksConfigFlag & HOOKS_FLAG_MASK == 0) {
470: uint256 calldataOffsetsPacked;
471: (reader, calldataOffsetsPacked) = reader.readU256();
472:
473: return (
474: reader, callData.extract(calldataOffsetsPacked, calldataOffsetsCount), calldataOffsetsPacked, address(0)
475: );
476: }
477:
478: address operationHooks;
479:
480: if (calldataOffsetsCount != 0) {
481: uint256 calldataOffsetsPacked;
482: (reader, calldataOffsetsPacked) = reader.readU256();
483:
484: (reader, operationHooks) = reader.readAddr();
485:
486: require(!_hasBeforeHooks(operationHooks), Aera__BeforeOperationHooksWithConfigurableHooks());
487:
488: return (
489: reader,
490: callData.extract(calldataOffsetsPacked, calldataOffsetsCount),
491: calldataOffsetsPacked,
492: operationHooks
493: );
494: }
495:
496:
497: (reader, operationHooks) = reader.readAddr();
498:
499: return (
500: reader,
501:
502: _beforeOperationHooks(operationHooks, callData, i),
503: 0,
504: operationHooks
505: );
506: }
[]
456: function _shouldPause(VaultPriceState storage state, uint256 price, uint32 timestamp)
457: internal
458: view
459: returns (bool)
460: {
461: unchecked {
462: uint256 lastUpdateTime = state.timestamp;
463:
464: if (timestamp < state.minUpdateIntervalMinutes * ONE_MINUTE + lastUpdateTime) {
465: return true;
466: }
467:
468:
469: if (timestamp - lastUpdateTime > state.maxUpdateDelayDays * ONE_DAY) {
470: return true;
471: }
472:
473: uint256 currentPrice = state.unitPrice;
474:
475: if (price > currentPrice) {
476:
477: return price * ONE_IN_BPS > currentPrice * state.maxPriceToleranceRatio;
478: } else {
479:
480: return price * ONE_IN_BPS < currentPrice * state.minPriceToleranceRatio;
481: }
482: }
483: }
['294']
294: function solveRequestsVault(IERC20 token, Request[] calldata requests) external requiresAuth nonReentrant { // <= FOUND
295:
296: uint256 priceAge = PRICE_FEE_CALCULATOR.getVaultsPriceAge(MULTI_DEPOSITOR_VAULT);
297:
298: uint256 solverTip;
299: Request calldata request;
300:
301: uint256 length = requests.length;
302: TokenDetails memory tokenDetails = tokensDetails[token];
303: bool depositsExist;
304: for (uint256 i = 0; i < length; i++) {
305: request = requests[i];
306: if (_isRequestTypeDeposit(request.requestType)) {
307:
308: if (!tokenDetails.asyncDepositEnabled) {
309:
310: emit AsyncDepositDisabled(i);
311: continue;
312: }
313:
314: if (!depositsExist) {
315: depositsExist = true;
316: token.forceApprove(MULTI_DEPOSITOR_VAULT, type(uint256).max);
317: }
318:
319: if (_isRequestTypeAutoPrice(request.requestType)) {
320:
321: solverTip +=
322: _solveDepositVaultAutoPrice(token, tokenDetails.depositMultiplier, request, priceAge, i);
323: } else {
324:
325: solverTip +=
326: _solveDepositVaultFixedPrice(token, tokenDetails.depositMultiplier, request, priceAge, i);
327: }
328: } else {
329:
330: if (!tokenDetails.asyncRedeemEnabled) {
331:
332: emit AsyncRedeemDisabled(i);
333: continue;
334: }
335:
336: if (_isRequestTypeAutoPrice(request.requestType)) {
337:
338: solverTip += _solveRedeemVaultAutoPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i);
339: } else {
340:
341: solverTip += _solveRedeemVaultFixedPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i);
342: }
343: }
344: }
345:
346: if (solverTip != 0) {
347:
348: token.safeTransfer(msg.sender, solverTip);
349: }
350:
351: if (depositsExist) {
352:
353: token.forceApprove(MULTI_DEPOSITOR_VAULT, 0);
354: }
355: }
In Solidity, it's essential for clarity and interoperability to correctly specify return types in function declarations. If the withdraw
function is expected to return a bool
to indicate success or failure, its omission could lead to ambiguity or unexpected behavior when interacting with or calling this function from other contracts or off-chain systems. Missing return values can mislead developers and potentially lead to contract integrations built on incorrect assumptions. To resolve this, the function declaration for withdraw
should be modified to explicitly include the bool
return type, ensuring clarity and correctness in contract interactions.
Num of instances: 1
Click to show findings
['47']
47: function withdraw(TokenAmount[] calldata tokenAmounts) external requiresAuth // <= FOUND
If these serve no purpose, they should be safely removed
Num of instances: 1
Click to show findings
['4']
4: import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol"; // <= FOUND
Unchecked increments in variables can lead to overflow, causing values to wrap around unexpectedly. This can disrupt contract logic. Always validate before incrementing.
Num of instances: 7
Click to show findings
['35']
35: unchecked {
36: params.owner = bytes32(slot).asAddress().tload();
37: params.authority = Authority(bytes32(++slot).asAddress().tload());
38: params.submitHooks = ISubmitHooks(bytes32(++slot).asAddress().tload());
39: params.whitelist = IWhitelist(bytes32(++slot).asAddress().tload());
40: }
['52']
52: unchecked {
53: bytes32(slot).asAddress().tstore(params.owner);
54: bytes32(++slot).asAddress().tstore(address(params.authority));
55: bytes32(++slot).asAddress().tstore(address(params.submitHooks));
56: bytes32(++slot).asAddress().tstore(address(params.whitelist));
57: }
['208']
208: unchecked {
209: spender = bytes32(++slotUint256).asAddress().tload();
210: }
['37']
37: unchecked {
38: params.feeCalculator = IFeeCalculator(bytes32(slot).asAddress().tload());
39: params.feeToken = IERC20(bytes32(++slot).asAddress().tload());
40: params.feeRecipient = bytes32(++slot).asAddress().tload();
41: }
['54']
54: unchecked {
55: bytes32(slot).asAddress().tstore(address(params.feeCalculator));
56: bytes32(++slot).asAddress().tstore(address(params.feeToken));
57: bytes32(++slot).asAddress().tstore(params.feeRecipient);
58: }
['394']
394: unchecked {
395: approvals[approvalsLength++] =
396: Approval({ token: ctx.target, spender: _extractApprovalSpender(callData) });
397: }
['133']
133: unchecked {
134:
135:
136: bytes32(currentSlot).asUint256().tstore(_packLengthAndToken(length, approval.token));
137: bytes32(++currentSlot).asAddress().tstore(approval.spender);
138: }
Unchecked decrements can lead to underflow, making variables drop to their maximum possible value. This can compromise contract integrity. Always validate before decrementing.
Num of instances: 1
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: 3
Click to show findings
['86']
86:
87: vaultSnapshot.finalizedAt = uint32(block.timestamp + DISPUTE_PERIOD); // <= FOUND
['480']
480: uint256 refundableUntil = block.timestamp + depositRefundTimeout; // <= FOUND
['101']
101: oracleData.commitTimestamp = (block.timestamp + ORACLE_UPDATE_DELAY).toUint32(); // <= FOUND
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: 7
Click to show findings
['150']
150:
151: currentSlot += existingLength * 2 - 1;
['173']
173:
174: return averageValue * tvlFee * timeDelta / ONE_IN_BPS / SECONDS_PER_YEAR;
['184']
184:
185: return profit * feeRate / ONE_IN_BPS;
['266']
266: return unitsAmount * vaultState.unitPrice / UNIT_PRICE_PRECISION;
['347']
347: uint256 tvl = minUnitPrice * minTotalSupply / UNIT_PRICE_PRECISION;
['937']
937: uint256 tokensAdjusted = tokens * multiplier / ONE_IN_BPS;
['957']
957: return tokensAmount * multiplier / ONE_IN_BPS;
In Solidity, a contract that's not meant to be deployed on its own but is intended to be inherited by other contracts should be marked abstract
. This ensures that developers recognize the contract's incomplete or intended-to-be-extended nature. If a contract has unimplemented functions or is designed with the intention that another contract should extend its functionality, it should be explicitly labeled as abstract
. This helps prevent inadvertent deployments and clearly communicates the contract's purpose to other developers. Resolution: Review the contract, and if it's not supposed to function standalone, mark it as abstract
to make the intention clear.
Num of instances: 5
Click to show findings
['11']
11: contract MultiDepositorVaultDeployDelegate is IVaultDeployDelegate
['11']
11: contract SingleDepositorVaultDeployDelegate is IVaultDeployDelegate
['10']
10: contract ComputeBaseVaultAddressLens
['10']
10: contract ComputeMultiDepositorVaultAddressLens
['10']
10: contract ComputeSingleDepositorVaultAddressLens
[NonCritical-43] A event should be emitted if a non immutable state variable is set in a constructor
Num of instances: 1
Click to show findings
['50']
50: constructor() BaseVault() {
51:
52: FeeVaultParameters memory params = IFeeVaultDeployer(msg.sender).feeVaultParameters();
53:
54: IFeeCalculator feeCalculator_ = params.feeCalculator;
55: IERC20 feeToken_ = params.feeToken;
56:
57:
58: require(address(feeCalculator_) != address(0), Aera__ZeroAddressFeeCalculator());
59: require(address(feeToken_) != address(0), Aera__ZeroAddressFeeToken());
60:
61:
62: feeCalculator_.registerVault();
63:
64: address feeRecipient_ = params.feeRecipient;
65:
66: require(feeRecipient_ != address(0), Aera__ZeroAddressFeeRecipient());
67:
68:
69: feeRecipient = feeRecipient_;
70: feeCalculator = feeCalculator_; // <= FOUND
71:
72:
73: FEE_TOKEN = feeToken_;
74: }
Num of instances: 4
Click to show findings
['142']
142: unchecked { // <= FOUND
143: uint256 newLength = existingLength + length; // <= FOUND
144:
145:
146: bytes32(currentSlot).asUint256().tstore(
147: _packLengthAndToken(newLength, address(uint160(existingApproval)))
148: );
149:
150: currentSlot += existingLength * 2 - 1;
151: }
['84']
84: unchecked { // <= FOUND
85:
86: vaultSnapshot.finalizedAt = uint32(block.timestamp + DISPUTE_PERIOD); // <= FOUND
87: }
['177']
177: unchecked { // <= FOUND
178:
179: vaultPriceState.accrualLag = uint24(timestamp - vaultPriceState.timestamp + vaultPriceState.accrualLag); // <= FOUND
180: }
['100']
100: unchecked { // <= FOUND
101: oracleData.commitTimestamp = (block.timestamp + ORACLE_UPDATE_DELAY).toUint32(); // <= FOUND
102: }
Downcasting numbers to addresses in blockchain contracts, particularly in Ethereum's Solidity, involves risks such as possible address collisions. A collision occurs when different inputs, when cast or hashed, generate the same output address, potentially compromising contract integrity and asset security. If an uint256, for instance, is downcast to an address (effectively an uint160) without ensuring it’s a legitimate, collision-free conversion, different uint256 inputs might yield the same address, creating vulnerabilities attackers might exploit. Implementing thorough checks and opting for secure practices, like avoiding downcasting in critical logic or utilizing mappings with original uint256 as keys, mitigates risks
Num of instances: 4
Click to show findings
['146']
146:
147:
148: bytes32(currentSlot).asUint256().tstore(
149: _packLengthAndToken(newLength, address(uint160(existingApproval))) // <= FOUND
150: );
['204']
204: address token = address(uint160(lengthWithToken)); // <= FOUND
['240']
240: target = address(uint160(packed)); // <= FOUND
['41']
41: returnData = abi.encode(destinationDomain, address(uint160(uint256(mintRecipient))), burnToken); // <= FOUND
Using transfer libraries like OpenZeppelin's Address.sendValue is preferred over low-level calls for transferring Ether in Solidity. These libraries provide clearer, more semantically meaningful methods compared to low-level call() functions. They encapsulate best practices for error handling and gas management, enhancing the security and readability of your code. Low-level calls lack these built-in safety checks and can be more error-prone, especially when dealing with Ether transfers.
Num of instances: 1
Click to show findings
['27']
27:
28: (bool success,) = msg.sender.call{ value: amount }(""); // <= FOUND
In Solidity, functions often declare named return variables, which are expected to be assigned new values within the function. However, a common issue arises when such a function concludes without actually mutating the named return variable. This results in the function returning the default value of the variable type, which might not be the intended behavior and can lead to incorrect or unexpected outcomes. To avoid this issue, ensure that all named return variables are appropriately assigned within the function. If a function is meant to modify and return a value, it should explicitly do so before reaching the end of its execution path. This practice not only clarifies the function's intent but also prevents potential bugs related to unanticipated return values.
Num of instances: 1
Click to show findings
['226']
226: function _handleCallbackOperations(bytes32 root, uint256 cursor) // <= FOUND
227: internal
228: virtual
229: override
230: returns (bytes memory returnValue)
231: {
232: CalldataReader reader = CalldataReader.wrap(cursor);
233: CalldataReader end = reader.readBytesEnd();
234:
235: Approval[] memory approvals;
236: uint256 approvalsLength;
237: bytes[] memory results;
238:
239: (approvals, approvalsLength, results, reader) = _executeSubmit(root, reader, true);
240:
241:
242: _storeCallbackApprovals(approvals, approvalsLength);
243:
244: (reader, returnValue) = _getReturnValue(reader, results);
245:
246:
247: reader.requireAtEndOf(end);
248:
249: return returnValue; // <= FOUND
250: }
[NonCritical-48] 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: 1
Click to show findings
['135']
135: CalldataReader reader = CalldataReaderLib.from(data); // <= FOUND 'CalldataReaderLib.'
Only some address parameters are checked against address(0), to ensure consistency ensure all address parameters are checked.
Num of instances: 4
Click to show findings
['30']
30: function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) { // <= FOUND
31: Authority auth = authority;
32:
33:
34:
35: return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig)) || user == owner;
36: }
['29']
29: function beforeTransfer(address from, address to, address transferAgent) public view virtual { // <= FOUND
30: if (from != transferAgent && to != transferAgent && from != address(0) && to != address(0)) {
31:
32: require(isVaultUnitTransferable[msg.sender], Aera__VaultUnitsNotTransferable(msg.sender));
33: }
34: }
['34']
34: function beforeTransfer(address from, address to, address transferAgent) // <= FOUND
35: public
36: view
37: override(AbstractTransferHook, IBeforeTransferHook)
38: {
39: super.beforeTransfer(from, to, transferAgent);
40:
41:
42: require(from == address(0) || !BLACKLIST_ORACLE.isSanctioned(from), AeraPeriphery__BlacklistedAddress(from));
43: require(to == address(0) || !BLACKLIST_ORACLE.isSanctioned(to), AeraPeriphery__BlacklistedAddress(to));
44: }
['42']
42: function beforeTransfer(address from, address to, address transferAgent) // <= FOUND
43: public
44: view
45: override(AbstractTransferHook, IBeforeTransferHook)
46: {
47: super.beforeTransfer(from, to, transferAgent);
48:
49:
50: require(
51: from == address(0) || from == transferAgent || whitelist[msg.sender][from],
52: AeraPeriphery__NotWhitelisted(from)
53: );
54: require(to == address(0) || to == transferAgent || whitelist[msg.sender][to], AeraPeriphery__NotWhitelisted(to));
55: }
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: 9
Click to show findings
['33']
33:
37: function beforeTransfer(address from, address to, address transferAgent) external view; // <= FOUND
['109']
109:
114: function _update(address from, address to, uint256 amount) internal override { // <= FOUND
['29']
29:
30: function beforeTransfer(address from, address to, address transferAgent) public view virtual { // <= FOUND
['42']
42:
47: function beforeTransfer(address from, address to, address transferAgent) // <= FOUND
48: public
49: view
50: override(AbstractTransferHook, IBeforeTransferHook)
51: {
['42']
42:
43: function beforeTransfer(address from, address to, address transferAgent) // <= FOUND
44: public
45: view
46: override(AbstractTransferHook, IBeforeTransferHook)
47: {
['16']
16:
21: mapping(address vault => mapping(address addr => bool isWhitelisted)) public whitelist; // <= FOUND
['23']
23:
28: function updateWhitelist(address vault, address[] calldata addresses, bool isWhitelisted) // <= FOUND
29: external
30: requiresVaultAuth(vault)
31: {
['14']
14:
19: event VaultWhitelistUpdated(address indexed vault, address[] addresses, bool isWhitelisted); // <= FOUND
['30']
30:
38: function updateWhitelist(address vault, address[] calldata addresses, bool isWhitelisted) external; // <= FOUND
Simplifying complex require statements with multiple logical OR (||) operators in Solidity can be achieved by using revert statements. This involves converting the conditional logic of require into separate if statements, each followed by a revert for specific failing conditions. This approach enhances readability and maintains clarity, especially when dealing with multiple conditions. It allows for more descriptive error messages for each specific case, improving the debugging process and making the code more maintainable.
Num of instances: 5
Click to show findings
['119']
119: require( // <= FOUND
120: from == address(0) || to == address(0) || !IProvisioner(provisioner).areUserUnitsLocked(from),
121: Aera__UnitsLocked()
122: );
['56']
56: require( // <= FOUND
57: msg.sender == vaultAccountant[vault] || msg.sender == Auth(vault).owner()
58: || Auth(vault).authority().canCall(msg.sender, address(this), msg.sig),
59: Aera__CallerIsNotAuthorized()
60: );
['50']
50: require( // <= FOUND
51: from == address(0) || from == transferAgent || whitelist[msg.sender][from],
52: AeraPeriphery__NotWhitelisted(from)
53: );
['54']
54: require(to == address(0) || to == transferAgent || whitelist[msg.sender][to], AeraPeriphery__NotWhitelisted(to)); // <= FOUND
['46']
46: require( // <= FOUND
47: msg.sender == user || msg.sender == Auth(user).owner()
48: || Auth(user).authority().canCall(msg.sender, address(this), msg.sig),
49: AeraPeriphery__CallerIsNotAuthorized()
50: );
Emitting 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: 11
Click to show findings
['93']
93: constructor() Pausable() Auth2Step(msg.sender, Authority(address(0))) { // <= FOUND
94:
95: BaseVaultParameters memory params = IBaseVaultFactory(msg.sender).baseVaultParameters();
96:
97: address initialOwner = params.owner;
98:
99: require(initialOwner != address(0), Aera__ZeroAddressOwner());
100:
101: transferOwnership(initialOwner);
102:
103: if (params.authority != Authority(address(0))) {
104:
105: setAuthority(params.authority);
106: }
107:
108:
109: WHITELIST = params.whitelist;
110:
111:
112: ISubmitHooks submitHooks_ = params.submitHooks;
113: if (address(submitHooks_) != address(0)) {
114: _setSubmitHooks(submitHooks_);
115: }
116: }
['35']
35: constructor(address owner_, Authority authority_, uint256 disputePeriod) BaseFeeCalculator(owner_, authority_) { // <= FOUND
36:
37: require(disputePeriod <= MAX_DISPUTE_PERIOD, Aera__DisputePeriodTooLong());
38:
39:
40: DISPUTE_PERIOD = disputePeriod;
41: }
['50']
50: constructor() BaseVault() { // <= FOUND
51:
52: FeeVaultParameters memory params = IFeeVaultDeployer(msg.sender).feeVaultParameters();
53:
54: IFeeCalculator feeCalculator_ = params.feeCalculator;
55: IERC20 feeToken_ = params.feeToken;
56:
57:
58: require(address(feeCalculator_) != address(0), Aera__ZeroAddressFeeCalculator());
59: require(address(feeToken_) != address(0), Aera__ZeroAddressFeeToken());
60:
61:
62: feeCalculator_.registerVault();
63:
64: address feeRecipient_ = params.feeRecipient;
65:
66: require(feeRecipient_ != address(0), Aera__ZeroAddressFeeRecipient());
67:
68:
69: feeRecipient = feeRecipient_;
70: feeCalculator = feeCalculator_;
71:
72:
73: FEE_TOKEN = feeToken_;
74: }
['20']
20: constructor(address numeraire_) { // <= FOUND
21:
22: require(numeraire_ != address(0), Aera__ZeroAddressNumeraire());
23:
24:
25: NUMERAIRE = numeraire_;
26: }
['50']
50: constructor(address initialOwner, Authority initialAuthority, address deployDelegate) // <= FOUND
51: Sweepable(initialOwner, initialAuthority)
52: {
53:
54: require(deployDelegate != address(0), Aera__ZeroAddressDeployDelegate());
55:
56:
57: _DEPLOY_DELEGATE = deployDelegate;
58: }
['42']
42: constructor() // <= FOUND
43: ERC20(
44: IMultiDepositorVaultFactory(msg.sender).getERC20Name(),
45: IMultiDepositorVaultFactory(msg.sender).getERC20Symbol()
46: )
47: FeeVault()
48: {
49:
50: IBeforeTransferHook beforeTransferHook_ =
51: IMultiDepositorVaultFactory(msg.sender).multiDepositorVaultParameters();
52:
53:
54: _setBeforeTransferHook(beforeTransferHook_);
55: }
['64']
64: constructor(IERC20 numeraire, IOracleRegistry oracleRegistry, address owner_, Authority authority_) // <= FOUND
65: BaseFeeCalculator(owner_, authority_)
66: HasNumeraire(address(numeraire))
67: {
68:
69: require(address(oracleRegistry) != address(0), Aera__ZeroAddressOracleRegistry());
70:
71:
72: ORACLE_REGISTRY = oracleRegistry;
73: }
['88']
88: constructor( // <= FOUND
89: IPriceAndFeeCalculator priceAndFeeCalculator,
90: address multiDepositorVault,
91: address owner_,
92: Authority authority_
93: ) Auth2Step(owner_, authority_) {
94:
95: require(address(priceAndFeeCalculator) != address(0), Aera__ZeroAddressPriceAndFeeCalculator());
96: require(multiDepositorVault != address(0), Aera__ZeroAddressMultiDepositorVault());
97:
98:
99: PRICE_FEE_CALCULATOR = priceAndFeeCalculator;
100: MULTI_DEPOSITOR_VAULT = multiDepositorVault;
101: }
['21']
21: constructor(IChainalysisSanctionsOracle oracle_) { // <= FOUND
22:
23: require(address(oracle_) != address(0), AeraPeriphery__ZeroAddressBlacklistOracle());
24:
25:
26: BLACKLIST_ORACLE = oracle_;
27: }
['49']
49: constructor(address vault_, address milkmanRoot_) { // <= FOUND
50:
51: require(vault_ != address(0), AeraPeriphery__ZeroAddressVault());
52:
53: require(milkmanRoot_ != address(0), AeraPeriphery__ZeroAddressMilkmanRoot());
54:
55:
56: vault = vault_;
57: milkmanRoot = IMilkman(milkmanRoot_);
58: }
['54']
54: constructor(address initialOwner, Authority initialAuthority, uint256 oracleUpdateDelay) // <= FOUND
55: Auth2Step(initialOwner, initialAuthority)
56: {
57:
58: require(initialOwner != address(0), AeraPeriphery__ZeroAddressOwner());
59:
60: require(oracleUpdateDelay <= MAXIMUM_UPDATE_DELAY, AeraPeriphery__OracleUpdateDelayTooLong());
61:
62: ORACLE_UPDATE_DELAY = oracleUpdateDelay;
63: }
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: 2
Click to show findings
['2']
2: pragma solidity >=0.5.0; // <= FOUND
['2']
2: pragma solidity >=0.5.0 >=0.7.5; // <= FOUND
Not casting an integer as a string before passing it into abi.encode can result in unintended behaviour. lets say '1' being encoded as '1' it will be encoded as char(1) which is the 'start of heading' control character or id of 60 would be encoded as '<'. This is may not be intended. To rectify this, simply cast the Id as a string with string(id) or ideally use solmate's libString library (toString)
Num of instances: 2
Click to show findings
['985']
985: function _getDepositHash( // <= FOUND
986: address user,
987: IERC20 token,
988: uint256 tokenAmount,
989: uint256 unitsAmount,
990: uint256 refundableUntil
991: ) internal pure returns (bytes32) {
992: return keccak256(abi.encodePacked(user, token, tokenAmount, unitsAmount, refundableUntil)); // <= FOUND
993: }
['1005']
1005: function _getRequestHashParams( // <= FOUND
1006: IERC20 token,
1007: address user,
1008: RequestType requestType,
1009: uint256 tokens,
1010: uint256 units,
1011: uint256 solverTip,
1012: uint256 deadline,
1013: uint256 maxPriceAge
1014: ) internal pure returns (bytes32) {
1015: return keccak256(abi.encodePacked(token, user, requestType, tokens, units, solverTip, deadline, maxPriceAge)); // <= FOUND
1016: }
Including external calls in modifiers is generally discouraged in Solidity programming because modifiers are primarily intended for checks and assertions. External calls increase complexity and can obscure the flow of transactions, making it harder for reviewers to identify potentially risky interactions. To maintain clarity and security, it's advisable to encapsulate external calls within internal functions. These functions can then be invoked within the main function body, rather than in modifiers, ensuring that external interactions are explicit and easier to audit, thus enhancing contract safety and readability.
Num of instances: 4
Click to show findings
['86']
86: modifier onlyAuthOrGuardian() { // <= FOUND
87: require(
88: isAuthorized(msg.sender, msg.sig) || guardianRoots.contains(msg.sender), Aera__CallerIsNotAuthOrGuardian() // <= FOUND
89: );
90: _;
91: }
['53']
53: modifier requiresVaultAuthOrAccountant(address vault) { // <= FOUND
54:
55:
56: require(
57: msg.sender == vaultAccountant[vault] || msg.sender == Auth(vault).owner()
58: || Auth(vault).authority().canCall(msg.sender, address(this), msg.sig), // <= FOUND
59: Aera__CallerIsNotAuthorized()
60: );
61: _;
62: }
['21']
21: modifier requiresVaultAuth(address vault) { // <= FOUND
22:
23: require(
24: msg.sender == Auth(vault).owner() || Auth(vault).authority().canCall(msg.sender, address(this), msg.sig), // <= FOUND
25: Aera__CallerIsNotAuthorized()
26: );
27: _;
28: }
['45']
45: modifier requiresUserAuth(address user) { // <= FOUND
46: require(
47: msg.sender == user || msg.sender == Auth(user).owner()
48: || Auth(user).authority().canCall(msg.sender, address(this), msg.sig), // <= FOUND
49: AeraPeriphery__CallerIsNotAuthorized()
50: );
51: _;
52: }
In 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: 123
Click to show findings
['18']
18:
22: error Aera__ZeroAddressAuthority(); // <= FOUND
['19']
19: error Aera__Unauthorized(); // <= FOUND
['36']
36:
41: error Aera__TvlFeeTooHigh(); // <= FOUND
['39']
39:
40: error Aera__PerformanceFeeTooHigh(); // <= FOUND
['42']
42:
43: error Aera__ZeroAddressProtocolFeeRecipient(); // <= FOUND
['45']
45:
46: error Aera__CallerIsNotVaultOwner(); // <= FOUND
['48']
48:
49: error Aera__CallerIsNotVaultAccountant(); // <= FOUND
['51']
51:
52: error Aera__VaultNotRegistered(); // <= FOUND
['13']
13:
18: error Aera__DescriptionIsEmpty(); // <= FOUND
['20']
20:
24: error Aera__ZeroAddressGuardian(); // <= FOUND
['21']
21: error Aera__ZeroAddressOwner(); // <= FOUND
['22']
22: error Aera__CallerIsNotGuardian(); // <= FOUND
['23']
23: error Aera__CallerIsNotAuthOrGuardian(); // <= FOUND
['26']
26: error Aera__ZeroAddressMerkleRoot(); // <= FOUND
['31']
31: error Aera__BeforeOperationHooksWithConfigurableHooks(); // <= FOUND
['32']
32: error Aera__ProofVerificationFailed(); // <= FOUND
['33']
33: error Aera__InvalidBeforeOperationHooksReturnDataLength(); // <= FOUND
['34']
34: error Aera__GuardianNotWhitelisted(); // <= FOUND
['35']
35: error Aera__ExpectedCallbackNotReceived(); // <= FOUND
['36']
36: error Aera__NoResults(); // <= FOUND
['17']
17:
21: error Aera__NotVaultOwner(); // <= FOUND
['12']
12:
17: error Aera__UnauthorizedCallback(); // <= FOUND
['25']
25:
30: error Aera__SnapshotTooOld(); // <= FOUND
['28']
28:
29: error Aera__SnapshotInFuture(); // <= FOUND
['31']
31:
32: error Aera__HighestProfitDecreased(); // <= FOUND
['34']
34:
35: error Aera__DisputePeriodTooLong(); // <= FOUND
['20']
20:
25: error Aera__VaultAlreadyRegistered(); // <= FOUND
['23']
23:
27: error Aera__ZeroAddressFeeCalculator(); // <= FOUND
['24']
24: error Aera__ZeroAddressFeeToken(); // <= FOUND
['25']
25: error Aera__ZeroAddressFeeRecipient(); // <= FOUND
['26']
26: error Aera__NoFeesToClaim(); // <= FOUND
['27']
27: error Aera__CallerIsNotFeeRecipient(); // <= FOUND
['28']
28: error Aera__CallerIsNotProtocolFeeRecipient(); // <= FOUND
['11']
11:
15: error Aera__ZeroAddressNumeraire(); // <= FOUND
['38']
38:
43: error Aera__ZeroAddressDeployDelegate(); // <= FOUND
['35']
35:
39: error Aera__UnitsLocked(); // <= FOUND
['36']
36: error Aera__ZeroAddressProvisioner(); // <= FOUND
['37']
37: error Aera__CallerIsNotProvisioner(); // <= FOUND
['49']
49:
53: error Aera__StalePrice(); // <= FOUND
['50']
50: error Aera__TimestampMustBeAfterLastUpdate(); // <= FOUND
['51']
51: error Aera__TimestampCantBeInFuture(); // <= FOUND
['52']
52: error Aera__ZeroAddressOracleRegistry(); // <= FOUND
['53']
53: error Aera__InvalidMaxPriceToleranceRatio(); // <= FOUND
['54']
54: error Aera__InvalidMinPriceToleranceRatio(); // <= FOUND
['55']
55: error Aera__InvalidMaxPriceAge(); // <= FOUND
['56']
56: error Aera__InvalidMaxUpdateDelayDays(); // <= FOUND
['57']
57: error Aera__ThresholdNotSet(); // <= FOUND
['58']
58: error Aera__VaultPaused(); // <= FOUND
['59']
59: error Aera__VaultNotPaused(); // <= FOUND
['60']
60: error Aera__UnitPriceMismatch(); // <= FOUND
['61']
61: error Aera__TimestampMismatch(); // <= FOUND
['62']
62: error Aera__VaultAlreadyInitialized(); // <= FOUND
['63']
63: error Aera__VaultNotInitialized(); // <= FOUND
['64']
64: error Aera__InvalidPrice(); // <= FOUND
['65']
65: error Aera__CurrentPriceAboveHighestPrice(); // <= FOUND
['136']
136:
140: error Aera__SyncDepositDisabled(); // <= FOUND
['137']
137: error Aera__AsyncDepositDisabled(); // <= FOUND
['138']
138: error Aera__AsyncRedeemDisabled(); // <= FOUND
['139']
139: error Aera__DepositCapExceeded(); // <= FOUND
['140']
140: error Aera__MinUnitsOutNotMet(); // <= FOUND
['141']
141: error Aera__TokensInZero(); // <= FOUND
['142']
142: error Aera__UnitsInZero(); // <= FOUND
['143']
143: error Aera__UnitsOutZero(); // <= FOUND
['144']
144: error Aera__MinUnitsOutZero(); // <= FOUND
['145']
145: error Aera__MaxTokensInZero(); // <= FOUND
['146']
146: error Aera__MaxTokensInExceeded(); // <= FOUND
['147']
147: error Aera__MaxDepositRefundTimeoutExceeded(); // <= FOUND
['148']
148: error Aera__DepositHashNotFound(); // <= FOUND
['149']
149: error Aera__HashNotFound(); // <= FOUND
['150']
150: error Aera__RefundPeriodExpired(); // <= FOUND
['151']
151: error Aera__DeadlineInPast(); // <= FOUND
['152']
152: error Aera__DeadlineTooFarInFuture(); // <= FOUND
['153']
153: error Aera__DeadlineInFutureAndUnauthorized(); // <= FOUND
['154']
154: error Aera__MinTokenOutZero(); // <= FOUND
['155']
155: error Aera__HashCollision(); // <= FOUND
['156']
156: error Aera__ZeroAddressPriceAndFeeCalculator(); // <= FOUND
['157']
157: error Aera__ZeroAddressMultiDepositorVault(); // <= FOUND
['158']
158: error Aera__DepositMultiplierTooLow(); // <= FOUND
['159']
159: error Aera__DepositMultiplierTooHigh(); // <= FOUND
['160']
160: error Aera__RedeemMultiplierTooLow(); // <= FOUND
['161']
161: error Aera__RedeemMultiplierTooHigh(); // <= FOUND
['162']
162: error Aera__DepositCapZero(); // <= FOUND
['163']
163: error Aera__PriceAndFeeCalculatorVaultPaused(); // <= FOUND
['164']
164: error Aera__AutoPriceSolveNotAllowed(); // <= FOUND
['165']
165: error Aera__FixedPriceSolverTipNotAllowed(); // <= FOUND
['166']
166: error Aera__TokenCantBePriced(); // <= FOUND
['167']
167: error Aera__CallerIsVault(); // <= FOUND
['168']
168: error Aera__InvalidToken(); // <= FOUND
['21']
21:
26: error Aera__FailedToSendNativeToken(); // <= FOUND
['22']
22:
26: error Aera__ExtractionNumberTooLarge(); // <= FOUND
['23']
23: error Aera__CalldataTooShort(); // <= FOUND
['24']
24: error Aera__OffsetOutOfBounds(); // <= FOUND
['45']
45:
49: error ReaderNotAtEnd(); // <= FOUND
['23']
23:
28: error Aera__CopyOffsetOutOfBounds(); // <= FOUND
['25']
25:
26: error Aera__PasteOffsetOutOfBounds(); // <= FOUND
['15']
15:
19: error Aera__CallerIsNotAuthorized(); // <= FOUND
['46']
46:
50: error AeraPeriphery__CallerNotVaultOwner(); // <= FOUND
['50']
50: error AeraPeriphery__ZeroAddressOracleRegistry(); // <= FOUND
['51']
51: error AeraPeriphery__InputAmountIsZero(); // <= FOUND
['52']
52: error AeraPeriphery__InputTokenIsETH(); // <= FOUND
['53']
53: error AeraPeriphery__OutputTokenIsETH(); // <= FOUND
['13']
13:
17: error AeraPeriphery__DestinationCallerNotZero(); // <= FOUND
['15']
15:
19: error AeraPeriphery__FeeReceiversNotEmpty(); // <= FOUND
['14']
14:
18: error AeraPeriphery__BadPathFormat(); // <= FOUND
['14']
14: error AeraPeriphery__ZeroAddressBlacklistOracle(); // <= FOUND
['40']
40:
45: error AeraPeriphery__ZeroAddressVault(); // <= FOUND
['43']
43:
44: error AeraPeriphery__ZeroAddressMilkmanRoot(); // <= FOUND
['46']
46:
47: error AeraPeriphery__CallerIsNotVault(); // <= FOUND
['49']
49:
50: error AeraPeriphery__CallerIsNotVaultOwner(); // <= FOUND
['52']
52:
53: error AeraPeriphery__MilkmanRouter__SellAmountIsZero(); // <= FOUND
['53']
53: error AeraPeriphery__CallerIsNotAuthorized(); // <= FOUND
['54']
54: error AeraPeriphery__OracleMismatch(); // <= FOUND
['55']
55: error AeraPeriphery__CommitTimestampNotReached(); // <= FOUND
['56']
56: error AeraPeriphery__OracleUpdateDelayTooLong(); // <= FOUND
['58']
58: error AeraPeriphery__NoPendingOracleUpdate(); // <= FOUND
['60']
60: error AeraPeriphery__CannotScheduleOracleUpdateForTheSameOracle(); // <= FOUND
['61']
61: error AeraPeriphery__OracleUpdateAlreadyScheduled(); // <= FOUND
['62']
62: error AeraPeriphery__ZeroAddressOracle(); // <= FOUND
['63']
63: error AeraPeriphery__OracleNotSet(); // <= FOUND
['64']
64: error AeraPeriphery__OracleAlreadySet(); // <= FOUND
['65']
65: error AeraPeriphery__OracleAlreadyDisabled(); // <= FOUND
['66']
66: error AeraPeriphery__ZeroAddressOwner(); // <= FOUND
['15']
15: error Aera__CallerIsNotAuthorized(); // <= FOUND
[NonCritical-57] 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: 4
Click to show findings
['278']
278: function _beforeSubmitHooks(address hooks, bytes calldata data) internal { // <= FOUND
279: if (_hasBeforeHooks(hooks)) {
280:
281: (bool success, bytes memory result) =
282: hooks.call(abi.encodeWithSelector(ISubmitHooks.beforeSubmit.selector, data, msg.sender)); // <= FOUND
283:
284: require(success, Aera__BeforeSubmitHooksFailed(result));
285: }
286: }
['291']
291: function _afterSubmitHooks(address hooks, bytes calldata data) internal { // <= FOUND
292: if (_hasAfterHooks(hooks)) {
293:
294: (bool success, bytes memory result) =
295: hooks.call(abi.encodeWithSelector(ISubmitHooks.afterSubmit.selector, data, msg.sender)); // <= FOUND
296:
297: require(success, Aera__AfterSubmitHooksFailed(result));
298: }
299: }
['306']
306: function _beforeOperationHooks(address operationHooks, bytes memory data, uint256 i) // <= FOUND
307: internal
308: returns (bytes memory result)
309: {
310: if (_hasBeforeHooks(operationHooks)) {
311:
312: _setHookCallType(HookCallType.BEFORE);
313:
314:
315: (bool success, bytes memory returnValue) = operationHooks.call(data); // <= FOUND
316:
317: require(success, Aera__BeforeOperationHooksFailed(i, returnValue));
318:
319:
320: require(returnValue.length % WORD_SIZE == 0, Aera__InvalidBeforeOperationHooksReturnDataLength());
321:
322:
323: _setHookCallType(HookCallType.NONE);
324:
325:
326:
327: (result) = abi.decode(returnValue, (bytes));
328: }
329: }
['335']
335: function _afterOperationHooks(address operationHooks, bytes memory data, uint256 i) internal { // <= FOUND
336: if (_hasAfterHooks(operationHooks)) {
337:
338: _setHookCallType(HookCallType.AFTER);
339:
340:
341: (bool success, bytes memory result) = operationHooks.call(data); // <= FOUND
342:
343: require(success, Aera__AfterOperationHooksFailed(i, result));
344:
345:
346: _setHookCallType(HookCallType.NONE);
347: }
348: }
Avoiding 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: 3
Click to show findings
['395']
395: approvals[approvalsLength++] =
396: Approval({ token: ctx.target, spender: _extractApprovalSpender(callData) });
['589']
589: returnValue = results[length - 1];
['38']
38: tokenOut = address(bytes20(params.path[pathLength - 20:]));
[NonCritical-59] Modifier checks msg.sender against two addresses. Consider using multiple modifiers for more control
In Solidity, using a single modifier to check msg.sender against two different addresses can make the code less flexible and harder to maintain. Instead, consider using multiple modifiers for greater control and clarity. Separating these checks into individual modifiers improves readability and reusability, as each modifier can then be applied independently to different functions according to specific access requirements. This approach enhances the modularity of the contract and makes it easier to update or extend access control logic in the future, aligning with best practices for smart contract development and security.
Num of instances: 2
Click to show findings
['53']
53: modifier requiresVaultAuthOrAccountant(address vault) { // <= FOUND
54:
55:
56: require(
57: msg.sender == vaultAccountant[vault] || msg.sender == Auth(vault).owner() // <= FOUND
58: || Auth(vault).authority().canCall(msg.sender, address(this), msg.sig),
59: Aera__CallerIsNotAuthorized()
60: );
61: _;
62: }
['45']
45: modifier requiresUserAuth(address user) { // <= FOUND
46: require(
47: msg.sender == user || msg.sender == Auth(user).owner() // <= FOUND
48: || Auth(user).authority().canCall(msg.sender, address(this), msg.sig),
49: AeraPeriphery__CallerIsNotAuthorized()
50: );
51: _;
52: }
In 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: 6
Click to show findings
['69']
69: struct OperationContext { // <= FOUND
70:
71: address target;
72:
73: bytes4 selector;
74:
75: uint208 callbackData;
76:
77: uint256 value;
78:
79: address operationHooks; // <= FOUND
80:
81: uint256 configurableOperationHooks;
82: }
['103']
103: struct TokenAmount { // <= FOUND
104:
105: IERC20 token; // <= FOUND
106:
107: uint256 amount;
108: }
['143']
143: struct FeeVaultParameters { // <= FOUND
144:
145: IFeeCalculator feeCalculator; // <= FOUND
146:
147: IERC20 feeToken;
148:
149: address feeRecipient;
150: }
['196']
196: struct TargetCalldata { // <= FOUND
197:
198: address target; // <= FOUND
199:
200: bytes data;
201: }
['251']
251: struct Request { // <= FOUND
252:
253: RequestType requestType; // <= FOUND
254:
255: address user;
256:
257: uint256 units;
258:
259: uint256 tokens;
260:
261: uint256 solverTip;
262:
263: uint256 deadline;
264:
265: uint256 maxPriceAge;
266: }
['269']
269: struct OracleData { // <= FOUND
270:
271: bool isScheduledForUpdate;
272:
273: bool isDisabled;
274:
275: IOracle oracle; // <= FOUND
276:
277: IOracle pendingOracle;
278:
279: uint32 commitTimestamp;
280: }
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: 6
Click to show findings
['62']
62: function enter(address sender, IERC20 token, uint256 tokenAmount, uint256 unitsAmount, address recipient) // <= FOUND
63: external
64: whenNotPaused
65: onlyProvisioner
66: {
67:
68: if (tokenAmount > 0) token.safeTransferFrom(sender, address(this), tokenAmount); // <= FOUND
69:
70:
71: _mint(recipient, unitsAmount);
72:
73:
74: emit Enter(sender, recipient, token, tokenAmount, unitsAmount);
75: }
['180']
180: function requestDeposit( // <= FOUND
181: IERC20 token,
182: uint256 tokensIn,
183: uint256 minUnitsOut,
184: uint256 solverTip,
185: uint256 deadline,
186: uint256 maxPriceAge,
187: bool isFixedPrice
188: ) external anyoneButVault {
189:
190:
191: require(tokensIn != 0, Aera__TokensInZero());
192: require(minUnitsOut != 0, Aera__MinUnitsOutZero());
193: require(deadline > block.timestamp, Aera__DeadlineInPast());
194: unchecked {
195: require(deadline - block.timestamp <= MAX_SECONDS_TO_DEADLINE, Aera__DeadlineTooFarInFuture());
196: }
197: require(tokensDetails[token].asyncDepositEnabled, Aera__AsyncDepositDisabled());
198: require(!PRICE_FEE_CALCULATOR.isVaultPaused(MULTI_DEPOSITOR_VAULT), Aera__PriceAndFeeCalculatorVaultPaused());
199: require(solverTip == 0 || !isFixedPrice, Aera__FixedPriceSolverTipNotAllowed());
200:
201:
202: token.safeTransferFrom(msg.sender, address(this), tokensIn); // <= FOUND
203:
204: RequestType requestType = isFixedPrice ? RequestType.DEPOSIT_FIXED_PRICE : RequestType.DEPOSIT_AUTO_PRICE;
205:
206: bytes32 depositHash = _getRequestHashParams(
207: token, msg.sender, requestType, tokensIn, minUnitsOut, solverTip, deadline, maxPriceAge
208: );
209:
210: require(!asyncDepositHashes[depositHash], Aera__HashCollision());
211:
212: asyncDepositHashes[depositHash] = true;
213:
214:
215: emit DepositRequested(
216: msg.sender, token, tokensIn, minUnitsOut, solverTip, deadline, maxPriceAge, isFixedPrice, depositHash
217: );
218: }
['221']
221: function requestRedeem( // <= FOUND
222: IERC20 token,
223: uint256 unitsIn,
224: uint256 minTokensOut,
225: uint256 solverTip,
226: uint256 deadline,
227: uint256 maxPriceAge,
228: bool isFixedPrice
229: ) external anyoneButVault {
230:
231:
232: require(unitsIn != 0, Aera__UnitsInZero());
233: require(minTokensOut != 0, Aera__MinTokenOutZero());
234: require(deadline > block.timestamp, Aera__DeadlineInPast());
235: unchecked {
236: require(deadline - block.timestamp <= MAX_SECONDS_TO_DEADLINE, Aera__DeadlineTooFarInFuture());
237: }
238: require(tokensDetails[token].asyncRedeemEnabled, Aera__AsyncRedeemDisabled());
239: require(!PRICE_FEE_CALCULATOR.isVaultPaused(MULTI_DEPOSITOR_VAULT), Aera__PriceAndFeeCalculatorVaultPaused());
240: require(solverTip == 0 || !isFixedPrice, Aera__FixedPriceSolverTipNotAllowed());
241:
242:
243: IERC20(MULTI_DEPOSITOR_VAULT).safeTransferFrom(msg.sender, address(this), unitsIn); // <= FOUND
244:
245: RequestType requestType = isFixedPrice ? RequestType.REDEEM_FIXED_PRICE : RequestType.REDEEM_AUTO_PRICE;
246:
247: bytes32 redeemHash = _getRequestHashParams(
248: token, msg.sender, requestType, minTokensOut, unitsIn, solverTip, deadline, maxPriceAge
249: );
250:
251: require(!asyncRedeemHashes[redeemHash], Aera__HashCollision());
252:
253: asyncRedeemHashes[redeemHash] = true;
254:
255:
256: emit RedeemRequested(
257: msg.sender, token, minTokensOut, unitsIn, solverTip, deadline, maxPriceAge, isFixedPrice, redeemHash
258: );
259: }
['764']
764: function _solveDepositDirect(IERC20 token, Request calldata request) internal { // <= FOUND
765: bytes32 depositHash = _getRequestHash(token, request);
766:
767: if (!asyncDepositHashes[depositHash]) {
768:
769: emit InvalidRequestHash(depositHash);
770: return;
771: }
772:
773:
774: asyncDepositHashes[depositHash] = false;
775:
776: if (request.deadline >= block.timestamp) {
777:
778: IERC20(MULTI_DEPOSITOR_VAULT).safeTransferFrom(msg.sender, request.user, request.units); // <= FOUND
779:
780:
781: token.safeTransfer(msg.sender, request.tokens);
782:
783:
784: emit DepositSolved(depositHash);
785: } else {
786:
787: token.safeTransfer(request.user, request.tokens);
788:
789:
790: emit DepositRefunded(depositHash);
791: }
792: }
['803']
803: function _solveRedeemDirect(IERC20 token, Request calldata request) internal { // <= FOUND
804: bytes32 redeemHash = _getRequestHash(token, request);
805:
806: if (!asyncRedeemHashes[redeemHash]) {
807:
808: emit InvalidRequestHash(redeemHash);
809: return;
810: }
811:
812:
813: asyncRedeemHashes[redeemHash] = false;
814:
815: if (request.deadline >= block.timestamp) {
816:
817: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(msg.sender, request.units);
818:
819:
820: token.safeTransferFrom(msg.sender, request.user, request.tokens); // <= FOUND
821:
822:
823: emit RedeemSolved(redeemHash);
824: } else {
825:
826: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
827:
828:
829: emit RedeemRefunded(redeemHash);
830: }
831: }
['27']
27: function deposit(TokenAmount[] calldata tokenAmounts) external requiresAuth { // <= FOUND
28: TokenAmount calldata tokenAmount;
29: uint256 length = tokenAmounts.length;
30: for (uint256 i = 0; i < length; ++i) {
31: tokenAmount = tokenAmounts[i];
32:
33: tokenAmount.token.safeTransferFrom(msg.sender, address(this), tokenAmount.amount); // <= FOUND
34:
35:
36: require(
37: tokenAmount.token.allowance(msg.sender, address(this)) == 0,
38: Aera__UnexpectedTokenAllowance(tokenAmount.token.allowance(msg.sender, address(this)))
39: );
40: }
41:
42:
43: emit Deposited(msg.sender, tokenAmounts);
44: }
Num of instances: 30
Click to show findings
['102']
102: function claimFees(uint256 feeTokenBalance) external virtual returns (uint256, uint256, address) {
103:
104: _beforeClaimFees();
105:
106: VaultAccruals storage vaultAccruals = _vaultAccruals[msg.sender];
107:
108: uint256 vaultEarnedFees = vaultAccruals.accruedFees;
109: uint256 protocolEarnedFees = vaultAccruals.accruedProtocolFees;
110: uint256 claimableProtocolFee = Math.min(feeTokenBalance, protocolEarnedFees);
111: uint256 claimableVaultFee; // <= FOUND
112: unchecked {
113: claimableVaultFee = Math.min(feeTokenBalance - claimableProtocolFee, vaultEarnedFees);
114: }
115:
116:
117: unchecked {
118: vaultAccruals.accruedProtocolFees = uint112(protocolEarnedFees - claimableProtocolFee);
119: vaultAccruals.accruedFees = uint112(vaultEarnedFees - claimableVaultFee);
120: }
121:
122: return (claimableVaultFee, claimableProtocolFee, protocolFeeRecipient);
123: }
['126']
126: function submit(bytes calldata data) external whenNotPaused nonReentrant {
127: (bool success, bytes32 root) = guardianRoots.tryGet(msg.sender);
128:
129: require(success, Aera__CallerIsNotGuardian());
130:
131: address submitHooks_ = address(submitHooks);
132:
133: _beforeSubmitHooks(submitHooks_, data);
134:
135: CalldataReader reader = CalldataReaderLib.from(data);
136: CalldataReader end = reader.readBytesEnd(data);
137:
138: Approval[] memory approvals;
139: uint256 approvalsLength; // <= FOUND
140:
141: (approvals, approvalsLength,, reader) = _executeSubmit(root, reader, false);
142:
143:
144: _afterSubmitHooks(submitHooks_, data);
145:
146:
147: _noPendingApprovalsInvariant(approvals, approvalsLength);
148:
149:
150: reader.requireAtEndOf(end);
151: }
['226']
226: function _handleCallbackOperations(bytes32 root, uint256 cursor)
227: internal
228: virtual
229: override
230: returns (bytes memory returnValue)
231: {
232: CalldataReader reader = CalldataReader.wrap(cursor);
233: CalldataReader end = reader.readBytesEnd();
234:
235: Approval[] memory approvals;
236: uint256 approvalsLength; // <= FOUND
237: bytes[] memory results;
238:
239: (approvals, approvalsLength, results, reader) = _executeSubmit(root, reader, true);
240:
241:
242: _storeCallbackApprovals(approvals, approvalsLength);
243:
244: (reader, returnValue) = _getReturnValue(reader, results);
245:
246:
247: reader.requireAtEndOf(end);
248:
249: return returnValue;
250: }
['258']
258: function _processExpectedCallback(CalldataReader reader, bytes32 root) internal returns (CalldataReader, uint208) {
259: bool hasCallback; // <= FOUND
260: (reader, hasCallback) = reader.readBool();
261:
262: if (!hasCallback) {
263: return (reader, 0);
264: }
265:
266: uint208 packedCallbackData;
267: (reader, packedCallbackData) = reader.readU208();
268:
269:
270: _allowCallback(root, packedCallbackData);
271:
272: return (reader, packedCallbackData);
273: }
['359']
359: function _executeSubmit(bytes32 root, CalldataReader reader, bool isCalledFromCallback)
360: internal
361: returns (Approval[] memory approvals, uint256 approvalsLength, bytes[] memory results, CalldataReader newReader)
362: {
363: uint256 operationsLength; // <= FOUND
364: (reader, operationsLength) = reader.readU8();
365:
366: results = new bytes[](operationsLength);
367:
368:
369: approvals = new Approval[](operationsLength);
370:
371:
372:
373: OperationContext memory ctx;
374: for (uint256 i = 0; i < operationsLength; ++i) { // <= FOUND
375: (reader, ctx.target) = reader.readAddr();
376:
377: bytes memory callData;
378: (reader, callData) = reader.readBytesToMemory();
379:
380: reader = callData.pipe(reader, results);
381:
382: bool isStaticCall;
383: (reader, isStaticCall) = reader.readBool();
384: if (isStaticCall) {
385:
386: (bool success, bytes memory result) = ctx.target.staticcall(callData);
387:
388: require(success, Aera__SubmissionFailed(i, result));
389:
390: results[i] = result; // <= FOUND
391: } else {
392: ctx.selector = bytes4(callData);
393: if (_isAllowanceSelector(ctx.selector)) {
394: unchecked {
395: approvals[approvalsLength++] =
396: Approval({ token: ctx.target, spender: _extractApprovalSpender(callData) });
397: }
398: }
399:
400: (reader, ctx.callbackData) = _processExpectedCallback(reader, root);
401:
402: bytes memory extractedData;
403:
404: (reader, extractedData, ctx.configurableOperationHooks, ctx.operationHooks) =
405: _processBeforeOperationHooks(reader, callData, i);
406:
407: bytes32[] memory proof;
408: (reader, proof) = reader.readBytes32Array();
409:
410: (reader, ctx.value) = reader.readOptionalU256();
411:
412:
413: _verifyOperation(proof, root, _createMerkleLeaf(ctx, extractedData));
414:
415:
416: (bool success, bytes memory result) = ctx.target.call{ value: ctx.value }(callData);
417:
418: require(success, Aera__SubmissionFailed(i, result));
419:
420: if (ctx.callbackData != 0) {
421:
422: require(_hasCallbackBeenCalled(), Aera__ExpectedCallbackNotReceived());
423:
424: if (!isCalledFromCallback) {
425:
426: Approval[] memory callbackApprovals = _getCallbackApprovals();
427:
428:
429: _noPendingApprovalsInvariant(callbackApprovals, callbackApprovals.length);
430: }
431: }
432:
433:
434: _afterOperationHooks(ctx.operationHooks, callData, i);
435:
436: results[i] = result; // <= FOUND
437: }
438: }
439:
440: return (approvals, approvalsLength, results, reader);
441: }
['455']
455: function _processBeforeOperationHooks(CalldataReader reader, bytes memory callData, uint256 i)
456: internal
457: returns (CalldataReader, bytes memory, uint256, address)
458: {
459: uint8 hooksConfigFlag; // <= FOUND
460: (reader, hooksConfigFlag) = reader.readU8();
461:
462: if (hooksConfigFlag == 0) {
463: return (reader, "", 0, address(0));
464: }
465:
466: uint256 calldataOffsetsCount = hooksConfigFlag & CONFIGURABLE_HOOKS_LENGTH_MASK;
467:
468:
469: if (hooksConfigFlag & HOOKS_FLAG_MASK == 0) {
470: uint256 calldataOffsetsPacked;
471: (reader, calldataOffsetsPacked) = reader.readU256();
472:
473: return (
474: reader, callData.extract(calldataOffsetsPacked, calldataOffsetsCount), calldataOffsetsPacked, address(0)
475: );
476: }
477:
478: address operationHooks;
479:
480: if (calldataOffsetsCount != 0) {
481: uint256 calldataOffsetsPacked;
482: (reader, calldataOffsetsPacked) = reader.readU256();
483:
484: (reader, operationHooks) = reader.readAddr();
485:
486: require(!_hasBeforeHooks(operationHooks), Aera__BeforeOperationHooksWithConfigurableHooks());
487:
488: return (
489: reader,
490: callData.extract(calldataOffsetsPacked, calldataOffsetsCount),
491: calldataOffsetsPacked,
492: operationHooks
493: );
494: }
495:
496:
497: (reader, operationHooks) = reader.readAddr();
498:
499: return (
500: reader,
501:
502: _beforeOperationHooks(operationHooks, callData, i),
503: 0,
504: operationHooks
505: );
506: }
['574']
574: function _getReturnValue(CalldataReader reader, bytes[] memory results)
575: internal
576: pure
577: returns (CalldataReader newReader, bytes memory returnValue)
578: {
579: uint8 returnTypeFlag; // <= FOUND
580: (reader, returnTypeFlag) = reader.readU8();
581:
582: if (returnTypeFlag == uint8(ReturnValueType.STATIC_RETURN)) {
583: (reader, returnValue) = reader.readBytesToMemory();
584: } else if (returnTypeFlag == uint8(ReturnValueType.DYNAMIC_RETURN)) {
585: uint256 length = results.length;
586: require(length > 0, Aera__NoResults());
587:
588: unchecked {
589: returnValue = results[length - 1];
590: }
591: }
592:
593: return (reader, returnValue);
594: }
['122']
122: function _storeCallbackApprovals(Approval[] memory approvals, uint256 length) internal {
123: if (length == 0) return;
124:
125: uint256 existingApproval = APPROVALS_SLOT.asUint256().tload();
126: uint256 existingLength = existingApproval >> ADDRESS_SIZE_BITS;
127:
128: uint256 i;
129: uint256 currentSlot = uint256(APPROVALS_SLOT);
130: Approval memory approval;
131: if (existingLength == 0) {
132: approval = approvals[0];
133: unchecked {
134:
135:
136: bytes32(currentSlot).asUint256().tstore(_packLengthAndToken(length, approval.token));
137: bytes32(++currentSlot).asAddress().tstore(approval.spender);
138: }
139:
140: i = 1;
141: } else {
142: unchecked {
143: uint256 newLength = existingLength + length; // <= FOUND
144:
145:
146: bytes32(currentSlot).asUint256().tstore(
147: _packLengthAndToken(newLength, address(uint160(existingApproval)))
148: );
149:
150: currentSlot += existingLength * 2 - 1;
151: }
152: }
153:
154: for (; i < length; ++i) { // <= FOUND
155: approval = approvals[i];
156: unchecked {
157:
158: bytes32(++currentSlot).asAddress().tstore(approval.token);
159: bytes32(++currentSlot).asAddress().tstore(approval.spender);
160: }
161: }
162: }
['194']
194: function _getCallbackApprovals() internal returns (Approval[] memory approvals) {
195: uint256 lengthWithToken = APPROVALS_SLOT.asUint256().tload();
196: uint256 length = lengthWithToken >> ADDRESS_SIZE_BITS;
197: if (length == 0) return approvals;
198:
199:
200: APPROVALS_SLOT.asUint256().tstore(0);
201:
202: approvals = new Approval[](length);
203:
204: address token = address(uint160(lengthWithToken));
205:
206: uint256 slotUint256 = uint256(APPROVALS_SLOT);
207: address spender; // <= FOUND
208: unchecked {
209: spender = bytes32(++slotUint256).asAddress().tload();
210: }
211:
212: approvals[0] = Approval({ token: token, spender: spender });
213:
214: for (uint256 i = 1; i < length; ++i) { // <= FOUND
215: unchecked {
216: token = bytes32(++slotUint256).asAddress().tload();
217: spender = bytes32(++slotUint256).asAddress().tload();
218: }
219: approvals[i] = Approval({ token: token, spender: spender });
220: }
221: }
['211']
211: function _calculatePerformanceFees(
212: uint256 vaultPerformanceFeeRate,
213: uint256 newHighestProfit,
214: uint256 oldHighestProfit
215: ) internal view returns (uint256, uint256) {
216: if (newHighestProfit <= oldHighestProfit) {
217: return (0, 0);
218: }
219:
220: uint256 profit; // <= FOUND
221: unchecked {
222: profit = newHighestProfit - oldHighestProfit;
223: }
224:
225: return (
226: _calculatePerformanceFee(profit, vaultPerformanceFeeRate),
227: _calculatePerformanceFee(profit, protocolFees.performance)
228: );
229: }
['238']
238: function _calculateTvlFees(
239: uint256 vaultTvlFeeRate,
240: uint256 averageValue,
241: uint256 snapshotTimestamp,
242: uint256 lastFeeAccrual
243: ) internal view returns (uint256, uint256) {
244: uint256 totalDuration; // <= FOUND
245: unchecked {
246: totalDuration = snapshotTimestamp - lastFeeAccrual;
247: }
248:
249: return (
250: _calculateTvlFee(averageValue, vaultTvlFeeRate, totalDuration),
251: _calculateTvlFee(averageValue, protocolFees.tvl, totalDuration)
252: );
253: }
['105']
105: function claimFees() external onlyFeeRecipient returns (uint256 feeRecipientFees, uint256 protocolFees) {
106: address protocolFeeRecipient; // <= FOUND
107:
108:
109: (feeRecipientFees, protocolFees, protocolFeeRecipient) =
110: feeCalculator.claimFees(FEE_TOKEN.balanceOf(address(this)));
111:
112:
113: require(feeRecipientFees != 0, Aera__NoFeesToClaim());
114:
115:
116: FEE_TOKEN.safeTransfer(msg.sender, feeRecipientFees);
117:
118: emit FeesClaimed(msg.sender, feeRecipientFees);
119:
120: if (protocolFees != 0) {
121:
122: FEE_TOKEN.safeTransfer(protocolFeeRecipient, protocolFees);
123:
124: emit ProtocolFeesClaimed(protocolFeeRecipient, protocolFees);
125: }
126: }
['129']
129: function claimProtocolFees() external returns (uint256 protocolFees) {
130: address protocolFeeRecipient; // <= FOUND
131:
132:
133: (protocolFees, protocolFeeRecipient) = feeCalculator.claimProtocolFees(FEE_TOKEN.balanceOf(address(this)));
134:
135:
136: require(msg.sender == protocolFeeRecipient, Aera__CallerIsNotProtocolFeeRecipient());
137:
138:
139: require(protocolFees != 0, Aera__NoFeesToClaim());
140:
141:
142: FEE_TOKEN.safeTransfer(protocolFeeRecipient, protocolFees);
143:
144: emit ProtocolFeesClaimed(protocolFeeRecipient, protocolFees);
145: }
['38']
38: function extract(bytes memory callData, uint256 calldataOffsetsPacked, uint256 calldataOffsetsCount)
39: internal
40: pure
41: returns (bytes memory)
42: {
43: unchecked {
44:
45: require(calldataOffsetsCount < MAX_EXTRACT_OFFSETS_EXCLUSIVE, Aera__ExtractionNumberTooLarge());
46:
47: bytes memory result = new bytes(calldataOffsetsCount * WORD_SIZE);
48:
49: uint256 resultPtr; // <= FOUND
50: assembly ("memory-safe") {
51: resultPtr := result
52: }
53:
54: resultPtr += WORD_SIZE;
55:
56: uint256 callDataLength = callData.length;
57:
58: require(callDataLength >= MINIMUM_CALLDATA_LENGTH, Aera__CalldataTooShort());
59:
60:
61: uint256 maxValidOffset = callDataLength - WORD_SIZE;
62:
63: uint256 calldataPointer;
64: assembly ("memory-safe") {
65: calldataPointer := callData
66: }
67:
68: calldataPointer += WORD_SIZE;
69:
70: uint256 resultWriteOffset;
71: for (uint256 i = 0; i < calldataOffsetsCount; ++i) {
72: uint256 extractionOffset = (calldataOffsetsPacked >> EXTRACTION_OFFSET_SHIFT_BITS) + SELECTOR_SIZE;
73:
74:
75: require(extractionOffset <= maxValidOffset, Aera__OffsetOutOfBounds());
76:
77: uint256 calldataOffsetPointer = calldataPointer + extractionOffset;
78:
79:
80:
81: bytes32 extracted;
82: assembly ("memory-safe") {
83: extracted := mload(calldataOffsetPointer)
84: }
85:
86:
87:
88: uint256 resultOffsetPointer = resultPtr + resultWriteOffset;
89: assembly ("memory-safe") {
90: mstore(resultOffsetPointer, extracted)
91: }
92:
93: resultWriteOffset += WORD_SIZE;
94: calldataOffsetsPacked = calldataOffsetsPacked << EXTRACT_OFFSET_SIZE_BITS;
95: }
96:
97: return result; // <= FOUND
98: }
99: }
['184']
184: function readOptionalU256(CalldataReader reader) internal pure returns (CalldataReader, uint256 u256) {
185: bool hasU256; // <= FOUND
186: (reader, hasU256) = reader.readBool();
187: if (hasU256) {
188: (reader, u256) = reader.readU256();
189: }
190: return (reader, u256);
191: }
['193']
193: function readBytes32Array(CalldataReader self) internal pure returns (CalldataReader, bytes32[] memory array) {
194: uint256 length; // <= FOUND
195: (self, length) = readU8(self);
196: array = new bytes32[](length);
197: assembly ("memory-safe") {
198: calldatacopy(add(array, 32), self, mul(length, 32))
199: self := add(self, mul(length, 32))
200: }
201: return (self, array);
202: }
['217']
217: function readBytesToMemory(CalldataReader self) internal pure returns (CalldataReader, bytes memory data) {
218: uint256 length; // <= FOUND
219: (self, length) = readU16(self);
220: return readBytesToMemory(self, length);
221: }
['35']
35: function pipe(bytes memory data, CalldataReader reader, bytes[] memory results)
36: internal
37: pure
38: returns (CalldataReader)
39: {
40: uint256 clipboardCount; // <= FOUND
41: (reader, clipboardCount) = reader.readU8();
42:
43: unchecked {
44: for (; clipboardCount != 0; --clipboardCount) {
45: uint256 clipboard;
46: (reader, clipboard) = reader.readU32();
47:
48: uint256 resultIndex = clipboard >> RESULTS_INDEX_OFFSET;
49:
50:
51: bytes memory result = results[resultIndex];
52:
53: uint256 copyOffset = (clipboard >> COPY_WORD_OFFSET & MASK_8_BIT) * WORD_SIZE;
54:
55: require(copyOffset + WORD_SIZE <= result.length, Aera__CopyOffsetOutOfBounds());
56:
57: uint256 pasteOffset = clipboard & MASK_16_BIT;
58:
59: require(pasteOffset + WORD_SIZE <= data.length, Aera__PasteOffsetOutOfBounds());
60:
61: uint256 operationCalldataPointer;
62: uint256 resultPointer;
63: assembly ("memory-safe") {
64:
65:
66: operationCalldataPointer := data
67: resultPointer := result
68: }
69:
70: uint256 pastePointer = operationCalldataPointer + pasteOffset + CALLDATA_OFFSET;
71: uint256 copyPointer = resultPointer + WORD_SIZE + copyOffset;
72:
73: assembly ("memory-safe") {
74: mcopy(pastePointer, copyPointer, WORD_SIZE)
75: }
76: }
77: }
78:
79: return reader;
80: }
['311']
311: function previewFees(address vault, uint256 feeTokenBalance) external view override returns (uint256, uint256) {
312: VaultAccruals storage vaultAccruals = _vaultAccruals[vault];
313:
314: uint256 claimableProtocolFee = Math.min(feeTokenBalance, vaultAccruals.accruedProtocolFees);
315: uint256 claimableVaultFee; // <= FOUND
316: unchecked {
317: claimableVaultFee = Math.min(feeTokenBalance - claimableProtocolFee, vaultAccruals.accruedFees);
318: }
319:
320: return (claimableVaultFee, claimableProtocolFee);
321: }
['334']
334: function _accrueFees(address vault, uint256 price, uint256 timestamp) internal {
335: VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
336:
337: uint256 timeDelta; // <= FOUND
338: unchecked {
339: timeDelta = timestamp - vaultPriceState.timestamp + vaultPriceState.accrualLag;
340: }
341:
342:
343: uint256 currentTotalSupply = IERC20(vault).totalSupply();
344: uint256 minTotalSupply = Math.min(currentTotalSupply, uint256(vaultPriceState.lastTotalSupply));
345: uint256 minUnitPrice = Math.min(price, uint256(vaultPriceState.unitPrice));
346:
347: uint256 tvl = minUnitPrice * minTotalSupply / UNIT_PRICE_PRECISION;
348:
349: VaultAccruals storage vaultAccruals = _vaultAccruals[vault];
350: uint256 vaultFeesEarned = _calculateTvlFee(tvl, vaultAccruals.fees.tvl, timeDelta);
351:
352: uint256 protocolFeesEarned = _calculateTvlFee(tvl, protocolFees.tvl, timeDelta);
353:
354: if (price > vaultPriceState.highestPrice) {
355: uint256 profit = (price - vaultPriceState.highestPrice) * minTotalSupply / UNIT_PRICE_PRECISION;
356: vaultFeesEarned += _calculatePerformanceFee(profit, vaultAccruals.fees.performance);
357: protocolFeesEarned += _calculatePerformanceFee(profit, protocolFees.performance);
358:
359:
360: vaultPriceState.highestPrice = uint128(price);
361: }
362:
363:
364: vaultAccruals.accruedFees += vaultFeesEarned.toUint112();
365: vaultAccruals.accruedProtocolFees += protocolFeesEarned.toUint112();
366:
367:
368: vaultPriceState.lastTotalSupply = currentTotalSupply.toUint128();
369: vaultPriceState.accrualLag = 0;
370: }
['294']
294: function solveRequestsVault(IERC20 token, Request[] calldata requests) external requiresAuth nonReentrant {
295:
296: uint256 priceAge = PRICE_FEE_CALCULATOR.getVaultsPriceAge(MULTI_DEPOSITOR_VAULT);
297:
298: uint256 solverTip; // <= FOUND
299: Request calldata request;
300:
301: uint256 length = requests.length;
302: TokenDetails memory tokenDetails = tokensDetails[token];
303: bool depositsExist;
304: for (uint256 i = 0; i < length; i++) { // <= FOUND
305: request = requests[i];
306: if (_isRequestTypeDeposit(request.requestType)) {
307:
308: if (!tokenDetails.asyncDepositEnabled) {
309:
310: emit AsyncDepositDisabled(i);
311: continue;
312: }
313:
314: if (!depositsExist) {
315: depositsExist = true;
316: token.forceApprove(MULTI_DEPOSITOR_VAULT, type(uint256).max);
317: }
318:
319: if (_isRequestTypeAutoPrice(request.requestType)) {
320:
321: solverTip +=
322: _solveDepositVaultAutoPrice(token, tokenDetails.depositMultiplier, request, priceAge, i);
323: } else {
324:
325: solverTip +=
326: _solveDepositVaultFixedPrice(token, tokenDetails.depositMultiplier, request, priceAge, i);
327: }
328: } else {
329:
330: if (!tokenDetails.asyncRedeemEnabled) {
331:
332: emit AsyncRedeemDisabled(i);
333: continue;
334: }
335:
336: if (_isRequestTypeAutoPrice(request.requestType)) {
337:
338: solverTip += _solveRedeemVaultAutoPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i);
339: } else {
340:
341: solverTip += _solveRedeemVaultFixedPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i);
342: }
343: }
344: }
345:
346: if (solverTip != 0) {
347:
348: token.safeTransfer(msg.sender, solverTip);
349: }
350:
351: if (depositsExist) {
352:
353: token.forceApprove(MULTI_DEPOSITOR_VAULT, 0);
354: }
355: }
['514']
514: function _solveDepositVaultAutoPrice(
515: IERC20 token,
516: uint256 depositMultiplier,
517: Request calldata request,
518: uint256 priceAge,
519: uint256 index
520: ) internal returns (uint256 solverTip) {
521:
522: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
523:
524: bytes32 depositHash = _getRequestHash(token, request);
525:
526: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0;
527:
528: if (request.deadline >= block.timestamp) {
529: solverTip = request.solverTip;
530: uint256 tokens = request.tokens;
531:
532:
533: if (_guardInsufficientTokensForTip(tokens, solverTip, index)) return 0;
534:
535: uint256 tokensAfterTip; // <= FOUND
536: unchecked {
537: tokensAfterTip = tokens - solverTip; // <= FOUND
538: }
539:
540:
541: uint256 unitsOut = _tokensToUnitsFloorIfActive(token, tokensAfterTip, depositMultiplier);
542:
543: if (_guardAmountBound(unitsOut, request.units, index)) return 0;
544:
545: if (_guardDepositCapExceeded(unitsOut, index)) return 0;
546:
547:
548: asyncDepositHashes[depositHash] = false;
549:
550: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter(
551: address(this), token, tokensAfterTip, unitsOut, request.user
552: );
553:
554:
555: emit DepositSolved(depositHash);
556: } else {
557:
558: asyncDepositHashes[depositHash] = false;
559:
560: token.safeTransfer(request.user, request.tokens);
561:
562: emit DepositRefunded(depositHash);
563: }
564: }
['643']
643: function _solveRedeemVaultAutoPrice(
644: IERC20 token,
645: uint256 redeemMultiplier,
646: Request calldata request,
647: uint256 priceAge,
648: uint256 index
649: ) internal returns (uint256 solverTip) {
650:
651: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
652:
653: bytes32 redeemHash = _getRequestHash(token, request);
654:
655: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0;
656:
657: if (request.deadline >= block.timestamp) {
658: solverTip = request.solverTip;
659:
660:
661: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
662:
663: if (_guardInsufficientTokensForTip(tokenOut, solverTip, index)) return 0;
664:
665: uint256 tokenOutAfterTip; // <= FOUND
666: unchecked {
667: tokenOutAfterTip = tokenOut - solverTip; // <= FOUND
668: }
669:
670:
671: if (_guardAmountBound(tokenOutAfterTip, request.tokens, index)) return 0;
672:
673:
674: asyncRedeemHashes[redeemHash] = false;
675:
676: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit(
677: address(this), token, tokenOut, request.units, address(this)
678: );
679:
680:
681: token.safeTransfer(request.user, tokenOutAfterTip);
682:
683:
684: emit RedeemSolved(redeemHash);
685: } else {
686:
687: asyncRedeemHashes[redeemHash] = false;
688:
689: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
690:
691: emit RedeemRefunded(redeemHash);
692: }
693: }
['61']
61: function execute(OperationPayable[] calldata operations) external requiresAuth {
62: bool success;
63: bytes memory result; // <= FOUND
64: OperationPayable calldata operation;
65: uint256 length = operations.length;
66: for (uint256 i = 0; i < length; ++i) { // <= FOUND
67: operation = operations[i];
68:
69:
70:
71: (success, result) = operation.target.call{ value: operation.value }(operation.data);
72:
73:
74: require(success, Aera__ExecutionFailed(i, result));
75: }
76:
77:
78: emit Executed(msg.sender, operations);
79: }
['34']
34: function execute(TargetCalldata[] calldata operations) external nonReentrant {
35: address target;
36: bytes memory data; // <= FOUND
37: bytes32 targetAndSelector;
38: mapping(bytes32 targetAndSelector => bool enabled) storage callerCapabilities = _canCall[msg.sender];
39:
40: uint256 length = operations.length;
41: for (uint256 i; i < length; ++i) { // <= FOUND
42: target = operations[i].target;
43: data = operations[i].data;
44: targetAndSelector = _packTargetSig(target, bytes4(data));
45:
46:
47: require(
48: callerCapabilities[targetAndSelector], AeraPeriphery__Unauthorized(msg.sender, target, bytes4(data))
49: );
50:
51:
52: (bool success, bytes memory returnData) = target.call(data);
53: Address.verifyCallResultFromTarget(target, success, returnData);
54: }
55:
56:
57: emit Executed(msg.sender, operations);
58: }
['175']
175: function _enforceSlippageLimitAndDailyLoss(uint256 valueBefore, uint256 valueAfter)
176: internal
177: returns (uint128 cumulativeDailyLossInNumeraire)
178: {
179: State storage state = _vaultStates[msg.sender];
180:
181: uint256 loss; // <= FOUND
182: unchecked {
183: loss = valueBefore - valueAfter;
184: }
185:
186:
187: _enforceSlippageLimit(state, loss, valueBefore);
188:
189:
190: cumulativeDailyLossInNumeraire = uint128(_enforceDailyLoss(state, loss));
191: state.cumulativeDailyLossInNumeraire = cumulativeDailyLossInNumeraire;
192: }
['44']
44: function checkPrice(
45: uint256 amountIn,
46: address fromToken,
47: address toToken,
48: uint256,
49: uint256 minOut,
50: bytes calldata data
51: ) external view returns (bool) {
52: address vault = abi.decode(data, (address));
53: State storage state = _vaultStates[vault];
54:
55:
56: uint256 expectedOut = state.oracleRegistry.getQuoteForUser(amountIn, fromToken, toToken, vault);
57: uint256 slippageMultiplier; // <= FOUND
58: unchecked {
59: slippageMultiplier = MAX_BPS - state.maxSlippagePerTrade;
60: }
61: return minOut > (expectedOut * slippageMultiplier / MAX_BPS);
62: }
['28']
28: function exactInput(ISwapRouter.ExactInputParams calldata params) external returns (bytes memory) {
29: uint256 pathLength = params.path.length;
30:
31:
32: require(pathLength % UNISWAP_PATH_CHUNK_SIZE == UNISWAP_PATH_ADDRESS_SIZE, AeraPeriphery__BadPathFormat());
33:
34: address tokenIn; // <= FOUND
35: address tokenOut;
36: unchecked {
37: tokenIn = address(bytes20(params.path[:20]));
38: tokenOut = address(bytes20(params.path[pathLength - 20:]));
39: }
40:
41:
42: (address _tokenIn, address _tokenOut, address _receiver) =
43: _handleBeforeExactInputSingle(tokenIn, tokenOut, params.recipient, params.amountIn, params.amountOutMinimum);
44: return abi.encode(_tokenIn, _tokenOut, _receiver);
45: }
['57']
57: function exactOutput(ISwapRouter.ExactOutputParams calldata params) external returns (bytes memory) {
58: uint256 pathLength = params.path.length;
59:
60:
61: require(pathLength % UNISWAP_PATH_CHUNK_SIZE == UNISWAP_PATH_ADDRESS_SIZE, AeraPeriphery__BadPathFormat());
62:
63: address tokenIn; // <= FOUND
64: address tokenOut;
65: unchecked {
66: tokenIn = address(bytes20(params.path[:20]));
67: tokenOut = address(bytes20(params.path[pathLength - 20:]));
68: }
69:
70:
71: (address _tokenIn, address _tokenOut, address _receiver) = _handleBeforeExactOutputSingle(
72: tokenIn, tokenOut, params.recipient, params.amountOut, params.amountInMaximum
73: );
74: return abi.encode(_tokenIn, _tokenOut, _receiver);
75: }
['23']
23: function updateWhitelist(address vault, address[] calldata addresses, bool isWhitelisted)
24: external
25: requiresVaultAuth(vault)
26: {
27: uint256 length = addresses.length;
28: address addr; // <= FOUND
29:
30: for (uint256 i; i < length; i++) { // <= FOUND
31: addr = addresses[i];
32:
33:
34: whitelist[vault][addr] = isWhitelisted;
35: }
36:
37:
38: emit VaultWhitelistUpdated(vault, addresses, isWhitelisted);
39: }
[NonCritical-63] Avoid using nested struct mappings as in solidity versions prior to 5.0.0
produced incorrect values for these nested struct mappings
Num of instances: 1
Click to show findings
['27']
27: mapping(address vault => VaultAccruals vaultAccruals) internal _vaultAccruals; // <= FOUND
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: 1
Click to show findings
['455']
455: function _processBeforeOperationHooks(CalldataReader reader, bytes memory callData, uint256 i)
456: internal
457: returns (CalldataReader, bytes memory, uint256, address)
458: {
459: uint8 hooksConfigFlag;
460: (reader, hooksConfigFlag) = reader.readU8();
461:
462: if (hooksConfigFlag == 0) {
463: return (reader, "", 0, address(0));
464: }
465:
466: uint256 calldataOffsetsCount = hooksConfigFlag & CONFIGURABLE_HOOKS_LENGTH_MASK;
467:
468:
469: if (hooksConfigFlag & HOOKS_FLAG_MASK == 0) {
470: uint256 calldataOffsetsPacked;
471: (reader, calldataOffsetsPacked) = reader.readU256(); // <= FOUND
472:
473: return (
474: reader, callData.extract(calldataOffsetsPacked, calldataOffsetsCount), calldataOffsetsPacked, address(0)
475: );
476: }
477:
478: address operationHooks;
479:
480: if (calldataOffsetsCount != 0) {
481: uint256 calldataOffsetsPacked;
482: (reader, calldataOffsetsPacked) = reader.readU256(); // <= FOUND
483:
484: (reader, operationHooks) = reader.readAddr();
485:
486: require(!_hasBeforeHooks(operationHooks), Aera__BeforeOperationHooksWithConfigurableHooks());
487:
488: return (
489: reader,
490: callData.extract(calldataOffsetsPacked, calldataOffsetsCount),
491: calldataOffsetsPacked,
492: operationHooks
493: );
494: }
495:
496:
497: (reader, operationHooks) = reader.readAddr();
498:
499: return (
500: reader,
501:
502: _beforeOperationHooks(operationHooks, callData, i),
503: 0,
504: operationHooks
505: );
506: }
Num of instances: 3
Click to show findings
['168']
168: function _accrueFees( // <= FOUND
169: VaultSnapshot storage vaultSnapshot,
170: VaultAccruals storage vaultAccruals,
171: uint256 lastFeeAccrualCached
172: ) internal returns (uint256 protocolFeesEarned, uint256 vaultFeesEarned) {
173: uint256 snapshotTimestamp = vaultSnapshot.timestamp;
174: if (lastFeeAccrualCached >= snapshotTimestamp || vaultSnapshot.finalizedAt > block.timestamp) {
175:
176: return (0, 0);
177: }
178:
179:
180: (uint256 vaultPerformanceFeeEarned, uint256 protocolPerformanceFeeEarned) = _calculatePerformanceFees(
181: vaultAccruals.fees.performance, vaultSnapshot.highestProfit, vaultSnapshot.lastHighestProfit
182: );
183:
184: (uint256 vaultTvlFeeEarned, uint256 protocolTvlFeeEarned) = _calculateTvlFees(
185: vaultAccruals.fees.tvl, vaultSnapshot.averageValue, snapshotTimestamp, lastFeeAccrualCached
186: );
187:
188:
189: vaultSnapshot.lastHighestProfit = vaultSnapshot.highestProfit; // <= FOUND
190: vaultSnapshot.lastFeeAccrual = uint32(snapshotTimestamp);
191: vaultAccruals.accruedFees += (vaultPerformanceFeeEarned + vaultTvlFeeEarned).toUint112();
192: vaultAccruals.accruedProtocolFees += (protocolPerformanceFeeEarned + protocolTvlFeeEarned).toUint112();
193:
194:
195: vaultSnapshot.averageValue = 0;
196: vaultSnapshot.highestProfit = 0;
197: vaultSnapshot.timestamp = 0;
198: vaultSnapshot.finalizedAt = 0;
199:
200: protocolFeesEarned = protocolPerformanceFeeEarned + protocolTvlFeeEarned;
201: vaultFeesEarned = vaultPerformanceFeeEarned + vaultTvlFeeEarned;
202: }
['376']
376: function _setVaultPaused(VaultPriceState storage vaultPriceState, address vault, bool paused) internal { // <= FOUND
377:
378: vaultPriceState.paused = paused; // <= FOUND
379:
380:
381: emit VaultPausedChanged(vault, paused);
382: }
['198']
198: function _enforceDailyLoss(State storage state, uint256 loss) internal returns (uint256 newLoss) { // <= FOUND
199: uint32 day = uint32(block.timestamp / 1 days);
200: if (state.currentDay != day) {
201:
202: state.currentDay = day; // <= FOUND
203: state.cumulativeDailyLossInNumeraire = 0;
204: }
205:
206: newLoss = state.cumulativeDailyLossInNumeraire + loss;
207:
208:
209: require(
210: newLoss <= state.maxDailyLossInNumeraire,
211: AeraPeriphery__ExcessiveDailyLoss(newLoss, state.maxDailyLossInNumeraire)
212: );
213: }
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: 3
Click to show findings
['15']
15: function computeBaseVaultAddress(BaseVaultFactory baseVaultFactory, bytes32 salt) public pure returns (address)
['15']
15: function computeMultiDepositorVaultAddress(MultiDepositorVaultFactory multiDepositorVaultFactory, bytes32 salt)
16: public
17: pure
18: returns (address)
19:
['15']
15: function computeSingleDepositorVaultAddress(SingleDepositorVaultFactory singleDepositorVaultFactory, bytes32 salt)
16: public
17: pure
18: returns (address)
19:
When using a smaller int/uint type it first needs to be converted to it's 256 bit counterpart to be operated, this increases the gas 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: 86
Click to show findings
['459']
459: uint8 hooksConfigFlag; // <= FOUND
['579']
579: uint8 returnTypeFlag; // <= FOUND
['21']
21:
31: event ThresholdsSet(
32: address indexed vault,
33: uint16 minPriceToleranceRatio, // <= FOUND
34: uint16 maxPriceToleranceRatio, // <= FOUND
35: uint16 minUpdateIntervalMinutes, // <= FOUND
36: uint8 maxPriceAge // <= FOUND
37: );
['84']
84:
91: function setThresholds(
92: address vault,
93: uint16 minPriceToleranceRatio, // <= FOUND
94: uint16 maxPriceToleranceRatio, // <= FOUND
95: uint16 minUpdateIntervalMinutes, // <= FOUND
96: uint8 maxPriceAge, // <= FOUND
97: uint8 maxUpdateDelayDays // <= FOUND
98: ) external;
['83']
83: function readU8(CalldataReader self) internal pure returns (CalldataReader, uint8 value) { // <= FOUND
['122']
122:
123: function setThresholds(
124: address vault,
125: uint16 minPriceToleranceRatio, // <= FOUND
126: uint16 maxPriceToleranceRatio, // <= FOUND
127: uint16 minUpdateIntervalMinutes, // <= FOUND
128: uint8 maxPriceAge, // <= FOUND
129: uint8 maxUpdateDelayDays // <= FOUND
130: ) external requiresVaultAuth(vault) {
['113']
113:
114: uint8 resultIndex; // <= FOUND
['115']
115:
116: uint8 copyWord; // <= FOUND
['208']
208:
209: uint8 maxPriceAge; // <= FOUND
['216']
216:
217: uint8 maxUpdateDelayDays; // <= FOUND
['61']
61:
62: function setProtocolFees(uint16 tvl, uint16 performance) external requiresAuth { // <= FOUND
['88']
88:
89: function setVaultFees(address vault, uint16 tvl, uint16 performance) external requiresVaultAuth(vault) { // <= FOUND
['63']
63: (address caller, bytes4 selector, uint16 userDataOffset) = _getAllowedCallback(); // <= FOUND
['170']
170:
176: function _getAllowedCallback() internal returns (address caller, bytes4 selector, uint16 userDataOffset) { // <= FOUND
['235']
235:
240: function _unpackCallbackData(uint256 packed)
241: private
242: pure
243: returns (address target, bytes4 selector, uint16 dataOffset) // <= FOUND
244: {
['15']
15:
23: event VaultFeesSet(address indexed vault, uint16 tvlFee, uint16 performanceFee); // <= FOUND
['24']
24:
27: event ProtocolFeesSet(uint16 tvlFee, uint16 performanceFee); // <= FOUND
['64']
64:
67: function setProtocolFees(uint16 tvl, uint16 performance) external; // <= FOUND
['70']
70:
74: function setVaultFees(address vault, uint16 tvl, uint16 performance) external; // <= FOUND
['91']
91: function readU16(CalldataReader self) internal pure returns (CalldataReader, uint16 value) { // <= FOUND
['117']
117:
118: uint16 pasteOffset; // <= FOUND
['127']
127:
128: uint16 calldataOffset; // <= FOUND
['164']
164:
165: uint16 tvl; // <= FOUND
['166']
166:
167: uint16 performance; // <= FOUND
['210']
210:
211: uint16 minUpdateIntervalMinutes; // <= FOUND
['212']
212:
213: uint16 maxPriceToleranceRatio; // <= FOUND
['214']
214:
215: uint16 minPriceToleranceRatio; // <= FOUND
['238']
238:
239: uint16 depositMultiplier; // <= FOUND
['240']
240:
241: uint16 redeemMultiplier; // <= FOUND
['49']
49:
50: function setMaxSlippagePerTrade(address vault, uint16 newMaxSlippage) external requiresVaultAuth(vault) { // <= FOUND
['19']
19:
20: uint16 maxSlippagePerTrade; // <= FOUND
['67']
67:
70: function setMaxSlippagePerTrade(address vault, uint16 newMaxSlippage) external; // <= FOUND
['107']
107: function readI24(CalldataReader self) internal pure returns (CalldataReader, int24 value) { // <= FOUND
['220']
220:
221: uint24 accrualLag; // <= FOUND
['13']
13: uint24 fee; // <= FOUND
['61']
61:
62: function submitSnapshot(address vault, uint160 averageValue, uint128 highestProfit, uint32 timestamp) // <= FOUND
63: external
64: onlyVaultAccountant(vault)
65: {
['18']
18:
27: event SnapshotSubmitted(address indexed vault, uint160 averageValue, uint128 highestProfit, uint32 timestamp); // <= FOUND
['45']
45:
54: function submitSnapshot(address vault, uint160 averageValue, uint128 highestProfit, uint32 timestamp) external; // <= FOUND
['33']
33:
37: event UnitPriceUpdated(address indexed vault, uint128 price, uint32 timestamp); // <= FOUND
['75']
75:
83: function setInitialPrice(address vault, uint128 price, uint32 timestamp) external; // <= FOUND
['97']
97:
101: function setUnitPrice(address vault, uint128 price, uint32 timestamp) external; // <= FOUND
['108']
108:
113: function unpauseVault(address vault, uint128 price, uint32 timestamp) external; // <= FOUND
['99']
99: function readU32(CalldataReader self) internal pure returns (CalldataReader, uint32 value) { // <= FOUND
['94']
94:
95: function setInitialPrice(address vault, uint128 price, uint32 timestamp) external requiresVaultAuth(vault) { // <= FOUND
['108']
108: uint32 timestampU32 = uint32(block.timestamp); // <= FOUND
['156']
156:
157: function setUnitPrice(address vault, uint128 price, uint32 timestamp) external onlyVaultAccountant(vault) { // <= FOUND
['203']
203:
204: function unpauseVault(address vault, uint128 price, uint32 timestamp) external requiresVaultAuth(vault) { // <= FOUND
['456']
456:
463: function _shouldPause(VaultPriceState storage state, uint256 price, uint32 timestamp) // <= FOUND
464: internal
465: view
466: returns (bool)
467: {
['182']
182:
183: uint32 lastFeeAccrual; // <= FOUND
['184']
184:
185: uint32 timestamp; // <= FOUND
['186']
186:
187: uint32 finalizedAt; // <= FOUND
['279']
279:
280: uint32 commitTimestamp; // <= FOUND
['28']
28:
48: function depositForBurn(
49: uint256 amount,
50: uint32 destinationDomain, // <= FOUND
51: bytes32 mintRecipient,
52: address burnToken,
53: bytes32 destinationCaller,
54: uint256 maxFee,
55: uint32 minFinalityThreshold // <= FOUND
56: ) external;
['24']
24:
29: function swap(
30: SwapTokenInfo calldata tokenInfo,
31: bytes calldata pathDefinition,
32: address executor,
33: uint32 referralCode // <= FOUND
34: ) external payable returns (uint256 amountOut);
['76']
76: uint32 day = uint32(block.timestamp / 1 days); // <= FOUND
['16']
16:
21: function depositForBurn(
22: uint256 amount,
23: uint32 destinationDomain, // <= FOUND
24: bytes32 mintRecipient,
25: address burnToken,
26: bytes32 destinationCaller,
27: uint256 maxFee,
28: uint32 // <= FOUND
29: ) external returns (bytes memory returnData) {
['18']
18:
23: function swap(
24: IOdosRouterV2.SwapTokenInfo calldata tokenInfo,
25: bytes calldata,
26: address,
27: uint32 // <= FOUND
28: ) external returns (bytes memory returnData) {
['21']
21:
22: uint32 currentDay; // <= FOUND
['29']
29:
43: function depositForBurn(
44: uint256 amount,
45: uint32 destinationDomain, // <= FOUND
46: bytes32 mintRecipient,
47: address burnToken,
48: bytes32 destinationCaller,
49: uint256 maxFee,
50: uint32 minFinalityThreshold // <= FOUND
51: ) external returns (bytes memory);
['25']
25:
30: event OracleScheduled(
31: address indexed base, address indexed quote, IOracle indexed pendingOracle, uint32 commitTimestamp // <= FOUND
32: );
['115']
115: function readU40(CalldataReader self) internal pure returns (CalldataReader, uint40 value) { // <= FOUND
['123']
123: function readU64(CalldataReader self) internal pure returns (CalldataReader, uint64 value) { // <= FOUND
['7']
7: function getRoundData(uint80 _roundId) // <= FOUND
8: external
9: view
10: returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); // <= FOUND
['12']
12: function latestRoundData()
13: external
14: view
15: returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); // <= FOUND
['174']
174:
175: uint112 accruedFees; // <= FOUND
['176']
176:
177: uint112 accruedProtocolFees; // <= FOUND
['43']
43:
46: event HighestPriceReset(address indexed vault, uint128 newHighestPrice); // <= FOUND
['131']
131: function readU128(CalldataReader self) internal pure returns (CalldataReader, uint128 value) { // <= FOUND
['221']
221: uint128 currentPrice = vaultPriceState.unitPrice; // <= FOUND
['190']
190:
191: uint128 highestProfit; // <= FOUND
['192']
192:
193: uint128 lastHighestProfit; // <= FOUND
['222']
222:
223: uint128 unitPrice; // <= FOUND
['224']
224:
225: uint128 highestPrice; // <= FOUND
['226']
226:
227: uint128 lastTotalSupply; // <= FOUND
['40']
40:
45: function setMaxDailyLoss(address vault, uint128 maxLoss) external requiresVaultAuth(vault) { // <= FOUND
['165']
165:
166: uint128 cumulativeDailyLossNumeraire = _enforceSlippageLimitAndDailyLoss(valueBefore, valueAfter); // <= FOUND
['175']
175:
179: function _enforceSlippageLimitAndDailyLoss(uint256 valueBefore, uint256 valueAfter)
180: internal
181: returns (uint128 cumulativeDailyLossInNumeraire) // <= FOUND
182: {
['15']
15:
16: uint128 cumulativeDailyLossInNumeraire; // <= FOUND
['17']
17:
18: uint128 maxDailyLossInNumeraire; // <= FOUND
['33']
33: event TradeSlippageChecked(
34: address indexed vault,
35: address indexed tokenIn,
36: address indexed tokenOut,
37: uint256 valueBeforeNumeraire,
38: uint256 valueAfterNumeraire,
39: uint128 cumulativeDailyLossNumeraire // <= FOUND
40: );
['62']
62:
69: function setMaxDailyLoss(address vault, uint128 maxLoss) external; // <= FOUND
['188']
188:
189: uint160 averageValue; // <= FOUND
['18']
18: uint160 sqrtPriceLimitX96; // <= FOUND
['266']
266: uint208 packedCallbackData; // <= FOUND
['176']
176:
178: function readU208(CalldataReader self) internal pure returns (CalldataReader, uint208 value) { // <= FOUND
['75']
75:
76: uint208 callbackData; // <= FOUND
Replace spotted instances with != 0 for uints as this uses less gas
Num of instances: 10
Click to show findings
['586']
586: require(length > 0, Aera__NoResults()); // <= FOUND
['613']
613: return keccak256(
614: abi.encodePacked(
615: ctx.target,
616: ctx.selector,
617: ctx.value > 0, // <= FOUND
618: ctx.operationHooks,
619: ctx.configurableOperationHooks,
620: ctx.callbackData,
621: extractedData
622: )
623: );
['68']
68:
69: if (tokenAmount > 0) token.safeTransferFrom(sender, address(this), tokenAmount); // <= FOUND
['87']
87:
88: if (tokenAmount > 0) token.safeTransfer(recipient, tokenAmount); // <= FOUND
['135']
135:
136: require(maxPriceAge > 0, Aera__InvalidMaxPriceAge()); // <= FOUND
['137']
137:
138: require(maxUpdateDelayDays > 0, Aera__InvalidMaxUpdateDelayDays()); // <= FOUND
['28']
28: if (maxFee > 0) { // <= FOUND
['74']
74:
75: require(sellAmount > 0, AeraPeriphery__MilkmanRouter__SellAmountIsZero()); // <= FOUND
[]
}
pragma solidity >=0.5.0; // <= FOUND
[]
}
pragma solidity >=0.5.0 >=0.7.5; // <= FOUND
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: 11
Click to show findings
['156']
156: function refundDeposit( // <= FOUND
157: address sender,
158: IERC20 token,
159: uint256 tokenAmount,
160: uint256 unitsAmount,
161: uint256 refundableUntil
162: ) external requiresAuth {
163:
164: require(refundableUntil >= block.timestamp, Aera__RefundPeriodExpired());
165:
166: bytes32 depositHash = _getDepositHash(sender, token, tokenAmount, unitsAmount, refundableUntil);
167:
168: require(syncDepositHashes[depositHash], Aera__DepositHashNotFound());
169:
170: syncDepositHashes[depositHash] = false; // <= FOUND
171:
172:
173: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit(sender, token, tokenAmount, unitsAmount, sender);
174:
175:
176: emit DirectDepositRefunded(depositHash);
177: }
['262']
262: function refundRequest(IERC20 token, Request calldata request) external nonReentrant { // <= FOUND
263:
264: require(
265: request.deadline < block.timestamp || isAuthorized(msg.sender, msg.sig),
266: Aera__DeadlineInFutureAndUnauthorized()
267: );
268:
269: bytes32 requestHash = _getRequestHash(token, request);
270:
271: if (_isRequestTypeDeposit(request.requestType)) {
272:
273: require(asyncDepositHashes[requestHash], Aera__HashNotFound());
274:
275: asyncDepositHashes[requestHash] = false; // <= FOUND
276:
277: token.safeTransfer(request.user, request.tokens);
278:
279: emit DepositRefunded(requestHash);
280: } else {
281:
282: require(asyncRedeemHashes[requestHash], Aera__HashNotFound());
283:
284: asyncRedeemHashes[requestHash] = false;
285:
286: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
287:
288: emit RedeemRefunded(requestHash);
289: }
290: }
['514']
514: function _solveDepositVaultAutoPrice( // <= FOUND
515: IERC20 token,
516: uint256 depositMultiplier,
517: Request calldata request,
518: uint256 priceAge,
519: uint256 index
520: ) internal returns (uint256 solverTip) {
521:
522: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
523:
524: bytes32 depositHash = _getRequestHash(token, request);
525:
526: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0;
527:
528: if (request.deadline >= block.timestamp) {
529: solverTip = request.solverTip;
530: uint256 tokens = request.tokens;
531:
532:
533: if (_guardInsufficientTokensForTip(tokens, solverTip, index)) return 0;
534:
535: uint256 tokensAfterTip;
536: unchecked {
537: tokensAfterTip = tokens - solverTip;
538: }
539:
540:
541: uint256 unitsOut = _tokensToUnitsFloorIfActive(token, tokensAfterTip, depositMultiplier);
542:
543: if (_guardAmountBound(unitsOut, request.units, index)) return 0;
544:
545: if (_guardDepositCapExceeded(unitsOut, index)) return 0;
546:
547:
548: asyncDepositHashes[depositHash] = false; // <= FOUND
549:
550: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter(
551: address(this), token, tokensAfterTip, unitsOut, request.user
552: );
553:
554:
555: emit DepositSolved(depositHash);
556: } else {
557:
558: asyncDepositHashes[depositHash] = false; // <= FOUND
559:
560: token.safeTransfer(request.user, request.tokens);
561:
562: emit DepositRefunded(depositHash);
563: }
564: }
['583']
583: function _solveDepositVaultFixedPrice( // <= FOUND
584: IERC20 token,
585: uint256 depositMultiplier,
586: Request calldata request,
587: uint256 priceAge,
588: uint256 index
589: ) internal returns (uint256 solverTip) {
590:
591: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
592:
593: bytes32 depositHash = _getRequestHash(token, request);
594:
595: if (_guardInvalidRequestHash(asyncDepositHashes[depositHash], depositHash)) return 0;
596:
597: if (request.deadline >= block.timestamp) {
598:
599: uint256 tokensNeeded = _unitsToTokensCeilIfActive(token, request.units, depositMultiplier);
600:
601: if (_guardAmountBound(request.tokens, tokensNeeded, index)) return 0;
602:
603: if (_guardDepositCapExceeded(request.units, index)) return 0;
604:
605:
606: asyncDepositHashes[depositHash] = false; // <= FOUND
607:
608: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter(
609: address(this), token, tokensNeeded, request.units, request.user
610: );
611:
612: unchecked {
613: solverTip = request.tokens - tokensNeeded;
614: }
615:
616:
617: emit DepositSolved(depositHash);
618: } else {
619:
620: asyncDepositHashes[depositHash] = false; // <= FOUND
621:
622: token.safeTransfer(request.user, request.tokens);
623:
624: emit DepositRefunded(depositHash);
625: }
626: }
['643']
643: function _solveRedeemVaultAutoPrice( // <= FOUND
644: IERC20 token,
645: uint256 redeemMultiplier,
646: Request calldata request,
647: uint256 priceAge,
648: uint256 index
649: ) internal returns (uint256 solverTip) {
650:
651: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
652:
653: bytes32 redeemHash = _getRequestHash(token, request);
654:
655: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0;
656:
657: if (request.deadline >= block.timestamp) {
658: solverTip = request.solverTip;
659:
660:
661: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
662:
663: if (_guardInsufficientTokensForTip(tokenOut, solverTip, index)) return 0;
664:
665: uint256 tokenOutAfterTip;
666: unchecked {
667: tokenOutAfterTip = tokenOut - solverTip;
668: }
669:
670:
671: if (_guardAmountBound(tokenOutAfterTip, request.tokens, index)) return 0;
672:
673:
674: asyncRedeemHashes[redeemHash] = false; // <= FOUND
675:
676: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit(
677: address(this), token, tokenOut, request.units, address(this)
678: );
679:
680:
681: token.safeTransfer(request.user, tokenOutAfterTip);
682:
683:
684: emit RedeemSolved(redeemHash);
685: } else {
686:
687: asyncRedeemHashes[redeemHash] = false; // <= FOUND
688:
689: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
690:
691: emit RedeemRefunded(redeemHash);
692: }
693: }
['710']
710: function _solveRedeemVaultFixedPrice( // <= FOUND
711: IERC20 token,
712: uint256 redeemMultiplier,
713: Request calldata request,
714: uint256 priceAge,
715: uint256 index
716: ) internal returns (uint256 solverTip) {
717:
718: if (_guardPriceAge(priceAge, request.maxPriceAge, index)) return 0;
719:
720: bytes32 redeemHash = _getRequestHash(token, request);
721:
722: if (_guardInvalidRequestHash(asyncRedeemHashes[redeemHash], redeemHash)) return 0;
723:
724: if (request.deadline >= block.timestamp) {
725:
726: uint256 tokenOut = _unitsToTokensFloorIfActive(token, request.units, redeemMultiplier);
727:
728: if (_guardAmountBound(tokenOut, request.tokens, index)) return 0;
729:
730:
731: asyncRedeemHashes[redeemHash] = false; // <= FOUND
732:
733: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit(
734: address(this), token, tokenOut, request.units, address(this)
735: );
736:
737: token.safeTransfer(request.user, request.tokens);
738:
739: unchecked {
740: solverTip = tokenOut - request.tokens;
741: }
742:
743:
744: emit RedeemSolved(redeemHash);
745: } else {
746:
747: asyncRedeemHashes[redeemHash] = false; // <= FOUND
748:
749: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
750:
751: emit RedeemRefunded(redeemHash);
752: }
753: }
['764']
764: function _solveDepositDirect(IERC20 token, Request calldata request) internal { // <= FOUND
765: bytes32 depositHash = _getRequestHash(token, request);
766:
767: if (!asyncDepositHashes[depositHash]) {
768:
769: emit InvalidRequestHash(depositHash);
770: return;
771: }
772:
773:
774: asyncDepositHashes[depositHash] = false; // <= FOUND
775:
776: if (request.deadline >= block.timestamp) {
777:
778: IERC20(MULTI_DEPOSITOR_VAULT).safeTransferFrom(msg.sender, request.user, request.units);
779:
780:
781: token.safeTransfer(msg.sender, request.tokens);
782:
783:
784: emit DepositSolved(depositHash);
785: } else {
786:
787: token.safeTransfer(request.user, request.tokens);
788:
789:
790: emit DepositRefunded(depositHash);
791: }
792: }
['803']
803: function _solveRedeemDirect(IERC20 token, Request calldata request) internal { // <= FOUND
804: bytes32 redeemHash = _getRequestHash(token, request);
805:
806: if (!asyncRedeemHashes[redeemHash]) {
807:
808: emit InvalidRequestHash(redeemHash);
809: return;
810: }
811:
812:
813: asyncRedeemHashes[redeemHash] = false; // <= FOUND
814:
815: if (request.deadline >= block.timestamp) {
816:
817: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(msg.sender, request.units);
818:
819:
820: token.safeTransferFrom(msg.sender, request.user, request.tokens);
821:
822:
823: emit RedeemSolved(redeemHash);
824: } else {
825:
826: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
827:
828:
829: emit RedeemRefunded(redeemHash);
830: }
831: }
['72']
72: function removeCallerCapability(address caller, address target, bytes4 sig) external requiresAuth { // <= FOUND
73: bytes32 targetAndSelector = _packTargetSig(target, sig);
74:
75:
76: _canCall[caller][targetAndSelector] = false; // <= FOUND
77:
78:
79: emit CallerCapabilityRemoved(caller, target, sig);
80: }
['114']
114: function commitOracleUpdate(address base, address quote) external { // <= FOUND
115: OracleData storage oracleData = _oracles[base][quote];
116:
117: IOracle pendingOracle = oracleData.pendingOracle;
118:
119:
120: require(pendingOracle != IOracle(address(0)), AeraPeriphery__NoPendingOracleUpdate());
121:
122: require(oracleData.commitTimestamp <= block.timestamp, AeraPeriphery__CommitTimestampNotReached());
123:
124:
125: oracleData.commitTimestamp = 0;
126: oracleData.pendingOracle = IOracle(address(0));
127: oracleData.oracle = pendingOracle;
128: oracleData.isScheduledForUpdate = false; // <= FOUND
129: oracleData.isDisabled = false;
130:
131:
132: _validateOracle(pendingOracle, base, quote);
133:
134:
135: emit OracleSet(base, quote, oracleData.oracle);
136: }
['139']
139: function cancelScheduledOracleUpdate(address base, address quote) external requiresAuth { // <= FOUND
140: OracleData storage oracleData = _oracles[base][quote];
141:
142:
143: require(oracleData.isScheduledForUpdate, AeraPeriphery__NoPendingOracleUpdate());
144:
145:
146: oracleData.pendingOracle = IOracle(address(0));
147: oracleData.isScheduledForUpdate = false; // <= FOUND
148: oracleData.commitTimestamp = 0;
149:
150:
151: emit OracleUpdateCancelled(base, quote);
152: }
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: 8
Click to show findings
['195']
195:
196: vaultSnapshot.averageValue = 0;
['196']
196: vaultSnapshot.highestProfit = 0;
['197']
197: vaultSnapshot.timestamp = 0;
['198']
198: vaultSnapshot.finalizedAt = 0;
['114']
114: vaultPriceState.accrualLag = 0;
['79']
79: state.cumulativeDailyLossInNumeraire = 0;
['125']
125:
126: oracleData.commitTimestamp = 0;
['125']
125: oracleData.commitTimestamp = 0;
Making 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: 11
Click to show findings
['374']
374: for (uint256 i = 0; i < operationsLength; ++i) {
375: (reader, ctx.target) = reader.readAddr();
376:
377: bytes memory callData;
378: (reader, callData) = reader.readBytesToMemory();
379:
380: reader = callData.pipe(reader, results);
381:
382: bool isStaticCall;
383: (reader, isStaticCall) = reader.readBool();
384: if (isStaticCall) {
385:
386: (bool success, bytes memory result) = ctx.target.staticcall(callData);
387:
388: require(success, Aera__SubmissionFailed(i, result));
389:
390: results[i] = result;
391: } else {
392: ctx.selector = bytes4(callData);
393: if (_isAllowanceSelector(ctx.selector)) {
394: unchecked {
395: approvals[approvalsLength++] =
396: Approval({ token: ctx.target, spender: _extractApprovalSpender(callData) });
397: }
398: }
399:
400: (reader, ctx.callbackData) = _processExpectedCallback(reader, root); // <= FOUND
401:
402: bytes memory extractedData;
403:
404: (reader, extractedData, ctx.configurableOperationHooks, ctx.operationHooks) =
405: _processBeforeOperationHooks(reader, callData, i);
406:
407: bytes32[] memory proof;
408: (reader, proof) = reader.readBytes32Array();
409:
410: (reader, ctx.value) = reader.readOptionalU256();
411:
412:
413: _verifyOperation(proof, root, _createMerkleLeaf(ctx, extractedData));
414:
415:
416: (bool success, bytes memory result) = ctx.target.call{ value: ctx.value }(callData);
417:
418: require(success, Aera__SubmissionFailed(i, result));
419:
420: if (ctx.callbackData != 0) {
421:
422: require(_hasCallbackBeenCalled(), Aera__ExpectedCallbackNotReceived());
423:
424: if (!isCalledFromCallback) {
425:
426: Approval[] memory callbackApprovals = _getCallbackApprovals();
427:
428:
429: _noPendingApprovalsInvariant(callbackApprovals, callbackApprovals.length);
430: }
431: }
432:
433:
434: _afterOperationHooks(ctx.operationHooks, callData, i);
435:
436: results[i] = result;
437: }
438: }
['374']
374: for (uint256 i = 0; i < operationsLength; ++i) {
375: (reader, ctx.target) = reader.readAddr();
376:
377: bytes memory callData;
378: (reader, callData) = reader.readBytesToMemory();
379:
380: reader = callData.pipe(reader, results);
381:
382: bool isStaticCall;
383: (reader, isStaticCall) = reader.readBool();
384: if (isStaticCall) {
385:
386: (bool success, bytes memory result) = ctx.target.staticcall(callData);
387:
388: require(success, Aera__SubmissionFailed(i, result));
389:
390: results[i] = result;
391: } else {
392: ctx.selector = bytes4(callData);
393: if (_isAllowanceSelector(ctx.selector)) {
394: unchecked {
395: approvals[approvalsLength++] =
396: Approval({ token: ctx.target, spender: _extractApprovalSpender(callData) });
397: }
398: }
399:
400: (reader, ctx.callbackData) = _processExpectedCallback(reader, root); // <= FOUND
401:
402: bytes memory extractedData;
403:
404: (reader, extractedData, ctx.configurableOperationHooks, ctx.operationHooks) =
405: _processBeforeOperationHooks(reader, callData, i);
406:
407: bytes32[] memory proof;
408: (reader, proof) = reader.readBytes32Array();
409:
410: (reader, ctx.value) = reader.readOptionalU256();
411:
412:
413: _verifyOperation(proof, root, _createMerkleLeaf(ctx, extractedData));
414:
415:
416: (bool success, bytes memory result) = ctx.target.call{ value: ctx.value }(callData);
417:
418: require(success, Aera__SubmissionFailed(i, result));
419:
420: if (ctx.callbackData != 0) {
421:
422: require(_hasCallbackBeenCalled(), Aera__ExpectedCallbackNotReceived());
423:
424: if (!isCalledFromCallback) {
425:
426: Approval[] memory callbackApprovals = _getCallbackApprovals();
427:
428:
429: _noPendingApprovalsInvariant(callbackApprovals, callbackApprovals.length);
430: }
431: }
432:
433:
434: _afterOperationHooks(ctx.operationHooks, callData, i);
435:
436: results[i] = result;
437: }
438: }
['44']
44: for (; clipboardCount != 0; --clipboardCount) {
45: uint256 clipboard;
46: (reader, clipboard) = reader.readU32(); // <= FOUND
47:
48: uint256 resultIndex = clipboard >> RESULTS_INDEX_OFFSET;
49:
50:
51: bytes memory result = results[resultIndex];
52:
53: uint256 copyOffset = (clipboard >> COPY_WORD_OFFSET & MASK_8_BIT) * WORD_SIZE;
54:
55: require(copyOffset + WORD_SIZE <= result.length, Aera__CopyOffsetOutOfBounds());
56:
57: uint256 pasteOffset = clipboard & MASK_16_BIT;
58:
59: require(pasteOffset + WORD_SIZE <= data.length, Aera__PasteOffsetOutOfBounds());
60:
61: uint256 operationCalldataPointer;
62: uint256 resultPointer;
63: assembly ("memory-safe") {
64:
65:
66: operationCalldataPointer := data
67: resultPointer := result
68: }
69:
70: uint256 pastePointer = operationCalldataPointer + pasteOffset + CALLDATA_OFFSET;
71: uint256 copyPointer = resultPointer + WORD_SIZE + copyOffset;
72:
73: assembly ("memory-safe") {
74: mcopy(pastePointer, copyPointer, WORD_SIZE)
75: }
76: }
['304']
304: for (uint256 i = 0; i < length; i++) {
305: request = requests[i];
306: if (_isRequestTypeDeposit(request.requestType)) {
307:
308: if (!tokenDetails.asyncDepositEnabled) {
309:
310: emit AsyncDepositDisabled(i);
311: continue;
312: }
313:
314: if (!depositsExist) {
315: depositsExist = true;
316: token.forceApprove(MULTI_DEPOSITOR_VAULT, type(uint256).max);
317: }
318:
319: if (_isRequestTypeAutoPrice(request.requestType)) {
320:
321: solverTip +=
322: _solveDepositVaultAutoPrice(token, tokenDetails.depositMultiplier, request, priceAge, i); // <= FOUND
323: } else {
324:
325: solverTip +=
326: _solveDepositVaultFixedPrice(token, tokenDetails.depositMultiplier, request, priceAge, i);
327: }
328: } else {
329:
330: if (!tokenDetails.asyncRedeemEnabled) {
331:
332: emit AsyncRedeemDisabled(i);
333: continue;
334: }
335:
336: if (_isRequestTypeAutoPrice(request.requestType)) {
337:
338: solverTip += _solveRedeemVaultAutoPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i);
339: } else {
340:
341: solverTip += _solveRedeemVaultFixedPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i);
342: }
343: }
344: }
['304']
304: for (uint256 i = 0; i < length; i++) {
305: request = requests[i];
306: if (_isRequestTypeDeposit(request.requestType)) {
307:
308: if (!tokenDetails.asyncDepositEnabled) {
309:
310: emit AsyncDepositDisabled(i);
311: continue;
312: }
313:
314: if (!depositsExist) {
315: depositsExist = true;
316: token.forceApprove(MULTI_DEPOSITOR_VAULT, type(uint256).max);
317: }
318:
319: if (_isRequestTypeAutoPrice(request.requestType)) {
320:
321: solverTip +=
322: _solveDepositVaultAutoPrice(token, tokenDetails.depositMultiplier, request, priceAge, i); // <= FOUND
323: } else {
324:
325: solverTip +=
326: _solveDepositVaultFixedPrice(token, tokenDetails.depositMultiplier, request, priceAge, i);
327: }
328: } else {
329:
330: if (!tokenDetails.asyncRedeemEnabled) {
331:
332: emit AsyncRedeemDisabled(i);
333: continue;
334: }
335:
336: if (_isRequestTypeAutoPrice(request.requestType)) {
337:
338: solverTip += _solveRedeemVaultAutoPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i);
339: } else {
340:
341: solverTip += _solveRedeemVaultFixedPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i);
342: }
343: }
344: }
['364']
364: for (uint256 i = 0; i < length; i++) {
365: if (_isRequestTypeDeposit(requests[i].requestType)) {
366:
367: require(tokenDetails.asyncDepositEnabled, Aera__AsyncDepositDisabled());
368:
369: require(!_isRequestTypeAutoPrice(requests[i].requestType), Aera__AutoPriceSolveNotAllowed());
370:
371: _solveDepositDirect(token, requests[i]); // <= FOUND
372: } else {
373:
374: require(tokenDetails.asyncRedeemEnabled, Aera__AsyncRedeemDisabled());
375:
376: require(!_isRequestTypeAutoPrice(requests[i].requestType), Aera__AutoPriceSolveNotAllowed());
377:
378: _solveRedeemDirect(token, requests[i]);
379: }
380: }
['364']
364: for (uint256 i = 0; i < length; i++) {
365: if (_isRequestTypeDeposit(requests[i].requestType)) {
366:
367: require(tokenDetails.asyncDepositEnabled, Aera__AsyncDepositDisabled());
368:
369: require(!_isRequestTypeAutoPrice(requests[i].requestType), Aera__AutoPriceSolveNotAllowed());
370:
371: _solveDepositDirect(token, requests[i]); // <= FOUND
372: } else {
373:
374: require(tokenDetails.asyncRedeemEnabled, Aera__AsyncRedeemDisabled());
375:
376: require(!_isRequestTypeAutoPrice(requests[i].requestType), Aera__AutoPriceSolveNotAllowed());
377:
378: _solveRedeemDirect(token, requests[i]);
379: }
380: }
['26']
26: for (uint256 i = 0; i < numOperations; ++i) {
27:
28: _executeOperation(operations[i]); // <= FOUND
29: }
['26']
26: for (uint256 i = 0; i < numOperations; ++i) {
27:
28: _executeOperation(operations[i]); // <= FOUND
29: }
['41']
41: for (uint256 i; i < length; ++i) {
42: target = operations[i].target;
43: data = operations[i].data;
44: targetAndSelector = _packTargetSig(target, bytes4(data)); // <= FOUND
45:
46:
47: require(
48: callerCapabilities[targetAndSelector], AeraPeriphery__Unauthorized(msg.sender, target, bytes4(data))
49: );
50:
51:
52: (bool success, bytes memory returnData) = target.call(data);
53: Address.verifyCallResultFromTarget(target, success, returnData);
54: }
['41']
41: for (uint256 i; i < length; ++i) {
42: target = operations[i].target;
43: data = operations[i].data;
44: targetAndSelector = _packTargetSig(target, bytes4(data)); // <= FOUND
45:
46:
47: require(
48: callerCapabilities[targetAndSelector], AeraPeriphery__Unauthorized(msg.sender, target, bytes4(data))
49: );
50:
51:
52: (bool success, bytes memory returnData) = target.call(data);
53: Address.verifyCallResultFromTarget(target, success, returnData);
54: }
[Gas-9] For loops in public or external functions should be avoided due to high gas costs and possible DOS
In Solidity, for loops can potentially cause Denial of Service (DoS) attacks if not handled carefully. DoS attacks can occur when an attacker intentionally exploits the gas cost of a function, causing it to run out of gas or making it too expensive for other users to call. Below are some scenarios where for loops can lead to DoS attacks: Nested for loops can become exceptionally gas expensive and should be used sparingly
Num of instances: 8
Click to show findings
['294']
294: function solveRequestsVault(IERC20 token, Request[] calldata requests) external requiresAuth nonReentrant {
295:
296: uint256 priceAge = PRICE_FEE_CALCULATOR.getVaultsPriceAge(MULTI_DEPOSITOR_VAULT);
297:
298: uint256 solverTip;
299: Request calldata request;
300:
301: uint256 length = requests.length;
302: TokenDetails memory tokenDetails = tokensDetails[token];
303: bool depositsExist;
304: for (uint256 i = 0; i < length; i++) { // <= FOUND
305: request = requests[i];
306: if (_isRequestTypeDeposit(request.requestType)) {
307:
308: if (!tokenDetails.asyncDepositEnabled) {
309:
310: emit AsyncDepositDisabled(i);
311: continue;
312: }
313:
314: if (!depositsExist) {
315: depositsExist = true;
316: token.forceApprove(MULTI_DEPOSITOR_VAULT, type(uint256).max);
317: }
318:
319: if (_isRequestTypeAutoPrice(request.requestType)) {
320:
321: solverTip +=
322: _solveDepositVaultAutoPrice(token, tokenDetails.depositMultiplier, request, priceAge, i);
323: } else {
324:
325: solverTip +=
326: _solveDepositVaultFixedPrice(token, tokenDetails.depositMultiplier, request, priceAge, i);
327: }
328: } else {
329:
330: if (!tokenDetails.asyncRedeemEnabled) {
331:
332: emit AsyncRedeemDisabled(i);
333: continue;
334: }
335:
336: if (_isRequestTypeAutoPrice(request.requestType)) {
337:
338: solverTip += _solveRedeemVaultAutoPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i);
339: } else {
340:
341: solverTip += _solveRedeemVaultFixedPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i);
342: }
343: }
344: }
345:
346: if (solverTip != 0) {
347:
348: token.safeTransfer(msg.sender, solverTip);
349: }
350:
351: if (depositsExist) {
352:
353: token.forceApprove(MULTI_DEPOSITOR_VAULT, 0);
354: }
355: }
['358']
358: function solveRequestsDirect(IERC20 token, Request[] calldata requests) external nonReentrant {
359:
360: require(!PRICE_FEE_CALCULATOR.isVaultPaused(MULTI_DEPOSITOR_VAULT), Aera__PriceAndFeeCalculatorVaultPaused());
361:
362: uint256 length = requests.length;
363: TokenDetails storage tokenDetails = tokensDetails[token];
364: for (uint256 i = 0; i < length; i++) { // <= FOUND
365: if (_isRequestTypeDeposit(requests[i].requestType)) {
366:
367: require(tokenDetails.asyncDepositEnabled, Aera__AsyncDepositDisabled());
368:
369: require(!_isRequestTypeAutoPrice(requests[i].requestType), Aera__AutoPriceSolveNotAllowed());
370:
371: _solveDepositDirect(token, requests[i]);
372: } else {
373:
374: require(tokenDetails.asyncRedeemEnabled, Aera__AsyncRedeemDisabled());
375:
376: require(!_isRequestTypeAutoPrice(requests[i].requestType), Aera__AutoPriceSolveNotAllowed());
377:
378: _solveRedeemDirect(token, requests[i]);
379: }
380: }
381: }
['27']
27: function deposit(TokenAmount[] calldata tokenAmounts) external requiresAuth {
28: TokenAmount calldata tokenAmount;
29: uint256 length = tokenAmounts.length;
30: for (uint256 i = 0; i < length; ++i) { // <= FOUND
31: tokenAmount = tokenAmounts[i];
32:
33: tokenAmount.token.safeTransferFrom(msg.sender, address(this), tokenAmount.amount);
34:
35:
36: require(
37: tokenAmount.token.allowance(msg.sender, address(this)) == 0,
38: Aera__UnexpectedTokenAllowance(tokenAmount.token.allowance(msg.sender, address(this)))
39: );
40: }
41:
42:
43: emit Deposited(msg.sender, tokenAmounts);
44: }
['47']
47: function withdraw(TokenAmount[] calldata tokenAmounts) external requiresAuth {
48: TokenAmount calldata tokenAmount;
49: uint256 length = tokenAmounts.length;
50: for (uint256 i = 0; i < length; ++i) { // <= FOUND
51: tokenAmount = tokenAmounts[i];
52:
53: tokenAmount.token.safeTransfer(msg.sender, tokenAmount.amount);
54: }
55:
56:
57: emit Withdrawn(msg.sender, tokenAmounts);
58: }
['61']
61: function execute(OperationPayable[] calldata operations) external requiresAuth {
62: bool success;
63: bytes memory result;
64: OperationPayable calldata operation;
65: uint256 length = operations.length;
66: for (uint256 i = 0; i < length; ++i) { // <= FOUND
67: operation = operations[i];
68:
69:
70:
71: (success, result) = operation.target.call{ value: operation.value }(operation.data);
72:
73:
74: require(success, Aera__ExecutionFailed(i, result));
75: }
76:
77:
78: emit Executed(msg.sender, operations);
79: }
['17']
17: function execute(OperationPayable[] calldata operations) external nonReentrant {
18:
19: _checkOperations(operations);
20:
21: uint256 numOperations = operations.length;
22:
23:
24: if (numOperations == 0) return;
25:
26: for (uint256 i = 0; i < numOperations; ++i) { // <= FOUND
27:
28: _executeOperation(operations[i]);
29: }
30: }
['34']
34: function execute(TargetCalldata[] calldata operations) external nonReentrant {
35: address target;
36: bytes memory data;
37: bytes32 targetAndSelector;
38: mapping(bytes32 targetAndSelector => bool enabled) storage callerCapabilities = _canCall[msg.sender];
39:
40: uint256 length = operations.length;
41: for (uint256 i; i < length; ++i) { // <= FOUND
42: target = operations[i].target;
43: data = operations[i].data;
44: targetAndSelector = _packTargetSig(target, bytes4(data));
45:
46:
47: require(
48: callerCapabilities[targetAndSelector], AeraPeriphery__Unauthorized(msg.sender, target, bytes4(data))
49: );
50:
51:
52: (bool success, bytes memory returnData) = target.call(data);
53: Address.verifyCallResultFromTarget(target, success, returnData);
54: }
55:
56:
57: emit Executed(msg.sender, operations);
58: }
['23']
23: function updateWhitelist(address vault, address[] calldata addresses, bool isWhitelisted)
24: external
25: requiresVaultAuth(vault)
26: {
27: uint256 length = addresses.length;
28: address addr;
29:
30: for (uint256 i; i < length; i++) { // <= FOUND
31: addr = addresses[i];
32:
33:
34: whitelist[vault][addr] = isWhitelisted;
35: }
36:
37:
38: emit VaultWhitelistUpdated(vault, addresses, isWhitelisted);
39: }
Cache such mappings and perform operations on them, if operations include modifications to the mapping(s) then remember to equate the mapping to it's cached counterpart at the end
Num of instances: 1
Click to show findings
['262']
262: function refundRequest(IERC20 token, Request calldata request) external nonReentrant { // <= FOUND
263:
264: require(
265: request.deadline < block.timestamp || isAuthorized(msg.sender, msg.sig),
266: Aera__DeadlineInFutureAndUnauthorized()
267: );
268:
269: bytes32 requestHash = _getRequestHash(token, request);
270:
271: if (_isRequestTypeDeposit(request.requestType)) {
272:
273: require(asyncDepositHashes[requestHash], Aera__HashNotFound());
274:
275: asyncDepositHashes[requestHash] = false;
276:
277: token.safeTransfer(request.user, request.tokens);
278:
279: emit DepositRefunded(requestHash);
280: } else {
281:
282: require(asyncRedeemHashes[requestHash], Aera__HashNotFound()); // <= FOUND
283:
284: asyncRedeemHashes[requestHash] = false; // <= FOUND
285:
286: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
287:
288: emit RedeemRefunded(requestHash);
289: }
290: }
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: 13
Click to show findings
['109']
109: function _update(address from, address to, uint256 amount) internal override {
110: IBeforeTransferHook hook = beforeTransferHook;
111: if (address(hook) != address(0)) { // <= FOUND
112:
113: hook.beforeTransfer(from, to, provisioner);
114: }
115:
116:
117:
118:
119: require(
120: from == address(0) || to == address(0) || !IProvisioner(provisioner).areUserUnitsLocked(from), // <= FOUND
121: Aera__UnitsLocked()
122: );
123:
124:
125: return super._update(from, to, amount);
126: }
['24']
24: function sweep(address token, uint256 amount) external requiresAuth {
25: if (token == address(0)) { // <= FOUND
26:
27: (bool success,) = msg.sender.call{ value: amount }("");
28:
29: require(success, Aera__FailedToSendNativeToken());
30: } else {
31:
32: IERC20(token).safeTransfer(msg.sender, amount);
33: }
34:
35:
36: emit Sweep(token, amount);
37: }
['34']
34: function beforeTransfer(address from, address to, address transferAgent)
35: public
36: view
37: override(AbstractTransferHook, IBeforeTransferHook)
38: {
39: super.beforeTransfer(from, to, transferAgent);
40:
41:
42: require(from == address(0) || !BLACKLIST_ORACLE.isSanctioned(from), AeraPeriphery__BlacklistedAddress(from)); // <= FOUND
43: require(to == address(0) || !BLACKLIST_ORACLE.isSanctioned(to), AeraPeriphery__BlacklistedAddress(to)); // <= FOUND
44: }
['42']
42: function beforeTransfer(address from, address to, address transferAgent)
43: public
44: view
45: override(AbstractTransferHook, IBeforeTransferHook)
46: {
47: super.beforeTransfer(from, to, transferAgent);
48:
49:
50: require(
51: from == address(0) || from == transferAgent || whitelist[msg.sender][from], // <= FOUND
52: AeraPeriphery__NotWhitelisted(from)
53: );
54: require(to == address(0) || to == transferAgent || whitelist[msg.sender][to], AeraPeriphery__NotWhitelisted(to)); // <= FOUND
55: }
['49']
49: function setProtocolFeeRecipient(address feeRecipient) external requiresAuth {
50:
51: require(feeRecipient != address(0), Aera__ZeroAddressProtocolFeeRecipient()); // <= FOUND
52:
53:
54: protocolFeeRecipient = feeRecipient;
55:
56:
57: emit ProtocolFeeRecipientSet(feeRecipient);
58: }
['61']
61: function setProtocolFees(uint16 tvl, uint16 performance) external requiresAuth {
62:
63: require(tvl <= MAX_TVL_FEE, Aera__TvlFeeTooHigh());
64: require(performance <= MAX_PERFORMANCE_FEE, Aera__PerformanceFeeTooHigh());
65: require(protocolFeeRecipient != address(0), Aera__ZeroAddressProtocolFeeRecipient()); // <= FOUND
66:
67:
68: protocolFees = Fee({ tvl: tvl, performance: performance });
69:
70:
71: emit ProtocolFeesSet(tvl, performance);
72: }
['521']
521: function _setGuardianRoot(address guardian, bytes32 root) internal virtual {
522:
523: require(guardian != address(0), Aera__ZeroAddressGuardian()); // <= FOUND
524:
525:
526: require(WHITELIST.isWhitelisted(guardian), Aera__GuardianNotWhitelisted());
527:
528:
529: require(root != bytes32(0), Aera__ZeroAddressMerkleRoot());
530:
531:
532: guardianRoots.set(guardian, root);
533:
534:
535: emit GuardianRootSet(guardian, root);
536: }
['81']
81: function setFeeCalculator(IFeeCalculator newFeeCalculator) external requiresAuth {
82:
83: feeCalculator = newFeeCalculator;
84:
85: emit FeeCalculatorUpdated(address(newFeeCalculator));
86:
87:
88: if (address(newFeeCalculator) != address(0)) { // <= FOUND
89: newFeeCalculator.registerVault();
90: }
91: }
['94']
94: function setFeeRecipient(address newFeeRecipient) external requiresAuth {
95:
96: require(newFeeRecipient != address(0), Aera__ZeroAddressFeeRecipient()); // <= FOUND
97:
98:
99: feeRecipient = newFeeRecipient;
100:
101: emit FeeRecipientUpdated(newFeeRecipient);
102: }
['140']
140: function _setProvisioner(address provisioner_) internal {
141:
142: require(provisioner_ != address(0), Aera__ZeroAddressProvisioner()); // <= FOUND
143:
144:
145: provisioner = provisioner_;
146:
147:
148: emit ProvisionerSet(provisioner_);
149: }
['30']
30: function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) {
31: Authority auth = authority;
32:
33:
34:
35: return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig)) || user == owner; // <= FOUND
36: }
['61']
61: function setOracleRegistry(address vault, address oracleRegistry) external requiresVaultAuth(vault) {
62:
63: require(oracleRegistry != address(0), AeraPeriphery__ZeroAddressOracleRegistry()); // <= FOUND
64:
65:
66: _vaultStates[vault].oracleRegistry = IOracleRegistry(oracleRegistry);
67:
68:
69: emit UpdateOracleRegistry(vault, oracleRegistry);
70: }
['29']
29: function beforeTransfer(address from, address to, address transferAgent) public view virtual {
30: if (from != transferAgent && to != transferAgent && from != address(0) && to != address(0)) { // <= FOUND
31:
32: require(isVaultUnitTransferable[msg.sender], Aera__VaultUnitsNotTransferable(msg.sender));
33: }
34: }
Consider adding more detail to these error strings
Num of instances: 77
Click to show findings
['33']
33: function acceptOwnership() external virtual override {
34: address pendingOwner_ = pendingOwner;
35:
36:
37: require(msg.sender == pendingOwner_, Aera__Unauthorized()); // <= FOUND
38:
39:
40: owner = pendingOwner_;
41: delete pendingOwner;
42:
43:
44: emit OwnershipTransferred(msg.sender, pendingOwner_);
45: }
['49']
49: function setProtocolFeeRecipient(address feeRecipient) external requiresAuth {
50:
51: require(feeRecipient != address(0), Aera__ZeroAddressProtocolFeeRecipient()); // <= FOUND
52:
53:
54: protocolFeeRecipient = feeRecipient;
55:
56:
57: emit ProtocolFeeRecipientSet(feeRecipient);
58: }
['61']
61: function setProtocolFees(uint16 tvl, uint16 performance) external requiresAuth {
62:
63: require(tvl <= MAX_TVL_FEE, Aera__TvlFeeTooHigh()); // <= FOUND
64: require(performance <= MAX_PERFORMANCE_FEE, Aera__PerformanceFeeTooHigh());
65: require(protocolFeeRecipient != address(0), Aera__ZeroAddressProtocolFeeRecipient());
66:
67:
68: protocolFees = Fee({ tvl: tvl, performance: performance });
69:
70:
71: emit ProtocolFeesSet(tvl, performance);
72: }
['88']
88: function setVaultFees(address vault, uint16 tvl, uint16 performance) external requiresVaultAuth(vault) {
89:
90: require(tvl <= MAX_TVL_FEE, Aera__TvlFeeTooHigh()); // <= FOUND
91: require(performance <= MAX_PERFORMANCE_FEE, Aera__PerformanceFeeTooHigh());
92:
93:
94: VaultAccruals storage vaultAccruals = _vaultAccruals[vault];
95: vaultAccruals.fees = Fee({ tvl: tvl, performance: performance });
96:
97:
98: emit VaultFeesSet(vault, tvl, performance);
99: }
['23']
23: function create(
24: bytes32 salt,
25: string calldata description,
26: BaseVaultParameters calldata baseVaultParams,
27: address expectedVaultAddress
28: ) external override requiresAuth returns (address deployedVault) {
29:
30: require(bytes(description).length != 0, Aera__DescriptionIsEmpty()); // <= FOUND
31:
32:
33: deployedVault = _deployVault(salt, description, baseVaultParams);
34:
35:
36: require(deployedVault == expectedVaultAddress, Aera__VaultAddressMismatch(deployedVault, expectedVaultAddress));
37: }
['65']
65: function create(
66: bytes32 salt,
67: string calldata description,
68: ERC20Parameters calldata erc20Params,
69: BaseVaultParameters calldata baseVaultParams,
70: FeeVaultParameters calldata feeVaultParams,
71: IBeforeTransferHook beforeTransferHook,
72: address expectedVaultAddress
73: ) external override requiresAuth returns (address deployedVault) {
74:
75: require(bytes(description).length != 0, Aera__DescriptionIsEmpty()); // <= FOUND
76:
77:
78: deployedVault =
79: _deployVault(salt, description, erc20Params, baseVaultParams, feeVaultParams, beforeTransferHook);
80:
81:
82: require(deployedVault == expectedVaultAddress, Aera__VaultAddressMismatch(deployedVault, expectedVaultAddress));
83: }
['41']
41: function create(
42: bytes32 salt,
43: string calldata description,
44: BaseVaultParameters calldata baseVaultParams,
45: FeeVaultParameters calldata singleDepositorVaultParams,
46: address expectedVaultAddress
47: ) external override requiresAuth returns (address deployedVault) {
48:
49: require(bytes(description).length != 0, Aera__DescriptionIsEmpty()); // <= FOUND
50:
51:
52: deployedVault = _deployVault(salt, description, baseVaultParams, singleDepositorVaultParams);
53:
54:
55: require(deployedVault == expectedVaultAddress, Aera__VaultAddressMismatch(deployedVault, expectedVaultAddress));
56: }
['126']
126: function submit(bytes calldata data) external whenNotPaused nonReentrant {
127: (bool success, bytes32 root) = guardianRoots.tryGet(msg.sender);
128:
129: require(success, Aera__CallerIsNotGuardian()); // <= FOUND
130:
131: address submitHooks_ = address(submitHooks);
132:
133: _beforeSubmitHooks(submitHooks_, data);
134:
135: CalldataReader reader = CalldataReaderLib.from(data);
136: CalldataReader end = reader.readBytesEnd(data);
137:
138: Approval[] memory approvals;
139: uint256 approvalsLength;
140:
141: (approvals, approvalsLength,, reader) = _executeSubmit(root, reader, false);
142:
143:
144: _afterSubmitHooks(submitHooks_, data);
145:
146:
147: _noPendingApprovalsInvariant(approvals, approvalsLength);
148:
149:
150: reader.requireAtEndOf(end);
151: }
['278']
278: function _beforeSubmitHooks(address hooks, bytes calldata data) internal {
279: if (_hasBeforeHooks(hooks)) {
280:
281: (bool success, bytes memory result) =
282: hooks.call(abi.encodeWithSelector(ISubmitHooks.beforeSubmit.selector, data, msg.sender));
283:
284: require(success, Aera__BeforeSubmitHooksFailed(result)); // <= FOUND
285: }
286: }
['291']
291: function _afterSubmitHooks(address hooks, bytes calldata data) internal {
292: if (_hasAfterHooks(hooks)) {
293:
294: (bool success, bytes memory result) =
295: hooks.call(abi.encodeWithSelector(ISubmitHooks.afterSubmit.selector, data, msg.sender));
296:
297: require(success, Aera__AfterSubmitHooksFailed(result)); // <= FOUND
298: }
299: }
['306']
306: function _beforeOperationHooks(address operationHooks, bytes memory data, uint256 i)
307: internal
308: returns (bytes memory result)
309: {
310: if (_hasBeforeHooks(operationHooks)) {
311:
312: _setHookCallType(HookCallType.BEFORE);
313:
314:
315: (bool success, bytes memory returnValue) = operationHooks.call(data);
316:
317: require(success, Aera__BeforeOperationHooksFailed(i, returnValue)); // <= FOUND
318:
319:
320: require(returnValue.length % WORD_SIZE == 0, Aera__InvalidBeforeOperationHooksReturnDataLength());
321:
322:
323: _setHookCallType(HookCallType.NONE);
324:
325:
326:
327: (result) = abi.decode(returnValue, (bytes));
328: }
329: }
['335']
335: function _afterOperationHooks(address operationHooks, bytes memory data, uint256 i) internal {
336: if (_hasAfterHooks(operationHooks)) {
337:
338: _setHookCallType(HookCallType.AFTER);
339:
340:
341: (bool success, bytes memory result) = operationHooks.call(data);
342:
343: require(success, Aera__AfterOperationHooksFailed(i, result)); // <= FOUND
344:
345:
346: _setHookCallType(HookCallType.NONE);
347: }
348: }
['359']
359: function _executeSubmit(bytes32 root, CalldataReader reader, bool isCalledFromCallback)
360: internal
361: returns (Approval[] memory approvals, uint256 approvalsLength, bytes[] memory results, CalldataReader newReader)
362: {
363: uint256 operationsLength;
364: (reader, operationsLength) = reader.readU8();
365:
366: results = new bytes[](operationsLength);
367:
368:
369: approvals = new Approval[](operationsLength);
370:
371:
372:
373: OperationContext memory ctx;
374: for (uint256 i = 0; i < operationsLength; ++i) {
375: (reader, ctx.target) = reader.readAddr();
376:
377: bytes memory callData;
378: (reader, callData) = reader.readBytesToMemory();
379:
380: reader = callData.pipe(reader, results);
381:
382: bool isStaticCall;
383: (reader, isStaticCall) = reader.readBool();
384: if (isStaticCall) {
385:
386: (bool success, bytes memory result) = ctx.target.staticcall(callData);
387:
388: require(success, Aera__SubmissionFailed(i, result)); // <= FOUND
389:
390: results[i] = result;
391: } else {
392: ctx.selector = bytes4(callData);
393: if (_isAllowanceSelector(ctx.selector)) {
394: unchecked {
395: approvals[approvalsLength++] =
396: Approval({ token: ctx.target, spender: _extractApprovalSpender(callData) });
397: }
398: }
399:
400: (reader, ctx.callbackData) = _processExpectedCallback(reader, root);
401:
402: bytes memory extractedData;
403:
404: (reader, extractedData, ctx.configurableOperationHooks, ctx.operationHooks) =
405: _processBeforeOperationHooks(reader, callData, i);
406:
407: bytes32[] memory proof;
408: (reader, proof) = reader.readBytes32Array();
409:
410: (reader, ctx.value) = reader.readOptionalU256();
411:
412:
413: _verifyOperation(proof, root, _createMerkleLeaf(ctx, extractedData));
414:
415:
416: (bool success, bytes memory result) = ctx.target.call{ value: ctx.value }(callData);
417:
418: require(success, Aera__SubmissionFailed(i, result)); // <= FOUND
419:
420: if (ctx.callbackData != 0) {
421:
422: require(_hasCallbackBeenCalled(), Aera__ExpectedCallbackNotReceived());
423:
424: if (!isCalledFromCallback) {
425:
426: Approval[] memory callbackApprovals = _getCallbackApprovals();
427:
428:
429: _noPendingApprovalsInvariant(callbackApprovals, callbackApprovals.length);
430: }
431: }
432:
433:
434: _afterOperationHooks(ctx.operationHooks, callData, i);
435:
436: results[i] = result;
437: }
438: }
439:
440: return (approvals, approvalsLength, results, reader);
441: }
['455']
455: function _processBeforeOperationHooks(CalldataReader reader, bytes memory callData, uint256 i)
456: internal
457: returns (CalldataReader, bytes memory, uint256, address)
458: {
459: uint8 hooksConfigFlag;
460: (reader, hooksConfigFlag) = reader.readU8();
461:
462: if (hooksConfigFlag == 0) {
463: return (reader, "", 0, address(0));
464: }
465:
466: uint256 calldataOffsetsCount = hooksConfigFlag & CONFIGURABLE_HOOKS_LENGTH_MASK;
467:
468:
469: if (hooksConfigFlag & HOOKS_FLAG_MASK == 0) {
470: uint256 calldataOffsetsPacked;
471: (reader, calldataOffsetsPacked) = reader.readU256();
472:
473: return (
474: reader, callData.extract(calldataOffsetsPacked, calldataOffsetsCount), calldataOffsetsPacked, address(0)
475: );
476: }
477:
478: address operationHooks;
479:
480: if (calldataOffsetsCount != 0) {
481: uint256 calldataOffsetsPacked;
482: (reader, calldataOffsetsPacked) = reader.readU256();
483:
484: (reader, operationHooks) = reader.readAddr();
485:
486: require(!_hasBeforeHooks(operationHooks), Aera__BeforeOperationHooksWithConfigurableHooks()); // <= FOUND
487:
488: return (
489: reader,
490: callData.extract(calldataOffsetsPacked, calldataOffsetsCount),
491: calldataOffsetsPacked,
492: operationHooks
493: );
494: }
495:
496:
497: (reader, operationHooks) = reader.readAddr();
498:
499: return (
500: reader,
501:
502: _beforeOperationHooks(operationHooks, callData, i),
503: 0,
504: operationHooks
505: );
506: }
['521']
521: function _setGuardianRoot(address guardian, bytes32 root) internal virtual {
522:
523: require(guardian != address(0), Aera__ZeroAddressGuardian()); // <= FOUND
524:
525:
526: require(WHITELIST.isWhitelisted(guardian), Aera__GuardianNotWhitelisted());
527:
528:
529: require(root != bytes32(0), Aera__ZeroAddressMerkleRoot());
530:
531:
532: guardianRoots.set(guardian, root);
533:
534:
535: emit GuardianRootSet(guardian, root);
536: }
[]
551: function _noPendingApprovalsInvariant(Approval[] memory approvals, uint256 approvalsLength) internal view {
552: Approval memory approval;
553: while (approvalsLength != 0) {
554: unchecked {
555: --approvalsLength;
556: }
557:
558: approval = approvals[approvalsLength];
559:
560:
561: require(
562:
563: IERC20(approval.token).allowance(address(this), approval.spender) == 0,
564: Aera__AllowanceIsNotZero(approval.token, approval.spender)
565: );
566: }
567: }
['574']
574: function _getReturnValue(CalldataReader reader, bytes[] memory results)
575: internal
576: pure
577: returns (CalldataReader newReader, bytes memory returnValue)
578: {
579: uint8 returnTypeFlag;
580: (reader, returnTypeFlag) = reader.readU8();
581:
582: if (returnTypeFlag == uint8(ReturnValueType.STATIC_RETURN)) {
583: (reader, returnValue) = reader.readBytesToMemory();
584: } else if (returnTypeFlag == uint8(ReturnValueType.DYNAMIC_RETURN)) {
585: uint256 length = results.length;
586: require(length > 0, Aera__NoResults()); // <= FOUND
587:
588: unchecked {
589: returnValue = results[length - 1];
590: }
591: }
592:
593: return (reader, returnValue);
594: }
['600']
600: function _verifyOperation(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure {
601: require(MerkleProof.verify(proof, root, leaf), Aera__ProofVerificationFailed()); // <= FOUND
602: }
['48']
48: function registerVault() external override {
49: VaultSnapshot storage vaultSnapshot = _vaultSnapshots[msg.sender];
50:
51: require(vaultSnapshot.lastFeeAccrual == 0, Aera__VaultAlreadyRegistered()); // <= FOUND
52:
53:
54: vaultSnapshot.lastFeeAccrual = uint32(block.timestamp);
55:
56:
57: emit VaultRegistered(msg.sender);
58: }
['61']
61: function submitSnapshot(address vault, uint160 averageValue, uint128 highestProfit, uint32 timestamp)
62: external
63: onlyVaultAccountant(vault)
64: {
65:
66: require(timestamp <= block.timestamp, Aera__SnapshotInFuture()); // <= FOUND
67:
68: VaultSnapshot storage vaultSnapshot = _vaultSnapshots[vault];
69: VaultAccruals storage vaultAccruals = _vaultAccruals[vault];
70:
71: uint256 lastFeeAccrualCached = vaultSnapshot.lastFeeAccrual;
72: require(lastFeeAccrualCached != 0, Aera__VaultNotRegistered());
73:
74:
75: _accrueFees(vaultSnapshot, vaultAccruals, lastFeeAccrualCached);
76:
77:
78: require(timestamp > vaultSnapshot.lastFeeAccrual, Aera__SnapshotTooOld());
79:
80: require(vaultSnapshot.lastHighestProfit <= highestProfit, Aera__HighestProfitDecreased());
81:
82:
83: vaultSnapshot.timestamp = timestamp;
84: unchecked {
85:
86: vaultSnapshot.finalizedAt = uint32(block.timestamp + DISPUTE_PERIOD);
87: }
88: vaultSnapshot.averageValue = averageValue;
89: vaultSnapshot.highestProfit = highestProfit;
90:
91:
92: emit SnapshotSubmitted(vault, averageValue, highestProfit, timestamp);
93: }
['94']
94: function setFeeRecipient(address newFeeRecipient) external requiresAuth {
95:
96: require(newFeeRecipient != address(0), Aera__ZeroAddressFeeRecipient()); // <= FOUND
97:
98:
99: feeRecipient = newFeeRecipient;
100:
101: emit FeeRecipientUpdated(newFeeRecipient);
102: }
['105']
105: function claimFees() external onlyFeeRecipient returns (uint256 feeRecipientFees, uint256 protocolFees) {
106: address protocolFeeRecipient;
107:
108:
109: (feeRecipientFees, protocolFees, protocolFeeRecipient) =
110: feeCalculator.claimFees(FEE_TOKEN.balanceOf(address(this)));
111:
112:
113: require(feeRecipientFees != 0, Aera__NoFeesToClaim()); // <= FOUND
114:
115:
116: FEE_TOKEN.safeTransfer(msg.sender, feeRecipientFees);
117:
118: emit FeesClaimed(msg.sender, feeRecipientFees);
119:
120: if (protocolFees != 0) {
121:
122: FEE_TOKEN.safeTransfer(protocolFeeRecipient, protocolFees);
123:
124: emit ProtocolFeesClaimed(protocolFeeRecipient, protocolFees);
125: }
126: }
['129']
129: function claimProtocolFees() external returns (uint256 protocolFees) {
130: address protocolFeeRecipient;
131:
132:
133: (protocolFees, protocolFeeRecipient) = feeCalculator.claimProtocolFees(FEE_TOKEN.balanceOf(address(this)));
134:
135:
136: require(msg.sender == protocolFeeRecipient, Aera__CallerIsNotProtocolFeeRecipient()); // <= FOUND
137:
138:
139: require(protocolFees != 0, Aera__NoFeesToClaim());
140:
141:
142: FEE_TOKEN.safeTransfer(protocolFeeRecipient, protocolFees);
143:
144: emit ProtocolFeesClaimed(protocolFeeRecipient, protocolFees);
145: }
['38']
38: function extract(bytes memory callData, uint256 calldataOffsetsPacked, uint256 calldataOffsetsCount)
39: internal
40: pure
41: returns (bytes memory)
42: {
43: unchecked {
44:
45: require(calldataOffsetsCount < MAX_EXTRACT_OFFSETS_EXCLUSIVE, Aera__ExtractionNumberTooLarge()); // <= FOUND
46:
47: bytes memory result = new bytes(calldataOffsetsCount * WORD_SIZE);
48:
49: uint256 resultPtr;
50: assembly ("memory-safe") {
51: resultPtr := result
52: }
53:
54: resultPtr += WORD_SIZE;
55:
56: uint256 callDataLength = callData.length;
57:
58: require(callDataLength >= MINIMUM_CALLDATA_LENGTH, Aera__CalldataTooShort());
59:
60:
61: uint256 maxValidOffset = callDataLength - WORD_SIZE;
62:
63: uint256 calldataPointer;
64: assembly ("memory-safe") {
65: calldataPointer := callData
66: }
67:
68: calldataPointer += WORD_SIZE;
69:
70: uint256 resultWriteOffset;
71: for (uint256 i = 0; i < calldataOffsetsCount; ++i) {
72: uint256 extractionOffset = (calldataOffsetsPacked >> EXTRACTION_OFFSET_SHIFT_BITS) + SELECTOR_SIZE;
73:
74:
75: require(extractionOffset <= maxValidOffset, Aera__OffsetOutOfBounds());
76:
77: uint256 calldataOffsetPointer = calldataPointer + extractionOffset;
78:
79:
80:
81: bytes32 extracted;
82: assembly ("memory-safe") {
83: extracted := mload(calldataOffsetPointer)
84: }
85:
86:
87:
88: uint256 resultOffsetPointer = resultPtr + resultWriteOffset;
89: assembly ("memory-safe") {
90: mstore(resultOffsetPointer, extracted)
91: }
92:
93: resultWriteOffset += WORD_SIZE;
94: calldataOffsetsPacked = calldataOffsetsPacked << EXTRACT_OFFSET_SIZE_BITS;
95: }
96:
97: return result;
98: }
99: }
['35']
35: function pipe(bytes memory data, CalldataReader reader, bytes[] memory results)
36: internal
37: pure
38: returns (CalldataReader)
39: {
40: uint256 clipboardCount;
41: (reader, clipboardCount) = reader.readU8();
42:
43: unchecked {
44: for (; clipboardCount != 0; --clipboardCount) {
45: uint256 clipboard;
46: (reader, clipboard) = reader.readU32();
47:
48: uint256 resultIndex = clipboard >> RESULTS_INDEX_OFFSET;
49:
50:
51: bytes memory result = results[resultIndex];
52:
53: uint256 copyOffset = (clipboard >> COPY_WORD_OFFSET & MASK_8_BIT) * WORD_SIZE;
54:
55: require(copyOffset + WORD_SIZE <= result.length, Aera__CopyOffsetOutOfBounds()); // <= FOUND
56:
57: uint256 pasteOffset = clipboard & MASK_16_BIT;
58:
59: require(pasteOffset + WORD_SIZE <= data.length, Aera__PasteOffsetOutOfBounds());
60:
61: uint256 operationCalldataPointer;
62: uint256 resultPointer;
63: assembly ("memory-safe") {
64:
65:
66: operationCalldataPointer := data
67: resultPointer := result
68: }
69:
70: uint256 pastePointer = operationCalldataPointer + pasteOffset + CALLDATA_OFFSET;
71: uint256 copyPointer = resultPointer + WORD_SIZE + copyOffset;
72:
73: assembly ("memory-safe") {
74: mcopy(pastePointer, copyPointer, WORD_SIZE)
75: }
76: }
77: }
78:
79: return reader;
80: }
[]
109: function _update(address from, address to, uint256 amount) internal override {
110: IBeforeTransferHook hook = beforeTransferHook;
111: if (address(hook) != address(0)) {
112:
113: hook.beforeTransfer(from, to, provisioner);
114: }
115:
116:
117:
118:
119: require(
120: from == address(0) || to == address(0) || !IProvisioner(provisioner).areUserUnitsLocked(from),
121: Aera__UnitsLocked()
122: );
123:
124:
125: return super._update(from, to, amount);
126: }
['140']
140: function _setProvisioner(address provisioner_) internal {
141:
142: require(provisioner_ != address(0), Aera__ZeroAddressProvisioner()); // <= FOUND
143:
144:
145: provisioner = provisioner_;
146:
147:
148: emit ProvisionerSet(provisioner_);
149: }
['80']
80: function registerVault() external override {
81: VaultPriceState storage vaultPriceState = _vaultPriceStates[msg.sender];
82:
83: require(vaultPriceState.timestamp == 0, Aera__VaultAlreadyRegistered()); // <= FOUND
84:
85:
86:
87: vaultPriceState.timestamp = uint32(block.timestamp);
88:
89:
90: emit VaultRegistered(msg.sender);
91: }
['94']
94: function setInitialPrice(address vault, uint128 price, uint32 timestamp) external requiresVaultAuth(vault) {
95: require(price != 0, Aera__InvalidPrice()); // <= FOUND
96:
97: VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
98:
99:
100:
101: require(vaultPriceState.maxPriceAge != 0, Aera__ThresholdNotSet());
102: require(vaultPriceState.unitPrice == 0, Aera__VaultAlreadyInitialized());
103:
104:
105:
106: require(block.timestamp - timestamp <= vaultPriceState.maxPriceAge, Aera__StalePrice());
107:
108: uint32 timestampU32 = uint32(block.timestamp);
109:
110:
111: vaultPriceState.unitPrice = price;
112: vaultPriceState.highestPrice = price;
113: vaultPriceState.timestamp = timestampU32;
114: vaultPriceState.accrualLag = 0;
115: vaultPriceState.lastTotalSupply = IERC20(vault).totalSupply().toUint128();
116:
117:
118: emit UnitPriceUpdated(vault, price, timestampU32);
119: }
['431']
431: function _validatePriceUpdate(VaultPriceState storage vaultPriceState, uint256 price, uint256 timestamp)
432: internal
433: view
434: {
435:
436: require(price != 0, Aera__InvalidPrice()); // <= FOUND
437:
438: require(timestamp > vaultPriceState.timestamp, Aera__TimestampMustBeAfterLastUpdate());
439:
440: require(block.timestamp >= timestamp, Aera__TimestampCantBeInFuture());
441:
442: uint256 maxPriceAge = vaultPriceState.maxPriceAge;
443:
444: require(maxPriceAge != 0, Aera__ThresholdNotSet());
445:
446: require(maxPriceAge + timestamp >= block.timestamp, Aera__StalePrice());
447: }
['122']
122: function setThresholds(
123: address vault,
124: uint16 minPriceToleranceRatio,
125: uint16 maxPriceToleranceRatio,
126: uint16 minUpdateIntervalMinutes,
127: uint8 maxPriceAge,
128: uint8 maxUpdateDelayDays
129: ) external requiresVaultAuth(vault) {
130:
131: require(minPriceToleranceRatio <= ONE_IN_BPS, Aera__InvalidMinPriceToleranceRatio()); // <= FOUND
132:
133: require(maxPriceToleranceRatio >= ONE_IN_BPS, Aera__InvalidMaxPriceToleranceRatio());
134:
135: require(maxPriceAge > 0, Aera__InvalidMaxPriceAge());
136:
137: require(maxUpdateDelayDays > 0, Aera__InvalidMaxUpdateDelayDays());
138:
139: VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
140:
141:
142: require(vaultPriceState.timestamp != 0, Aera__VaultNotRegistered());
143:
144:
145: vaultPriceState.minPriceToleranceRatio = minPriceToleranceRatio;
146: vaultPriceState.maxPriceToleranceRatio = maxPriceToleranceRatio;
147: vaultPriceState.minUpdateIntervalMinutes = minUpdateIntervalMinutes;
148: vaultPriceState.maxPriceAge = maxPriceAge;
149: vaultPriceState.maxUpdateDelayDays = maxUpdateDelayDays;
150:
151:
152: emit ThresholdsSet(vault, minPriceToleranceRatio, maxPriceToleranceRatio, minUpdateIntervalMinutes, maxPriceAge);
153: }
['192']
192: function pauseVault(address vault) external requiresVaultAuthOrAccountant(vault) {
193: VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
194:
195:
196: require(!vaultPriceState.paused, Aera__VaultPaused()); // <= FOUND
197:
198:
199: _setVaultPaused(vaultPriceState, vault, true);
200: }
['203']
203: function unpauseVault(address vault, uint128 price, uint32 timestamp) external requiresVaultAuth(vault) {
204: VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
205:
206:
207: require(vaultPriceState.paused, Aera__VaultNotPaused()); // <= FOUND
208: require(vaultPriceState.unitPrice == price, Aera__UnitPriceMismatch());
209: require(vaultPriceState.timestamp == timestamp, Aera__TimestampMismatch());
210:
211:
212: _accrueFees(vault, price, timestamp);
213:
214:
215: _setVaultPaused(vaultPriceState, vault, false);
216: }
['219']
219: function resetHighestPrice(address vault) external requiresVaultAuth(vault) {
220: VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
221: uint128 currentPrice = vaultPriceState.unitPrice;
222:
223:
224: require(currentPrice != 0, Aera__VaultNotInitialized()); // <= FOUND
225:
226:
227: require(currentPrice < vaultPriceState.highestPrice, Aera__CurrentPriceAboveHighestPrice());
228:
229:
230: vaultPriceState.highestPrice = currentPrice;
231:
232:
233: emit HighestPriceReset(vault, currentPrice);
234: }
['246']
246: function convertUnitsToTokenIfActive(address vault, IERC20 token, uint256 unitsAmount, Math.Rounding rounding)
247: external
248: view
249: returns (uint256 tokenAmount)
250: {
251: VaultPriceState storage vaultState = _vaultPriceStates[vault];
252:
253:
254: require(!vaultState.paused, Aera__VaultPaused()); // <= FOUND
255:
256: return _convertUnitsToToken(vault, token, unitsAmount, vaultState.unitPrice, rounding);
257: }
['260']
260: function convertUnitsToNumeraire(address vault, uint256 unitsAmount) external view returns (uint256) {
261: VaultPriceState storage vaultState = _vaultPriceStates[vault];
262:
263:
264: require(!vaultState.paused, Aera__VaultPaused()); // <= FOUND
265:
266: return unitsAmount * vaultState.unitPrice / UNIT_PRICE_PRECISION;
267: }
['279']
279: function convertTokenToUnitsIfActive(address vault, IERC20 token, uint256 tokenAmount, Math.Rounding rounding)
280: external
281: view
282: returns (uint256 unitsAmount)
283: {
284: VaultPriceState storage vaultState = _vaultPriceStates[vault];
285:
286:
287: require(!vaultState.paused, Aera__VaultPaused()); // <= FOUND
288:
289: return _convertTokenToUnits(vault, token, tokenAmount, vaultState.unitPrice, rounding);
290: }
['108']
108: function deposit(IERC20 token, uint256 tokensIn, uint256 minUnitsOut)
109: external
110: anyoneButVault
111: returns (uint256 unitsOut)
112: {
113:
114: require(tokensIn != 0, Aera__TokensInZero()); // <= FOUND
115: require(minUnitsOut != 0, Aera__MinUnitsOutZero());
116:
117:
118: TokenDetails storage tokenDetails = _requireSyncDepositsEnabled(token);
119:
120:
121: unitsOut = _tokensToUnitsFloorIfActive(token, tokensIn, tokenDetails.depositMultiplier);
122:
123: require(unitsOut >= minUnitsOut, Aera__MinUnitsOutNotMet());
124:
125: _requireDepositCapNotExceeded(unitsOut);
126:
127:
128: _syncDeposit(token, tokensIn, unitsOut);
129: }
['180']
180: function requestDeposit(
181: IERC20 token,
182: uint256 tokensIn,
183: uint256 minUnitsOut,
184: uint256 solverTip,
185: uint256 deadline,
186: uint256 maxPriceAge,
187: bool isFixedPrice
188: ) external anyoneButVault {
189:
190:
191: require(tokensIn != 0, Aera__TokensInZero()); // <= FOUND
192: require(minUnitsOut != 0, Aera__MinUnitsOutZero());
193: require(deadline > block.timestamp, Aera__DeadlineInPast()); // <= FOUND
194: unchecked {
195: require(deadline - block.timestamp <= MAX_SECONDS_TO_DEADLINE, Aera__DeadlineTooFarInFuture());
196: }
197: require(tokensDetails[token].asyncDepositEnabled, Aera__AsyncDepositDisabled());
198: require(!PRICE_FEE_CALCULATOR.isVaultPaused(MULTI_DEPOSITOR_VAULT), Aera__PriceAndFeeCalculatorVaultPaused()); // <= FOUND
199: require(solverTip == 0 || !isFixedPrice, Aera__FixedPriceSolverTipNotAllowed());
200:
201:
202: token.safeTransferFrom(msg.sender, address(this), tokensIn);
203:
204: RequestType requestType = isFixedPrice ? RequestType.DEPOSIT_FIXED_PRICE : RequestType.DEPOSIT_AUTO_PRICE;
205:
206: bytes32 depositHash = _getRequestHashParams(
207: token, msg.sender, requestType, tokensIn, minUnitsOut, solverTip, deadline, maxPriceAge
208: );
209:
210: require(!asyncDepositHashes[depositHash], Aera__HashCollision());
211:
212: asyncDepositHashes[depositHash] = true;
213:
214:
215: emit DepositRequested(
216: msg.sender, token, tokensIn, minUnitsOut, solverTip, deadline, maxPriceAge, isFixedPrice, depositHash
217: );
218: }
['132']
132: function mint(IERC20 token, uint256 unitsOut, uint256 maxTokensIn)
133: external
134: anyoneButVault
135: returns (uint256 tokensIn)
136: {
137:
138: require(unitsOut != 0, Aera__UnitsOutZero()); // <= FOUND
139: require(maxTokensIn != 0, Aera__MaxTokensInZero());
140:
141:
142: TokenDetails storage tokenDetails = _requireSyncDepositsEnabled(token);
143:
144:
145: _requireDepositCapNotExceeded(unitsOut);
146:
147: tokensIn = _unitsToTokensCeilIfActive(token, unitsOut, tokenDetails.depositMultiplier);
148:
149: require(tokensIn <= maxTokensIn, Aera__MaxTokensInExceeded());
150:
151:
152: _syncDeposit(token, tokensIn, unitsOut);
153: }
['156']
156: function refundDeposit(
157: address sender,
158: IERC20 token,
159: uint256 tokenAmount,
160: uint256 unitsAmount,
161: uint256 refundableUntil
162: ) external requiresAuth {
163:
164: require(refundableUntil >= block.timestamp, Aera__RefundPeriodExpired()); // <= FOUND
165:
166: bytes32 depositHash = _getDepositHash(sender, token, tokenAmount, unitsAmount, refundableUntil);
167:
168: require(syncDepositHashes[depositHash], Aera__DepositHashNotFound());
169:
170: syncDepositHashes[depositHash] = false;
171:
172:
173: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).exit(sender, token, tokenAmount, unitsAmount, sender);
174:
175:
176: emit DirectDepositRefunded(depositHash);
177: }
['221']
221: function requestRedeem(
222: IERC20 token,
223: uint256 unitsIn,
224: uint256 minTokensOut,
225: uint256 solverTip,
226: uint256 deadline,
227: uint256 maxPriceAge,
228: bool isFixedPrice
229: ) external anyoneButVault {
230:
231:
232: require(unitsIn != 0, Aera__UnitsInZero());
233: require(minTokensOut != 0, Aera__MinTokenOutZero());
234: require(deadline > block.timestamp, Aera__DeadlineInPast()); // <= FOUND
235: unchecked {
236: require(deadline - block.timestamp <= MAX_SECONDS_TO_DEADLINE, Aera__DeadlineTooFarInFuture());
237: }
238: require(tokensDetails[token].asyncRedeemEnabled, Aera__AsyncRedeemDisabled());
239: require(!PRICE_FEE_CALCULATOR.isVaultPaused(MULTI_DEPOSITOR_VAULT), Aera__PriceAndFeeCalculatorVaultPaused()); // <= FOUND
240: require(solverTip == 0 || !isFixedPrice, Aera__FixedPriceSolverTipNotAllowed());
241:
242:
243: IERC20(MULTI_DEPOSITOR_VAULT).safeTransferFrom(msg.sender, address(this), unitsIn);
244:
245: RequestType requestType = isFixedPrice ? RequestType.REDEEM_FIXED_PRICE : RequestType.REDEEM_AUTO_PRICE;
246:
247: bytes32 redeemHash = _getRequestHashParams(
248: token, msg.sender, requestType, minTokensOut, unitsIn, solverTip, deadline, maxPriceAge
249: );
250:
251: require(!asyncRedeemHashes[redeemHash], Aera__HashCollision());
252:
253: asyncRedeemHashes[redeemHash] = true;
254:
255:
256: emit RedeemRequested(
257: msg.sender, token, minTokensOut, unitsIn, solverTip, deadline, maxPriceAge, isFixedPrice, redeemHash
258: );
259: }
['358']
358: function solveRequestsDirect(IERC20 token, Request[] calldata requests) external nonReentrant {
359:
360: require(!PRICE_FEE_CALCULATOR.isVaultPaused(MULTI_DEPOSITOR_VAULT), Aera__PriceAndFeeCalculatorVaultPaused()); // <= FOUND
361:
362: uint256 length = requests.length;
363: TokenDetails storage tokenDetails = tokensDetails[token];
364: for (uint256 i = 0; i < length; i++) {
365: if (_isRequestTypeDeposit(requests[i].requestType)) {
366:
367: require(tokenDetails.asyncDepositEnabled, Aera__AsyncDepositDisabled());
368:
369: require(!_isRequestTypeAutoPrice(requests[i].requestType), Aera__AutoPriceSolveNotAllowed());
370:
371: _solveDepositDirect(token, requests[i]);
372: } else {
373:
374: require(tokenDetails.asyncRedeemEnabled, Aera__AsyncRedeemDisabled());
375:
376: require(!_isRequestTypeAutoPrice(requests[i].requestType), Aera__AutoPriceSolveNotAllowed());
377:
378: _solveRedeemDirect(token, requests[i]);
379: }
380: }
381: }
[]
262: function refundRequest(IERC20 token, Request calldata request) external nonReentrant {
263:
264: require(
265: request.deadline < block.timestamp || isAuthorized(msg.sender, msg.sig),
266: Aera__DeadlineInFutureAndUnauthorized()
267: );
268:
269: bytes32 requestHash = _getRequestHash(token, request);
270:
271: if (_isRequestTypeDeposit(request.requestType)) {
272:
273: require(asyncDepositHashes[requestHash], Aera__HashNotFound());
274:
275: asyncDepositHashes[requestHash] = false;
276:
277: token.safeTransfer(request.user, request.tokens);
278:
279: emit DepositRefunded(requestHash);
280: } else {
281:
282: require(asyncRedeemHashes[requestHash], Aera__HashNotFound());
283:
284: asyncRedeemHashes[requestHash] = false;
285:
286: IERC20(MULTI_DEPOSITOR_VAULT).safeTransfer(request.user, request.units);
287:
288: emit RedeemRefunded(requestHash);
289: }
290: }
['384']
384: function setDepositDetails(uint256 depositCap_, uint256 depositRefundTimeout_) external requiresAuth {
385:
386: require(depositCap_ != 0, Aera__DepositCapZero()); // <= FOUND
387:
388: require(depositRefundTimeout_ <= MAX_DEPOSIT_REFUND_TIMEOUT, Aera__MaxDepositRefundTimeoutExceeded());
389:
390:
391: depositCap = depositCap_;
392: depositRefundTimeout = depositRefundTimeout_;
393:
394:
395: emit DepositDetailsUpdated(depositCap_, depositRefundTimeout_);
396: }
['399']
399: function setTokenDetails(IERC20 token, TokenDetails calldata details) external requiresAuth {
400:
401: require(address(token) != MULTI_DEPOSITOR_VAULT, Aera__InvalidToken()); // <= FOUND
402:
403: uint256 depositMultiplier = details.depositMultiplier;
404:
405: require(depositMultiplier >= MIN_DEPOSIT_MULTIPLIER, Aera__DepositMultiplierTooLow());
406: require(depositMultiplier <= ONE_IN_BPS, Aera__DepositMultiplierTooHigh());
407: uint256 redeemMultiplier = details.redeemMultiplier;
408:
409: require(redeemMultiplier >= MIN_REDEEM_MULTIPLIER, Aera__RedeemMultiplierTooLow());
410:
411: require(redeemMultiplier <= ONE_IN_BPS, Aera__RedeemMultiplierTooHigh());
412:
413:
414: tokensDetails[token] = details;
415:
416: if (details.asyncRedeemEnabled || details.asyncDepositEnabled || details.syncDepositEnabled) {
417:
418:
419: require(
420: PRICE_FEE_CALCULATOR.convertUnitsToToken(MULTI_DEPOSITOR_VAULT, token, ONE_UNIT) != 0,
421: Aera__TokenCantBePriced()
422: );
423: }
424:
425:
426: emit TokenDetailsSet(token, details);
427: }
['479']
479: function _syncDeposit(IERC20 token, uint256 tokenAmount, uint256 unitAmount) internal {
480: uint256 refundableUntil = block.timestamp + depositRefundTimeout;
481: bytes32 depositHash = _getDepositHash(msg.sender, token, tokenAmount, unitAmount, refundableUntil);
482:
483:
484: require(!syncDepositHashes[depositHash], Aera__HashCollision()); // <= FOUND
485:
486: syncDepositHashes[depositHash] = true;
487:
488:
489: userUnitsRefundableUntil[msg.sender] = refundableUntil;
490:
491:
492: IMultiDepositorVault(MULTI_DEPOSITOR_VAULT).enter(msg.sender, token, tokenAmount, unitAmount, msg.sender);
493:
494:
495: emit Deposited(msg.sender, token, tokenAmount, unitAmount, depositHash);
496: }
['904']
904: function _requireSyncDepositsEnabled(IERC20 token) internal view returns (TokenDetails storage tokenDetails) {
905: tokenDetails = tokensDetails[token];
906:
907: require(tokenDetails.syncDepositEnabled, Aera__SyncDepositDisabled()); // <= FOUND
908: }
['912']
912: function _requireDepositCapNotExceeded(uint256 units) internal view {
913:
914: require(!_isDepositCapExceeded(units), Aera__DepositCapExceeded()); // <= FOUND
915: }
[]
27: function deposit(TokenAmount[] calldata tokenAmounts) external requiresAuth {
28: TokenAmount calldata tokenAmount;
29: uint256 length = tokenAmounts.length;
30: for (uint256 i = 0; i < length; ++i) {
31: tokenAmount = tokenAmounts[i];
32:
33: tokenAmount.token.safeTransferFrom(msg.sender, address(this), tokenAmount.amount);
34:
35:
36: require(
37: tokenAmount.token.allowance(msg.sender, address(this)) == 0,
38: Aera__UnexpectedTokenAllowance(tokenAmount.token.allowance(msg.sender, address(this)))
39: );
40: }
41:
42:
43: emit Deposited(msg.sender, tokenAmounts);
44: }
['61']
61: function execute(OperationPayable[] calldata operations) external requiresAuth {
62: bool success;
63: bytes memory result;
64: OperationPayable calldata operation;
65: uint256 length = operations.length;
66: for (uint256 i = 0; i < length; ++i) {
67: operation = operations[i];
68:
69:
70:
71: (success, result) = operation.target.call{ value: operation.value }(operation.data);
72:
73:
74: require(success, Aera__ExecutionFailed(i, result)); // <= FOUND
75: }
76:
77:
78: emit Executed(msg.sender, operations);
79: }
['24']
24: function sweep(address token, uint256 amount) external requiresAuth {
25: if (token == address(0)) {
26:
27: (bool success,) = msg.sender.call{ value: amount }("");
28:
29: require(success, Aera__FailedToSendNativeToken()); // <= FOUND
30: } else {
31:
32: IERC20(token).safeTransfer(msg.sender, amount);
33: }
34:
35:
36: emit Sweep(token, amount);
37: }
['38']
38: function setAuthority(Authority newAuthority) public virtual {
39:
40:
41: require(msg.sender == owner || authority.canCall(msg.sender, address(this), msg.sig)); // <= FOUND
42:
43: authority = newAuthority;
44:
45: emit AuthorityUpdated(msg.sender, newAuthority);
46: }
['37']
37: function _executeOperation(OperationPayable calldata operation) internal virtual {
38:
39: _checkOperation(operation);
40:
41:
42:
43: (bool success, bytes memory result) = operation.target.call{ value: operation.value }(operation.data);
44:
45:
46:
47:
48: require(success, AeraPeriphery__ExecutionFailed(result)); // <= FOUND
49:
50:
51: emit Executed(msg.sender, operation);
52: }
[]
34: function execute(TargetCalldata[] calldata operations) external nonReentrant {
35: address target;
36: bytes memory data;
37: bytes32 targetAndSelector;
38: mapping(bytes32 targetAndSelector => bool enabled) storage callerCapabilities = _canCall[msg.sender];
39:
40: uint256 length = operations.length;
41: for (uint256 i; i < length; ++i) {
42: target = operations[i].target;
43: data = operations[i].data;
44: targetAndSelector = _packTargetSig(target, bytes4(data));
45:
46:
47: require(
48: callerCapabilities[targetAndSelector], AeraPeriphery__Unauthorized(msg.sender, target, bytes4(data))
49: );
50:
51:
52: (bool success, bytes memory returnData) = target.call(data);
53: Address.verifyCallResultFromTarget(target, success, returnData);
54: }
55:
56:
57: emit Executed(msg.sender, operations);
58: }
['49']
49: function setMaxSlippagePerTrade(address vault, uint16 newMaxSlippage) external requiresVaultAuth(vault) {
50:
51: require(newMaxSlippage < MAX_BPS, AeraPeriphery__MaxSlippagePerTradeTooHigh(newMaxSlippage)); // <= FOUND
52:
53:
54: _vaultStates[vault].maxSlippagePerTrade = newMaxSlippage;
55:
56:
57: emit UpdateMaxSlippage(vault, newMaxSlippage);
58: }
['61']
61: function setOracleRegistry(address vault, address oracleRegistry) external requiresVaultAuth(vault) {
62:
63: require(oracleRegistry != address(0), AeraPeriphery__ZeroAddressOracleRegistry()); // <= FOUND
64:
65:
66: _vaultStates[vault].oracleRegistry = IOracleRegistry(oracleRegistry);
67:
68:
69: emit UpdateOracleRegistry(vault, oracleRegistry);
70: }
[]
198: function _enforceDailyLoss(State storage state, uint256 loss) internal returns (uint256 newLoss) {
199: uint32 day = uint32(block.timestamp / 1 days);
200: if (state.currentDay != day) {
201:
202: state.currentDay = day;
203: state.cumulativeDailyLossInNumeraire = 0;
204: }
205:
206: newLoss = state.cumulativeDailyLossInNumeraire + loss;
207:
208:
209: require(
210: newLoss <= state.maxDailyLossInNumeraire,
211: AeraPeriphery__ExcessiveDailyLoss(newLoss, state.maxDailyLossInNumeraire)
212: );
213: }
[]
219: function _enforceSlippageLimit(State storage state, uint256 loss, uint256 valueBefore) internal view {
220:
221: require(
222: loss * MAX_BPS <= valueBefore * state.maxSlippagePerTrade,
223: AeraPeriphery__ExcessiveSlippage(loss, valueBefore, state.maxSlippagePerTrade)
224: );
225: }
['16']
16: function depositForBurn(
17: uint256 amount,
18: uint32 destinationDomain,
19: bytes32 mintRecipient,
20: address burnToken,
21: bytes32 destinationCaller,
22: uint256 maxFee,
23: uint32
24: ) external returns (bytes memory returnData) {
25:
26: require(destinationCaller == bytes32(0), AeraPeriphery__DestinationCallerNotZero()); // <= FOUND
27:
28: if (maxFee > 0) {
29:
30: uint256 sourceAmountInNumeraire = _convertToNumeraire(amount, burnToken);
31:
32:
33: uint256 destinationAmountInNumeraire = _convertToNumeraire(amount - maxFee, burnToken);
34:
35:
36: _enforceSlippageLimitAndDailyLossLog(
37: msg.sender, burnToken, burnToken, sourceAmountInNumeraire, destinationAmountInNumeraire
38: );
39: }
40:
41: returnData = abi.encode(destinationDomain, address(uint160(uint256(mintRecipient))), burnToken);
42: }
['41']
41: function _processSwapHooks(IMetaAggregationRouterV2.SwapDescriptionV2 calldata desc, address executor)
42: internal
43: returns (bytes memory returnData)
44: {
45:
46: require(address(desc.srcToken) != KYBERSWAP_ETH_ADDRESS, AeraPeriphery__InputTokenIsETH()); // <= FOUND
47:
48:
49: require(address(desc.dstToken) != KYBERSWAP_ETH_ADDRESS, AeraPeriphery__OutputTokenIsETH());
50:
51:
52: require(desc.feeReceivers.length == 0, AeraPeriphery__FeeReceiversNotEmpty());
53:
54: (address tokenIn, address tokenOut, address receiver) = _handleBeforeExactInputSingle(
55: address(desc.srcToken), address(desc.dstToken), desc.dstReceiver, desc.amount, desc.minReturnAmount
56: );
57: return abi.encode(tokenIn, tokenOut, receiver, executor);
58: }
['16']
16: function requestSell(
17: uint256 sellAmount,
18: IERC20 sellToken,
19: IERC20 receiveToken,
20: bytes32,
21: address priceChecker,
22: bytes calldata priceCheckerData
23: ) external returns (bytes memory returnData) {
24:
25: require(priceChecker == address(this), AeraPeriphery__InvalidPriceChecker(address(this), priceChecker)); // <= FOUND
26:
27:
28: address vault = abi.decode(priceCheckerData, (address));
29: require(msg.sender == vault, AeraPeriphery__InvalidVaultInPriceCheckerData(msg.sender, vault));
30:
31:
32: _handleMilkmanBeforeHook(address(sellToken), sellAmount, msg.sender);
33:
34:
35: returnData = abi.encode(address(sellToken), address(receiveToken));
36: }
['18']
18: function swap(
19: IOdosRouterV2.SwapTokenInfo calldata tokenInfo,
20: bytes calldata,
21: address,
22: uint32
23: ) external returns (bytes memory returnData) {
24:
25: require(tokenInfo.inputAmount != 0, AeraPeriphery__InputAmountIsZero()); // <= FOUND
26:
27:
28: require(tokenInfo.inputToken != ODOS_ROUTER_V2_ETH_ADDRESS, AeraPeriphery__InputTokenIsETH());
29:
30:
31: require(tokenInfo.outputToken != ODOS_ROUTER_V2_ETH_ADDRESS, AeraPeriphery__OutputTokenIsETH());
32:
33:
34: (address tokenIn, address tokenOut, address receiver) = _handleBeforeExactInputSingle(
35: tokenInfo.inputToken,
36: tokenInfo.outputToken,
37: tokenInfo.outputReceiver,
38: tokenInfo.inputAmount,
39: tokenInfo.outputMin
40: );
41: return abi.encode(tokenIn, tokenOut, receiver);
42: }
['28']
28: function exactInput(ISwapRouter.ExactInputParams calldata params) external returns (bytes memory) {
29: uint256 pathLength = params.path.length;
30:
31:
32: require(pathLength % UNISWAP_PATH_CHUNK_SIZE == UNISWAP_PATH_ADDRESS_SIZE, AeraPeriphery__BadPathFormat()); // <= FOUND
33:
34: address tokenIn;
35: address tokenOut;
36: unchecked {
37: tokenIn = address(bytes20(params.path[:20]));
38: tokenOut = address(bytes20(params.path[pathLength - 20:]));
39: }
40:
41:
42: (address _tokenIn, address _tokenOut, address _receiver) =
43: _handleBeforeExactInputSingle(tokenIn, tokenOut, params.recipient, params.amountIn, params.amountOutMinimum);
44: return abi.encode(_tokenIn, _tokenOut, _receiver);
45: }
['57']
57: function exactOutput(ISwapRouter.ExactOutputParams calldata params) external returns (bytes memory) {
58: uint256 pathLength = params.path.length;
59:
60:
61: require(pathLength % UNISWAP_PATH_CHUNK_SIZE == UNISWAP_PATH_ADDRESS_SIZE, AeraPeriphery__BadPathFormat()); // <= FOUND
62:
63: address tokenIn;
64: address tokenOut;
65: unchecked {
66: tokenIn = address(bytes20(params.path[:20]));
67: tokenOut = address(bytes20(params.path[pathLength - 20:]));
68: }
69:
70:
71: (address _tokenIn, address _tokenOut, address _receiver) = _handleBeforeExactOutputSingle(
72: tokenIn, tokenOut, params.recipient, params.amountOut, params.amountInMaximum
73: );
74: return abi.encode(_tokenIn, _tokenOut, _receiver);
75: }
['29']
29: function beforeTransfer(address from, address to, address transferAgent) public view virtual {
30: if (from != transferAgent && to != transferAgent && from != address(0) && to != address(0)) {
31:
32: require(isVaultUnitTransferable[msg.sender], Aera__VaultUnitsNotTransferable(msg.sender)); // <= FOUND
33: }
34: }
['34']
34: function beforeTransfer(address from, address to, address transferAgent)
35: public
36: view
37: override(AbstractTransferHook, IBeforeTransferHook)
38: {
39: super.beforeTransfer(from, to, transferAgent);
40:
41:
42: require(from == address(0) || !BLACKLIST_ORACLE.isSanctioned(from), AeraPeriphery__BlacklistedAddress(from)); // <= FOUND
43: require(to == address(0) || !BLACKLIST_ORACLE.isSanctioned(to), AeraPeriphery__BlacklistedAddress(to));
44: }
[]
42: function beforeTransfer(address from, address to, address transferAgent)
43: public
44: view
45: override(AbstractTransferHook, IBeforeTransferHook)
46: {
47: super.beforeTransfer(from, to, transferAgent);
48:
49:
50: require(
51: from == address(0) || from == transferAgent || whitelist[msg.sender][from],
52: AeraPeriphery__NotWhitelisted(from)
53: );
54: require(to == address(0) || to == transferAgent || whitelist[msg.sender][to], AeraPeriphery__NotWhitelisted(to));
55: }
['65']
65: function requestSell(
66: uint256 sellAmount,
67: IERC20 sellToken,
68: IERC20 receiveToken,
69: bytes32 appData,
70: address priceChecker,
71: bytes calldata priceCheckerData
72: ) external onlyVault nonReentrant {
73:
74: require(sellAmount > 0, AeraPeriphery__MilkmanRouter__SellAmountIsZero()); // <= FOUND
75:
76:
77: sellToken.safeTransferFrom(msg.sender, address(this), sellAmount);
78:
79:
80: sellToken.safeIncreaseAllowance(address(milkmanRoot), sellAmount);
81:
82:
83: milkmanRoot.requestSwapExactTokensForTokens(
84: sellAmount, sellToken, receiveToken, address(this), appData, priceChecker, priceCheckerData
85: );
86:
87:
88:
89: require(
90: sellToken.allowance(address(this), address(milkmanRoot)) == 0,
91: AeraPeriphery__MilkmanRequestSwapExactTokensForTokensFailed(sellToken)
92: );
93:
94:
95: emit SellRequested(sellAmount, sellToken, receiveToken, appData, priceChecker, priceCheckerData);
96: }
['70']
70: function addOracle(address base, address quote, IOracle oracle) external requiresAuth {
71:
72: require(_oracles[base][quote].oracle == IOracle(address(0)), AeraPeriphery__OracleAlreadySet()); // <= FOUND
73:
74:
75: _oracles[base][quote] = OracleData({
76: isScheduledForUpdate: false,
77: isDisabled: false,
78: oracle: oracle,
79: pendingOracle: IOracle(address(0)),
80: commitTimestamp: 0
81: });
82:
83:
84: _validateOracle(oracle, base, quote);
85:
86:
87: emit OracleSet(base, quote, oracle);
88: }
['91']
91: function scheduleOracleUpdate(address base, address quote, IOracle oracle) external requiresAuth {
92: OracleData storage oracleData = _oracles[base][quote];
93:
94:
95: require(!oracleData.isScheduledForUpdate, AeraPeriphery__OracleUpdateAlreadyScheduled()); // <= FOUND
96:
97: require(oracleData.oracle != oracle, AeraPeriphery__CannotScheduleOracleUpdateForTheSameOracle());
98:
99:
100: unchecked {
101: oracleData.commitTimestamp = (block.timestamp + ORACLE_UPDATE_DELAY).toUint32();
102: }
103: oracleData.isScheduledForUpdate = true;
104: oracleData.pendingOracle = oracle;
105:
106:
107: _validateOracle(oracle, base, quote);
108:
109:
110: emit OracleScheduled(base, quote, oracle, oracleData.commitTimestamp);
111: }
['114']
114: function commitOracleUpdate(address base, address quote) external {
115: OracleData storage oracleData = _oracles[base][quote];
116:
117: IOracle pendingOracle = oracleData.pendingOracle;
118:
119:
120: require(pendingOracle != IOracle(address(0)), AeraPeriphery__NoPendingOracleUpdate()); // <= FOUND
121:
122: require(oracleData.commitTimestamp <= block.timestamp, AeraPeriphery__CommitTimestampNotReached());
123:
124:
125: oracleData.commitTimestamp = 0;
126: oracleData.pendingOracle = IOracle(address(0));
127: oracleData.oracle = pendingOracle;
128: oracleData.isScheduledForUpdate = false;
129: oracleData.isDisabled = false;
130:
131:
132: _validateOracle(pendingOracle, base, quote);
133:
134:
135: emit OracleSet(base, quote, oracleData.oracle);
136: }
['139']
139: function cancelScheduledOracleUpdate(address base, address quote) external requiresAuth {
140: OracleData storage oracleData = _oracles[base][quote];
141:
142:
143: require(oracleData.isScheduledForUpdate, AeraPeriphery__NoPendingOracleUpdate()); // <= FOUND
144:
145:
146: oracleData.pendingOracle = IOracle(address(0));
147: oracleData.isScheduledForUpdate = false;
148: oracleData.commitTimestamp = 0;
149:
150:
151: emit OracleUpdateCancelled(base, quote);
152: }
['155']
155: function disableOracle(address base, address quote, IOracle oracle) external requiresAuth {
156: OracleData storage oracleData = _oracles[base][quote];
157:
158:
159: require(!oracleData.isDisabled, AeraPeriphery__OracleAlreadyDisabled()); // <= FOUND
160:
161: require(oracleData.oracle == oracle, AeraPeriphery__OracleMismatch());
162:
163: require(oracle != IOracle(address(0)), AeraPeriphery__ZeroAddressOracle()); // <= FOUND
164:
165:
166: oracleData.isDisabled = true;
167:
168:
169: emit OracleDisabled(base, quote, oracle);
170: }
['173']
173: function acceptPendingOracle(address base, address quote, address user, IOracle oracle)
174: external
175: requiresUserAuth(user)
176: {
177:
178: require(oracle != IOracle(address(0)), AeraPeriphery__ZeroAddressOracle()); // <= FOUND
179:
180: require(_oracles[base][quote].pendingOracle == oracle, AeraPeriphery__OracleMismatch());
181:
182:
183: oracleOverrides[user][base][quote] = oracle;
184:
185:
186: emit PendingOracleAccepted(user, base, quote, oracle);
187: }
['235']
235: function _getOracleForVault(address user, address base, address quote) internal view returns (IOracle) {
236: OracleData storage oracleData = _oracles[base][quote];
237:
238: if (oracleData.isScheduledForUpdate) {
239: IOracle oracleOverride = oracleOverrides[user][base][quote];
240: if (oracleOverride == oracleData.pendingOracle) {
241: return oracleOverride;
242: }
243: }
244:
245: require(!oracleData.isDisabled, AeraPeriphery__OracleIsDisabled(base, quote, oracleData.oracle)); // <= FOUND
246:
247: IOracle oracle = oracleData.oracle;
248: require(oracle != IOracle(address(0)), AeraPeriphery__OracleNotSet());
249:
250: return oracle;
251: }
[]
255: function _validateOracle(IOracle oracle, address base, address quote) internal view {
256: uint256 oneBaseToken = 10 ** _getDecimals(base);
257: require(
258: oracle.getQuote(oneBaseToken, base, quote) != 0,
259: AeraPeriphery__OracleConvertsOneBaseTokenToZeroQuoteTokens(base, quote)
260: );
261: }
[Gas-13] Divisions which do not divide by -X cannot overflow or underflow so such operations can be unchecked to save gas
Make such found divisions are unchecked when ensured it is safe to do so
Num of instances: 2
Click to show findings
['73']
73: function vaultStates(address vault) external view returns (State memory state) { // <= FOUND
74: state = _vaultStates[vault];
75:
76: uint32 day = uint32(block.timestamp / 1 days); // <= FOUND
77: if (state.currentDay != day) {
78: state.currentDay = day;
79: state.cumulativeDailyLossInNumeraire = 0;
80: }
81: }
['198']
198: function _enforceDailyLoss(State storage state, uint256 loss) internal returns (uint256 newLoss) { // <= FOUND
199: uint32 day = uint32(block.timestamp / 1 days); // <= FOUND
200: if (state.currentDay != day) {
201:
202: state.currentDay = day;
203: state.cumulativeDailyLossInNumeraire = 0;
204: }
205:
206: newLoss = state.cumulativeDailyLossInNumeraire + loss;
207:
208:
209: require(
210: newLoss <= state.maxDailyLossInNumeraire,
211: AeraPeriphery__ExcessiveDailyLoss(newLoss, state.maxDailyLossInNumeraire)
212: );
213: }
Getters for public state variables are automatically generated so there is no need to code them manually and lose gas
Num of instances: 1
Click to show findings
['34']
34: function _getNumeraire() internal view virtual returns (address) {
35: return NUMERAIRE; // <= FOUND
36: }
[Gas-15] State variables which are not modified within functions should be set as constants or immutable for values set at deployment
Set state variables listed below as constant or immutable for values set at deployment. Ensure it is safe to do so
Num of instances: 2
Click to show findings
['79']
79: EnumerableMap.AddressToBytes32Map internal guardianRoots;
['19']
19: EnumerableMap.AddressToUintMap internal whitelist;
Replace such found multiplications with left shift operations when ensured it is safe to do so. NOTE: This only applies to uint variables!
Num of instances: 1
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: 8
Click to show findings
['69']
69: struct OperationContext { // <= FOUND
70:
71: address target;
72:
73: bytes4 selector;
74:
75: uint208 callbackData;
76:
77: uint256 value; // <= FOUND
78:
79: address operationHooks;
80:
81: uint256 configurableOperationHooks; // <= FOUND
82: }
['251']
251: struct Request { // <= FOUND
252:
253: RequestType requestType;
254:
255: address user;
256:
257: uint256 units; // <= FOUND
258:
259: uint256 tokens; // <= FOUND
260:
261: uint256 solverTip; // <= FOUND
262:
263: uint256 deadline; // <= FOUND
264:
265: uint256 maxPriceAge; // <= FOUND
266: }
['8']
8: struct SwapDescriptionV2 { // <= FOUND
9: IERC20 srcToken;
10: IERC20 dstToken;
11: address[] srcReceivers;
12: uint256[] srcAmounts;
13: address[] feeReceivers;
14: uint256[] feeAmounts;
15: address dstReceiver;
16: uint256 amount; // <= FOUND
17: uint256 minReturnAmount; // <= FOUND
18: uint256 flags; // <= FOUND
19: bytes permit;
20: }
['9']
9: struct SwapTokenInfo { // <= FOUND
10: address inputToken;
11: uint256 inputAmount; // <= FOUND
12: address inputReceiver;
13: address outputToken;
14: uint256 outputQuote; // <= FOUND
15: uint256 outputMin; // <= FOUND
16: address outputReceiver;
17: }
['10']
10: struct ExactInputSingleParams { // <= FOUND
11: address tokenIn;
12: address tokenOut;
13: uint24 fee;
14: address recipient;
15: uint256 deadline; // <= FOUND
16: uint256 amountIn; // <= FOUND
17: uint256 amountOutMinimum; // <= FOUND
18: uint160 sqrtPriceLimitX96;
19: }
['23']
23: struct ExactInputParams { // <= FOUND
24: bytes path;
25: address recipient;
26: uint256 deadline; // <= FOUND
27: uint256 amountIn; // <= FOUND
28: uint256 amountOutMinimum; // <= FOUND
29: }
['33']
33: struct ExactOutputSingleParams { // <= FOUND
34: address tokenIn;
35: address tokenOut;
36: uint24 fee;
37: address recipient;
38: uint256 deadline; // <= FOUND
39: uint256 amountOut; // <= FOUND
40: uint256 amountInMaximum; // <= FOUND
41: uint160 sqrtPriceLimitX96;
42: }
['46']
46: struct ExactOutputParams { // <= FOUND
47: bytes path;
48: address recipient;
49: uint256 deadline; // <= FOUND
50: uint256 amountOut; // <= FOUND
51: uint256 amountInMaximum; // <= FOUND
52: }
Private functions which are only called once can be inlined to save GAS.
Num of instances: 1
Click to show findings
['235']
235: function _unpackCallbackData(uint256 packed) // <= FOUND
236: private
237: pure
238: returns (address target, bytes4 selector, uint16 dataOffset)
239:
Nested mappings and multi-dimensional arrays in Solidity operate through a process of double hashing, wherein the original storage slot and the first key are concatenated and hashed, and then this hash is again concatenated with the second key and hashed. This process can be quite gas expensive due to the double-hashing operation and subsequent storage operation (sstore).
A possible optimization involves manually concatenating the keys followed by a single hash operation and an sstore. However, this technique introduces the risk of storage collision, especially when there are other nested hash maps in the contract that use the same key types. Because Solidity is unaware of the number and structure of nested hash maps in a contract, it follows a conservative approach in computing the storage slot to avoid possible collisions.
OpenZeppelin's EnumerableSet provides a potential solution to this problem. It creates a data structure that combines the benefits of set operations with the ability to enumerate stored elements, which is not natively available in Solidity. EnumerableSet handles the element uniqueness internally and can therefore provide a more gas-efficient and collision-resistant alternative to nested mappings or multi-dimensional arrays in certain scenarios.
Num of instances: 4
Click to show findings
['21']
21: mapping(address caller => mapping(bytes32 targetAndSelector => bool enabled)) internal _canCall; // <= FOUND
['16']
16: mapping(address vault => mapping(address addr => bool isWhitelisted)) public whitelist; // <= FOUND
['36']
36: mapping(address base => mapping(address quote => OracleData oracleData)) internal _oracles; // <= FOUND
['39']
39: mapping(address user => mapping(address base => mapping(address quote => IOracle))) public oracleOverrides; // <= FOUND
With 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: 79
Click to show findings
['44']
44:
45: emit OwnershipTransferred(msg.sender, pendingOwner_); // <= FOUND
['58']
58:
59: emit OwnershipTransferStarted(owner, newOwner); // <= FOUND
['57']
57:
58: emit ProtocolFeeRecipientSet(feeRecipient); // <= FOUND
['71']
71:
72: emit ProtocolFeesSet(tvl, performance); // <= FOUND
['80']
80:
81: emit VaultAccountantSet(vault, accountant); // <= FOUND
['98']
98:
99: emit VaultFeesSet(vault, tvl, performance); // <= FOUND
['59']
59:
60: emit VaultCreated(deployed, baseVaultParams.owner, address(baseVaultParams.submitHooks), description); // <= FOUND
['165']
165:
166: emit GuardianRootSet(guardian, bytes32(0)); // <= FOUND
['178']
178:
179: emit GuardianRootSet(guardian, bytes32(0)); // <= FOUND
['515']
515:
516: emit SubmitHooksSet(address(submitHooks_)); // <= FOUND
['535']
535:
536: emit GuardianRootSet(guardian, root); // <= FOUND
['57']
57:
58: emit VaultRegistered(msg.sender); // <= FOUND
['92']
92:
93: emit SnapshotSubmitted(vault, averageValue, highestProfit, timestamp); // <= FOUND
['85']
85:
86: emit FeeCalculatorUpdated(address(newFeeCalculator)); // <= FOUND
['101']
101:
102: emit FeeRecipientUpdated(newFeeRecipient); // <= FOUND
['118']
118:
119: emit FeesClaimed(msg.sender, feeRecipientFees); // <= FOUND
['124']
124:
125: emit ProtocolFeesClaimed(protocolFeeRecipient, protocolFees); // <= FOUND
['124']
124:
125: emit ProtocolFeesClaimed(protocolFeeRecipient, protocolFees); // <= FOUND
['130']
130:
131: emit VaultCreated( // <= FOUND
132: deployed,
133: baseVaultParams.owner,
134: address(baseVaultParams.submitHooks),
135: erc20Params,
136: feeVaultParams,
137: beforeTransferHook,
138: description
139: );
['74']
74:
75: emit Enter(sender, recipient, token, tokenAmount, unitsAmount); // <= FOUND
['90']
90:
91: emit Exit(sender, recipient, token, tokenAmount, unitsAmount); // <= FOUND
['135']
135:
136: emit BeforeTransferHookSet(address(hook_)); // <= FOUND
['148']
148:
149: emit ProvisionerSet(provisioner_); // <= FOUND
['118']
118:
119: emit UnitPriceUpdated(vault, price, timestampU32); // <= FOUND
['152']
152:
153: emit ThresholdsSet(vault, minPriceToleranceRatio, maxPriceToleranceRatio, minUpdateIntervalMinutes, maxPriceAge); // <= FOUND
['188']
188:
189: emit UnitPriceUpdated(vault, price, timestamp); // <= FOUND
['233']
233:
234: emit HighestPriceReset(vault, currentPrice); // <= FOUND
['381']
381:
382: emit VaultPausedChanged(vault, paused); // <= FOUND
['176']
176:
177: emit DirectDepositRefunded(depositHash); // <= FOUND
['215']
215:
216: emit DepositRequested( // <= FOUND
217: msg.sender, token, tokensIn, minUnitsOut, solverTip, deadline, maxPriceAge, isFixedPrice, depositHash
218: );
['256']
256:
257: emit RedeemRequested( // <= FOUND
258: msg.sender, token, minTokensOut, unitsIn, solverTip, deadline, maxPriceAge, isFixedPrice, redeemHash
259: );
['279']
279:
280: emit DepositRefunded(requestHash); // <= FOUND
['288']
288:
289: emit RedeemRefunded(requestHash); // <= FOUND
['310']
310:
311: emit AsyncDepositDisabled(i); // <= FOUND
['332']
332:
333: emit AsyncRedeemDisabled(i); // <= FOUND
['395']
395:
396: emit DepositDetailsUpdated(depositCap_, depositRefundTimeout_); // <= FOUND
['426']
426:
427: emit TokenDetailsSet(token, details); // <= FOUND
['435']
435:
436: emit TokenRemoved(token); // <= FOUND
['495']
495:
496: emit Deposited(msg.sender, token, tokenAmount, unitAmount, depositHash); // <= FOUND
['555']
555:
556: emit DepositSolved(depositHash); // <= FOUND
['562']
562:
563: emit DepositRefunded(depositHash); // <= FOUND
['684']
684:
685: emit RedeemSolved(redeemHash); // <= FOUND
['691']
691:
692: emit RedeemRefunded(redeemHash); // <= FOUND
['769']
769:
770: emit InvalidRequestHash(depositHash); // <= FOUND
['808']
808:
809: emit InvalidRequestHash(redeemHash); // <= FOUND
['840']
840: emit PriceAgeExceeded(index); // <= FOUND
['853']
853:
854: emit InvalidRequestHash(requestHash); // <= FOUND
['867']
867:
868: emit InsufficientTokensForTip(index); // <= FOUND
['881']
881:
882: emit AmountBoundExceeded(index, amount, bound); // <= FOUND
['895']
895:
896: emit DepositCapExceeded(index); // <= FOUND
['82']
82:
83: emit VaultCreated( // <= FOUND
84: deployed,
85: baseVaultParams.owner,
86: address(baseVaultParams.submitHooks),
87: singleDepositorVaultParams.feeToken,
88: singleDepositorVaultParams.feeCalculator,
89: singleDepositorVaultParams.feeRecipient,
90: description
91: );
['43']
43:
44: emit Deposited(msg.sender, tokenAmounts); // <= FOUND
['57']
57:
58: emit Withdrawn(msg.sender, tokenAmounts); // <= FOUND
['78']
78:
79: emit Executed(msg.sender, operations); // <= FOUND
['36']
36:
37: emit Sweep(token, amount); // <= FOUND
['37']
37:
38: emit WhitelistSet(addr, isAddressWhitelisted); // <= FOUND
['20']
20: emit OwnershipTransferred(msg.sender, _owner); // <= FOUND
['21']
21: emit AuthorityUpdated(msg.sender, _authority); // <= FOUND
['45']
45: emit AuthorityUpdated(msg.sender, newAuthority); // <= FOUND
['51']
51: emit OwnershipTransferred(msg.sender, newOwner); // <= FOUND
['51']
51:
52: emit Executed(msg.sender, operation); // <= FOUND
['68']
68:
69: emit CallerCapabilityAdded(caller, target, sig); // <= FOUND
['79']
79:
80: emit CallerCapabilityRemoved(caller, target, sig); // <= FOUND
['45']
45:
46: emit UpdateMaxDailyLoss(vault, maxLoss); // <= FOUND
['57']
57:
58: emit UpdateMaxSlippage(vault, newMaxSlippage); // <= FOUND
['69']
69:
70: emit UpdateOracleRegistry(vault, oracleRegistry); // <= FOUND
['168']
168:
169: emit TradeSlippageChecked(vault, tokenIn, tokenOut, valueBefore, valueAfter, cumulativeDailyLossNumeraire); // <= FOUND
['48']
48:
49: emit VaultUnitTransferableSet(vault, isTransferable); // <= FOUND
['38']
38:
39: emit VaultWhitelistUpdated(vault, addresses, isWhitelisted); // <= FOUND
['95']
95:
96: emit SellRequested(sellAmount, sellToken, receiveToken, appData, priceChecker, priceCheckerData); // <= FOUND
['117']
117:
118: emit SellCancelled( // <= FOUND
119: milkmanOrderContract, sellAmount, sellToken, receiveToken, appData, priceChecker, priceCheckerData
120: );
['134']
134:
135: emit Claimed(token, balance); // <= FOUND
['87']
87:
88: emit OracleSet(base, quote, oracle); // <= FOUND
['110']
110:
111: emit OracleScheduled(base, quote, oracle, oracleData.commitTimestamp); // <= FOUND
['135']
135:
136: emit OracleSet(base, quote, oracleData.oracle); // <= FOUND
['151']
151:
152: emit OracleUpdateCancelled(base, quote); // <= FOUND
['169']
169:
170: emit OracleDisabled(base, quote, oracle); // <= FOUND
['186']
186:
187: emit PendingOracleAccepted(user, base, quote, oracle); // <= FOUND
['195']
195:
196: emit OracleOverrideRemoved(user, base, quote); // <= FOUND
The following OpenZeppelin imports have a Solady equivalent, as such they can be used to save GAS as Solady modules have been specifically designed to be as GAS efficient as possible
Num of instances: 2
Click to show findings
['35']
35: import { IERC20WithAllowance } from "src/dependencies/openzeppelin/token/ERC20/IERC20WithAllowance.sol"; // <= FOUND
['5']
5: import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; // <= FOUND
Using 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
['327']
327:
328:
329: (result) = abi.decode(returnValue, (bytes)); // <= FOUND
['167']
167: deployed = abi.decode(data, (address)); // <= FOUND
['28']
28:
29: address vault = abi.decode(priceCheckerData, (address)); // <= FOUND
['52']
52: address vault = abi.decode(data, (address)); // <= FOUND
['268']
268: return success && data.length == 32 ? abi.decode(data, (uint8)) : 18; // <= FOUND
Struct uint variables in Solidity are typically stored in 32-byte storage slots. When dealing with timestamps, which generally fit into a smaller byte size, it can be beneficial to truncate these bytes and pack them with other variables. This reduces the number of required storage slots, saving both storage space and associated gas costs. For example, a timestamp generally fits into a uint32, so it can be combined with other small variables within a single storage slot. When designing a contract, carefully structuring struct variables to utilize truncation and packing can lead to a more efficient and cost-effective implementation.
Num of instances: 6
Click to show findings
['251']
251: struct Request { // <= FOUND
252:
253: RequestType requestType;
254:
255: address user;
256:
257: uint256 units;
258:
259: uint256 tokens;
260:
261: uint256 solverTip;
262:
263: uint256 deadline; // <= FOUND
264:
265: uint256 maxPriceAge;
266: }
['30']
30: struct SimpleSwapData { // <= FOUND
31: address[] firstPools;
32: uint256[] firstSwapAmounts;
33: bytes[] swapDatas;
34: uint256 deadline; // <= FOUND
35: bytes destTokenFeeData;
36: }
['10']
10: struct ExactInputSingleParams { // <= FOUND
11: address tokenIn;
12: address tokenOut;
13: uint24 fee;
14: address recipient;
15: uint256 deadline; // <= FOUND
16: uint256 amountIn;
17: uint256 amountOutMinimum;
18: uint160 sqrtPriceLimitX96;
19: }
['23']
23: struct ExactInputParams { // <= FOUND
24: bytes path;
25: address recipient;
26: uint256 deadline; // <= FOUND
27: uint256 amountIn;
28: uint256 amountOutMinimum;
29: }
['33']
33: struct ExactOutputSingleParams { // <= FOUND
34: address tokenIn;
35: address tokenOut;
36: uint24 fee;
37: address recipient;
38: uint256 deadline; // <= FOUND
39: uint256 amountOut;
40: uint256 amountInMaximum;
41: uint160 sqrtPriceLimitX96;
42: }
['46']
46: struct ExactOutputParams { // <= FOUND
47: bytes path;
48: address recipient;
49: uint256 deadline; // <= FOUND
50: uint256 amountOut;
51: uint256 amountInMaximum;
52: }
Using 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: 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: 2
Click to show findings
['52']
52: function transferOwnership(address newOwner) public virtual override onlyOwner {
53:
54:
55: pendingOwner = newOwner;
56:
57:
58: emit OwnershipTransferStarted(owner, newOwner);
59: }
['30']
30: function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) {
31: Authority auth = authority;
32:
33:
34:
35: return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig)) || user == owner;
36: }
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: 18
Click to show findings
['304']
304: for (uint256 i = 0; i < length; i++) {
305: request = requests[i];
306: if (_isRequestTypeDeposit(request.requestType)) {
307:
308: if (!tokenDetails.asyncDepositEnabled) {
309:
310: emit AsyncDepositDisabled(i);
311: continue;
312: }
313:
314: if (!depositsExist) {
315: depositsExist = true;
316: token.forceApprove(MULTI_DEPOSITOR_VAULT, type(uint256).max);
317: }
318:
319: if (_isRequestTypeAutoPrice(request.requestType)) {
320:
321: solverTip +=
322: _solveDepositVaultAutoPrice(token, tokenDetails.depositMultiplier, request, priceAge, i);
323: } else {
324:
325: solverTip +=
326: _solveDepositVaultFixedPrice(token, tokenDetails.depositMultiplier, request, priceAge, i);
327: }
328: } else {
329:
330: if (!tokenDetails.asyncRedeemEnabled) {
331:
332: emit AsyncRedeemDisabled(i);
333: continue;
334: }
335:
336: if (_isRequestTypeAutoPrice(request.requestType)) {
337:
338: solverTip += _solveRedeemVaultAutoPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i);
339: } else {
340:
341: solverTip += _solveRedeemVaultFixedPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i);
342: }
343: }
344: }
['364']
364: for (uint256 i = 0; i < length; i++) {
365: if (_isRequestTypeDeposit(requests[i].requestType)) {
366:
367: require(tokenDetails.asyncDepositEnabled, Aera__AsyncDepositDisabled());
368:
369: require(!_isRequestTypeAutoPrice(requests[i].requestType), Aera__AutoPriceSolveNotAllowed());
370:
371: _solveDepositDirect(token, requests[i]);
372: } else {
373:
374: require(tokenDetails.asyncRedeemEnabled, Aera__AsyncRedeemDisabled());
375:
376: require(!_isRequestTypeAutoPrice(requests[i].requestType), Aera__AutoPriceSolveNotAllowed());
377:
378: _solveRedeemDirect(token, requests[i]);
379: }
380: }
['30']
30: for (uint256 i = 0; i < length; ++i) {
31: tokenAmount = tokenAmounts[i];
32:
33: tokenAmount.token.safeTransferFrom(msg.sender, address(this), tokenAmount.amount);
34:
35:
36: require(
37: tokenAmount.token.allowance(msg.sender, address(this)) == 0,
38: Aera__UnexpectedTokenAllowance(tokenAmount.token.allowance(msg.sender, address(this)))
39: );
40: }
['50']
50: for (uint256 i = 0; i < length; ++i) {
51: tokenAmount = tokenAmounts[i];
52:
53: tokenAmount.token.safeTransfer(msg.sender, tokenAmount.amount);
54: }
['66']
66: for (uint256 i = 0; i < length; ++i) {
67: operation = operations[i];
68:
69:
70:
71: (success, result) = operation.target.call{ value: operation.value }(operation.data);
72:
73:
74: require(success, Aera__ExecutionFailed(i, result));
75: }
['26']
26: for (uint256 i = 0; i < numOperations; ++i) {
27:
28: _executeOperation(operations[i]);
29: }
['41']
41: for (uint256 i; i < length; ++i) {
42: target = operations[i].target;
43: data = operations[i].data;
44: targetAndSelector = _packTargetSig(target, bytes4(data));
45:
46:
47: require(
48: callerCapabilities[targetAndSelector], AeraPeriphery__Unauthorized(msg.sender, target, bytes4(data))
49: );
50:
51:
52: (bool success, bytes memory returnData) = target.call(data);
53: Address.verifyCallResultFromTarget(target, success, returnData);
54: }
['30']
30: for (uint256 i; i < length; i++) {
31: addr = addresses[i];
32:
33:
34: whitelist[vault][addr] = isWhitelisted;
35: }
['71']
71: for (uint256 i = 0; i < calldataOffsetsCount; ++i) {
72: uint256 extractionOffset = (calldataOffsetsPacked >> EXTRACTION_OFFSET_SHIFT_BITS) + SELECTOR_SIZE;
73:
74:
75: require(extractionOffset <= maxValidOffset, Aera__OffsetOutOfBounds());
76:
77: uint256 calldataOffsetPointer = calldataPointer + extractionOffset;
78:
79:
80:
81: bytes32 extracted;
82: assembly ("memory-safe") {
83: extracted := mload(calldataOffsetPointer)
84: }
85:
86:
87:
88: uint256 resultOffsetPointer = resultPtr + resultWriteOffset;
89: assembly ("memory-safe") {
90: mstore(resultOffsetPointer, extracted)
91: }
92:
93: resultWriteOffset += WORD_SIZE;
94: calldataOffsetsPacked = calldataOffsetsPacked << EXTRACT_OFFSET_SIZE_BITS;
95: }
['44']
44: for (; clipboardCount != 0; --clipboardCount) {
45: uint256 clipboard;
46: (reader, clipboard) = reader.readU32();
47:
48: uint256 resultIndex = clipboard >> RESULTS_INDEX_OFFSET;
49:
50:
51: bytes memory result = results[resultIndex];
52:
53: uint256 copyOffset = (clipboard >> COPY_WORD_OFFSET & MASK_8_BIT) * WORD_SIZE;
54:
55: require(copyOffset + WORD_SIZE <= result.length, Aera__CopyOffsetOutOfBounds());
56:
57: uint256 pasteOffset = clipboard & MASK_16_BIT;
58:
59: require(pasteOffset + WORD_SIZE <= data.length, Aera__PasteOffsetOutOfBounds());
60:
61: uint256 operationCalldataPointer;
62: uint256 resultPointer;
63: assembly ("memory-safe") {
64:
65:
66: operationCalldataPointer := data
67: resultPointer := result
68: }
69:
70: uint256 pastePointer = operationCalldataPointer + pasteOffset + CALLDATA_OFFSET;
71: uint256 copyPointer = resultPointer + WORD_SIZE + copyOffset;
72:
73: assembly ("memory-safe") {
74: mcopy(pastePointer, copyPointer, WORD_SIZE)
75: }
76: }
['304']
304: for (uint256 i = 0; i < length; i++) {
305: request = requests[i];
306: if (_isRequestTypeDeposit(request.requestType)) {
307:
308: if (!tokenDetails.asyncDepositEnabled) {
309:
310: emit AsyncDepositDisabled(i);
311: continue;
312: }
313:
314: if (!depositsExist) {
315: depositsExist = true;
316: token.forceApprove(MULTI_DEPOSITOR_VAULT, type(uint256).max);
317: }
318:
319: if (_isRequestTypeAutoPrice(request.requestType)) {
320:
321: solverTip +=
322: _solveDepositVaultAutoPrice(token, tokenDetails.depositMultiplier, request, priceAge, i);
323: } else {
324:
325: solverTip +=
326: _solveDepositVaultFixedPrice(token, tokenDetails.depositMultiplier, request, priceAge, i);
327: }
328: } else {
329:
330: if (!tokenDetails.asyncRedeemEnabled) {
331:
332: emit AsyncRedeemDisabled(i);
333: continue;
334: }
335:
336: if (_isRequestTypeAutoPrice(request.requestType)) {
337:
338: solverTip += _solveRedeemVaultAutoPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i);
339: } else {
340:
341: solverTip += _solveRedeemVaultFixedPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i);
342: }
343: }
344: }
['364']
364: for (uint256 i = 0; i < length; i++) {
365: if (_isRequestTypeDeposit(requests[i].requestType)) {
366:
367: require(tokenDetails.asyncDepositEnabled, Aera__AsyncDepositDisabled());
368:
369: require(!_isRequestTypeAutoPrice(requests[i].requestType), Aera__AutoPriceSolveNotAllowed());
370:
371: _solveDepositDirect(token, requests[i]);
372: } else {
373:
374: require(tokenDetails.asyncRedeemEnabled, Aera__AsyncRedeemDisabled());
375:
376: require(!_isRequestTypeAutoPrice(requests[i].requestType), Aera__AutoPriceSolveNotAllowed());
377:
378: _solveRedeemDirect(token, requests[i]);
379: }
380: }
['30']
30: for (uint256 i = 0; i < length; ++i) {
31: tokenAmount = tokenAmounts[i];
32:
33: tokenAmount.token.safeTransferFrom(msg.sender, address(this), tokenAmount.amount);
34:
35:
36: require(
37: tokenAmount.token.allowance(msg.sender, address(this)) == 0,
38: Aera__UnexpectedTokenAllowance(tokenAmount.token.allowance(msg.sender, address(this)))
39: );
40: }
['50']
50: for (uint256 i = 0; i < length; ++i) {
51: tokenAmount = tokenAmounts[i];
52:
53: tokenAmount.token.safeTransfer(msg.sender, tokenAmount.amount);
54: }
['66']
66: for (uint256 i = 0; i < length; ++i) {
67: operation = operations[i];
68:
69:
70:
71: (success, result) = operation.target.call{ value: operation.value }(operation.data);
72:
73:
74: require(success, Aera__ExecutionFailed(i, result));
75: }
['26']
26: for (uint256 i = 0; i < numOperations; ++i) {
27:
28: _executeOperation(operations[i]);
29: }
['41']
41: for (uint256 i; i < length; ++i) {
42: target = operations[i].target;
43: data = operations[i].data;
44: targetAndSelector = _packTargetSig(target, bytes4(data));
45:
46:
47: require(
48: callerCapabilities[targetAndSelector], AeraPeriphery__Unauthorized(msg.sender, target, bytes4(data))
49: );
50:
51:
52: (bool success, bytes memory returnData) = target.call(data);
53: Address.verifyCallResultFromTarget(target, success, returnData);
54: }
['30']
30: for (uint256 i; i < length; i++) {
31: addr = addresses[i];
32:
33:
34: whitelist[vault][addr] = isWhitelisted;
35: }
Casting 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: 7
Click to show findings
['81']
81: function setFeeCalculator(IFeeCalculator newFeeCalculator) external requiresAuth { // <= FOUND
82:
83: feeCalculator = newFeeCalculator;
84:
85: emit FeeCalculatorUpdated(address(newFeeCalculator)); // <= FOUND
86:
87:
88: if (address(newFeeCalculator) != address(0)) { // <= FOUND
89: newFeeCalculator.registerVault();
90: }
91: }
['390']
390: function _convertTokenToUnits( // <= FOUND
391: address vault,
392: IERC20 token,
393: uint256 tokenAmount,
394: uint256 unitPrice,
395: Math.Rounding rounding
396: ) internal view returns (uint256 unitsAmount) {
397: if (address(token) != NUMERAIRE) { // <= FOUND
398: tokenAmount = ORACLE_REGISTRY.getQuoteForUser(tokenAmount, address(token), NUMERAIRE, vault); // <= FOUND
399: }
400:
401: return Math.mulDiv(tokenAmount, UNIT_PRICE_PRECISION, unitPrice, rounding);
402: }
['410']
410: function _convertUnitsToToken( // <= FOUND
411: address vault,
412: IERC20 token,
413: uint256 unitsAmount,
414: uint256 unitPrice,
415: Math.Rounding rounding
416: ) internal view returns (uint256 tokenAmount) {
417: uint256 numeraireAmount = Math.mulDiv(unitsAmount, unitPrice, UNIT_PRICE_PRECISION, rounding);
418:
419: if (address(token) == NUMERAIRE) { // <= FOUND
420: return numeraireAmount;
421: }
422:
423: return ORACLE_REGISTRY.getQuoteForUser(numeraireAmount, NUMERAIRE, address(token), vault); // <= FOUND
424: }
['16']
16: function requestSell( // <= FOUND
17: uint256 sellAmount,
18: IERC20 sellToken,
19: IERC20 receiveToken,
20: bytes32,
21: address priceChecker,
22: bytes calldata priceCheckerData
23: ) external returns (bytes memory returnData) {
24:
25: require(priceChecker == address(this), AeraPeriphery__InvalidPriceChecker(address(this), priceChecker));
26:
27:
28: address vault = abi.decode(priceCheckerData, (address));
29: require(msg.sender == vault, AeraPeriphery__InvalidVaultInPriceCheckerData(msg.sender, vault));
30:
31:
32: _handleMilkmanBeforeHook(address(sellToken), sellAmount, msg.sender); // <= FOUND
33:
34:
35: returnData = abi.encode(address(sellToken), address(receiveToken)); // <= FOUND
36: }
['390']
390: function _convertTokenToUnits(
391: address vault,
392: IERC20 token,
393: uint256 tokenAmount,
394: uint256 unitPrice,
395: Math.Rounding rounding
396: ) internal view returns (uint256 unitsAmount) {
397: if (address(token) != NUMERAIRE) { // <= FOUND 'address(token)'
398: tokenAmount = ORACLE_REGISTRY.getQuoteForUser(tokenAmount, address(token), NUMERAIRE, vault); // <= FOUND 'address(token)'
399: }
400:
401: return Math.mulDiv(tokenAmount, UNIT_PRICE_PRECISION, unitPrice, rounding);
402: }
['410']
410: function _convertUnitsToToken(
411: address vault,
412: IERC20 token,
413: uint256 unitsAmount,
414: uint256 unitPrice,
415: Math.Rounding rounding
416: ) internal view returns (uint256 tokenAmount) {
417: uint256 numeraireAmount = Math.mulDiv(unitsAmount, unitPrice, UNIT_PRICE_PRECISION, rounding);
418:
419: if (address(token) == NUMERAIRE) { // <= FOUND 'address(token)'
420: return numeraireAmount;
421: }
422:
423: return ORACLE_REGISTRY.getQuoteForUser(numeraireAmount, NUMERAIRE, address(token), vault); // <= FOUND 'address(token)'
424: }
['41']
41: function _processSwapHooks(IMetaAggregationRouterV2.SwapDescriptionV2 calldata desc, address executor)
42: internal
43: returns (bytes memory returnData)
44: {
45:
46: require(address(desc.srcToken) != KYBERSWAP_ETH_ADDRESS, AeraPeriphery__InputTokenIsETH()); // <= FOUND 'address(desc.srcToken)'
47:
48:
49: require(address(desc.dstToken) != KYBERSWAP_ETH_ADDRESS, AeraPeriphery__OutputTokenIsETH());
50:
51:
52: require(desc.feeReceivers.length == 0, AeraPeriphery__FeeReceiversNotEmpty());
53:
54: (address tokenIn, address tokenOut, address receiver) = _handleBeforeExactInputSingle(
55: address(desc.srcToken), address(desc.dstToken), desc.dstReceiver, desc.amount, desc.minReturnAmount // <= FOUND 'address(desc.srcToken)'
56: );
57: return abi.encode(tokenIn, tokenOut, receiver, executor);
58: }
If a variable is only once, it makes more sense to use the value the variable holds directly
Num of instances: 4
Click to show findings
['632']
632: assembly ("memory-safe") {
633: let offset := add(data, ERC20_SPENDER_OFFSET) // <= FOUND
634: spender := mload(offset) // <= FOUND
635: }
['58']
58: assembly ("memory-safe") {
59: let end := add(data.offset, data.length) // <= FOUND
60: if iszero(eq(self, end)) { // <= FOUND
61: mstore(0x00, 0x01842f8c )
62: revert(0x1c, 0x04)
63: }
64: }
['156']
156: assembly ("memory-safe") {
157: let len := shr(232, calldataload(self)) // <= FOUND
158: self := add(self, 3)
159: end := add(self, len) // <= FOUND
160: }
['205']
205: assembly ("memory-safe") {
206: let length := calldataload(sub(self, 32)) // <= FOUND
207: end := add(self, length) // <= FOUND
208: }
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: 14
Click to show findings
['22']
22: require(msg.sender == owner, Aera__Unauthorized()); // <= FOUND
['37']
37:
38: require(msg.sender == pendingOwner_, Aera__Unauthorized()); // <= FOUND
['38']
38: require(msg.sender == vaultAccountant[vault], Aera__CallerIsNotVaultAccountant()); // <= FOUND
['67']
67:
68: require(msg.sender == caller, Aera__UnauthorizedCallback()); // <= FOUND
['46']
46:
47: require(msg.sender == feeRecipient, Aera__CallerIsNotFeeRecipient()); // <= FOUND
['136']
136:
137: require(msg.sender == protocolFeeRecipient, Aera__CallerIsNotProtocolFeeRecipient()); // <= FOUND
['34']
34:
35: require(msg.sender == provisioner, Aera__CallerIsNotProvisioner()); // <= FOUND
['56']
56:
57:
58: require( // <= FOUND
59: msg.sender == vaultAccountant[vault] || msg.sender == Auth(vault).owner() // <= FOUND
60: || Auth(vault).authority().canCall(msg.sender, address(this), msg.sig),
61: Aera__CallerIsNotAuthorized()
62: );
['23']
23:
24: require( // <= FOUND
25: msg.sender == Auth(vault).owner() || Auth(vault).authority().canCall(msg.sender, address(this), msg.sig), // <= FOUND
26: Aera__CallerIsNotAuthorized()
27: );
['41']
41:
42:
43: require(msg.sender == owner || authority.canCall(msg.sender, address(this), msg.sig)); // <= FOUND
['29']
29: require(msg.sender == vault, AeraPeriphery__InvalidVaultInPriceCheckerData(msg.sender, vault)); // <= FOUND
['38']
38:
39: require(msg.sender == vault, AeraPeriphery__CallerIsNotVault()); // <= FOUND
['46']
46: require( // <= FOUND
47: msg.sender == user || msg.sender == Auth(user).owner() // <= FOUND
48: || Auth(user).authority().canCall(msg.sender, address(this), msg.sig),
49: AeraPeriphery__CallerIsNotAuthorized()
50: );
['84']
84:
85: require(msg.sender != MULTI_DEPOSITOR_VAULT, Aera__CallerIsVault()); // <= FOUND
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: 29
Click to show findings
['123']
123: if (length == 0) return; // <= FOUND
['197']
197: if (length == 0) return approvals; // <= FOUND
['1040']
1040: return uint8(requestType) & DEPOSIT_REDEEM_FLAG == 0; // <= FOUND
['1047']
1047: return uint8(requestType) & AUTO_PRICE_FIXED_PRICE_FLAG == 0; // <= FOUND
['24']
24:
25: if (numOperations == 0) return; // <= FOUND
['128']
128:
129:
130: if (balance == 0) return; // <= FOUND
['643']
643:
644: return uint160(hooks) & BEFORE_HOOK_MASK != 0; // <= FOUND
['651']
651:
652: return uint160(hooks) & AFTER_HOOK_MASK != 0; // <= FOUND
['72']
72: require(lastFeeAccrualCached != 0, Aera__VaultNotRegistered()); // <= FOUND
['113']
113:
114: require(feeRecipientFees != 0, Aera__NoFeesToClaim()); // <= FOUND
['139']
139:
140: require(protocolFees != 0, Aera__NoFeesToClaim()); // <= FOUND
['95']
95: require(price != 0, Aera__InvalidPrice()); // <= FOUND
['224']
224:
225: require(currentPrice != 0, Aera__VaultNotInitialized()); // <= FOUND
['95']
95:
96: require(price != 0, Aera__InvalidPrice()); // <= FOUND
['444']
444:
445: require(maxPriceAge != 0, Aera__ThresholdNotSet()); // <= FOUND
['114']
114:
115: require(tokensIn != 0, Aera__TokensInZero()); // <= FOUND
['115']
115: require(minUnitsOut != 0, Aera__MinUnitsOutZero()); // <= FOUND
['138']
138:
139: require(unitsOut != 0, Aera__UnitsOutZero()); // <= FOUND
['139']
139: require(maxTokensIn != 0, Aera__MaxTokensInZero()); // <= FOUND
['114']
114:
115:
116: require(tokensIn != 0, Aera__TokensInZero()); // <= FOUND
['232']
232:
233:
234: require(unitsIn != 0, Aera__UnitsInZero()); // <= FOUND
['233']
233: require(minTokensOut != 0, Aera__MinTokenOutZero()); // <= FOUND
['386']
386:
387: require(depositCap_ != 0, Aera__DepositCapZero()); // <= FOUND
['586']
586: require(length > 0, Aera__NoResults());
['68']
68:
69: if (tokenAmount > 0) token.safeTransferFrom(sender, address(this), tokenAmount);
['87']
87:
88: if (tokenAmount > 0) token.safeTransfer(recipient, tokenAmount);
['135']
135:
136: require(maxPriceAge > 0, Aera__InvalidMaxPriceAge());
['137']
137:
138: require(maxUpdateDelayDays > 0, Aera__InvalidMaxUpdateDelayDays());
['74']
74:
75: require(sellAmount > 0, AeraPeriphery__MilkmanRouter__SellAmountIsZero());
Using 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: 7
Click to show findings
['112']
112: if (vaultSnapshot.lastFeeAccrual < vaultSnapshot.timestamp && vaultSnapshot.finalizedAt <= block.timestamp) { // <= FOUND
113:
114: (uint256 vaultPerformanceFeeEarned, uint256 protocolPerformanceFeeEarned) = _calculatePerformanceFees(
115: vaultAccruals.fees.performance, vaultSnapshot.highestProfit, vaultSnapshot.lastHighestProfit
116: );
117:
118: (uint256 vaultTvlFeeEarned, uint256 protocolTvlFeeEarned) = _calculateTvlFees(
119: vaultAccruals.fees.tvl,
120: vaultSnapshot.averageValue,
121: vaultSnapshot.timestamp,
122: vaultSnapshot.lastFeeAccrual
123: );
124:
125: claimableProtocolFee += protocolPerformanceFeeEarned + protocolTvlFeeEarned;
126: claimableVaultFee += vaultPerformanceFeeEarned + vaultTvlFeeEarned;
127: }
['30']
30: if (from != transferAgent && to != transferAgent && from != address(0) && to != address(0)) { // <= FOUND
31:
32: require(isVaultUnitTransferable[msg.sender], Aera__VaultUnitsNotTransferable(msg.sender));
33: }
['112']
112: if (vaultSnapshot.lastFeeAccrual < vaultSnapshot.timestamp && vaultSnapshot.finalizedAt <= block.timestamp) { // <= FOUND
['43']
43: return exists && value == IS_WHITELISTED_FLAG; // <= FOUND
['35']
35:
36:
37: return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig)) || user == owner; // <= FOUND
['30']
30: if (from != transferAgent && to != transferAgent && from != address(0) && to != address(0)) { // <= FOUND
['268']
268: return success && data.length == 32 ? abi.decode(data, (uint8)) : 18; // <= FOUND
Storage optimization in Solidity contracts is vital for reducing gas costs, especially when storing time-related state variables. Using uint32
for storing time values like timestamps is often sufficient, given it can represent dates up to the year 2106. By truncating larger default integer types to uint32
, you significantly save on storage space and consequently on gas costs for deployment and state modifications. However, ensure that the truncation does not lead to overflow issues and that the variable's size is adequate for the application's expected lifespan and precision requirements. Adopting this optimization practice contributes to more efficient and cost-effective smart contract development.
Num of instances: 1
Num of instances: 1
When 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: 5
Click to show findings
['130']
130:
131: emit VaultCreated( // <= FOUND
132: deployed,
133: baseVaultParams.owner,
134: address(baseVaultParams.submitHooks),
135: erc20Params,
136: feeVaultParams,
137: beforeTransferHook, // <= FOUND
138: description
139: );
['124']
124:
125: emit ProtocolFeesClaimed(protocolFeeRecipient, protocolFees); // <= FOUND
['124']
124:
125: emit ProtocolFeesClaimed(protocolFeeRecipient, protocolFees); // <= FOUND
['58']
58:
59: emit OwnershipTransferStarted(owner, newOwner); // <= FOUND
['57']
57:
58: emit ProtocolFeeRecipientSet(feeRecipient); // <= FOUND
Optimizing 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: 19
Click to show findings
['416']
416:
417: (bool success, bytes memory result) = ctx.target.call{ value: ctx.value }(callData); // <= FOUND
['71']
71:
72:
73: (success, result) = operation.target.call{ value: operation.value }(operation.data); // <= FOUND
['27']
27:
28: (bool success,) = msg.sender.call{ value: amount }(""); // <= FOUND
['43']
43:
44:
45: (bool success, bytes memory result) = operation.target.call{ value: operation.value }(operation.data); // <= FOUND
['281']
281:
282: (bool success, bytes memory result) =
283: hooks.call(abi.encodeWithSelector(ISubmitHooks.beforeSubmit.selector, data, msg.sender)); // <= FOUND
['294']
294:
295: (bool success, bytes memory result) =
296: hooks.call(abi.encodeWithSelector(ISubmitHooks.afterSubmit.selector, data, msg.sender)); // <= FOUND
['315']
315:
316: (bool success, bytes memory returnValue) = operationHooks.call(data); // <= FOUND
['341']
341:
342: (bool success, bytes memory result) = operationHooks.call(data); // <= FOUND
['52']
52:
53: (bool success, bytes memory returnData) = target.call(data); // <= FOUND
['416']
416:
417: (bool success, bytes memory result) = ctx.target.call{ value: ctx.value }(callData); // <= FOUND
['71']
71:
72:
73: (success, result) = operation.target.call{ value: operation.value }(operation.data); // <= FOUND
['27']
27:
28: (bool success,) = msg.sender.call{ value: amount }(""); // <= FOUND
['43']
43:
44:
45: (bool success, bytes memory result) = operation.target.call{ value: operation.value }(operation.data); // <= FOUND
['281']
281:
282: (bool success, bytes memory result) =
283: hooks.call(abi.encodeWithSelector(ISubmitHooks.beforeSubmit.selector, data, msg.sender)); // <= FOUND
['294']
294:
295: (bool success, bytes memory result) =
296: hooks.call(abi.encodeWithSelector(ISubmitHooks.afterSubmit.selector, data, msg.sender)); // <= FOUND
['315']
315:
316: (bool success, bytes memory returnValue) = operationHooks.call(data); // <= FOUND
['341']
341:
342: (bool success, bytes memory result) = operationHooks.call(data); // <= FOUND
['52']
52:
53: (bool success, bytes memory returnData) = target.call(data); // <= FOUND
['27']
27:
28: (bool success,) = msg.sender.call{ value: amount }(""); // <= FOUND
Num of instances: 4
Click to show findings
['21']
21: modifier onlyOwner() virtual { // <= FOUND
22: require(msg.sender == owner, Aera__Unauthorized());
23: _;
24: }
['86']
86: modifier onlyAuthOrGuardian() { // <= FOUND
87: require(
88: isAuthorized(msg.sender, msg.sig) || guardianRoots.contains(msg.sender), Aera__CallerIsNotAuthOrGuardian()
89: );
90: _;
91: }
['44']
44: modifier onlyFeeRecipient() { // <= FOUND
45:
46: require(msg.sender == feeRecipient, Aera__CallerIsNotFeeRecipient());
47: _;
48: }
['53']
53: modifier requiresVaultAuthOrAccountant(address vault) { // <= FOUND
54:
55:
56: require(
57: msg.sender == vaultAccountant[vault] || msg.sender == Auth(vault).owner()
58: || Auth(vault).authority().canCall(msg.sender, address(this), msg.sig),
59: Aera__CallerIsNotAuthorized()
60: );
61: _;
62: }
In 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: 2
Click to show findings
['122']
122: function _storeCallbackApprovals(Approval[] memory approvals, uint256 length) internal { // <= FOUND
123: if (length == 0) return;
124:
125: uint256 existingApproval = APPROVALS_SLOT.asUint256().tload();
126: uint256 existingLength = existingApproval >> ADDRESS_SIZE_BITS;
127:
128: uint256 i;
129: uint256 currentSlot = uint256(APPROVALS_SLOT);
130: Approval memory approval;
131: if (existingLength == 0) {
132: approval = approvals[0];
133: unchecked {
134:
135:
136: bytes32(currentSlot).asUint256().tstore(_packLengthAndToken(length, approval.token)); // <= FOUND
137: bytes32(++currentSlot).asAddress().tstore(approval.spender); // <= FOUND
138: }
139:
140: i = 1;
141: } else {
142: unchecked {
143: uint256 newLength = existingLength + length;
144:
145:
146: bytes32(currentSlot).asUint256().tstore(
147: _packLengthAndToken(newLength, address(uint160(existingApproval)))
148: );
149:
150: currentSlot += existingLength * 2 - 1;
151: }
152: }
153:
154: for (; i < length; ++i) {
155: approval = approvals[i];
156: unchecked {
157:
158: bytes32(++currentSlot).asAddress().tstore(approval.token); // <= FOUND
159: bytes32(++currentSlot).asAddress().tstore(approval.spender); // <= FOUND
160: }
161: }
162: }
['294']
294: function solveRequestsVault(IERC20 token, Request[] calldata requests) external requiresAuth nonReentrant { // <= FOUND
295:
296: uint256 priceAge = PRICE_FEE_CALCULATOR.getVaultsPriceAge(MULTI_DEPOSITOR_VAULT);
297:
298: uint256 solverTip;
299: Request calldata request;
300:
301: uint256 length = requests.length;
302: TokenDetails memory tokenDetails = tokensDetails[token];
303: bool depositsExist;
304: for (uint256 i = 0; i < length; i++) {
305: request = requests[i];
306: if (_isRequestTypeDeposit(request.requestType)) {
307:
308: if (!tokenDetails.asyncDepositEnabled) { // <= FOUND
309:
310: emit AsyncDepositDisabled(i);
311: continue;
312: }
313:
314: if (!depositsExist) {
315: depositsExist = true;
316: token.forceApprove(MULTI_DEPOSITOR_VAULT, type(uint256).max);
317: }
318:
319: if (_isRequestTypeAutoPrice(request.requestType)) {
320:
321: solverTip +=
322: _solveDepositVaultAutoPrice(token, tokenDetails.depositMultiplier, request, priceAge, i); // <= FOUND
323: } else {
324:
325: solverTip +=
326: _solveDepositVaultFixedPrice(token, tokenDetails.depositMultiplier, request, priceAge, i); // <= FOUND
327: }
328: } else {
329:
330: if (!tokenDetails.asyncRedeemEnabled) { // <= FOUND
331:
332: emit AsyncRedeemDisabled(i);
333: continue;
334: }
335:
336: if (_isRequestTypeAutoPrice(request.requestType)) {
337:
338: solverTip += _solveRedeemVaultAutoPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i); // <= FOUND
339: } else {
340:
341: solverTip += _solveRedeemVaultFixedPrice(token, tokenDetails.redeemMultiplier, request, priceAge, i); // <= FOUND
342: }
343: }
344: }
345:
346: if (solverTip != 0) {
347:
348: token.safeTransfer(msg.sender, solverTip);
349: }
350:
351: if (depositsExist) {
352:
353: token.forceApprove(MULTI_DEPOSITOR_VAULT, 0);
354: }
355: }
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
If 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: 42
Click to show findings
['153']
153: function _beforeClaimFees() internal virtual // <= FOUND
['147']
147: function _beforeClaimFees() internal override // <= FOUND
['158']
158: function _beforeClaimProtocolFees() internal virtual // <= FOUND
['154']
154: function _beforeClaimProtocolFees() internal override // <= FOUND
['258']
258: function _processExpectedCallback(CalldataReader reader, bytes32 root) internal returns (CalldataReader, uint208) // <= FOUND
['278']
278: function _beforeSubmitHooks(address hooks, bytes calldata data) internal // <= FOUND
['291']
291: function _afterSubmitHooks(address hooks, bytes calldata data) internal // <= FOUND
['306']
306: function _beforeOperationHooks(address operationHooks, bytes memory data, uint256 i) // <= FOUND
307: internal
308: returns (bytes memory result)
309:
['335']
335: function _afterOperationHooks(address operationHooks, bytes memory data, uint256 i) internal // <= FOUND
['455']
455: function _processBeforeOperationHooks(CalldataReader reader, bytes memory callData, uint256 i) // <= FOUND
456: internal
457: returns (CalldataReader, bytes memory, uint256, address)
458:
['521']
521: function _setGuardianRoot(address guardian, bytes32 root) internal virtual // <= FOUND
['574']
574: function _getReturnValue(CalldataReader reader, bytes[] memory results) // <= FOUND
575: internal
576: pure
577: returns (CalldataReader newReader, bytes memory returnValue)
578:
['600']
600: function _verifyOperation(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure // <= FOUND
['608']
608: function _createMerkleLeaf(OperationContext memory ctx, bytes memory extractedData) // <= FOUND
609: internal
610: pure
611: returns (bytes32)
612:
['629']
629: function _extractApprovalSpender(bytes memory data) internal pure returns (address spender) // <= FOUND
['657']
657: function _isAllowanceSelector(bytes4 selector) internal pure returns (bool) // <= FOUND
['107']
107: function _allowCallback(bytes32 root, uint256 packedCallbackData) internal // <= FOUND
['122']
122: function _storeCallbackApprovals(Approval[] memory approvals, uint256 length) internal // <= FOUND
['194']
194: function _getCallbackApprovals() internal returns (Approval[] memory approvals) // <= FOUND
['226']
226: function _hasCallbackBeenCalled() internal view returns (bool) // <= FOUND
['91']
91: function readU16(CalldataReader self) internal pure returns (CalldataReader, uint16 value) // <= FOUND
['99']
99: function readU32(CalldataReader self) internal pure returns (CalldataReader, uint32 value) // <= FOUND
['176']
176: function readU208(CalldataReader self) internal pure returns (CalldataReader, uint208 value) // <= FOUND
['184']
184: function readOptionalU256(CalldataReader reader) internal pure returns (CalldataReader, uint256 u256) // <= FOUND
['193']
193: function readBytes32Array(CalldataReader self) internal pure returns (CalldataReader, bytes32[] memory array) // <= FOUND
['35']
35: function pipe(bytes memory data, CalldataReader reader, bytes[] memory results) // <= FOUND
36: internal
37: pure
38: returns (CalldataReader)
39:
['143']
143: function _storeERC20Parameters(ERC20Parameters calldata params) internal // <= FOUND
['155']
155: function _storeMultiDepositorVaultParameters(IBeforeTransferHook beforeTransferHook) internal // <= FOUND
['140']
140: function _setProvisioner(address provisioner_) internal // <= FOUND
['431']
431: function _validatePriceUpdate(VaultPriceState storage vaultPriceState, uint256 price, uint256 timestamp) // <= FOUND
432: internal
433: view
434:
['456']
456: function _shouldPause(VaultPriceState storage state, uint256 price, uint32 timestamp) // <= FOUND
457: internal
458: view
459: returns (bool)
460:
['514']
514: function _solveDepositVaultAutoPrice( // <= FOUND
515: IERC20 token,
516: uint256 depositMultiplier,
517: Request calldata request,
518: uint256 priceAge,
519: uint256 index
520: ) internal returns (uint256 solverTip)
['583']
583: function _solveDepositVaultFixedPrice( // <= FOUND
584: IERC20 token,
585: uint256 depositMultiplier,
586: Request calldata request,
587: uint256 priceAge,
588: uint256 index
589: ) internal returns (uint256 solverTip)
['643']
643: function _solveRedeemVaultAutoPrice( // <= FOUND
644: IERC20 token,
645: uint256 redeemMultiplier,
646: Request calldata request,
647: uint256 priceAge,
648: uint256 index
649: ) internal returns (uint256 solverTip)
['710']
710: function _solveRedeemVaultFixedPrice( // <= FOUND
711: IERC20 token,
712: uint256 redeemMultiplier,
713: Request calldata request,
714: uint256 priceAge,
715: uint256 index
716: ) internal returns (uint256 solverTip)
['764']
764: function _solveDepositDirect(IERC20 token, Request calldata request) internal // <= FOUND
['803']
803: function _solveRedeemDirect(IERC20 token, Request calldata request) internal // <= FOUND
['37']
37: function _executeOperation(OperationPayable calldata operation) internal virtual // <= FOUND
['72']
72: function _handleMilkmanBeforeHook(address sellToken, uint256 sellAmount, address vault) internal // <= FOUND
['43']
43: function _setIsVaultUnitsTransferable(address vault, bool isTransferable) internal // <= FOUND
['143']
143: function _checkOperations(OperationPayable[] calldata operations) internal view override requiresVaultAuth(vault) // <= FOUND
['266']
266: function _getDecimals(address asset) internal view returns (uint8) // <= FOUND
Num of instances: 18
Click to show findings
['26']
26: constructor(address newOwner_, Authority authority_) Auth(newOwner_, authority_) { }
['21']
21: constructor(address initialOwner, Authority initialAuthority) Auth2Step(initialOwner, initialAuthority) { }
['16']
16: constructor(address initialOwner, Authority initialAuthority) Sweepable(initialOwner, initialAuthority) { }
['93']
93: constructor() Pausable() Auth2Step(msg.sender, Authority(address(0))) {
94:
95: BaseVaultParameters memory params = IBaseVaultFactory(msg.sender).baseVaultParameters();
96:
97: address initialOwner = params.owner;
98:
99: require(initialOwner != address(0), Aera__ZeroAddressOwner());
100:
101: transferOwnership(initialOwner);
102:
103: if (params.authority != Authority(address(0))) {
104:
105: setAuthority(params.authority);
106: }
107:
108:
109: WHITELIST = params.whitelist;
110:
111:
112: ISubmitHooks submitHooks_ = params.submitHooks;
113: if (address(submitHooks_) != address(0)) {
114: _setSubmitHooks(submitHooks_);
115: }
116: }
['35']
35: constructor(address owner_, Authority authority_, uint256 disputePeriod) BaseFeeCalculator(owner_, authority_) {
36:
37: require(disputePeriod <= MAX_DISPUTE_PERIOD, Aera__DisputePeriodTooLong());
38:
39:
40: DISPUTE_PERIOD = disputePeriod;
41: }
['50']
50: constructor() BaseVault() {
51:
52: FeeVaultParameters memory params = IFeeVaultDeployer(msg.sender).feeVaultParameters();
53:
54: IFeeCalculator feeCalculator_ = params.feeCalculator;
55: IERC20 feeToken_ = params.feeToken;
56:
57:
58: require(address(feeCalculator_) != address(0), Aera__ZeroAddressFeeCalculator());
59: require(address(feeToken_) != address(0), Aera__ZeroAddressFeeToken());
60:
61:
62: feeCalculator_.registerVault();
63:
64: address feeRecipient_ = params.feeRecipient;
65:
66: require(feeRecipient_ != address(0), Aera__ZeroAddressFeeRecipient());
67:
68:
69: feeRecipient = feeRecipient_;
70: feeCalculator = feeCalculator_;
71:
72:
73: FEE_TOKEN = feeToken_;
74: }
['20']
20: constructor(address numeraire_) {
21:
22: require(numeraire_ != address(0), Aera__ZeroAddressNumeraire());
23:
24:
25: NUMERAIRE = numeraire_;
26: }
['50']
50: constructor(address initialOwner, Authority initialAuthority, address deployDelegate)
51: Sweepable(initialOwner, initialAuthority)
52: {
53:
54: require(deployDelegate != address(0), Aera__ZeroAddressDeployDelegate());
55:
56:
57: _DEPLOY_DELEGATE = deployDelegate;
58: }
['42']
42: constructor()
43: ERC20(
44: IMultiDepositorVaultFactory(msg.sender).getERC20Name(),
45: IMultiDepositorVaultFactory(msg.sender).getERC20Symbol()
46: )
47: FeeVault()
48: {
49:
50: IBeforeTransferHook beforeTransferHook_ =
51: IMultiDepositorVaultFactory(msg.sender).multiDepositorVaultParameters();
52:
53:
54: _setBeforeTransferHook(beforeTransferHook_);
55: }
['64']
64: constructor(IERC20 numeraire, IOracleRegistry oracleRegistry, address owner_, Authority authority_)
65: BaseFeeCalculator(owner_, authority_)
66: HasNumeraire(address(numeraire))
67: {
68:
69: require(address(oracleRegistry) != address(0), Aera__ZeroAddressOracleRegistry());
70:
71:
72: ORACLE_REGISTRY = oracleRegistry;
73: }
['88']
88: constructor(
89: IPriceAndFeeCalculator priceAndFeeCalculator,
90: address multiDepositorVault,
91: address owner_,
92: Authority authority_
93: ) Auth2Step(owner_, authority_) {
94:
95: require(address(priceAndFeeCalculator) != address(0), Aera__ZeroAddressPriceAndFeeCalculator());
96: require(multiDepositorVault != address(0), Aera__ZeroAddressMultiDepositorVault());
97:
98:
99: PRICE_FEE_CALCULATOR = priceAndFeeCalculator;
100: MULTI_DEPOSITOR_VAULT = multiDepositorVault;
101: }
['20']
20: constructor() FeeVault() { }
['16']
16: constructor(address _owner, Authority _authority) {
17: owner = _owner;
18: authority = _authority;
19:
20: emit OwnershipTransferred(msg.sender, _owner);
21: emit AuthorityUpdated(msg.sender, _authority);
22: }
['27']
27: constructor(address initialOwner, Authority initialAuthority) Auth(initialOwner, initialAuthority) { }
['12']
12: constructor(address numeraire_) HasNumeraire(numeraire_) { }
['21']
21: constructor(IChainalysisSanctionsOracle oracle_) {
22:
23: require(address(oracle_) != address(0), AeraPeriphery__ZeroAddressBlacklistOracle());
24:
25:
26: BLACKLIST_ORACLE = oracle_;
27: }
['49']
49: constructor(address vault_, address milkmanRoot_) {
50:
51: require(vault_ != address(0), AeraPeriphery__ZeroAddressVault());
52:
53: require(milkmanRoot_ != address(0), AeraPeriphery__ZeroAddressMilkmanRoot());
54:
55:
56: vault = vault_;
57: milkmanRoot = IMilkman(milkmanRoot_);
58: }
['54']
54: constructor(address initialOwner, Authority initialAuthority, uint256 oracleUpdateDelay)
55: Auth2Step(initialOwner, initialAuthority)
56: {
57:
58: require(initialOwner != address(0), AeraPeriphery__ZeroAddressOwner());
59:
60: require(oracleUpdateDelay <= MAXIMUM_UPDATE_DELAY, AeraPeriphery__OracleUpdateDelayTooLong());
61:
62: ORACLE_UPDATE_DELAY = oracleUpdateDelay;
63: }
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: 5
Click to show findings
['61']
61: function setProtocolFees(uint16 tvl, uint16 performance) external requiresAuth { // <= FOUND
62:
63: require(tvl <= MAX_TVL_FEE, Aera__TvlFeeTooHigh());
64: require(performance <= MAX_PERFORMANCE_FEE, Aera__PerformanceFeeTooHigh());
65: require(protocolFeeRecipient != address(0), Aera__ZeroAddressProtocolFeeRecipient());
66:
67:
68: protocolFees = Fee({ tvl: tvl, performance: performance }); // <= FOUND
69:
70:
71: emit ProtocolFeesSet(tvl, performance);
72: }
['88']
88: function setVaultFees(address vault, uint16 tvl, uint16 performance) external requiresVaultAuth(vault) { // <= FOUND
89:
90: require(tvl <= MAX_TVL_FEE, Aera__TvlFeeTooHigh());
91: require(performance <= MAX_PERFORMANCE_FEE, Aera__PerformanceFeeTooHigh());
92:
93:
94: VaultAccruals storage vaultAccruals = _vaultAccruals[vault];
95: vaultAccruals.fees = Fee({ tvl: tvl, performance: performance }); // <= FOUND
96:
97:
98: emit VaultFeesSet(vault, tvl, performance);
99: }
['359']
359: function _executeSubmit(bytes32 root, CalldataReader reader, bool isCalledFromCallback) // <= FOUND
360: internal
361: returns (Approval[] memory approvals, uint256 approvalsLength, bytes[] memory results, CalldataReader newReader)
362: {
363: uint256 operationsLength;
364: (reader, operationsLength) = reader.readU8();
365:
366: results = new bytes[](operationsLength);
367:
368:
369: approvals = new Approval[](operationsLength);
370:
371:
372:
373: OperationContext memory ctx;
374: for (uint256 i = 0; i < operationsLength; ++i) {
375: (reader, ctx.target) = reader.readAddr();
376:
377: bytes memory callData;
378: (reader, callData) = reader.readBytesToMemory();
379:
380: reader = callData.pipe(reader, results);
381:
382: bool isStaticCall;
383: (reader, isStaticCall) = reader.readBool();
384: if (isStaticCall) {
385:
386: (bool success, bytes memory result) = ctx.target.staticcall(callData);
387:
388: require(success, Aera__SubmissionFailed(i, result));
389:
390: results[i] = result;
391: } else {
392: ctx.selector = bytes4(callData);
393: if (_isAllowanceSelector(ctx.selector)) {
394: unchecked {
395: approvals[approvalsLength++] = // <= FOUND
396: Approval({ token: ctx.target, spender: _extractApprovalSpender(callData) });
397: }
398: }
399:
400: (reader, ctx.callbackData) = _processExpectedCallback(reader, root);
401:
402: bytes memory extractedData;
403:
404: (reader, extractedData, ctx.configurableOperationHooks, ctx.operationHooks) =
405: _processBeforeOperationHooks(reader, callData, i);
406:
407: bytes32[] memory proof;
408: (reader, proof) = reader.readBytes32Array();
409:
410: (reader, ctx.value) = reader.readOptionalU256();
411:
412:
413: _verifyOperation(proof, root, _createMerkleLeaf(ctx, extractedData));
414:
415:
416: (bool success, bytes memory result) = ctx.target.call{ value: ctx.value }(callData);
417:
418: require(success, Aera__SubmissionFailed(i, result));
419:
420: if (ctx.callbackData != 0) {
421:
422: require(_hasCallbackBeenCalled(), Aera__ExpectedCallbackNotReceived());
423:
424: if (!isCalledFromCallback) {
425:
426: Approval[] memory callbackApprovals = _getCallbackApprovals();
427:
428:
429: _noPendingApprovalsInvariant(callbackApprovals, callbackApprovals.length);
430: }
431: }
432:
433:
434: _afterOperationHooks(ctx.operationHooks, callData, i);
435:
436: results[i] = result;
437: }
438: }
439:
440: return (approvals, approvalsLength, results, reader);
441: }
['194']
194: function _getCallbackApprovals() internal returns (Approval[] memory approvals) { // <= FOUND
195: uint256 lengthWithToken = APPROVALS_SLOT.asUint256().tload();
196: uint256 length = lengthWithToken >> ADDRESS_SIZE_BITS;
197: if (length == 0) return approvals;
198:
199:
200: APPROVALS_SLOT.asUint256().tstore(0);
201:
202: approvals = new Approval[](length);
203:
204: address token = address(uint160(lengthWithToken));
205:
206: uint256 slotUint256 = uint256(APPROVALS_SLOT);
207: address spender;
208: unchecked {
209: spender = bytes32(++slotUint256).asAddress().tload();
210: }
211:
212: approvals[0] = Approval({ token: token, spender: spender }); // <= FOUND
213:
214: for (uint256 i = 1; i < length; ++i) {
215: unchecked {
216: token = bytes32(++slotUint256).asAddress().tload();
217: spender = bytes32(++slotUint256).asAddress().tload();
218: }
219: approvals[i] = Approval({ token: token, spender: spender });
220: }
221: }
['70']
70: function addOracle(address base, address quote, IOracle oracle) external requiresAuth { // <= FOUND
71:
72: require(_oracles[base][quote].oracle == IOracle(address(0)), AeraPeriphery__OracleAlreadySet());
73:
74:
75: _oracles[base][quote] = OracleData({ // <= FOUND
76: isScheduledForUpdate: false,
77: isDisabled: false,
78: oracle: oracle,
79: pendingOracle: IOracle(address(0)),
80: commitTimestamp: 0
81: });
82:
83:
84: _validateOracle(oracle, base, quote);
85:
86:
87: emit OracleSet(base, quote, oracle);
88: }
Internal functions which are never used use unnecessary gas and should be safely removed.
Num of instances: 9
Click to show findings
['170']
170: function _getAllowedCallback() internal returns (address caller, bytes4 selector, uint16 userDataOffset) // <= FOUND
['181']
181: function _getAllowedMerkleRoot() internal returns (bytes32 root) // <= FOUND
['107']
107: function readI24(CalldataReader self) internal pure returns (CalldataReader, int24 value) // <= FOUND
['115']
115: function readU40(CalldataReader self) internal pure returns (CalldataReader, uint40 value) // <= FOUND
['123']
123: function readU64(CalldataReader self) internal pure returns (CalldataReader, uint64 value) // <= FOUND
['131']
131: function readU128(CalldataReader self) internal pure returns (CalldataReader, uint128 value) // <= FOUND
['155']
155: function readU24End(CalldataReader self) internal pure returns (CalldataReader, CalldataReader end) // <= FOUND
['16']
16: function isBeforeHook() internal view returns (bool) // <= FOUND
['22']
22: function isAfterHook() internal view returns (bool) // <= FOUND
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
['75']
75: function setVaultAccountant(address vault, address accountant) external requiresVaultAuth(vault) { // <= FOUND
76:
77: vaultAccountant[vault] = accountant;
78:
79:
80: emit VaultAccountantSet(vault, accountant); // <= FOUND
81: }
['88']
88: function setVaultFees(address vault, uint16 tvl, uint16 performance) external requiresVaultAuth(vault) { // <= FOUND
89:
90: require(tvl <= MAX_TVL_FEE, Aera__TvlFeeTooHigh());
91: require(performance <= MAX_PERFORMANCE_FEE, Aera__PerformanceFeeTooHigh());
92:
93:
94: VaultAccruals storage vaultAccruals = _vaultAccruals[vault];
95: vaultAccruals.fees = Fee({ tvl: tvl, performance: performance });
96:
97:
98: emit VaultFeesSet(vault, tvl, performance); // <= FOUND
99: }
['156']
156: function setUnitPrice(address vault, uint128 price, uint32 timestamp) external onlyVaultAccountant(vault) { // <= FOUND
157: VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
158:
159:
160: _validatePriceUpdate(vaultPriceState, price, timestamp);
161:
162: if (!vaultPriceState.paused) {
163: if (_shouldPause(vaultPriceState, price, timestamp)) {
164:
165: _setVaultPaused(vaultPriceState, vault, true);
166:
167: unchecked {
168:
169: vaultPriceState.accrualLag = uint24(timestamp - vaultPriceState.timestamp);
170: }
171: } else {
172:
173: _accrueFees(vault, price, timestamp);
174: }
175: } else {
176:
177: unchecked {
178:
179: vaultPriceState.accrualLag = uint24(timestamp - vaultPriceState.timestamp + vaultPriceState.accrualLag);
180: }
181: }
182:
183:
184: vaultPriceState.unitPrice = price;
185: vaultPriceState.timestamp = timestamp;
186:
187:
188: emit UnitPriceUpdated(vault, price, timestamp); // <= FOUND
189: }
['28']
28: function setWhitelisted(address addr, bool isAddressWhitelisted) external requiresAuth { // <= FOUND
29:
30: if (isAddressWhitelisted) {
31: whitelist.set(addr, IS_WHITELISTED_FLAG);
32: } else {
33: whitelist.remove(addr);
34: }
35:
36:
37: emit WhitelistSet(addr, isAddressWhitelisted); // <= FOUND
38: }
['40']
40: function setMaxDailyLoss(address vault, uint128 maxLoss) external requiresVaultAuth(vault) { // <= FOUND
41:
42: _vaultStates[vault].maxDailyLossInNumeraire = maxLoss;
43:
44:
45: emit UpdateMaxDailyLoss(vault, maxLoss); // <= FOUND
46: }
['49']
49: function setMaxSlippagePerTrade(address vault, uint16 newMaxSlippage) external requiresVaultAuth(vault) { // <= FOUND
50:
51: require(newMaxSlippage < MAX_BPS, AeraPeriphery__MaxSlippagePerTradeTooHigh(newMaxSlippage));
52:
53:
54: _vaultStates[vault].maxSlippagePerTrade = newMaxSlippage;
55:
56:
57: emit UpdateMaxSlippage(vault, newMaxSlippage); // <= FOUND
58: }
['510']
510: function _setSubmitHooks(ISubmitHooks submitHooks_) internal { // <= FOUND
511:
512: submitHooks = submitHooks_;
513:
514:
515: emit SubmitHooksSet(address(submitHooks_)); // <= FOUND
516: }
['130']
130: function _setBeforeTransferHook(IBeforeTransferHook hook_) internal { // <= FOUND
131:
132: beforeTransferHook = hook_;
133:
134:
135: emit BeforeTransferHookSet(address(hook_)); // <= FOUND
136: }
['376']
376: function _setVaultPaused(VaultPriceState storage vaultPriceState, address vault, bool paused) internal { // <= FOUND
377:
378: vaultPriceState.paused = paused;
379:
380:
381: emit VaultPausedChanged(vault, paused); // <= FOUND
382: }
['43']
43: function _setIsVaultUnitsTransferable(address vault, bool isTransferable) internal { // <= FOUND
44:
45: isVaultUnitTransferable[vault] = isTransferable;
46:
47:
48: emit VaultUnitTransferableSet(vault, isTransferable); // <= FOUND
49: }
['23']
23: function updateWhitelist(address vault, address[] calldata addresses, bool isWhitelisted) // <= FOUND
24: external
25: requiresVaultAuth(vault)
26: {
27: uint256 length = addresses.length;
28: address addr;
29:
30: for (uint256 i; i < length; i++) {
31: addr = addresses[i];
32:
33:
34: whitelist[vault][addr] = isWhitelisted;
35: }
36:
37:
38: emit VaultWhitelistUpdated(vault, addresses, isWhitelisted); // <= FOUND
39: }
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: 2
Click to show findings
['165']
165:
166: emit GuardianRootSet(guardian, bytes32(0)); // <= FOUND
['178']
178:
179: emit GuardianRootSet(guardian, bytes32(0)); // <= FOUND
Short-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: 1
Click to show findings
['87']
87: require( // <= FOUND
88: isAuthorized(msg.sender, msg.sig) || guardianRoots.contains(msg.sender), Aera__CallerIsNotAuthOrGuardian()
89: );
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: 14
Click to show findings
['390']
390: results[i] = result; // <= FOUND
['155']
155: approval = approvals[i]; // <= FOUND
['219']
219: approvals[i] = Approval({ token: token, spender: spender }); // <= FOUND
['305']
305: request = requests[i]; // <= FOUND
['365']
365: if (_isRequestTypeDeposit(requests[i].requestType)) { // <= FOUND
['369']
369:
370: require(!_isRequestTypeAutoPrice(requests[i].requestType), Aera__AutoPriceSolveNotAllowed()); // <= FOUND
['371']
371:
372: _solveDepositDirect(token, requests[i]); // <= FOUND
['378']
378:
379: _solveRedeemDirect(token, requests[i]); // <= FOUND
['31']
31: tokenAmount = tokenAmounts[i]; // <= FOUND
['67']
67: operation = operations[i]; // <= FOUND
['28']
28:
29: _executeOperation(operations[i]); // <= FOUND
['42']
42: target = operations[i].target; // <= FOUND
['43']
43: data = operations[i].data; // <= FOUND
['31']
31: addr = addresses[i]; // <= FOUND
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: 18
Click to show findings
['175']
175: isRemoved = true; // <= FOUND
['212']
212:
213: asyncDepositHashes[depositHash] = true; // <= FOUND
['253']
253:
254: asyncRedeemHashes[redeemHash] = true; // <= FOUND
['315']
315: depositsExist = true; // <= FOUND
['486']
486:
487: syncDepositHashes[depositHash] = true; // <= FOUND
['65']
65:
66: _canCall[caller][targetAndSelector] = true; // <= FOUND
['103']
103: oracleData.isScheduledForUpdate = true; // <= FOUND
['166']
166:
167: oracleData.isDisabled = true; // <= FOUND
['170']
170:
171: syncDepositHashes[depositHash] = false; // <= FOUND
['275']
275:
276: asyncDepositHashes[requestHash] = false; // <= FOUND
['284']
284:
285: asyncRedeemHashes[requestHash] = false; // <= FOUND
['548']
548:
549: asyncDepositHashes[depositHash] = false; // <= FOUND
['674']
674:
675: asyncRedeemHashes[redeemHash] = false; // <= FOUND
['548']
548:
549: asyncDepositHashes[depositHash] = false; // <= FOUND
['674']
674:
675: asyncRedeemHashes[redeemHash] = false; // <= FOUND
['76']
76:
77: _canCall[caller][targetAndSelector] = false; // <= FOUND
['128']
128: oracleData.isScheduledForUpdate = false; // <= FOUND
['129']
129: oracleData.isDisabled = false; // <= FOUND
Emitting events inside loops can significantly increase gas costs in Ethereum smart contracts, as each event emission consumes gas. This practice can quickly escalate transaction fees, especially with a high number of iterations. To optimize for efficiency and cost, it's advisable to minimize event emissions within loops, possibly aggregating data to emit a single event post-loop or reconsidering the design to reduce looped emissions. This approach helps maintain manageable transaction costs and enhances contract performance.
Num of instances: 4
Click to show findings
['304']
304: for (uint256 i = 0; i < length; i++) {
305: request = requests[i];
306: if (_isRequestTypeDeposit(request.requestType)) {
307:
308: if (!tokenDetails.asyncDepositEnabled) {
309:
310: emit AsyncDepositDisabled(i); // <= FOUND
311: continue;
312: }
313:
314: if (!depositsExist) {
315: depositsExist = true;
316: token.forceApprove(MULTI_DEPOSITOR_VAULT, type(uint256).max);
317: }
318:
319: if (_isRequestTypeAutoPrice(request.requestType)) {
320:
321: solverTip +=
322: _solveDepositVaultAutoPrice(token, tokenDetails.depositMultiplier, request, priceAge, i);
323: } else {
324:
325: solverTip +=
326: _solveDepositVaultFixedPrice(token, tokenDetails.depositMultiplier, request, priceAge, i);
327: }
328: