| Number | Details | Instances |
|---|---|---|
| [High-1] | Important function with no access control | 1 |
| [High-2] | Users can manipulate mapping values for other users | 3 |
| Number | Details | Instances |
|---|---|---|
| [Medium-1] | Privileged functions can create points of failure | 26 |
| [Medium-2] | safeMint should be used in place of mint | 1 |
| [Medium-3] | ERC721 (NFT) tokens minted to a smart contract address which can't handle ERC721 tokens will be permanently stuck | 2 |
| [Medium-4] | Contract can't receive NFTs sent with safeTransferFrom method | 3 |
| [Medium-5] | Using block.number for time comparisons | 4 |
| [Medium-6] | Missing checks for whether the L2 Sequencer is active | 2 |
| [Medium-7] | Withdraw/Redeem functions can fail due to blocked USDT/USDC accounts | 2 |
| [Medium-8] | 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 | 2 |
| [Medium-9] | Withdrawal mechanism can be Dos'd with dust withdrawals | 5 |
| [Medium-10] | Deposit mechanism can be DOS'd with dust deposits due to lack of minAmount checks against deposit amount | 2 |
| [Medium-11] | If protocol fees are too high, user's rewards can be permanently lost in instances where they are not enough to cover fees | 1 |
| [Medium-12] | Transfer flow from msg.sender to protocol favours msg sender by its amount value rounding down when it should round up to prevent exploitation |
1 |
| [Medium-13] | Ownable is inherited but not used | 1 |
| Number | Details | Instances |
|---|---|---|
| [Low-1] | Potential division by zero should have zero checks in place | 5 |
| [Low-2] | Gas grief possible on unsafe external calls | 9 |
| [Low-3] | Missing checks for address(0x0) when updating address state variables | 3 |
| [Low-4] | Code does not follow the best practice of check-effects-interaction | 13 |
| [Low-5] | Solidity version 0.8.23 won't work on all chains due to MCOPY | 7 |
| [Low-6] | Some tokens may revert when zero value transfers are made | 7 |
| [Low-7] | Unsafe uint to int conversion | 14 |
| [Low-8] | Experimental functionality should not be used in production code | 1 |
| [Low-9] | Empty receive functions can cause gas issues | 1 |
| [Low-10] | The call abi.encodeWithSelector is not type safe | 16 |
| [Low-11] | Upgradable contracts should have a __gap variable | 1 |
| [Low-12] | Using > when declaring solidity version without specifying an upperbound can cause future vulnerabilities | 2 |
| [Low-13] | The nonReentrant modifier should be first in a function declaration | 1 |
| [Low-14] | Initializer function can be front run | 1 |
| [Low-15] | Use _disableInitializers() to ensure initialization occurs once | 1 |
| [Low-16] | Minting to the zero address should be avoided | 1 |
| [Low-17] | Loss of precision | 11 |
| [Low-18] | Missing zero address check in constructor | 3 |
| [Low-19] | Return values of transfer()/transferFrom() not checked | 2 |
| [Low-20] | The image field for NFT metadata should be used for URLs | 1 |
| [Low-21] | TokenURI does not revert if NFT doesn't exist | 2 |
| [Low-22] | safeApprove is deprecated | 2 |
| [Low-23] | Constant decimal values | 2 |
| [Low-24] | NFT doesn't handle hard forks | 2 |
| [Low-25] | Revert on Transfer to the Zero Address | 3 |
| [Low-26] | Chainlink price feed decimals not checked | 2 |
| [Low-27] | Chainlink answer is not compared against min/max values | 2 |
| [Low-28] | No access control on receive/payable fallback | 1 |
| [Low-29] | External call recipient may consume all transaction gas | 1 |
| [Low-30] | Events may be emitted out of order due to code not follow the best practice of check-effects-interaction | 2 |
| [Low-31] | Critical functions should have a timelock | 20 |
| [Low-32] | Unbounded loop may run out of gas | 1 |
| [Low-33] | Consider implementing two-step procedure for updating protocol addresses | 10 |
| [Low-34] | Avoid mutating function parameters | 2 |
| [Low-35] | Missing contract-existence checks before low-level calls | 4 |
| [Low-36] | Low Level Calls to Custom Addresses | 10 |
| [Low-37] | transfer will always revert as the IERC20 interface mismatch | 2 |
| [Low-38] | No license selected for project | 1 |
| [Low-39] | Constructors missing validation | 3 |
| [Low-40] | Inconsistent use of _msgSender() and msg.sender in contract | 1 |
| [Low-41] | State variables not capped at reasonable values | 1 |
| [Low-42] | Solidity file is susceptible to .selector-related optimizer bug due to the version used | 4 |
| [Low-43] | Chainlink oracle will return the wrong price if the aggregator hits minAnswer | 2 |
| [Low-44] | Constructor doesn't set initial owner | 2 |
| [Low-45] | Functions calling contracts/addresses with transfer hooks are missing reentrancy guards | 14 |
| [Low-46] | Contract which utilized ERC20 defines a standard ERC20 function who's selector does not match the ERC20 specification thus potentially not making it callable by external contracts. |
1 |
| [Low-47] | Contract which utilized ERC721 defines a standard ERC721 function who's selector does not match the ERC721 specification thus potentially not making it callable by external contracts. |
1 |
| [Low-48] | Solidity version 0.8.20 won't work on all chains due to PUSH0 | 6 |
| [Low-49] | Using block.number is not fully L2 compatible | 15 |
| [Low-50] | Bps variable not checked that they are below 1e4 | 4 |
| [Low-51] | Missing events in functions that are either setters, privileged or voting related | 13 |
| [Low-52] | Unsafe use of transfer()/transferFrom() with IERC20 | 2 |
| [Low-53] | Indexed dynamic arrays in events can result in data loss | 1 |
| [Low-54] | Avoid floating pragma in non interface/library files | 4 |
| [Low-55] | Read only reentrancy risk detected | 13 |
| [Low-56] | Consider a uptime feed on L2 deployments to prevent issues caused by downtime | 10 |
| Number | Details | Instances |
|---|---|---|
| [NonCritical-1] | Subtraction may underflow if multiplication is too large | 3 |
| [NonCritical-2] | Pure function is not defined as such in interface | 1 |
| [NonCritical-3] | Contracts with multiple onlyXYZ modifiers where XYZ is a role can introduce complexities when managing privileges | 1 |
| [NonCritical-4] | Local variable shadowing | 11 |
| [NonCritical-5] | Greater than comparisons made on state uints that can be set to max | 12 |
| [NonCritical-6] | Unchecked blocks with subtractions may underflow | 1 |
| [NonCritical-7] | Floating pragma should be avoided | 7 |
| [NonCritical-8] | Interfaces should be declared in a separate file | 1 |
| [NonCritical-9] | Require statements should have error string | 9 |
| [NonCritical-10] | Events regarding state variable changes should emit the previous state variable value | 1 |
| [NonCritical-11] | In functions which accept an address as a parameter, there should be a zero address check to prevent bugs | 37 |
| [NonCritical-12] | Default int values are manually set | 2 |
| [NonCritical-13] | Ownable2Step should be used in place of Ownable | 2 |
| [NonCritical-14] | Revert statements within external and public functions can be used to perform DOS attacks | 16 |
| [NonCritical-15] | Functions which are either private or internal should have a preceding _ in their name | 47 |
| [NonCritical-16] | Private and internal state variables should have a preceding _ in their name unless they are constants | 4 |
| [NonCritical-17] | Contract lines should not be longer than 120 characters for readability | 71 |
| [NonCritical-18] | Avoid updating storage when the value hasn't changed | 13 |
| [NonCritical-19] | Specific imports should be used where possible so only used code is imported | 20 |
| [NonCritical-20] | Old Solidity version | 4 |
| [NonCritical-21] | Not all event definitions are utilizing indexed variables. | 4 |
| [NonCritical-22] | Explicitly define visibility of state variables to prevent misconceptions on what can access the variable | 4 |
| [NonCritical-23] | Contracts should have all public/external functions exposed by interfaces | 67 |
| [NonCritical-24] | Functions within contracts are not ordered according to the solidity style guide | 3 |
| [NonCritical-25] | Emits without msg.sender parameter | 4 |
| [NonCritical-26] | All interfaces used within a project should be imported | 5 |
| [NonCritical-27] | A function which defines named returns in it's declaration doesn't need to use return | 12 |
| [NonCritical-28] | Unused state variables present | 3 |
| [NonCritical-29] | Constants should be on the left side of the comparison | 40 |
| [NonCritical-30] | Defined named returns not used within function | 6 |
| [NonCritical-31] | Initialize functions do not emit an event | 1 |
| [NonCritical-32] | Both immutable and constant state variables should be CONSTANT_CASE | 7 |
| [NonCritical-33] | Overly complicated arithmetic | 3 |
| [NonCritical-34] | Use of non-named numeric constants | 148 |
| [NonCritical-35] | Redundant else statement | 2 |
| [NonCritical-36] | Use immutable not constant for keccak state variables | 2 |
| [NonCritical-37] | Event emit should emit a parameter | 1 |
| [NonCritical-38] | Unused structs present | 1 |
| [NonCritical-39] | Two or more functions contain the exact same code | 1 |
| [NonCritical-40] | Empty bytes check is missing | 1 |
| [NonCritical-41] | Return bool not explicit | 1 |
| [NonCritical-42] | call bypasses function existence check, type checking and argument packing | 6 |
| [NonCritical-43] | Cyclomatic complexity in functions | 10 |
| [NonCritical-44] | Incorrect withdraw declaration | 2 |
| [NonCritical-45] | Unused events present | 1 |
| [NonCritical-46] | Unused import | 5 |
| [NonCritical-47] | Unchecked increments can overflow | 3 |
| [NonCritical-48] | Unchecked decrements can underflow | 1 |
| [NonCritical-49] | Don't only depend on Solidity's arithmetic ordering. | 11 |
| [NonCritical-50] | A event should be emitted if a non immutable state variable is set in a constructor | 3 |
| [NonCritical-51] | Non constant/immutable state variables are missing a setter post deployment | 2 |
| [NonCritical-52] | Contracts use both += 1 and ++ (-- and -= 1) | 1 |
| [NonCritical-53] | Addition/multiplication in unchecked block is unsafe | 2 |
| [NonCritical-54] | Use type(uint).max in place of 2**n - 1 | 24 |
| [NonCritical-55] | Use -= for mappings | 1 |
| [NonCritical-56] | Use transfer libraries instead of low level calls | 2 |
| [NonCritical-57] | Floating pragma defined inconsistently | 9 |
| [NonCritical-58] | Use 'using' keyword when using specific imports rather than calling the specific import directly | 42 |
| [NonCritical-59] | Try catch statement without human readable error | 1 |
| [NonCritical-60] | Inconsistent checks of address params against address(0) | 1 |
| [NonCritical-61] | Avoid declaring variables with the names of defined functions within the project | 1 |
| [NonCritical-62] | Constructors should emit an event | 4 |
| [NonCritical-63] | Contract and Abstract files should have a fixed compiler version | 10 |
| [NonCritical-64] | int/uint passed into abi.encodePacked without casting to a string. | 1 |
| [NonCritical-65] | Avoid external calls in modifiers | 1 |
| [NonCritical-66] | Events should have parameters | 1 |
| [NonCritical-67] | Errors should have parameters | 24 |
| [NonCritical-68] | Consider using OpenZeppelins SafeCall library when making calls to arbitrary contracts | 10 |
| [NonCritical-69] | Avoid using 'owner' or '_owner' as a parameter name | 3 |
| [NonCritical-70] | Avoid arithmetic directly within array indices | 4 |
| [NonCritical-71] | Memory-safe annotation missing | 2 |
| [NonCritical-72] | Constant state variables defined more than once | 6 |
| [NonCritical-73] | Unnecessary struct attribute prefix | 1 |
| [NonCritical-74] | ERC777 tokens can introduce reentrancy risks | 6 |
| [NonCritical-75] | Avoid assembly in view or pure functions |
3 |
| [NonCritical-76] | It is best practise to initialize a local variable when they are defined | 11 |
| [NonCritical-77] | Local variables declared without ever initializing them. If these are meant to be the default values, make this clear by initializing them as such. | 3 |
| [NonCritical-78] | Contract doesn't follow upgradeable paradigm despite importing Upgradeable OpenZeppelin libraries | 1 |
| [NonCritical-79] | Custom implementation of a roundUp operation, consider using mulDivUp instead |
1 |
| [NonCritical-80] | ERC20 Permit is vulnerable to front-running | 1 |
| Number | Details | Instances | Gas |
|---|---|---|---|
| [Gas-1] | State variables used within a function more than once should be cached to save gas | 4 | 1600 |
| [Gas-2] | Shortcircuit rules can be be used to optimize some gas usage | 2 | 16800 |
| [Gas-3] | x + y is more efficient than using += for state variables (likewise for -=) | 9 | 405 |
| [Gas-4] | There is a 32 byte length threshold for error strings, strings longer than this consume more gas | 1 | 14 |
| [Gas-5] | Public functions not used internally can be marked as external to save gas | 4 | 0.0 |
| [Gas-6] | Usage of smaller uint/int types causes overhead | 3 | 495 |
| [Gas-7] | Use != 0 instead of > 0 | 150 | 67500 |
| [Gas-8] | Integer increments by one can be unchecked to save on gas fees | 2 | 480 |
| [Gas-9] | Default bool values are manually reset | 1 | 0.0 |
| [Gas-10] | Function calls within for loops | 1 | 0.0 |
| [Gas-11] | For loops in public or external functions should be avoided due to high gas costs and possible DOS | 1 | 0.0 |
| [Gas-12] | Use assembly to check for the zero address | 14 | 0.0 |
| [Gas-13] | Some error strings are not descriptive | 6 | 0.0 |
| [Gas-14] | Divisions which do not divide by -X cannot overflow or underflow so such operations can be unchecked to save gas | 1 | 0.0 |
| [Gas-15] | Can transfer 0 | 1 | 0.0 |
| [Gas-16] | multiplications of powers of 2 can be replaced by a left shift operation to save gas | 2 | 0.0 |
| [Gas-17] | Structs can be packed into fewer storage slots | 5 | 62500 |
| [Gas-18] | Don't use _msgSender() if not supporting EIP-2771 | 3 | 144 |
| [Gas-19] | Private functions used once can be inlined | 2 | 0.0 |
| [Gas-20] | Consider upgrading to ERC721A | 2 | 40000 |
| [Gas-21] | Redundant safeMint emit | 1 | 750 |
| [Gas-22] | Use selfBalance() in place of address(this).balance | 2 | 3200 |
| [Gas-23] | Use assembly to emit events | 41 | 63878 |
| [Gas-24] | Use solady library where possible to save gas | 15 | 225000 |
| [Gas-25] | Use assembly in place of abi.decode to extract calldata values more efficiently | 4 | 0.0 |
| [Gas-26] | Struct variables can be packed into fewer storage slots by truncating timestamp bytes | 1 | 2500 |
| [Gas-27] | Using private rather than public for constants and immutables, saves gas | 7 | 0.0 |
| [Gas-28] | Mark Functions That Revert For Normal Users As payable | 26 | 16900 |
| [Gas-29] | Lack of unchecked in loops | 2 | 480 |
| [Gas-30] | Use assembly to validate msg.sender | 1 | 0.0 |
| [Gas-31] | Use += for mappings | 1 | 0.0 |
| [Gas-32] | Simple checks for zero uint can be done using assembly to save gas | 131 | 102966 |
| [Gas-33] | Use Unchecked for Divisions on Constant or Immutable Values | 1 | 0.0 |
| [Gas-34] | Using nested if to save gas | 12 | 864 |
| [Gas-35] | Optimize Storage with Byte Truncation for Time Related State Variables | 2 | 8000 |
| [Gas-36] | Using delete instead of setting mapping to 0 saves gas | 1 | 5 |
| [Gas-37] | Stack variable cost less than state variables while used in emiting event | 2 | 36 |
| [Gas-38] | Low level call can be optimized with assembly | 15 | 55800 |
| [Gas-39] | Inline modifiers used only once | 1 | 0.0 |
| [Gas-40] | Use s.x = s.x + y instead of s.x += y for memory structs (same for -= etc) | 2 | 400 |
| [Gas-41] | Solidity versions 0.8.19 and above are more gas efficient | 4 | 16000 |
| [Gas-42] | Calling .length in a for loop wastes gas | 2 | 388 |
| [Gas-43] | Internal functions only used once can be inlined to save gas | 21 | 13230 |
| [Gas-44] | Constructors can be marked as payable to save deployment gas | 10 | 0.0 |
| [Gas-45] | Internal functions never used once can be removed | 9 | 0.0 |
| [Gas-46] | Only emit event in setter function if the state variable was changed | 13 | 0.0 |
| [Gas-47] | It is a waste of GAS to emit variable literals | 2 | 32 |
| [Gas-48] | Use OZ Array.unsafeAccess() to avoid repeated array length checks | 1 | 2100 |
| [Gas-49] | State variable read in a loop | 1 | 5794 |
| [Gas-50] | Use uint256(1)/uint256(2) instead of true/false to save gas for changes | 5 | 425000 |
| [Gas-51] | Consider pre-calculating the address of address(this) to save gas | 15 | 0.0 |
| [Gas-52] | Use 'storage' instead of 'memory' for struct/array state variables | 1 | 2100 |
| [Gas-53] | Use constants instead of type(uint).max | 5 | 0.0 |
| [Gas-54] | Unused SafeCast imported into contract through 'using for' statement | 1 | 0.0 |
| [Gas-55] | Using named returns for pure and view functions is cheaper than using regular returns | 59 | 90506 |
Important usually admin protected functions such as pause/unpause, setters, mint/burn functions must have adequate access control. Failing to do so is a critical security vulnerability as it allows unintended users from calling said functions.
Num of instances: 1
Click to show findings
['105']
105: function burnFrom(address account, uint256 amount) public virtual { // <= FOUND
106: uint256 decreasedAllowance = allowance(account, _msgSender()) - amount;
107:
108: _approve(account, _msgSender(), decreasedAllowance);
109: _burn(account, amount);
110: }Functions that allow users to specify an arbitrary address as an input parameter, particularly when used to modify critical state variables or mappings, pose significant security risks. For example, if a function allows a user to pass an address parameter (e.g., from) and modifies a mapping like rewards[from] = 0, malicious users can exploit this to reset or alter values for other users. This enables unauthorized control over sensitive data such as rewards, balances, or permissions, leading to potential loss of funds or disruption in protocol operations.
Furthermore, even if such functions are restricted to admins, it introduces the risk of abuse by malicious administrators or exploitation in the event of an admin key compromise. If an admin can arbitrarily modify user data, they could intentionally or unintentionally cause significant harm to the protocol or its users, such as resetting rewards, draining funds, or disrupting balances.
To mitigate these risks, ensure that users can only modify their own data through strict validation, such as require(msg.sender == from), and minimize admin powers by decentralizing sensitive actions through governance mechanisms or multi-signature wallets. For mappings that must be modifiable by admins, consider logging all changes and implementing a timelock for added transparency and security.
Num of instances: 3
Click to show findings
['65']
65: function setWhitelist(address _toWhitelist, bool _state) external {
66: whiteList[_toWhitelist] = _state; // <= FOUND
67: }['73']
73: function pauseAlchemist(address _toPause, bool _state) external {
74: paused[_toPause] = _state; // <= FOUND
75: }['81']
81: function setCeiling(address _toSetCeiling, uint256 _ceiling) external {
82: ceiling[_toSetCeiling] = _ceiling; // <= FOUND
83: }Ensure such accounts are protected and consider implementing multi sig to prevent a single point of failure
Num of instances: 26
Click to show findings
['57']
57: function setAuthorization(address account, bool status) external onlyOwner // <= FOUND['182']
182: function setAlchemistPositionNFT(address nft) external onlyAdmin // <= FOUND['195']
195: function setAlchemistFeeVault(address value) external onlyAdmin // <= FOUND['204']
204: function setPendingAdmin(address value) external onlyAdmin // <= FOUND['211']
211: function acceptAdmin() external // <= FOUND['226']
226: function setDepositCap(uint256 value) external onlyAdmin // <= FOUND['234']
234: function setProtocolFeeReceiver(address value) external onlyAdmin // <= FOUND['242']
242: function setProtocolFee(uint256 fee) external onlyAdmin // <= FOUND['250']
250: function setLiquidatorFee(uint256 fee) external onlyAdmin // <= FOUND['258']
258: function setTokenAdapter(address value) external onlyAdmin // <= FOUND['266']
266: function setTransmuter(address value) external onlyAdmin // <= FOUND['277']
277: function setGuardian(address guardian, bool isActive) external onlyAdmin // <= FOUND['285']
285: function setMinimumCollateralization(uint256 value) external onlyAdmin // <= FOUND['293']
293: function setGlobalMinimumCollateralization(uint256 value) external onlyAdmin // <= FOUND['300']
300: function setCollateralizationLowerBound(uint256 value) external onlyAdmin // <= FOUND['308']
308: function pauseDeposits(bool isPaused) external onlyAdminOrGuardian // <= FOUND['314']
314: function pauseLoans(bool isPaused) external onlyAdminOrGuardian // <= FOUND['116']
116: function setAlchemist(address value) external onlyAdmin // <= FOUND['123']
123: function setDepositCap(uint256 cap) external onlyAdmin // <= FOUND['131']
131: function setTransmutationFee(uint256 fee) external onlyAdmin // <= FOUND['139']
139: function setExitFee(uint256 fee) external onlyAdmin // <= FOUND['147']
147: function setTransmutationTime(uint256 time) external onlyAdmin // <= FOUND['234']
234: function setProtocolFeeReceiver(address value) external onlyAdmin // <= FOUND['58']
58: function _onlyAdmin() internal view // <= FOUND['81']
81: function withdraw(address recipient, uint256 amount) external override onlyAuthorized nonReentrant // <= FOUND['37']
37: function withdraw(address recipient, uint256 amount) external override onlyAuthorized // <= FOUNDSafeMint should be used in place of mint in Solidity contracts to enhance security and error handling during token creation. The safeMint function includes additional checks and validations compared to the basic mint function, ensuring that tokens are minted only to valid, non-zero addresses. This prevents tokens from being accidentally minted to the zero address, which would render them irretrievable and result in a loss of value. Another critical issue here is that a loss of NFTs can occur when NFTs are transfered to contracts which are not designed to hold or handle them, this is precisily why the safeMint function checks if the receiving contract has a correct onERC721Recieved function, this is to ensure that the receiver contract can handle ERC721 tokens.
Num of instances: 1
[Medium-3] ERC721 (NFT) tokens minted to a smart contract address which can't handle ERC721 tokens will be permanently stuck
Both ERC721 and ERC1155 implement ERC165. Therefore the supportsInterface function present in ERC165 can be used to tell if a smart contract address of which a NFT is being minted to supports ERC721. Please refer to EIP-165 for more details.
Num of instances: 2
Click to show findings
['16']
16: contract AlchemistV3Position is ERC721Enumerable { // <= FOUND
17: using Strings for uint256;
18:
57: }
58:
65: function mint(address to) external onlyAlchemist returns (uint256) { // <= FOUND
['21']
21: contract Transmuter is ITransmuter, ERC721 { // <= FOUND
22: using StakingGraph for StakingGraph.Graph;
23: using SafeCast for int256;
192: totalLocked += syntheticDepositAmount;
193:
194: _mint(msg.sender, _nonce); // <= FOUND
The contract under consideration is designed to receive and store ERC721 tokens. However, certain smart wallets or contracts might utilize the safeTransferFrom method to send an NFT. The safeTransferFrom method checks for the implementation of the onERC721Received method when the recipient is a contract. This is to ensure that the recipient contract can appropriately handle ERC721 tokens. Therefore, it's essential for the contract to extend the ERC721Holder contract from OpenZeppelin. The ERC721Holder contract has the onERC721Received method implemented, which allows the contract to correctly receive and store ERC721 tokens sent using safeTransferFrom. Do note that the current OZ implementation ERC721 includes a safeTransferFrom function.
Num of instances: 3
Click to show findings
['16']
16: contract AlchemistV3Position is ERC721Enumerable // <= FOUND['23']
23: contract AlchemistV3 is IAlchemistV3, Initializable ['21']
21: contract Transmuter is ITransmuter, ERC721 // <= FOUNDUsing block.number in time comparisons can break compatibility with some L2s such as Optimism whos time between blocks differ from Ethereum and isn't constant. Consider using block.timestamp to prevent compatibility issues
Num of instances: 4
Click to show findings
['974']
974: function _sync(uint256 tokenId) internal { // <= FOUND
975: Account storage account = _accounts[tokenId];
976: RedemptionInfo memory previousRedemption = _redemptions[lastRedemptionBlock];
977:
978: uint256 debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, _earmarkWeight - account.lastAccruedEarmarkWeight);
979: uint256 earmarkedState = account.earmarked + debtToEarmark;
980:
981:
982: uint256 earmarkToRedeem;
983: uint256 earmarkPreviousState;
984: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) { // <= FOUND
985: debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, previousRedemption.earmarkWeight - account.lastAccruedEarmarkWeight);
986:
987: earmarkPreviousState = account.earmarked + debtToEarmark;
988: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight);
989: } else {
990: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedState, _redemptionWeight - account.lastAccruedRedemptionWeight);
991: }
992:
993: uint256 collateralToRemove = PositionDecay.ScaleByWeightDelta(account.rawLocked, _collateralWeight - account.lastCollateralWeight);
994:
995:
996: account.earmarked = earmarkedState - earmarkToRedeem;
997: account.debt -= earmarkToRedeem;
998: account.lastAccruedRedemptionWeight = _redemptionWeight;
999: account.lastAccruedEarmarkWeight = _earmarkWeight;
1000:
1001: account.collateralBalance -= collateralToRemove;
1002: account.rawLocked -= collateralToRemove;
1003: account.lastCollateralWeight = _collateralWeight;
1004: }['1007']
1007: function _earmark() internal { // <= FOUND
1008: if (totalDebt == 0) return;
1009:
1010: if (block.number > lastEarmarkBlock) { // <= FOUND
1011: uint256 amount = ITransmuter(transmuter).queryGraph(lastEarmarkBlock + 1, block.number);
1012: if (amount > 0) {
1013: _earmarkWeight += PositionDecay.WeightIncrement(amount, totalDebt - cumulativeEarmarked);
1014: cumulativeEarmarked += amount;
1015: }
1016:
1017: lastEarmarkBlock = block.number;
1018: }
1019: }['1028']
1028: function _calculateUnrealizedDebt(uint256 tokenId) internal view returns (uint256, uint256, uint256) { // <= FOUND
1029: Account storage account = _accounts[tokenId];
1030: RedemptionInfo memory previousRedemption = _redemptions[lastRedemptionBlock];
1031:
1032: uint256 amount;
1033: uint256 earmarkWeightCopy = _earmarkWeight;
1034:
1035:
1036: if (block.number > lastEarmarkBlock) { // <= FOUND
1037: amount = ITransmuter(transmuter).queryGraph(lastEarmarkBlock + 1, block.number);
1038: if (amount > 0) {
1039: earmarkWeightCopy += PositionDecay.WeightIncrement(amount, totalDebt - cumulativeEarmarked);
1040: }
1041: }
1042:
1043: uint256 debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, earmarkWeightCopy - account.lastAccruedEarmarkWeight);
1044: uint256 earmarkedState = account.earmarked + debtToEarmark;
1045:
1046:
1047: uint256 earmarkedPreviousState;
1048: uint256 earmarkToRedeem;
1049: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) {
1050: debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, previousRedemption.earmarkWeight - account.lastAccruedEarmarkWeight);
1051:
1052: earmarkedPreviousState = account.earmarked + debtToEarmark;
1053: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight);
1054: } else {
1055: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedState, _redemptionWeight - account.lastAccruedRedemptionWeight);
1056: }
1057:
1058: uint256 collateralToRemove = PositionDecay.ScaleByWeightDelta(account.rawLocked, _collateralWeight - account.lastCollateralWeight);
1059:
1060: return (account.debt - earmarkToRedeem, earmarkedState - earmarkToRedeem, account.collateralBalance - collateralToRemove);
1061: }['200']
200: function claimRedemption(uint256 id) external { // <= FOUND
201: StakingPosition storage position = _positions[id];
202:
203: if (position.maturationBlock == 0) {
204: revert PositionNotFound();
205: }
206:
207: uint256 transmutationTime = position.maturationBlock - position.startBlock;
208: uint256 blocksLeft = position.maturationBlock > block.number ? position.maturationBlock - block.number : 0; // <= FOUND
209: uint256 amountNottransmuted = blocksLeft > 0 ? position.amount * blocksLeft / transmutationTime : 0;
210: uint256 amountTransmuted = position.amount - amountNottransmuted;
211:
212: if (_requireOwned(id) != msg.sender) {
213: revert CallerNotOwner();
214: }
215:
216:
217: _burn(id);
218:
219:
220: uint256 yieldTokenBalance = TokenUtils.safeBalanceOf(alchemist.yieldToken(), address(this));
221: uint256 debtValue = alchemist.convertYieldTokensToDebt(yieldTokenBalance);
222: uint256 amountToRedeem = amountTransmuted > debtValue ? amountTransmuted - debtValue : 0;
223: if (amountToRedeem > 0) alchemist.redeem(amountToRedeem);
224:
225: uint256 feeAmount = amountTransmuted * transmutationFee / BPS;
226: uint256 claimAmount = amountTransmuted - feeAmount;
227:
228: uint256 syntheticFee = amountNottransmuted * exitFee / BPS;
229: uint256 syntheticReturned = amountNottransmuted - syntheticFee;
230:
231:
232: if (blocksLeft > 0) _updateStakingGraph(-position.amount.toInt256() * BLOCK_SCALING_FACTOR / transmutationTime.toInt256(), blocksLeft);
233:
234:
235:
236: uint256 badDebtRatio = alchemist.totalSyntheticsIssued() * 10**TokenUtils.expectDecimals(alchemist.yieldToken()) / alchemist.getTotalUnderlyingValue();
237:
238: if (badDebtRatio > 1e18) {
239: claimAmount = claimAmount * FIXED_POINT_SCALAR / badDebtRatio;
240: feeAmount = feeAmount * FIXED_POINT_SCALAR / badDebtRatio;
241: }
242:
243: TokenUtils.safeTransfer(alchemist.yieldToken(), msg.sender, alchemist.convertDebtTokensToYield(claimAmount));
244: TokenUtils.safeTransfer(alchemist.yieldToken(), protocolFeeReceiver, alchemist.convertDebtTokensToYield(feeAmount));
245:
246: TokenUtils.safeTransfer(syntheticToken, msg.sender, syntheticReturned);
247: TokenUtils.safeTransfer(syntheticToken, protocolFeeReceiver, syntheticFee);
248:
249:
250: TokenUtils.safeBurn(syntheticToken, amountTransmuted);
251:
252: totalLocked -= position.amount;
253:
254: emit PositionClaimed(msg.sender, claimAmount, syntheticReturned);
255:
256: delete _positions[id];
257: }Chainlink advises users relying on price oracles to monitor the status of the L2 Sequencer such as for Arbitrum. In situations where the sequencer becomes inactive, Chainlink oracles might reflect outdated prices from the period prior to the downtime, until a new L2 OCR transaction is processed. Users who conduct transactions via the L1 Delayed Inbox could exploit these outdated prices. To prevent this, implement a Chainlink oracle to identify when the sequencer is offline and prohibit transactions during these periods of inactivity.
Num of instances: 2
Click to show findings
['21']
21: function ethToUSD(uint256 ethAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 usdAmount) {
22:
23: AggregatorV3Interface priceFeed = AggregatorV3Interface(usdPriceFeed);
24: (, int256 price,, uint256 updateTime,) = priceFeed.latestRoundData();
25:
26: if (price <= 0) {
27: revert ChainlinkMalfunction(usdPriceFeed, price);
28: }
29:
30: if (updateTime == 0) {
31: revert IncompleteRound(usdPriceFeed, updateTime);
32: }
33:
34: if (updateTime < block.timestamp - expectedUpdateTime) {
35: revert IncompleteRound(usdPriceFeed, updateTime);
36: }
37:
38:
39: return (ethAmount * uint256(price)) / 1e20;
40: }['48']
48: function usdToETH(uint256 usdAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 ethAmount) {
49:
50: AggregatorV3Interface priceFeed = AggregatorV3Interface(usdPriceFeed);
51: (, int256 price,, uint256 updateTime,) = priceFeed.latestRoundData();
52:
53: if (price <= 0) {
54: revert ChainlinkMalfunction(usdPriceFeed, price);
55: }
56:
57: if (updateTime == 0) {
58: revert IncompleteRound(usdPriceFeed, updateTime);
59: }
60:
61: if (updateTime < block.timestamp - expectedUpdateTime) {
62: revert IncompleteRound(usdPriceFeed, updateTime);
63: }
64:
65:
66:
67: return (usdAmount * 1e20) / uint256(price);
68: }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: 2
Click to show findings
['771']
771: function _liquidate(uint256 accountId) internal returns (uint256 debtAmount, uint256 feeInYield, uint256 feeInUnderlying) {
772:
773: _earmark();
774:
775: _sync(accountId);
776:
777: Account storage account = _accounts[accountId];
778:
779:
780: if (account.debt == 0) {
781: return (0, 0, 0);
782: }
783:
784:
785: uint256 collateralInUnderlying = totalValue(accountId);
786: uint256 collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
787:
788:
789: if (collateralizationRatio > collateralizationLowerBound) {
790: return (0, 0, 0);
791: }
792:
793:
794: uint256 repaidAmountInYield = 0;
795: if (account.earmarked > 0) {
796: repaidAmountInYield = _forceRepay(accountId, convertDebtTokensToYield(account.earmarked));
797: }
798:
799: if (account.debt == 0) {
800: return (repaidAmountInYield, 0, 0);
801: }
802:
803:
804: collateralInUnderlying = totalValue(accountId);
805: collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
806:
807: if (collateralizationRatio <= collateralizationLowerBound) {
808: uint256 alchemistCurrentCollateralization = normalizeUnderlyingTokensToDebt(_getTotalUnderlyingValue()) * FIXED_POINT_SCALAR / totalDebt;
809:
810: (uint256 liquidationAmount, uint256 debtToBurn, uint256 baseFee) = calculateLiquidation(
811: collateralInUnderlying, account.debt, minimumCollateralization, alchemistCurrentCollateralization, globalMinimumCollateralization, liquidatorFee
812: );
813:
814: uint256 feeBonus = debtToBurn * liquidatorFee / BPS;
815: uint256 adjustedLiquidationAmount = convertDebtTokensToYield(liquidationAmount);
816: uint256 adjustedDebtToBurn = convertDebtTokensToYield(debtToBurn);
817: debtAmount = adjustedLiquidationAmount;
818: feeInYield = convertDebtTokensToYield(baseFee);
819:
820:
821: account.collateralBalance = account.collateralBalance > adjustedLiquidationAmount ? account.collateralBalance - adjustedLiquidationAmount : 0;
822:
823:
824: _subDebt(accountId, debtToBurn);
825:
826:
827: TokenUtils.safeTransfer(yieldToken, transmuter, adjustedDebtToBurn);
828:
829: if (feeInYield > 0) {
830:
831: TokenUtils.safeTransfer(yieldToken, msg.sender, feeInYield);
832: }
833:
834:
835:
836: if (feeBonus > 0) {
837: uint256 vaultBalance = IFeeVault(alchemistFeeVault).totalDeposits();
838: if (vaultBalance > 0) {
839: feeInUnderlying = vaultBalance > feeBonus ? feeBonus : vaultBalance;
840: IFeeVault(alchemistFeeVault).withdraw(msg.sender, feeInUnderlying);
841: }
842: }
843: }
844:
845: return (debtAmount + repaidAmountInYield, feeInYield, feeInUnderlying);
846: }['200']
200: function claimRedemption(uint256 id) external {
201: StakingPosition storage position = _positions[id];
202:
203: if (position.maturationBlock == 0) {
204: revert PositionNotFound();
205: }
206:
207: uint256 transmutationTime = position.maturationBlock - position.startBlock;
208: uint256 blocksLeft = position.maturationBlock > block.number ? position.maturationBlock - block.number : 0;
209: uint256 amountNottransmuted = blocksLeft > 0 ? position.amount * blocksLeft / transmutationTime : 0;
210: uint256 amountTransmuted = position.amount - amountNottransmuted;
211:
212: if (_requireOwned(id) != msg.sender) {
213: revert CallerNotOwner();
214: }
215:
216:
217: _burn(id);
218:
219:
220: uint256 yieldTokenBalance = TokenUtils.safeBalanceOf(alchemist.yieldToken(), address(this));
221: uint256 debtValue = alchemist.convertYieldTokensToDebt(yieldTokenBalance);
222: uint256 amountToRedeem = amountTransmuted > debtValue ? amountTransmuted - debtValue : 0;
223: if (amountToRedeem > 0) alchemist.redeem(amountToRedeem);
224:
225: uint256 feeAmount = amountTransmuted * transmutationFee / BPS;
226: uint256 claimAmount = amountTransmuted - feeAmount;
227:
228: uint256 syntheticFee = amountNottransmuted * exitFee / BPS;
229: uint256 syntheticReturned = amountNottransmuted - syntheticFee;
230:
231:
232: if (blocksLeft > 0) _updateStakingGraph(-position.amount.toInt256() * BLOCK_SCALING_FACTOR / transmutationTime.toInt256(), blocksLeft);
233:
234:
235:
236: uint256 badDebtRatio = alchemist.totalSyntheticsIssued() * 10**TokenUtils.expectDecimals(alchemist.yieldToken()) / alchemist.getTotalUnderlyingValue();
237:
238: if (badDebtRatio > 1e18) {
239: claimAmount = claimAmount * FIXED_POINT_SCALAR / badDebtRatio;
240: feeAmount = feeAmount * FIXED_POINT_SCALAR / badDebtRatio;
241: }
242:
243: TokenUtils.safeTransfer(alchemist.yieldToken(), msg.sender, alchemist.convertDebtTokensToYield(claimAmount));
244: TokenUtils.safeTransfer(alchemist.yieldToken(), protocolFeeReceiver, alchemist.convertDebtTokensToYield(feeAmount));
245:
246: TokenUtils.safeTransfer(syntheticToken, msg.sender, syntheticReturned);
247: TokenUtils.safeTransfer(syntheticToken, protocolFeeReceiver, syntheticFee);
248:
249:
250: TokenUtils.safeBurn(syntheticToken, amountTransmuted);
251:
252: totalLocked -= position.amount;
253:
254: emit PositionClaimed(msg.sender, claimAmount, syntheticReturned);
255:
256: delete _positions[id];
257: }[Medium-8] 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: 2
Click to show findings
['54']
54: function depositWETH(uint256 amount) external nonReentrant { // <= FOUND
55: if (amount == 0) revert ZeroAmount();
56:
57:
58: IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
59:
60:
61: IWETH(token).withdraw(amount);
62:
63:
64: _deposit(msg.sender, amount);
65: }['26']
26: function deposit(uint256 amount) external { // <= FOUND
27: _checkNonZeroAmount(amount);
28: IERC20(token).transferFrom(msg.sender, address(this), amount);
29: emit Deposited(msg.sender, amount);
30: }As the 'amount' value is not validated against a minimum amount, a potential attacker can make a large number of micro withdrawal requests such a 1e1 for a token with 1e18 decimals. As such they can prevent other users from withdrawing their assets. A resolution to this is implementing a minimum withdrawal amount, although this will need setter limits of it's own to prevent it from being changed to a number so high, no one can withdraw.
Num of instances: 5
Click to show findings
['384']
384: function withdraw(uint256 amount, address recipient, uint256 tokenId) external returns (uint256) {
385: _checkArgument(recipient != address(0));
386: _checkForValidAccountId(tokenId);
387: _checkArgument(amount > 0);
388: _checkAccountOwnership(IAlchemistV3Position(alchemistPositionNFT).ownerOf(tokenId), msg.sender);
389: _earmark();
390:
391: _sync(tokenId);
392:
393: _checkArgument(_accounts[tokenId].freeCollateral >= amount);
394:
395: _accounts[tokenId].collateralBalance -= amount;
396: _accounts[tokenId].freeCollateral -= amount;
397:
398:
399: _validate(tokenId);
400:
401:
402: TokenUtils.safeTransfer(yieldToken, recipient, amount);
403:
404: emit Withdraw(amount, tokenId, recipient);
405:
406: return amount;
407: }['54']
54: function depositWETH(uint256 amount) external nonReentrant { // <= FOUND
55: if (amount == 0) revert ZeroAmount();
56:
57:
58: IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
59:
60:
61: IWETH(token).withdraw(amount); // <= FOUND
62:
63:
64: _deposit(msg.sender, amount);
65: }['81']
81: function withdraw(address recipient, uint256 amount) external override onlyAuthorized nonReentrant { // <= FOUND
82: if (amount == 0) revert ZeroAmount();
83:
84:
85: if (amount > address(this).balance) revert InsufficientBalance();
86:
87:
88: (bool success,) = recipient.call{value: amount}("");
89: if (!success) revert TransferFailed();
90:
91: emit Withdrawn(recipient, amount);
92: }['37']
37: function withdraw(address recipient, uint256 amount) external override onlyAuthorized { // <= FOUND
38: _checkNonZeroAddress(recipient);
39: _checkNonZeroAmount(amount);
40:
41: IERC20(token).transfer(recipient, amount);
42: emit Withdrawn(recipient, amount);
43: }['384']
384: function withdraw(uint256 amount, address recipient, uint256 tokenId) external returns (uint256) { // <= FOUND
385: _checkArgument(recipient != address(0));
386: _checkForValidAccountId(tokenId);
387: _checkArgument(amount > 0);
388: _checkAccountOwnership(IAlchemistV3Position(alchemistPositionNFT).ownerOf(tokenId), msg.sender);
389: _earmark();
390:
391: _sync(tokenId);
392:
393: _checkArgument(_accounts[tokenId].freeCollateral >= amount);
394:
395: _accounts[tokenId].collateralBalance -= amount;
396: _accounts[tokenId].freeCollateral -= amount;
397:
398:
399: _validate(tokenId);
400:
401:
402: TokenUtils.safeTransfer(yieldToken, recipient, amount);
403:
404: emit Withdraw(amount, tokenId, recipient);
405:
406: return amount;
407: }[Medium-10] 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
['26']
26: function deposit(uint256 amount) external { // <= FOUND
27: _checkNonZeroAmount(amount);
28: IERC20(token).transferFrom(msg.sender, address(this), amount);
29: emit Deposited(msg.sender, amount);
30: }['357']
357: function deposit(uint256 amount, address recipient, uint256 recipientId) external returns (uint256) { // <= FOUND
358: _checkArgument(recipient != address(0));
359: _checkArgument(amount > 0);
360: _checkState(!depositsPaused);
361: _checkState(IERC20(yieldToken).balanceOf(address(this)) + amount <= depositCap);
362: uint256 tokenId = recipientId;
363:
364:
365: if (tokenId == 0) {
366: tokenId = IAlchemistV3Position(alchemistPositionNFT).mint(recipient);
367: emit AlchemistV3PositionNFTMinted(recipient, tokenId);
368: } else {
369: _checkForValidAccountId(tokenId);
370: }
371:
372: _accounts[tokenId].collateralBalance += amount;
373: _accounts[tokenId].freeCollateral += amount;
374:
375:
376: TokenUtils.safeTransferFrom(yieldToken, msg.sender, address(this), amount);
377:
378: emit Deposit(amount, tokenId);
379:
380: return convertYieldTokensToDebt(amount);
381: }[Medium-11] If protocol fees are too high, user's rewards can be permanently lost in instances where they are not enough to cover fees
Many protocols implement reward accumulation mechanisms, such as staking, where users earn rewards over time while the protocol charges fees for providing the service. However, if a user's redeemable rewards are less than the fees owed, they may be unable to claim their rewards. This can lead to a scenario where users who recently unstaked or have low balances cannot retrieve their rewards, effectively making those assets permanently lost.
While this behavior may be intended to prevent microtransactions, it can create unfair edge cases where users unknowingly lose their earnings. A possible fix is implementing a minimum claimable threshold or allowing partial withdrawals to ensure users always have a way to access their funds.
Num of instances: 1
Click to show findings
['200']
200: function claimRedemption(uint256 id) external { // <= FOUND
201: StakingPosition storage position = _positions[id];
202:
203: if (position.maturationBlock == 0) {
204: revert PositionNotFound();
205: }
206:
207: uint256 transmutationTime = position.maturationBlock - position.startBlock;
208: uint256 blocksLeft = position.maturationBlock > block.number ? position.maturationBlock - block.number : 0;
209: uint256 amountNottransmuted = blocksLeft > 0 ? position.amount * blocksLeft / transmutationTime : 0;
210: uint256 amountTransmuted = position.amount - amountNottransmuted;
211:
212: if (_requireOwned(id) != msg.sender) {
213: revert CallerNotOwner();
214: }
215:
216:
217: _burn(id);
218:
219:
220: uint256 yieldTokenBalance = TokenUtils.safeBalanceOf(alchemist.yieldToken(), address(this));
221: uint256 debtValue = alchemist.convertYieldTokensToDebt(yieldTokenBalance);
222: uint256 amountToRedeem = amountTransmuted > debtValue ? amountTransmuted - debtValue : 0;
223: if (amountToRedeem > 0) alchemist.redeem(amountToRedeem);
224:
225: uint256 feeAmount = amountTransmuted * transmutationFee / BPS;
226: uint256 claimAmount = amountTransmuted - feeAmount;
227:
228: uint256 syntheticFee = amountNottransmuted * exitFee / BPS;
229: uint256 syntheticReturned = amountNottransmuted - syntheticFee; // <= FOUND
230:
231:
232: if (blocksLeft > 0) _updateStakingGraph(-position.amount.toInt256() * BLOCK_SCALING_FACTOR / transmutationTime.toInt256(), blocksLeft);
233:
234:
235:
236: uint256 badDebtRatio = alchemist.totalSyntheticsIssued() * 10**TokenUtils.expectDecimals(alchemist.yieldToken()) / alchemist.getTotalUnderlyingValue();
237:
238: if (badDebtRatio > 1e18) {
239: claimAmount = claimAmount * FIXED_POINT_SCALAR / badDebtRatio;
240: feeAmount = feeAmount * FIXED_POINT_SCALAR / badDebtRatio;
241: }
242:
243: TokenUtils.safeTransfer(alchemist.yieldToken(), msg.sender, alchemist.convertDebtTokensToYield(claimAmount));
244: TokenUtils.safeTransfer(alchemist.yieldToken(), protocolFeeReceiver, alchemist.convertDebtTokensToYield(feeAmount));
245:
246: TokenUtils.safeTransfer(syntheticToken, msg.sender, syntheticReturned);
247: TokenUtils.safeTransfer(syntheticToken, protocolFeeReceiver, syntheticFee);
248:
249:
250: TokenUtils.safeBurn(syntheticToken, amountTransmuted);
251:
252: totalLocked -= position.amount;
253:
254: emit PositionClaimed(msg.sender, claimAmount, syntheticReturned);
255:
256: delete _positions[id];
257: }[Medium-12] Transfer flow from msg.sender to protocol favours msg sender by its amount value rounding down when it should round up to prevent exploitation
Transfer logic that calculates the amount deducted from msg.sender using rounding down (e.g. via integer division) can introduce unintended advantages for the sender. This is particularly problematic in fee or reward calculations where precision is critical. Rounding down favors the sender by potentially underpaying or avoiding marginal amounts, which can accumulate over many transactions and be exploited. Instead, the calculation should round up to ensure the full value owed is captured. Using safe mathematical utilities like mulDivUp or explicitly checking for remainders before rounding can help mitigate this. Failure to address this can lead to economic imbalances or protocol loss over time.
Num of instances: 1
Click to show findings
['488']
488: function repay(uint256 amount, uint256 recipientTokenId) public returns (uint256) {
489: _checkArgument(amount > 0);
490: _checkForValidAccountId(recipientTokenId);
491: Account storage account = _accounts[recipientTokenId];
492:
493:
494: if (block.number == account.lastMintBlock) revert CannotRepayOnMintBlock();
495:
496:
497: _earmark();
498:
499:
500: _sync(recipientTokenId);
501:
502: uint256 debt;
503:
504:
505: _checkState((debt = account.debt) > 0);
506:
507: uint256 yieldToDebt = convertYieldTokensToDebt(amount);
508: uint256 credit = yieldToDebt > debt ? debt : yieldToDebt;
509: uint256 creditToYield = convertDebtTokensToYield(credit);
510:
511:
512: uint256 earmarkToRemove = credit > account.earmarked ? account.earmarked : credit;
513: account.earmarked -= earmarkToRemove;
514:
515:
516: account.collateralBalance -= creditToYield * protocolFee / BPS;
517:
518: _subDebt(recipientTokenId, credit);
519:
520:
521: TokenUtils.safeTransferFrom(yieldToken, msg.sender, transmuter, creditToYield);
522: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, creditToYield);
523:
524: emit Repay(msg.sender, amount, recipientTokenId, creditToYield);
525:
526: return creditToYield;
527: }In Solidity, inheriting the Ownable contract from OpenZeppelin without utilizing its features can lead to unnecessary code bloat and potential confusion. The Ownable contract is designed to provide a basic access control mechanism, where there is an account (an owner) that can be granted exclusive access to specific functions. However, if the inherited Ownable functionalities like onlyOwner modifier are not used in the contract, it indicates an oversight in the contract design. This unnecessary inheritance could mislead developers or auditors into assuming certain access control mechanisms are in place when they are not. The resolution is straightforward: if Ownable is not needed, it should be removed from the inheritance chain to streamline the contract and avoid misunderstandings about its access control mechanisms.
Num of instances: 1
Implement a zero address check for found instances
Num of instances: 5
Click to show findings
['75']
75: function underlyingTokenToUSD(uint256 underlyingTokenAmount, uint256 underlyingTokenDecimals) internal pure returns (uint256 usdAmount) { // <= FOUND
76: uint256 usdConversionFactor = 10 ** (underlyingTokenDecimals - USDC_DECIMALS);
77: return underlyingTokenAmount / usdConversionFactor; // <= FOUND
78: }['331']
331: function getMaxBorrowable(uint256 tokenId) external view returns (uint256) { // <= FOUND
332: (uint256 debt,, uint256 collateral) = _calculateUnrealizedDebt(tokenId);
333: uint256 debtValueOfCollateral = convertYieldTokensToDebt(collateral);
334: return (debtValueOfCollateral * FIXED_POINT_SCALAR / minimumCollateralization) - debt; // <= FOUND
335: }['631']
631: function calculateLiquidation( // <= FOUND
632: uint256 collateral,
633: uint256 debt,
634: uint256 targetCollateralization,
635: uint256 alchemistCurrentCollateralization,
636: uint256 alchemistMinimumCollateralization,
637: uint256 feeBps
638: ) public pure returns (uint256 grossCollateralToSeize, uint256 debtToBurn, uint256 fee) {
639: if (debt >= collateral) {
640:
641: return (debt, debt, 0);
642: }
643:
644: if (alchemistCurrentCollateralization < alchemistMinimumCollateralization) {
645:
646: return (debt, debt, 0);
647: }
648:
649:
650: uint256 surplus = collateral > debt ? collateral - debt : 0;
651: fee = (surplus * feeBps) / BPS;
652:
653:
654: uint256 adjCollat = collateral - fee;
655:
656:
657: uint256 md = (targetCollateralization * debt) / FIXED_POINT_SCALAR;
658:
659:
660: if (md <= adjCollat) {
661: return (0, 0, fee);
662: }
663:
664:
665: uint256 num = md - adjCollat;
666:
667:
668: uint256 denom = targetCollateralization - FIXED_POINT_SCALAR;
669:
670:
671: debtToBurn = (num * FIXED_POINT_SCALAR) / denom; // <= FOUND
672:
673:
674: grossCollateralToSeize = debtToBurn + fee;
675: }['705']
705: function normalizeDebtTokensToUnderlying(uint256 amount) public view returns (uint256) { // <= FOUND
706: return amount / underlyingConversionFactor; // <= FOUND
707: }['1080']
1080: function _getLiquidationAmount(uint256 collateral, uint256 debt, uint256 globalRatio) internal view returns (uint256 liquidationAmount) { // <= FOUND
1081: _checkState(minimumCollateralization > FIXED_POINT_SCALAR);
1082: if (debt >= collateral) {
1083:
1084: return debt;
1085: }
1086:
1087: if (globalRatio < globalMinimumCollateralization) {
1088:
1089: return debt;
1090: }
1091:
1092: uint256 expectedCollateralForCurrentDebt = (debt * minimumCollateralization) / FIXED_POINT_SCALAR;
1093: uint256 collateralDiff = expectedCollateralForCurrentDebt - collateral;
1094: uint256 ratioDiff = minimumCollateralization - FIXED_POINT_SCALAR;
1095: liquidationAmount = collateralDiff * FIXED_POINT_SCALAR / ratioDiff; // <= FOUND
1096: return liquidationAmount;
1097: }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: 9
Click to show findings
['47']
47: function safeTransfer(address token, address recipient, uint256 amount) internal { // <= FOUND
48: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, recipient, amount));
49:
50: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
51: revert ERC20CallFailed(token, success, data);
52: }
53: }['61']
61: function safeTransfer(address token, address recipient, uint256 amount) internal { // <= FOUND
62: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, recipient, amount));
63:
64: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
65: revert ERC20CallFailed(token, success, data);
66: }
67: }['63']
63: function safeApprove(address token, address spender, uint256 value) internal { // <= FOUND
64: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, value));
65:
66: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
67: revert ERC20CallFailed(token, success, data);
68: }
69: }['76']
76: function safeApprove(address token, address spender, uint256 value) internal { // <= FOUND
77: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, value));
78:
79: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
80: revert ERC20CallFailed(token, success, data);
81: }
82: }['80']
80: function safeTransferFrom(address token, address owner, address recipient, uint256 amount) internal { // <= FOUND
81: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, owner, recipient, amount));
82:
83: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
84: revert ERC20CallFailed(token, success, data);
85: }
86: }['92']
92: function safeTransferFrom(address token, address owner, address recipient, uint256 amount) internal { // <= FOUND
93: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, owner, recipient, amount));
94:
95: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
96: revert ERC20CallFailed(token, success, data);
97: }
98: }['107']
107: function safeMint(address token, address recipient, uint256 amount) internal { // <= FOUND
108: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Mintable.mint.selector, recipient, amount));
109:
110: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
111: revert ERC20CallFailed(token, success, data);
112: }
113: }['121']
121: function safeBurn(address token, uint256 amount) internal { // <= FOUND
122: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Burnable.burn.selector, amount));
123:
124: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
125: revert ERC20CallFailed(token, success, data);
126: }
127: }['136']
136: function safeBurnFrom(address token, address owner, uint256 amount) internal { // <= FOUND
137: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Burnable.burnFrom.selector, owner, amount));
138:
139: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
140: revert ERC20CallFailed(token, success, data);
141: }
142: }Num of instances: 3
Click to show findings
['195']
195: function setAlchemistFeeVault(address value) external onlyAdmin {
196: if (IFeeVault(value).token() != underlyingToken) {
197: revert AlchemistVaultTokenMismatchError();
198: }
199: alchemistFeeVault = value;
200: emit AlchemistFeeVaultUpdated(value);
201: }['204']
204: function setPendingAdmin(address value) external onlyAdmin {
205: pendingAdmin = value;
206:
207: emit PendingAdminUpdated(value);
208: }['116']
116: function setAlchemist(address value) external onlyAdmin {
117: alchemist = IAlchemistV3(value);
118:
119: emit AlchemistUpdated(value);
120: }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: 13
Click to show findings
['771']
771: function _liquidate(uint256 accountId) internal returns (uint256 debtAmount, uint256 feeInYield, uint256 feeInUnderlying) {
772:
773: _earmark();
774:
775: _sync(accountId);
776:
777: Account storage account = _accounts[accountId];
778:
779:
780: if (account.debt == 0) {
781: return (0, 0, 0);
782: }
783:
784:
785: uint256 collateralInUnderlying = totalValue(accountId);
786: uint256 collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
787:
788:
789: if (collateralizationRatio > collateralizationLowerBound) {
790: return (0, 0, 0);
791: }
792:
793:
794: uint256 repaidAmountInYield = 0;
795: if (account.earmarked > 0) {
796: repaidAmountInYield = _forceRepay(accountId, convertDebtTokensToYield(account.earmarked));
797: }
798:
799: if (account.debt == 0) {
800: return (repaidAmountInYield, 0, 0);
801: }
802:
803:
804: collateralInUnderlying = totalValue(accountId);
805: collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
806:
807: if (collateralizationRatio <= collateralizationLowerBound) {
808: uint256 alchemistCurrentCollateralization = normalizeUnderlyingTokensToDebt(_getTotalUnderlyingValue()) * FIXED_POINT_SCALAR / totalDebt;
809:
810: (uint256 liquidationAmount, uint256 debtToBurn, uint256 baseFee) = calculateLiquidation(
811: collateralInUnderlying, account.debt, minimumCollateralization, alchemistCurrentCollateralization, globalMinimumCollateralization, liquidatorFee
812: );
813:
814: uint256 feeBonus = debtToBurn * liquidatorFee / BPS;
815: uint256 adjustedLiquidationAmount = convertDebtTokensToYield(liquidationAmount);
816: uint256 adjustedDebtToBurn = convertDebtTokensToYield(debtToBurn);
817: debtAmount = adjustedLiquidationAmount;
818: feeInYield = convertDebtTokensToYield(baseFee);
819:
820:
821: account.collateralBalance = account.collateralBalance > adjustedLiquidationAmount ? account.collateralBalance - adjustedLiquidationAmount : 0;
822:
823:
824: _subDebt(accountId, debtToBurn);
825:
826:
827: TokenUtils.safeTransfer(yieldToken, transmuter, adjustedDebtToBurn);
828:
829: if (feeInYield > 0) {
830:
831: TokenUtils.safeTransfer(yieldToken, msg.sender, feeInYield);
832: }
833:
834:
835:
836: if (feeBonus > 0) {
837: uint256 vaultBalance = IFeeVault(alchemistFeeVault).totalDeposits(); // <= FOUND
838: if (vaultBalance > 0) {
839: feeInUnderlying = vaultBalance > feeBonus ? feeBonus : vaultBalance;
840: IFeeVault(alchemistFeeVault).withdraw(msg.sender, feeInUnderlying);
841: }
842: }
843: }
844:
845: return (debtAmount + repaidAmountInYield, feeInYield, feeInUnderlying);
846: }['200']
200: function claimRedemption(uint256 id) external {
201: StakingPosition storage position = _positions[id];
202:
203: if (position.maturationBlock == 0) {
204: revert PositionNotFound();
205: }
206:
207: uint256 transmutationTime = position.maturationBlock - position.startBlock;
208: uint256 blocksLeft = position.maturationBlock > block.number ? position.maturationBlock - block.number : 0;
209: uint256 amountNottransmuted = blocksLeft > 0 ? position.amount * blocksLeft / transmutationTime : 0;
210: uint256 amountTransmuted = position.amount - amountNottransmuted;
211:
212: if (_requireOwned(id) != msg.sender) {
213: revert CallerNotOwner();
214: }
215:
216:
217: _burn(id);
218:
219:
220: uint256 yieldTokenBalance = TokenUtils.safeBalanceOf(alchemist.yieldToken(), address(this)); // <= FOUND
221: uint256 debtValue = alchemist.convertYieldTokensToDebt(yieldTokenBalance);
222: uint256 amountToRedeem = amountTransmuted > debtValue ? amountTransmuted - debtValue : 0;
223: if (amountToRedeem > 0) alchemist.redeem(amountToRedeem);
224:
225: uint256 feeAmount = amountTransmuted * transmutationFee / BPS;
226: uint256 claimAmount = amountTransmuted - feeAmount;
227:
228: uint256 syntheticFee = amountNottransmuted * exitFee / BPS;
229: uint256 syntheticReturned = amountNottransmuted - syntheticFee;
230:
231:
232: if (blocksLeft > 0) _updateStakingGraph(-position.amount.toInt256() * BLOCK_SCALING_FACTOR / transmutationTime.toInt256(), blocksLeft);
233:
234:
235:
236: uint256 badDebtRatio = alchemist.totalSyntheticsIssued() * 10**TokenUtils.expectDecimals(alchemist.yieldToken()) / alchemist.getTotalUnderlyingValue(); // <= FOUND
237:
238: if (badDebtRatio > 1e18) {
239: claimAmount = claimAmount * FIXED_POINT_SCALAR / badDebtRatio;
240: feeAmount = feeAmount * FIXED_POINT_SCALAR / badDebtRatio;
241: }
242:
243: TokenUtils.safeTransfer(alchemist.yieldToken(), msg.sender, alchemist.convertDebtTokensToYield(claimAmount));
244: TokenUtils.safeTransfer(alchemist.yieldToken(), protocolFeeReceiver, alchemist.convertDebtTokensToYield(feeAmount));
245:
246: TokenUtils.safeTransfer(syntheticToken, msg.sender, syntheticReturned);
247: TokenUtils.safeTransfer(syntheticToken, protocolFeeReceiver, syntheticFee);
248:
249:
250: TokenUtils.safeBurn(syntheticToken, amountTransmuted);
251:
252: totalLocked -= position.amount;
253:
254: emit PositionClaimed(msg.sender, claimAmount, syntheticReturned);
255:
256: delete _positions[id];
257: }['573']
573: function redeem(uint256 amount) external onlyTransmuter {
574: _earmark();
575:
576: _redemptionWeight += PositionDecay.WeightIncrement(amount, cumulativeEarmarked); // <= FOUND
577:
578:
579: uint256 collRedeemed = convertDebtTokensToYield(amount);
580: uint256 feeCollateral = collRedeemed * protocolFee / BPS;
581: uint256 totalOut = collRedeemed + feeCollateral;
582:
583:
584: uint256 old = _totalLocked;
585: _totalLocked = old - totalOut;
586: _collateralWeight += PositionDecay.WeightIncrement(totalOut, old); // <= FOUND
587: cumulativeEarmarked -= amount;
588: totalDebt -= amount;
589: totalSyntheticsIssued -= amount;
590:
591: lastRedemptionBlock = block.number;
592:
593: _redemptions[block.number] = RedemptionInfo(cumulativeEarmarked, totalDebt, _earmarkWeight);
594:
595: TokenUtils.safeTransfer(yieldToken, transmuter, collRedeemed);
596: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, feeCollateral);
597:
598: emit Redemption(amount);
599: }['1007']
1007: function _earmark() internal {
1008: if (totalDebt == 0) return;
1009:
1010: if (block.number > lastEarmarkBlock) {
1011: uint256 amount = ITransmuter(transmuter).queryGraph(lastEarmarkBlock + 1, block.number);
1012: if (amount > 0) {
1013: _earmarkWeight += PositionDecay.WeightIncrement(amount, totalDebt - cumulativeEarmarked); // <= FOUND
1014: cumulativeEarmarked += amount;
1015: }
1016:
1017: lastEarmarkBlock = block.number;
1018: }
1019: }['1028']
1028: function _calculateUnrealizedDebt(uint256 tokenId) internal view returns (uint256, uint256, uint256) {
1029: Account storage account = _accounts[tokenId];
1030: RedemptionInfo memory previousRedemption = _redemptions[lastRedemptionBlock];
1031:
1032: uint256 amount;
1033: uint256 earmarkWeightCopy = _earmarkWeight;
1034:
1035:
1036: if (block.number > lastEarmarkBlock) {
1037: amount = ITransmuter(transmuter).queryGraph(lastEarmarkBlock + 1, block.number);
1038: if (amount > 0) {
1039: earmarkWeightCopy += PositionDecay.WeightIncrement(amount, totalDebt - cumulativeEarmarked); // <= FOUND
1040: }
1041: }
1042:
1043: uint256 debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, earmarkWeightCopy - account.lastAccruedEarmarkWeight); // <= FOUND
1044: uint256 earmarkedState = account.earmarked + debtToEarmark;
1045:
1046:
1047: uint256 earmarkedPreviousState;
1048: uint256 earmarkToRedeem;
1049: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) {
1050: debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, previousRedemption.earmarkWeight - account.lastAccruedEarmarkWeight); // <= FOUND
1051:
1052: earmarkedPreviousState = account.earmarked + debtToEarmark;
1053: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight); // <= FOUND
1054: } else {
1055: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedState, _redemptionWeight - account.lastAccruedRedemptionWeight); // <= FOUND
1056: }
1057:
1058: uint256 collateralToRemove = PositionDecay.ScaleByWeightDelta(account.rawLocked, _collateralWeight - account.lastCollateralWeight); // <= FOUND
1059:
1060: return (account.debt - earmarkToRedeem, earmarkedState - earmarkToRedeem, account.collateralBalance - collateralToRemove);
1061: }['974']
974: function _sync(uint256 tokenId) internal {
975: Account storage account = _accounts[tokenId];
976: RedemptionInfo memory previousRedemption = _redemptions[lastRedemptionBlock];
977:
978: uint256 debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, _earmarkWeight - account.lastAccruedEarmarkWeight); // <= FOUND
979: uint256 earmarkedState = account.earmarked + debtToEarmark;
980:
981:
982: uint256 earmarkToRedeem;
983: uint256 earmarkPreviousState;
984: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) {
985: debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, previousRedemption.earmarkWeight - account.lastAccruedEarmarkWeight); // <= FOUND
986:
987: earmarkPreviousState = account.earmarked + debtToEarmark;
988: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight); // <= FOUND
989: } else {
990: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedState, _redemptionWeight - account.lastAccruedRedemptionWeight); // <= FOUND
991: }
992:
993: uint256 collateralToRemove = PositionDecay.ScaleByWeightDelta(account.rawLocked, _collateralWeight - account.lastCollateralWeight); // <= FOUND
994:
995:
996: account.earmarked = earmarkedState - earmarkToRedeem;
997: account.debt -= earmarkToRedeem;
998: account.lastAccruedRedemptionWeight = _redemptionWeight;
999: account.lastAccruedEarmarkWeight = _earmarkWeight;
1000:
1001: account.collateralBalance -= collateralToRemove;
1002: account.rawLocked -= collateralToRemove;
1003: account.lastCollateralWeight = _collateralWeight;
1004: }['123']
123: function setDepositCap(uint256 cap) external onlyAdmin {
124: _checkArgument(cap <= type(int256).max.toUint256()); // <= FOUND
125:
126: depositCap = cap;
127: emit DepositCapUpdated(cap);
128: }['155']
155: function initialize(AlchemistInitializationParams memory params) external initializer {
156: _checkArgument(params.protocolFee <= BPS);
157: _checkArgument(params.liquidatorFee <= BPS);
158:
159: debtToken = params.debtToken;
160: underlyingToken = params.underlyingToken;
161: underlyingConversionFactor = 10 ** (TokenUtils.expectDecimals(params.debtToken) - TokenUtils.expectDecimals(params.underlyingToken)); // <= FOUND
162: yieldToken = params.yieldToken;
163: depositCap = params.depositCap;
164: blocksPerYear = params.blocksPerYear;
165: minimumCollateralization = params.minimumCollateralization;
166: globalMinimumCollateralization = params.globalMinimumCollateralization;
167: collateralizationLowerBound = params.collateralizationLowerBound;
168: admin = params.admin;
169: tokenAdapter = params.tokenAdapter;
170: transmuter = params.transmuter;
171: protocolFee = params.protocolFee;
172: protocolFeeReceiver = params.protocolFeeReceiver;
173: liquidatorFee = params.liquidatorFee;
174: lastEarmarkBlock = block.number;
175: lastRedemptionBlock = block.number;
176: }['172']
172: function createRedemption(uint256 syntheticDepositAmount) external {
173: if (syntheticDepositAmount == 0) {
174: revert DepositZeroAmount();
175: }
176:
177: if (totalLocked + syntheticDepositAmount > depositCap) {
178: revert DepositCapReached();
179: }
180:
181: if (totalLocked + syntheticDepositAmount > alchemist.totalSyntheticsIssued()) {
182: revert DepositCapReached();
183: }
184:
185: TokenUtils.safeTransferFrom(syntheticToken, msg.sender, address(this), syntheticDepositAmount); // <= FOUND
186:
187: _positions[++_nonce] = StakingPosition(syntheticDepositAmount, block.number, block.number + timeToTransmute);
188:
189:
190: _updateStakingGraph(syntheticDepositAmount.toInt256() * BLOCK_SCALING_FACTOR / timeToTransmute.toInt256(), timeToTransmute);
191:
192: totalLocked += syntheticDepositAmount;
193:
194: _mint(msg.sender, _nonce);
195:
196: emit PositionCreated(msg.sender, syntheticDepositAmount, _nonce);
197: }['20']
20: function add(AddressSet storage self, address value) internal returns (bool) {
21: if (self.contains(value)) { // <= FOUND
22: return false;
23: }
24: self.values.push(value);
25: self.indexes[value] = self.values.length;
26: return true;
27: }['266']
266: function setTransmuter(address value) external onlyAdmin {
267: _checkArgument(value != address(0));
268:
269:
270: require(convertYieldTokensToDebt(TokenUtils.safeBalanceOf(yieldToken, transmuter)) >= ITransmuter(transmuter).totalLocked()); // <= FOUND
271:
272: transmuter = value;
273: emit TransmuterUpdated(value);
274: }['357']
357: function deposit(uint256 amount, address recipient, uint256 recipientId) external returns (uint256) {
358: _checkArgument(recipient != address(0));
359: _checkArgument(amount > 0);
360: _checkState(!depositsPaused);
361: _checkState(IERC20(yieldToken).balanceOf(address(this)) + amount <= depositCap);
362: uint256 tokenId = recipientId;
363:
364:
365: if (tokenId == 0) {
366: tokenId = IAlchemistV3Position(alchemistPositionNFT).mint(recipient); // <= FOUND
367: emit AlchemistV3PositionNFTMinted(recipient, tokenId);
368: } else {
369: _checkForValidAccountId(tokenId);
370: }
371:
372: _accounts[tokenId].collateralBalance += amount;
373: _accounts[tokenId].freeCollateral += amount;
374:
375:
376: TokenUtils.safeTransferFrom(yieldToken, msg.sender, address(this), amount); // <= FOUND
377:
378: emit Deposit(amount, tokenId);
379:
380: return convertYieldTokensToDebt(amount);
381: }['447']
447: function burn(uint256 amount, uint256 recipientId) external returns (uint256) {
448: _checkArgument(amount > 0);
449: _checkForValidAccountId(recipientId);
450:
451:
452: if (block.number == _accounts[recipientId].lastMintBlock) revert CannotRepayOnMintBlock();
453:
454:
455: _earmark();
456:
457:
458: _sync(recipientId);
459:
460: uint256 debt;
461:
462: _checkState((debt = _accounts[recipientId].debt - _accounts[recipientId].earmarked) > 0);
463:
464: uint256 credit = amount > debt ? debt : amount;
465:
466:
467: if (credit > totalDebt - ITransmuter(transmuter).totalLocked()) {
468: revert BurnLimitExceeded(credit, totalDebt - ITransmuter(transmuter).totalLocked());
469: }
470:
471:
472: TokenUtils.safeBurnFrom(debtToken, msg.sender, credit); // <= FOUND
473:
474:
475: _accounts[recipientId].collateralBalance -= convertDebtTokensToYield(credit) * protocolFee / BPS;
476:
477:
478: _subDebt(recipientId, credit);
479:
480: totalSyntheticsIssued -= credit;
481:
482: emit Burn(msg.sender, credit, recipientId);
483:
484: return credit;
485: }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: 7
Click to show findings
['2']
2: pragma solidity ^0.8.26; // <= FOUND['1']
1: pragma solidity ^0.8.23; // <= FOUND['4']
4: pragma solidity ^0.8.20; // <= FOUND['9']
9: pragma solidity ^0.8.7; // <= FOUND['1']
1: pragma solidity ^0.8.13; // <= FOUND['2']
2: pragma solidity >=0.8.4; // <= FOUND['2']
2: pragma solidity 0.8.26; // <= FOUNDReason: 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: 7
Click to show findings
['26']
26: function deposit(uint256 amount) external { // <= FOUND
27: _checkNonZeroAmount(amount);
28: IERC20(token).transferFrom(msg.sender, address(this), amount); // <= FOUND
29: emit Deposited(msg.sender, amount);
30: }['37']
37: function withdraw(address recipient, uint256 amount) external override onlyAuthorized { // <= FOUND
38: _checkNonZeroAddress(recipient);
39: _checkNonZeroAmount(amount);
40:
41: IERC20(token).transfer(recipient, amount); // <= FOUND
42: emit Withdrawn(recipient, amount);
43: }['488']
488: function repay(uint256 amount, uint256 recipientTokenId) public returns (uint256) { // <= FOUND
489: _checkArgument(amount > 0);
490: _checkForValidAccountId(recipientTokenId);
491: Account storage account = _accounts[recipientTokenId];
492:
493:
494: if (block.number == account.lastMintBlock) revert CannotRepayOnMintBlock();
495:
496:
497: _earmark();
498:
499:
500: _sync(recipientTokenId);
501:
502: uint256 debt;
503:
504:
505: _checkState((debt = account.debt) > 0);
506:
507: uint256 yieldToDebt = convertYieldTokensToDebt(amount);
508: uint256 credit = yieldToDebt > debt ? debt : yieldToDebt;
509: uint256 creditToYield = convertDebtTokensToYield(credit);
510:
511:
512: uint256 earmarkToRemove = credit > account.earmarked ? account.earmarked : credit;
513: account.earmarked -= earmarkToRemove;
514:
515:
516: account.collateralBalance -= creditToYield * protocolFee / BPS;
517:
518: _subDebt(recipientTokenId, credit);
519:
520:
521: TokenUtils.safeTransferFrom(yieldToken, msg.sender, transmuter, creditToYield);
522: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, creditToYield); // <= FOUND
523:
524: emit Repay(msg.sender, amount, recipientTokenId, creditToYield);
525:
526: return creditToYield;
527: }['573']
573: function redeem(uint256 amount) external onlyTransmuter { // <= FOUND
574: _earmark();
575:
576: _redemptionWeight += PositionDecay.WeightIncrement(amount, cumulativeEarmarked);
577:
578:
579: uint256 collRedeemed = convertDebtTokensToYield(amount);
580: uint256 feeCollateral = collRedeemed * protocolFee / BPS;
581: uint256 totalOut = collRedeemed + feeCollateral;
582:
583:
584: uint256 old = _totalLocked;
585: _totalLocked = old - totalOut;
586: _collateralWeight += PositionDecay.WeightIncrement(totalOut, old);
587: cumulativeEarmarked -= amount;
588: totalDebt -= amount;
589: totalSyntheticsIssued -= amount;
590:
591: lastRedemptionBlock = block.number;
592:
593: _redemptions[block.number] = RedemptionInfo(cumulativeEarmarked, totalDebt, _earmarkWeight);
594:
595: TokenUtils.safeTransfer(yieldToken, transmuter, collRedeemed); // <= FOUND
596: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, feeCollateral);
597:
598: emit Redemption(amount);
599: }['735']
735: function _forceRepay(uint256 accountId, uint256 amount) internal returns (uint256) { // <= FOUND
736: _checkArgument(amount > 0);
737: _checkForValidAccountId(accountId);
738: Account storage account = _accounts[accountId];
739:
740:
741: _earmark();
742:
743:
744: _sync(accountId);
745:
746: uint256 debt;
747:
748:
749: _checkState((debt = account.debt) > 0);
750:
751: uint256 yieldToDebt = convertYieldTokensToDebt(amount);
752: uint256 credit = yieldToDebt > debt ? debt : yieldToDebt;
753: uint256 creditToYield = convertDebtTokensToYield(credit);
754: _subDebt(accountId, credit);
755:
756:
757: account.earmarked -= credit > account.earmarked ? account.earmarked : credit;
758:
759: account.collateralBalance -= creditToYield;
760:
761:
762: TokenUtils.safeTransfer(yieldToken, address(this), creditToYield); // <= FOUND
763: return creditToYield;
764: }['771']
771: function _liquidate(uint256 accountId) internal returns (uint256 debtAmount, uint256 feeInYield, uint256 feeInUnderlying) { // <= FOUND
772:
773: _earmark();
774:
775: _sync(accountId);
776:
777: Account storage account = _accounts[accountId];
778:
779:
780: if (account.debt == 0) {
781: return (0, 0, 0);
782: }
783:
784:
785: uint256 collateralInUnderlying = totalValue(accountId);
786: uint256 collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
787:
788:
789: if (collateralizationRatio > collateralizationLowerBound) {
790: return (0, 0, 0);
791: }
792:
793:
794: uint256 repaidAmountInYield = 0;
795: if (account.earmarked > 0) {
796: repaidAmountInYield = _forceRepay(accountId, convertDebtTokensToYield(account.earmarked));
797: }
798:
799: if (account.debt == 0) {
800: return (repaidAmountInYield, 0, 0);
801: }
802:
803:
804: collateralInUnderlying = totalValue(accountId);
805: collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
806:
807: if (collateralizationRatio <= collateralizationLowerBound) {
808: uint256 alchemistCurrentCollateralization = normalizeUnderlyingTokensToDebt(_getTotalUnderlyingValue()) * FIXED_POINT_SCALAR / totalDebt;
809:
810: (uint256 liquidationAmount, uint256 debtToBurn, uint256 baseFee) = calculateLiquidation(
811: collateralInUnderlying, account.debt, minimumCollateralization, alchemistCurrentCollateralization, globalMinimumCollateralization, liquidatorFee
812: );
813:
814: uint256 feeBonus = debtToBurn * liquidatorFee / BPS;
815: uint256 adjustedLiquidationAmount = convertDebtTokensToYield(liquidationAmount);
816: uint256 adjustedDebtToBurn = convertDebtTokensToYield(debtToBurn);
817: debtAmount = adjustedLiquidationAmount;
818: feeInYield = convertDebtTokensToYield(baseFee);
819:
820:
821: account.collateralBalance = account.collateralBalance > adjustedLiquidationAmount ? account.collateralBalance - adjustedLiquidationAmount : 0;
822:
823:
824: _subDebt(accountId, debtToBurn);
825:
826:
827: TokenUtils.safeTransfer(yieldToken, transmuter, adjustedDebtToBurn); // <= FOUND
828:
829: if (feeInYield > 0) {
830:
831: TokenUtils.safeTransfer(yieldToken, msg.sender, feeInYield);
832: }
833:
834:
835:
836: if (feeBonus > 0) {
837: uint256 vaultBalance = IFeeVault(alchemistFeeVault).totalDeposits();
838: if (vaultBalance > 0) {
839: feeInUnderlying = vaultBalance > feeBonus ? feeBonus : vaultBalance;
840: IFeeVault(alchemistFeeVault).withdraw(msg.sender, feeInUnderlying);
841: }
842: }
843: }
844:
845: return (debtAmount + repaidAmountInYield, feeInYield, feeInUnderlying);
846: }['200']
200: function claimRedemption(uint256 id) external { // <= FOUND
201: StakingPosition storage position = _positions[id];
202:
203: if (position.maturationBlock == 0) {
204: revert PositionNotFound();
205: }
206:
207: uint256 transmutationTime = position.maturationBlock - position.startBlock;
208: uint256 blocksLeft = position.maturationBlock > block.number ? position.maturationBlock - block.number : 0;
209: uint256 amountNottransmuted = blocksLeft > 0 ? position.amount * blocksLeft / transmutationTime : 0;
210: uint256 amountTransmuted = position.amount - amountNottransmuted;
211:
212: if (_requireOwned(id) != msg.sender) {
213: revert CallerNotOwner();
214: }
215:
216:
217: _burn(id);
218:
219:
220: uint256 yieldTokenBalance = TokenUtils.safeBalanceOf(alchemist.yieldToken(), address(this));
221: uint256 debtValue = alchemist.convertYieldTokensToDebt(yieldTokenBalance);
222: uint256 amountToRedeem = amountTransmuted > debtValue ? amountTransmuted - debtValue : 0;
223: if (amountToRedeem > 0) alchemist.redeem(amountToRedeem);
224:
225: uint256 feeAmount = amountTransmuted * transmutationFee / BPS;
226: uint256 claimAmount = amountTransmuted - feeAmount;
227:
228: uint256 syntheticFee = amountNottransmuted * exitFee / BPS;
229: uint256 syntheticReturned = amountNottransmuted - syntheticFee;
230:
231:
232: if (blocksLeft > 0) _updateStakingGraph(-position.amount.toInt256() * BLOCK_SCALING_FACTOR / transmutationTime.toInt256(), blocksLeft);
233:
234:
235:
236: uint256 badDebtRatio = alchemist.totalSyntheticsIssued() * 10**TokenUtils.expectDecimals(alchemist.yieldToken()) / alchemist.getTotalUnderlyingValue();
237:
238: if (badDebtRatio > 1e18) {
239: claimAmount = claimAmount * FIXED_POINT_SCALAR / badDebtRatio;
240: feeAmount = feeAmount * FIXED_POINT_SCALAR / badDebtRatio;
241: }
242:
243: TokenUtils.safeTransfer(alchemist.yieldToken(), msg.sender, alchemist.convertDebtTokensToYield(claimAmount)); // <= FOUND
244: TokenUtils.safeTransfer(alchemist.yieldToken(), protocolFeeReceiver, alchemist.convertDebtTokensToYield(feeAmount));
245:
246: TokenUtils.safeTransfer(syntheticToken, msg.sender, syntheticReturned);
247: TokenUtils.safeTransfer(syntheticToken, protocolFeeReceiver, syntheticFee);
248:
249:
250: TokenUtils.safeBurn(syntheticToken, amountTransmuted);
251:
252: totalLocked -= position.amount;
253:
254: emit PositionClaimed(msg.sender, claimAmount, syntheticReturned);
255:
256: delete _positions[id];
257: }Unsafe conversion from uint to int in Solidity can lead to unexpected overflows if the unsigned integer value exceeds the positive limit of the corresponding signed integer type. This is due to the fact that signed integers use one bit for the sign, effectively halving the positive range. An example is converting a uint256 number greater than type(uint128).max to an int256, which can result in an overflow. To mitigate this risk, consider using libraries like SafeCast, which include functions specifically designed to safely cast between different numerical types. They perform necessary checks and revert the transaction if an unsafe cast is attempted, thereby enhancing the security and robustness of the code.
Num of instances: 14
Click to show findings
['12']
12: function toInt256(uint256 y) internal pure returns (int256 z) { // <= FOUND
13: if (y >= 2 ** 255) {
14: revert IllegalArgument();
15: }
16: z = int256(y); // <= FOUND
17: }['50']
50: function addStake(Graph storage g, int256 amount, uint256 start, uint256 duration) internal { // <= FOUND
51: unchecked {
52: require(amount <= DELTA_MAX && amount >= DELTA_MIN);
53: require(start < GRAPH_MAX-1);
54:
55: uint256 expiration = start + duration;
56: require(expiration < GRAPH_MAX-1);
57:
58: uint256 graphSize = g.size;
59:
60:
61:
62: uint256 newSize = expiration + 2;
63: if (newSize >= graphSize) {
64:
65: newSize |= newSize >> 1;
66: newSize |= newSize >> 2;
67: newSize |= newSize >> 4;
68: newSize |= newSize >> 8;
69: newSize |= newSize >> 16;
70: if (GRAPH_MAX > 2**32) {
71: newSize |= newSize >> 32;
72: newSize |= newSize >> 64;
73: newSize |= newSize >> 128;
74: }
75: newSize++;
76:
77:
78:
79:
80: require (newSize <= GRAPH_MAX);
81:
82: if (graphSize != 0) {
83:
84: uint256 copy = g.g[graphSize];
85: while (graphSize <= newSize) {
86: g.g[graphSize] = copy;
87: graphSize += graphSize;
88: }
89: }
90: graphSize = newSize;
91: g.size = newSize;
92: }
93:
94:
95: update(g.g, start + 1, graphSize, amount, amount * int256(start)); // <= FOUND
96: update(g.g, expiration + 1, graphSize, -amount, -amount * int256(expiration));
97: }
98: }['108']
108: function queryStake(Graph storage g, uint256 start, uint256 end) internal view returns (int256) { // <= FOUND
109: int256 begDelta;
110: int256 begProd;
111: int256 endDelta;
112: int256 endProd;
113: unchecked {
114: require (end <= GRAPH_MAX);
115:
116: start--;
117: require (start <= GRAPH_MAX);
118:
119: (begDelta,begProd) = query(g.g, start);
120: (endDelta,endProd) = query(g.g, end);
121:
122: return ((int256(end) * endDelta) - endProd) - ((int256(start) * begDelta) - begProd); // <= FOUND
123: }
124: }['132']
132: function update(uint256[GRAPH_MAX + 1] storage graph, uint256 index, uint256 treeSize, int256 delta, int256 deltaProd) private { // <= FOUND
133: unchecked {
134: index += 1;
135: while (index <= treeSize) {
136:
137: uint256 packed = graph[index];
138: int256 ad;
139: int256 ap;
140:
141:
142: if ((packed&DELTA_SIGNBIT) != 0) {
143: ad = int256(packed | ~DELTA_MASK); // <= FOUND
144: } else {
145: ad = int256(packed & DELTA_MASK);
146: }
147: ap = int256(packed)>>DELTA_BITS;
148:
149: ad+=delta;
150: ap+=deltaProd;
151:
152:
153: require(ad <= DELTA_MAX && ad >= DELTA_MIN);
154: require(ap <= PRODUCT_MAX && ap >= PRODUCT_MIN);
155: graph[index] = (uint256(ad)&DELTA_MASK)|uint256(ap<<DELTA_BITS);
156:
157: assembly {
158: index := add(index, and(index, sub(0, index)))
159: }
160: }
161: }
162: }['170']
170: function query(uint256[GRAPH_MAX + 1] storage graph, uint256 index) private view returns (int256 sum, int256 sumProd) { // <= FOUND
171: unchecked {
172: index += 1;
173: while (index > 0) {
174:
175: uint256 packed = graph[index];
176: int256 ad;
177: int256 ap;
178:
179:
180: if ((packed&(2**(DELTA_BITS-1))) != 0) {
181: ad = int256(packed | ~DELTA_MASK); // <= FOUND
182: } else {
183: ad = int256(packed & DELTA_MASK);
184: }
185: ap = int256(packed)>>DELTA_BITS;
186:
187: sum += ad;
188: sumProd += ap;
189:
190: assembly {
191: index := sub(index, and(index, sub(0, index)))
192: }
193: }
194: }
195: }['16']
16: z = int256(y); // <= FOUND['27']
27:
28: int256 private constant DELTA_MAX = int256(2**DELTA_BITS)-1; // <= FOUND['29']
29: int256 private constant PRODUCT_MAX = int256(2**PRODUCT_BITS)-1; // <= FOUND['95']
95:
96: update(g.g, start + 1, graphSize, amount, amount * int256(start)); // <= FOUND['96']
96: update(g.g, expiration + 1, graphSize, -amount, -amount * int256(expiration)); // <= FOUND['143']
143: ad = int256(packed | ~DELTA_MASK); // <= FOUND['145']
145: ad = int256(packed & DELTA_MASK); // <= FOUND['147']
147: ap = int256(packed)>>DELTA_BITS; // <= FOUND['122']
122: return ((int256(end) * endDelta) - endProd) - ((int256(start) * begDelta) - begProd); // <= FOUNDExperimental pragma features should not be used within production code due to their unstable and untested nature. These features are still under development and have not undergone rigorous scrutiny, making them susceptible to bugs, security vulnerabilities, and potential breaking changes in future Solidity releases.
Num of instances: 1
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: 16
Click to show findings
['27']
27: (bool success, bytes memory data) = token.staticcall(abi.encodeWithSelector(IERC20Metadata.decimals.selector)); // <= FOUND['62']
62: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, recipient, amount)); // <= FOUND['77']
77: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, value)); // <= FOUND['93']
93: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, owner, recipient, amount)); // <= FOUND['45']
45: (bool success, bytes memory data) = token.staticcall(abi.encodeWithSelector(IERC20.balanceOf.selector, account)); // <= FOUND['108']
108: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Mintable.mint.selector, recipient, amount)); // <= FOUND['122']
122: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Burnable.burn.selector, amount)); // <= FOUND['137']
137: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Burnable.burnFrom.selector, owner, amount)); // <= FOUND['27']
27: (bool success, bytes memory data) = token.staticcall(abi.encodeWithSelector(IERC20Metadata.decimals.selector)); // <= FOUND['62']
62: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, recipient, amount)); // <= FOUND['77']
77: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, value)); // <= FOUND['93']
93: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, owner, recipient, amount)); // <= FOUND['45']
45: (bool success, bytes memory data) = token.staticcall(abi.encodeWithSelector(IERC20.balanceOf.selector, account)); // <= FOUND['108']
108: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Mintable.mint.selector, recipient, amount)); // <= FOUND['122']
122: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Burnable.burn.selector, amount)); // <= FOUND['137']
137: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Burnable.burnFrom.selector, owner, amount)); // <= FOUNDThis is to ensure the team can add new state variables without compromising compatability.
Num of instances: 1
[Low-12] 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
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: 1
Click to show findings
['81']
81: function withdraw(address recipient, uint256 amount) external override onlyAuthorized nonReentrant { // <= FOUND
82: if (amount == 0) revert ZeroAmount();
83:
84:
85: if (amount > address(this).balance) revert InsufficientBalance();
86:
87:
88: (bool success,) = recipient.call{value: amount}("");
89: if (!success) revert TransferFailed();
90:
91: emit Withdrawn(recipient, amount);
92: }In Solidity contract deployment, not making the initialize() function call atomic with the contract creation can leave a vulnerability window. A malicious actor could exploit this time gap and call initialize() before the intended initialization. This action could disrupt the contract's setup, potentially necessitating a full contract re-deployment to ensure proper initialization. To mitigate such risks, it's advised to use a factory contract. This factory contract can be programmed to deploy and initialize a new contract in a single atomic transaction, closing the window of vulnerability and ensuring correct and secure contract initialization.
Num of instances: 1
Click to show findings
['155']
155: function initialize(AlchemistInitializationParams memory params) external initializer // <= FOUNDdisableInitializers() should be used in upgradeable contracts to ensure the initializer functions can't be called more than once. In upgradeable contracts, initializer functions set initial state and values, but if they can be invoked multiple times, it could lead to unexpected behavior or vulnerabilities. By calling disableInitializers() after the initial setup, you essentially lock the initializer functions, ensuring they can only be called once during the contract's lifecycle. This prevents repeated initializations, helping to maintain the integrity and security of the contract, and providing a safeguard against potential manipulation or misuse of the initialization functions.
Num of instances: 1
Minting tokens to the zero address in Solidity is a potential pitfall that should be carefully guarded against. The zero address (0x0) is generally used as a default value and represents an uninitialized or null address. Minting tokens to this address effectively burns them, making them inaccessible and permanently removing them from the total supply. This could lead to unintended token loss if performed accidentally. To prevent this, it's important to include a check in the minting function to ensure that the target address is not the zero address. Using a library like OpenZeppelin's Address can provide utility functions like requireNonZero, which simplifies this check and enhances the security of the minting function.
Num of instances: 1
Click to show findings
['55']
55: function mint(address _recipient, uint256 _amount) external onlyWhitelisted {
56: require(!paused[msg.sender], "AlETH: Alchemist is currently paused.");
57: hasMinted[msg.sender] = hasMinted[msg.sender] + _amount;
58: _mint(_recipient, _amount); // <= FOUND
59: }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: 11
Click to show findings
['75']
75: function underlyingTokenToUSD(uint256 underlyingTokenAmount, uint256 underlyingTokenDecimals) internal pure returns (uint256 usdAmount) { // <= FOUND
76: uint256 usdConversionFactor = 10 ** (underlyingTokenDecimals - USDC_DECIMALS);
77: return underlyingTokenAmount / usdConversionFactor; // <= FOUND
78: }['331']
331: function getMaxBorrowable(uint256 tokenId) external view returns (uint256) { // <= FOUND
332: (uint256 debt,, uint256 collateral) = _calculateUnrealizedDebt(tokenId);
333: uint256 debtValueOfCollateral = convertYieldTokensToDebt(collateral);
334: return (debtValueOfCollateral * FIXED_POINT_SCALAR / minimumCollateralization) - debt; // <= FOUND
335: }['573']
573: function redeem(uint256 amount) external onlyTransmuter { // <= FOUND
574: _earmark();
575:
576: _redemptionWeight += PositionDecay.WeightIncrement(amount, cumulativeEarmarked);
577:
578:
579: uint256 collRedeemed = convertDebtTokensToYield(amount);
580: uint256 feeCollateral = collRedeemed * protocolFee / BPS; // <= FOUND
581: uint256 totalOut = collRedeemed + feeCollateral;
582:
583:
584: uint256 old = _totalLocked;
585: _totalLocked = old - totalOut;
586: _collateralWeight += PositionDecay.WeightIncrement(totalOut, old);
587: cumulativeEarmarked -= amount;
588: totalDebt -= amount;
589: totalSyntheticsIssued -= amount;
590:
591: lastRedemptionBlock = block.number;
592:
593: _redemptions[block.number] = RedemptionInfo(cumulativeEarmarked, totalDebt, _earmarkWeight);
594:
595: TokenUtils.safeTransfer(yieldToken, transmuter, collRedeemed);
596: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, feeCollateral);
597:
598: emit Redemption(amount);
599: }['631']
631: function calculateLiquidation( // <= FOUND
632: uint256 collateral,
633: uint256 debt,
634: uint256 targetCollateralization,
635: uint256 alchemistCurrentCollateralization,
636: uint256 alchemistMinimumCollateralization,
637: uint256 feeBps
638: ) public pure returns (uint256 grossCollateralToSeize, uint256 debtToBurn, uint256 fee) {
639: if (debt >= collateral) {
640:
641: return (debt, debt, 0);
642: }
643:
644: if (alchemistCurrentCollateralization < alchemistMinimumCollateralization) {
645:
646: return (debt, debt, 0);
647: }
648:
649:
650: uint256 surplus = collateral > debt ? collateral - debt : 0;
651: fee = (surplus * feeBps) / BPS; // <= FOUND
652:
653:
654: uint256 adjCollat = collateral - fee;
655:
656:
657: uint256 md = (targetCollateralization * debt) / FIXED_POINT_SCALAR;
658:
659:
660: if (md <= adjCollat) {
661: return (0, 0, fee);
662: }
663:
664:
665: uint256 num = md - adjCollat;
666:
667:
668: uint256 denom = targetCollateralization - FIXED_POINT_SCALAR;
669:
670:
671: debtToBurn = (num * FIXED_POINT_SCALAR) / denom;
672:
673:
674: grossCollateralToSeize = debtToBurn + fee;
675: }['694']
694: function convertUnderlyingTokensToYield(uint256 amount) public view returns (uint256) { // <= FOUND
695: uint8 decimals = TokenUtils.expectDecimals(yieldToken);
696: return amount * 10 ** decimals / ITokenAdapter(tokenAdapter).price(); // <= FOUND
697: }['705']
705: function normalizeDebtTokensToUnderlying(uint256 amount) public view returns (uint256) { // <= FOUND
706: return amount / underlyingConversionFactor; // <= FOUND
707: }['852']
852: function _addDebt(uint256 tokenId, uint256 amount) internal { // <= FOUND
853: Account storage account = _accounts[tokenId];
854: account.debt += amount;
855: totalDebt += amount;
856:
857:
858: uint256 toLock = convertDebtTokensToYield(amount) * minimumCollateralization / FIXED_POINT_SCALAR; // <= FOUND
859:
860: if (account.freeCollateral < toLock) revert Undercollateralized();
861: account.freeCollateral -= toLock;
862: account.rawLocked += toLock;
863: _totalLocked += toLock;
864: }['869']
869: function _subDebt(uint256 tokenId, uint256 amount) internal { // <= FOUND
870: Account storage account = _accounts[tokenId];
871: account.debt -= amount;
872: totalDebt -= amount;
873:
874:
875: uint256 toFree = convertDebtTokensToYield(amount) * minimumCollateralization / FIXED_POINT_SCALAR; // <= FOUND
876:
877:
878: if (toFree > _totalLocked) {
879: toFree = _totalLocked;
880: }
881:
882: _totalLocked -= toFree;
883: account.rawLocked -= toFree;
884: account.freeCollateral += toFree;
885: }['1080']
1080: function _getLiquidationAmount(uint256 collateral, uint256 debt, uint256 globalRatio) internal view returns (uint256 liquidationAmount) { // <= FOUND
1081: _checkState(minimumCollateralization > FIXED_POINT_SCALAR);
1082: if (debt >= collateral) {
1083:
1084: return debt;
1085: }
1086:
1087: if (globalRatio < globalMinimumCollateralization) {
1088:
1089: return debt;
1090: }
1091:
1092: uint256 expectedCollateralForCurrentDebt = (debt * minimumCollateralization) / FIXED_POINT_SCALAR; // <= FOUND
1093: uint256 collateralDiff = expectedCollateralForCurrentDebt - collateral;
1094: uint256 ratioDiff = minimumCollateralization - FIXED_POINT_SCALAR;
1095: liquidationAmount = collateralDiff * FIXED_POINT_SCALAR / ratioDiff;
1096: return liquidationAmount;
1097: }['115']
115: function mul(Number memory self, Number memory number) internal pure returns (Number memory) { // <= FOUND
116: return Number((self.n * number.n) / ONE); // <= FOUND
117: }['179']
179: function truncate(Number memory self) internal pure returns (uint256) { // <= FOUND
180: return self.n / ONE; // <= FOUND
181: }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: 3
Click to show findings
['43']
43: constructor(address _token, address _alchemist, address _owner) Ownable(_owner) { // <= FOUND
44: _checkNonZeroAddress(_token);
45: _checkNonZeroAddress(_alchemist);
46: token = _token;
47: authorized[_alchemist] = true;
48: authorized[_owner] = true;
49: emit AuthorizationUpdated(_alchemist, true);
50: }['37']
37: constructor(address _priceFeed, uint256 _updateTimeInSeconds, uint256 _underlyingTokenDecimals) { // <= FOUND
38: priceFeed = _priceFeed;
39: updateTimeInSeconds = _updateTimeInSeconds;
40: underlyingTokenDecimals = _underlyingTokenDecimals;
41: }['17']
17: constructor(address _token, address _underlyingToken) { // <= FOUND
18: token = _token;
19: underlyingToken = _underlyingToken;
20: }In Solidity, it's crucial to check the return value of .transfer and .transferFrom methods because not all ERC20 and ERC721 token implementations revert on failure. Notably, If these methods fail silently and the contract doesn't verify their return value, the contract might continue execution as if the tokens were transferred successfully, leading to incorrect balances, loss of funds, or other unexpected behaviors. Therefore, the return values of these methods should always be checked, ensuring a failed token transfer operation correctly halts the execution of the contract.
Num of instances: 2
Click to show findings
['26']
26: function deposit(uint256 amount) external { // <= FOUND
27: _checkNonZeroAmount(amount);
28: IERC20(token).transferFrom(msg.sender, address(this), amount); // <= FOUND
29: emit Deposited(msg.sender, amount);
30: }['37']
37: function withdraw(address recipient, uint256 amount) external override onlyAuthorized { // <= FOUND
38: _checkNonZeroAddress(recipient);
39: _checkNonZeroAmount(amount);
40:
41: IERC20(token).transfer(recipient, amount); // <= FOUND
42: emit Withdrawn(recipient, amount);
43: }Reason: The "image" field in Non-Fungible Tokens (NFTs) is primarily designed to store the URL of the token's associated image. Misusing this field could result in incompatibilities with major platforms like OpenSea, which expect this standard to be followed. Moreover, storing raw SVG data in the "image" field, as opposed to a URL, could lead to unexpected issues with image rendering or loading.
Resolution: To ensure interoperability and maintain standards, developers should use the "image" field exclusively for storing the image URL. For raw SVG data, the "image_data" field can be utilized if it's supported by the NFT standard. Adhering to these field specifications can guarantee correct display and accessibility of the NFT on platforms like OpenSea and other NFT marketplaces.
Num of instances: 1
Click to show findings
['53']
53: function generateJSONString(uint256 tokenId, string memory svg) internal pure returns (string memory) { // <= FOUND
54: string memory json = Base64.encode( // <= FOUND
55: abi.encodePacked(
56: '{"name": "AlchemistV3 Position #',
57: tokenId.toString(),
58: '", ',
59: '"description": "Position token for Alchemist V3", ',
60: '"image": "data:image/svg+xml;base64,',
61: Base64.encode(bytes(svg)),
62: '"}'
63: )
64: );
65: return json;
66: }The code of tokenURI does not check is the inputted NFT data is valid and/or exists. This is against the EIP-721 standard. Implement checks to ensure inputted tokenURI parameters are valid and the NFT in question exists.
Num of instances: 2
Click to show findings
['84']
84: function tokenURI(uint256 tokenId) public view override returns (string memory) { // <= FOUND
85:
86: ERC721(address(this)).ownerOf(tokenId);
87: return NFTMetadataGenerator.generateTokenURI(tokenId, "Alchemist V3 Position");
88: }['160']
160: function tokenURI(uint256 id) public view override returns (string memory) { // <= FOUND
161:
162: ERC721(address(this)).ownerOf(id);
163: return NFTMetadataGenerator.generateTokenURI(id, "Transmuter V3 Position");
164: }The use of safeApprove has been deprecated in favour of safeIncreaseAllowance and safeDecreaseAllowance. Transition to these to ensure compatibility and functionality (if a bug is found) for the future.
Num of instances: 2
Click to show findings
['76']
76:
84: function safeApprove(address token, address spender, uint256 value) internal { // <= FOUND['76']
76:
83: function safeApprove(address token, address spender, uint256 value) internal { // <= FOUNDThe use of fixed decimal values such as 1e18 or 1e8 in Solidity contracts can lead to inaccuracies, bugs, and vulnerabilities, particularly when interacting with tokens having different decimal configurations. Not all ERC20 tokens follow the standard 18 decimal places, and assumptions about decimal places can lead to miscalculations.
Resolution: Always retrieve and use the decimals() function from the token contract itself when performing calculations involving token amounts. This ensures that your contract correctly handles tokens with any number of decimal places, mitigating the risk of numerical errors or under/overflows that could jeopardize contract integrity and user funds.
Num of instances: 2
Click to show findings
['30']
30: uint256 public constant FIXED_POINT_SCALAR = 1e18; // <= FOUND['238']
238: if (badDebtRatio > 1e18) { // <= FOUNDDuring hard forks in blockchain networks, users often face complexities to retain ownership control across all forked chains. This could potentially create confusion and ambiguity regarding the actual ownership of NFTs on the various chains. A recommended mitigation strategy is to hard-code the preferred chain ID into your contract functions, using a require(1 == block.chainId), or whichever chain ID you prefer. Alternatively, you can embed the chain ID within the URI of your NFT. These measures ensure clear and unambiguous ownership, as the NFTs are strictly associated with a specific chain, thereby avoiding any potential disputes or confusion arising from hard forks.
Num of instances: 2
Click to show findings
['84']
84: function tokenURI(uint256 tokenId) public view override returns (string memory) { // <= FOUND
85:
86: ERC721(address(this)).ownerOf(tokenId);
87: return NFTMetadataGenerator.generateTokenURI(tokenId, "Alchemist V3 Position");
88: }['160']
160: function tokenURI(uint256 id) public view override returns (string memory) { // <= FOUND
161:
162: ERC721(address(this)).ownerOf(id);
163: return NFTMetadataGenerator.generateTokenURI(id, "Transmuter V3 Position");
164: }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: 3
Click to show findings
['37']
37: function withdraw(address recipient, uint256 amount) external override onlyAuthorized { // <= FOUND
38: _checkNonZeroAddress(recipient);
39: _checkNonZeroAmount(amount);
40:
41: IERC20(token).transfer(recipient, amount); // <= FOUND
42: emit Withdrawn(recipient, amount);
43: }['488']
488: function repay(uint256 amount, uint256 recipientTokenId) public returns (uint256) { // <= FOUND
489: _checkArgument(amount > 0);
490: _checkForValidAccountId(recipientTokenId);
491: Account storage account = _accounts[recipientTokenId];
492:
493:
494: if (block.number == account.lastMintBlock) revert CannotRepayOnMintBlock();
495:
496:
497: _earmark();
498:
499:
500: _sync(recipientTokenId);
501:
502: uint256 debt;
503:
504:
505: _checkState((debt = account.debt) > 0);
506:
507: uint256 yieldToDebt = convertYieldTokensToDebt(amount);
508: uint256 credit = yieldToDebt > debt ? debt : yieldToDebt;
509: uint256 creditToYield = convertDebtTokensToYield(credit);
510:
511:
512: uint256 earmarkToRemove = credit > account.earmarked ? account.earmarked : credit;
513: account.earmarked -= earmarkToRemove;
514:
515:
516: account.collateralBalance -= creditToYield * protocolFee / BPS;
517:
518: _subDebt(recipientTokenId, credit);
519:
520:
521: TokenUtils.safeTransferFrom(yieldToken, msg.sender, transmuter, creditToYield); // <= FOUND
522: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, creditToYield);
523:
524: emit Repay(msg.sender, amount, recipientTokenId, creditToYield);
525:
526: return creditToYield;
527: }['573']
573: function redeem(uint256 amount) external onlyTransmuter { // <= FOUND
574: _earmark();
575:
576: _redemptionWeight += PositionDecay.WeightIncrement(amount, cumulativeEarmarked);
577:
578:
579: uint256 collRedeemed = convertDebtTokensToYield(amount);
580: uint256 feeCollateral = collRedeemed * protocolFee / BPS;
581: uint256 totalOut = collRedeemed + feeCollateral;
582:
583:
584: uint256 old = _totalLocked;
585: _totalLocked = old - totalOut;
586: _collateralWeight += PositionDecay.WeightIncrement(totalOut, old);
587: cumulativeEarmarked -= amount;
588: totalDebt -= amount;
589: totalSyntheticsIssued -= amount;
590:
591: lastRedemptionBlock = block.number;
592:
593: _redemptions[block.number] = RedemptionInfo(cumulativeEarmarked, totalDebt, _earmarkWeight);
594:
595: TokenUtils.safeTransfer(yieldToken, transmuter, collRedeemed); // <= FOUND
596: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, feeCollateral);
597:
598: emit Redemption(amount);
599: }Price feeds provided by Chainlink oracles are an essential resource for many DeFi applications in the Ethereum ecosystem, enabling smart contracts to access reliable, tamper-resistant price data. However, these price feeds are often normalized using a certain number of decimal places, a fact which can potentially lead to discrepancies or inaccuracies if not properly accounted for.
For example, if a Chainlink price feed returns a price with 8 decimal places, but your contract assumes the price has 18 decimal places (which is common with Ether and many ERC20 tokens), this could cause an underflow in your contract's calculations, which might lead to incorrect operations or financial discrepancies.
As a best practice, your contract should dynamically check the number of decimals that the Chainlink price feed uses. This information can be retrieved via the decimals() function provided by the Chainlink AggregatorV3Interface. Once you have this information, you can correctly scale the price data to match your contract's assumptions, ensuring that all calculations are accurate and consistent.
This practice not only makes your contract more resilient to potential discrepancies in the data feed but also enhances its interoperability by ensuring it can correctly interact with different types of price feeds, each possibly having different decimal normalization.
Num of instances: 2
Click to show findings
['21']
21: function ethToUSD(uint256 ethAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 usdAmount) {
22:
23: AggregatorV3Interface priceFeed = AggregatorV3Interface(usdPriceFeed);
24: (, int256 price,, uint256 updateTime,) = priceFeed.latestRoundData(); // <= FOUND
25:
26: if (price <= 0) {
27: revert ChainlinkMalfunction(usdPriceFeed, price);
28: }
29:
30: if (updateTime == 0) {
31: revert IncompleteRound(usdPriceFeed, updateTime);
32: }
33:
34: if (updateTime < block.timestamp - expectedUpdateTime) {
35: revert IncompleteRound(usdPriceFeed, updateTime);
36: }
37:
38:
39: return (ethAmount * uint256(price)) / 1e20;
40: }['48']
48: function usdToETH(uint256 usdAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 ethAmount) {
49:
50: AggregatorV3Interface priceFeed = AggregatorV3Interface(usdPriceFeed);
51: (, int256 price,, uint256 updateTime,) = priceFeed.latestRoundData(); // <= FOUND
52:
53: if (price <= 0) {
54: revert ChainlinkMalfunction(usdPriceFeed, price);
55: }
56:
57: if (updateTime == 0) {
58: revert IncompleteRound(usdPriceFeed, updateTime);
59: }
60:
61: if (updateTime < block.timestamp - expectedUpdateTime) {
62: revert IncompleteRound(usdPriceFeed, updateTime);
63: }
64:
65:
66:
67: return (usdAmount * 1e20) / uint256(price);
68: }Chainlink oracle provides reliable, real-time data feeds to smart contracts. However, in order to enhance security and minimize the potential impact of an oracle failure or manipulation, it's a good practice to establish minimum and maximum thresholds for these data inputs. Without this safeguard, an erroneous or maliciously manipulated data point from the oracle could potentially lead to severe consequences in contract behavior. Therefore, the values retrieved from Chainlink's oracle should be cross-verified against preset min/max boundaries to ensure they fall within the expected range. This extra layer of validation adds robustness and reduces the risk of oracle-related issues.
Num of instances: 2
Click to show findings
['21']
21: function ethToUSD(uint256 ethAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 usdAmount) {
22:
23: AggregatorV3Interface priceFeed = AggregatorV3Interface(usdPriceFeed);
24: (, int256 price,, uint256 updateTime,) = priceFeed.latestRoundData();
25:
26: if (price <= 0) {
27: revert ChainlinkMalfunction(usdPriceFeed, price);
28: }
29:
30: if (updateTime == 0) {
31: revert IncompleteRound(usdPriceFeed, updateTime);
32: }
33:
34: if (updateTime < block.timestamp - expectedUpdateTime) {
35: revert IncompleteRound(usdPriceFeed, updateTime);
36: }
37:
38:
39: return (ethAmount * uint256(price)) / 1e20;
40: }['48']
48: function usdToETH(uint256 usdAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 ethAmount) {
49:
50: AggregatorV3Interface priceFeed = AggregatorV3Interface(usdPriceFeed);
51: (, int256 price,, uint256 updateTime,) = priceFeed.latestRoundData();
52:
53: if (price <= 0) {
54: revert ChainlinkMalfunction(usdPriceFeed, price);
55: }
56:
57: if (updateTime == 0) {
58: revert IncompleteRound(usdPriceFeed, updateTime);
59: }
60:
61: if (updateTime < block.timestamp - expectedUpdateTime) {
62: revert IncompleteRound(usdPriceFeed, updateTime);
63: }
64:
65:
66:
67: return (usdAmount * 1e20) / uint256(price);
68: }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: 1
Click to show findings
['88']
88:
89: (bool success,) = recipient.call{value: amount}(""); // <= FOUND[Low-30] Events may be emitted out of order due to code not follow the best practice of check-effects-interaction
The "check-effects-interaction" pattern also impacts event ordering. When a contract doesn't adhere to this pattern, events might be emitted in a sequence that doesn't reflect the actual logical flow of operations. This can cause confusion during event tracking, potentially leading to erroneous off-chain interpretations. To rectify this, always ensure that checks are performed first, state modifications come next, and interactions with external contracts or addresses are done last. This will ensure events are emitted in a logical, consistent manner, providing a clear and accurate chronological record of on-chain actions for off-chain systems and observers.
Num of instances: 2
Click to show findings
['81']
81: function withdraw(address recipient, uint256 amount) external override onlyAuthorized nonReentrant { // <= FOUND
82: if (amount == 0) revert ZeroAmount();
83:
84:
85: if (amount > address(this).balance) revert InsufficientBalance();
86:
87:
88: (bool success,) = recipient.call{value: amount}(""); // <= FOUND
89: if (!success) revert TransferFailed();
90:
91: emit Withdrawn(recipient, amount); // <= FOUND
92: }['195']
195: function setAlchemistFeeVault(address value) external onlyAdmin { // <= FOUND
196: if (IFeeVault(value).token() != underlyingToken) { // <= FOUND
197: revert AlchemistVaultTokenMismatchError();
198: }
199: alchemistFeeVault = value;
200: emit AlchemistFeeVaultUpdated(value); // <= FOUND
201: }Critical functions, especially those affecting protocol parameters or user funds, are potential points of failure or exploitation. To mitigate risks, incorporating a timelock on such functions can be beneficial. A timelock requires a waiting period between the time an action is initiated and when it's executed, giving stakeholders time to react, potentially vetoing malicious or erroneous changes. To implement, integrate a smart contract like OpenZeppelin's TimelockController or build a custom mechanism. This ensures governance decisions or administrative changes are transparent and allows for community or multi-signature interventions, enhancing protocol security and trustworthiness.
Num of instances: 20
Click to show findings
['57']
57: function setAuthorization(address account, bool status) external onlyOwner // <= FOUND['182']
182: function setAlchemistPositionNFT(address nft) external onlyAdmin // <= FOUND['195']
195: function setAlchemistFeeVault(address value) external onlyAdmin // <= FOUND['204']
204: function setPendingAdmin(address value) external onlyAdmin // <= FOUND['226']
226: function setDepositCap(uint256 value) external onlyAdmin // <= FOUND['234']
234: function setProtocolFeeReceiver(address value) external onlyAdmin // <= FOUND['242']
242: function setProtocolFee(uint256 fee) external onlyAdmin // <= FOUND['250']
250: function setLiquidatorFee(uint256 fee) external onlyAdmin // <= FOUND['258']
258: function setTokenAdapter(address value) external onlyAdmin // <= FOUND['266']
266: function setTransmuter(address value) external onlyAdmin // <= FOUND['277']
277: function setGuardian(address guardian, bool isActive) external onlyAdmin // <= FOUND['285']
285: function setMinimumCollateralization(uint256 value) external onlyAdmin // <= FOUND['293']
293: function setGlobalMinimumCollateralization(uint256 value) external onlyAdmin // <= FOUND['300']
300: function setCollateralizationLowerBound(uint256 value) external onlyAdmin // <= FOUND['116']
116: function setAlchemist(address value) external onlyAdmin // <= FOUND['123']
123: function setDepositCap(uint256 cap) external onlyAdmin // <= FOUND['131']
131: function setTransmutationFee(uint256 fee) external onlyAdmin // <= FOUND['139']
139: function setExitFee(uint256 fee) external onlyAdmin // <= FOUND['147']
147: function setTransmutationTime(uint256 time) external onlyAdmin // <= FOUND['234']
234: function setProtocolFeeReceiver(address value) external onlyAdmin // <= FOUNDUnbounded loops in smart contracts pose a risk because they iterate over an unknown number of elements, potentially consuming all available gas for a transaction. This can result in unintended transaction failures. Gas consumption increases linearly with the number of iterations, and if it surpasses the gas limit, the transaction reverts, wasting the gas spent. To mitigate this, developers should either set a maximum limit on loop iterations.
Num of instances: 1
Click to show findings
['543']
543: function batchLiquidate(uint256[] memory accountIds) // <= FOUND
544: external
545: returns (uint256 totalAmountLiquidated, uint256 totalFeesInYield, uint256 totalFeesInUnderlying)
546: {
547:
548:
549: if (accountIds.length == 0) {
550: revert MissingInputData();
551: }
552:
553: for (uint256 i = 0; i < accountIds.length; i++) { // <= FOUND
554: uint256 accountId = accountIds[i];
555: if (accountId == 0 || !_tokenExists(alchemistPositionNFT, accountId)) {
556: continue;
557: }
558: (uint256 underlyingAmount, uint256 feeInYield, uint256 feeInUnderlying) = _liquidate(accountId);
559: totalAmountLiquidated += underlyingAmount;
560: totalFeesInYield += feeInYield;
561: totalFeesInUnderlying += feeInUnderlying;
562: }
563:
564: if (totalAmountLiquidated > 0) {
565: return (totalAmountLiquidated, totalFeesInYield, totalFeesInUnderlying);
566: } else {
567:
568: revert LiquidationError();
569: }
570: }Implementing a two-step procedure for updating protocol addresses adds an extra layer of security. In such a system, the first step initiates the change, and the second step, after a predefined delay, confirms and finalizes it. This delay allows stakeholders or monitoring tools to observe and react to unintended or malicious changes. If an unauthorized change is detected, corrective actions can be taken before the change is finalized. To achieve this, introduce a "proposed address" state variable and a "delay period". Upon an update request, set the "proposed address". After the delay, if not contested, the main protocol address can be updated.
Num of instances: 10
Click to show findings
['57']
57: function setAuthorization(address account, bool status) external onlyOwner { // <= FOUND
58: _checkNonZeroAddress(account);
59: authorized[account] = status;
60: emit AuthorizationUpdated(account, status);
61: }['182']
182: function setAlchemistPositionNFT(address nft) external onlyAdmin { // <= FOUND
183: if (nft == address(0)) {
184: revert AlchemistV3NFTZeroAddressError();
185: }
186:
187: if (alchemistPositionNFT != address(0)) {
188: revert AlchemistV3NFTAlreadySetError();
189: }
190:
191: alchemistPositionNFT = nft;
192: }['195']
195: function setAlchemistFeeVault(address value) external onlyAdmin { // <= FOUND
196: if (IFeeVault(value).token() != underlyingToken) {
197: revert AlchemistVaultTokenMismatchError();
198: }
199: alchemistFeeVault = value;
200: emit AlchemistFeeVaultUpdated(value);
201: }['204']
204: function setPendingAdmin(address value) external onlyAdmin { // <= FOUND
205: pendingAdmin = value;
206:
207: emit PendingAdminUpdated(value);
208: }['234']
234: function setProtocolFeeReceiver(address value) external onlyAdmin { // <= FOUND
235: _checkArgument(value != address(0));
236:
237: protocolFeeReceiver = value;
238: emit ProtocolFeeReceiverUpdated(value);
239: }['258']
258: function setTokenAdapter(address value) external onlyAdmin { // <= FOUND
259: _checkArgument(value != address(0));
260:
261: tokenAdapter = value;
262: emit TokenAdapterUpdated(value);
263: }['266']
266: function setTransmuter(address value) external onlyAdmin { // <= FOUND
267: _checkArgument(value != address(0));
268:
269:
270: require(convertYieldTokensToDebt(TokenUtils.safeBalanceOf(yieldToken, transmuter)) >= ITransmuter(transmuter).totalLocked());
271:
272: transmuter = value;
273: emit TransmuterUpdated(value);
274: }['277']
277: function setGuardian(address guardian, bool isActive) external onlyAdmin { // <= FOUND
278: _checkArgument(guardian != address(0));
279:
280: guardians[guardian] = isActive;
281: emit GuardianSet(guardian, isActive);
282: }['116']
116: function setAlchemist(address value) external onlyAdmin { // <= FOUND
117: alchemist = IAlchemistV3(value);
118:
119: emit AlchemistUpdated(value);
120: }['154']
154: function setProtocolFeeReceiver(address value) external onlyAdmin { // <= FOUND
155: _checkArgument(value != address(0));
156: protocolFeeReceiver = value;
157: emit ProtocolFeeReceiverUpdated(value);
158: }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: 2
Click to show findings
['132']
132: function update(uint256[GRAPH_MAX + 1] storage graph, uint256 index, uint256 treeSize, int256 delta, int256 deltaProd) private { // <= FOUND
133: unchecked {
134: index += 1; // <= FOUND
135: while (index <= treeSize) {
136:
137: uint256 packed = graph[index];
138: int256 ad;
139: int256 ap;
140:
141:
142: if ((packed&DELTA_SIGNBIT) != 0) {
143: ad = int256(packed | ~DELTA_MASK);
144: } else {
145: ad = int256(packed & DELTA_MASK);
146: }
147: ap = int256(packed)>>DELTA_BITS;
148:
149: ad+=delta;
150: ap+=deltaProd;
151:
152:
153: require(ad <= DELTA_MAX && ad >= DELTA_MIN);
154: require(ap <= PRODUCT_MAX && ap >= PRODUCT_MIN);
155: graph[index] = (uint256(ad)&DELTA_MASK)|uint256(ap<<DELTA_BITS);
156:
157: assembly {
158: index := add(index, and(index, sub(0, index)))
159: }
160: }
161: }
162: }['170']
170: function query(uint256[GRAPH_MAX + 1] storage graph, uint256 index) private view returns (int256 sum, int256 sumProd) { // <= FOUND
171: unchecked {
172: index += 1; // <= FOUND
173: while (index > 0) {
174:
175: uint256 packed = graph[index];
176: int256 ad;
177: int256 ap;
178:
179:
180: if ((packed&(2**(DELTA_BITS-1))) != 0) {
181: ad = int256(packed | ~DELTA_MASK);
182: } else {
183: ad = int256(packed & DELTA_MASK);
184: }
185: ap = int256(packed)>>DELTA_BITS;
186:
187: sum += ad;
188: sumProd += ap;
189:
190: assembly {
191: index := sub(index, and(index, sub(0, index)))
192: }
193: }
194: }
195: }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: 4
Click to show findings
['47']
47: function safeTransfer(address token, address recipient, uint256 amount) internal {
48: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, recipient, amount)); // <= FOUND
49:
50: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
51: revert ERC20CallFailed(token, success, data);
52: }
53: }['63']
63: function safeApprove(address token, address spender, uint256 value) internal {
64: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, value)); // <= FOUND
65:
66: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
67: revert ERC20CallFailed(token, success, data);
68: }
69: }['80']
80: function safeTransferFrom(address token, address owner, address recipient, uint256 amount) internal {
81: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, owner, recipient, amount)); // <= FOUND
82:
83: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
84: revert ERC20CallFailed(token, success, data);
85: }
86: }['81']
81: function withdraw(address recipient, uint256 amount) external override onlyAuthorized nonReentrant {
82: if (amount == 0) revert ZeroAmount();
83:
84:
85: if (amount > address(this).balance) revert InsufficientBalance();
86:
87:
88: (bool success,) = recipient.call{value: amount}(""); // <= FOUND
89: if (!success) revert TransferFailed();
90:
91: emit Withdrawn(recipient, amount);
92: }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: 10
Click to show findings
['81']
81: function withdraw(address recipient, uint256 amount) external override onlyAuthorized nonReentrant { // <= FOUND
82: if (amount == 0) revert ZeroAmount();
83:
84:
85: if (amount > address(this).balance) revert InsufficientBalance();
86:
87:
88: (bool success,) = recipient.call{value: amount}(""); // <= FOUND
89: if (!success) revert TransferFailed();
90:
91: emit Withdrawn(recipient, amount);
92: }['47']
47: function safeTransfer(address token, address recipient, uint256 amount) internal { // <= FOUND
48: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, recipient, amount)); // <= FOUND
49:
50: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
51: revert ERC20CallFailed(token, success, data);
52: }
53: }['61']
61: function safeTransfer(address token, address recipient, uint256 amount) internal { // <= FOUND
62: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, recipient, amount)); // <= FOUND
63:
64: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
65: revert ERC20CallFailed(token, success, data);
66: }
67: }['63']
63: function safeApprove(address token, address spender, uint256 value) internal { // <= FOUND
64: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, value)); // <= FOUND
65:
66: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
67: revert ERC20CallFailed(token, success, data);
68: }
69: }['76']
76: function safeApprove(address token, address spender, uint256 value) internal { // <= FOUND
77: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, value)); // <= FOUND
78:
79: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
80: revert ERC20CallFailed(token, success, data);
81: }
82: }['80']
80: function safeTransferFrom(address token, address owner, address recipient, uint256 amount) internal { // <= FOUND
81: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, owner, recipient, amount)); // <= FOUND
82:
83: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
84: revert ERC20CallFailed(token, success, data);
85: }
86: }['92']
92: function safeTransferFrom(address token, address owner, address recipient, uint256 amount) internal { // <= FOUND
93: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, owner, recipient, amount)); // <= FOUND
94:
95: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
96: revert ERC20CallFailed(token, success, data);
97: }
98: }['107']
107: function safeMint(address token, address recipient, uint256 amount) internal { // <= FOUND
108: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Mintable.mint.selector, recipient, amount)); // <= FOUND
109:
110: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
111: revert ERC20CallFailed(token, success, data);
112: }
113: }['121']
121: function safeBurn(address token, uint256 amount) internal { // <= FOUND
122: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Burnable.burn.selector, amount)); // <= FOUND
123:
124: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
125: revert ERC20CallFailed(token, success, data);
126: }
127: }['136']
136: function safeBurnFrom(address token, address owner, uint256 amount) internal { // <= FOUND
137: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Burnable.burnFrom.selector, owner, amount)); // <= FOUND
138:
139: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
140: revert ERC20CallFailed(token, success, data);
141: }
142: }In Solidity, using the ERC20 transfer function can be problematic with tokens like USDT, which may not fully adhere to the standard interface, potentially causing transaction reverts. To avoid issues, it’s crucial to interact directly with the token's specific ABI rather than the generic IERC20 interface. Before integrating any token, thoroughly review its contract to ensure compatibility, especially for the transfer method.
Num of instances: 2
Click to show findings
['26']
26: function deposit(uint256 amount) external { // <= FOUND
27: _checkNonZeroAmount(amount);
28: IERC20(token).transferFrom(msg.sender, address(this), amount); // <= FOUND
29: emit Deposited(msg.sender, amount);
30: }['37']
37: function withdraw(address recipient, uint256 amount) external override onlyAuthorized { // <= FOUND
38: _checkNonZeroAddress(recipient);
39: _checkNonZeroAmount(amount);
40:
41: IERC20(token).transfer(recipient, amount); // <= FOUND
42: emit Withdrawn(recipient, amount);
43: }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
In Solidity, when values are being assigned in constructors to unsigned or integer variables, it's crucial to ensure the provided values adhere to the protocol's specific operational boundaries as laid out in the project specifications and documentation. If the constructors lack appropriate validation checks, there's a risk of setting state variables with values that could cause unexpected and potentially detrimental behavior within the contract's operations, violating the intended logic of the protocol. This can compromise the contract's security and impact the maintainability and reliability of the system. In order to avoid such issues, it is recommended to incorporate rigorous validation checks in constructors. These checks should align with the project's defined rules and constraints, making use of Solidity's built-in require function to enforce these conditions. If the validation checks fail, the require function will cause the transaction to revert, ensuring the integrity and adherence to the protocol's expected behavior.
Num of instances: 3
Click to show findings
['43']
43: constructor(address _token, address _alchemist, address _owner) Ownable(_owner) {
44: _checkNonZeroAddress(_token);
45: _checkNonZeroAddress(_alchemist);
46: token = _token; // <= FOUND
47: authorized[_alchemist] = true;
48: authorized[_owner] = true;
49: emit AuthorizationUpdated(_alchemist, true);
50: }['37']
37: constructor(address _priceFeed, uint256 _updateTimeInSeconds, uint256 _underlyingTokenDecimals) {
38: priceFeed = _priceFeed; // <= FOUND
39: updateTimeInSeconds = _updateTimeInSeconds;
40: underlyingTokenDecimals = _underlyingTokenDecimals;
41: }['17']
17: constructor(address _token, address _underlyingToken) {
18: token = _token; // <= FOUND
19: underlyingToken = _underlyingToken;
20: }For the sake of consistency, stick to using only one of these values throughout the contract. Not doing so in this case can be quite harmful as _msgSender and msg.sender do have some differences, one being that msgSender cannot be used to determine if an account is a EOA but msg.sender can. Differences like these can introduce vulnerabilities is they are not properly acknowledged by the dev team.
Num of instances: 1
Click to show findings
['18']
18: contract AlEth is ERC20("Alchemix ETH", "alETH") {
19: using SafeERC20 for ERC20;
45: require(whiteList[msg.sender], "AlETH: Alchemist is not whitelisted"); // <= FOUND
56: require(!paused[msg.sender], "AlETH: Alchemist is currently paused."); // <= FOUND
57: hasMinted[msg.sender] = hasMinted[msg.sender] + _amount; // <= FOUND
91: _burn(_msgSender(), amount); // <= FOUND
106: uint256 decreasedAllowance = allowance(account, _msgSender()) - amount; // <= FOUND
108: _approve(account, _msgSender(), decreasedAllowance); // <= FOUND
117: if (amount >= hasMinted[msg.sender]) { // <= FOUND
118: hasMinted[msg.sender] = 0; // <= FOUND
120: hasMinted[msg.sender] = hasMinted[msg.sender] - amount; // <= FOUND
Setting boundaries on state variables in smart contracts is essential for maintaining system integrity and user protection. Without caps on values, variables could reach extremes that exploit or disrupt contract functionality, leading to potential loss or unintended consequences for users. Implementing checks for minimum and maximum permissible values can prevent such issues, ensuring variables remain within a safe and reasonable range. This practice guards against attacks aimed at destabilizing the contract, such as griefing, where attackers intentionally cause distress by exploiting vulnerabilities. Proper validation promotes contract reliability, user trust, and a healthier ecosystem by mitigating risks associated with unbounded state changes.
Num of instances: 1
Click to show findings
['81']
81: function setCeiling(address _toSetCeiling, uint256 _ceiling) external {
82: ceiling[_toSetCeiling] = _ceiling; // <= FOUND
83: }In Solidity versions prior to 0.8.21, a bug related to the .selector usage in optimizer settings could lead to incorrect code generation, where function evaluations using .selector might not execute as intended. This issue is classified as low-severity, mainly affecting uncommon code patterns where a function call is used instead of a direct contract name for selector lookup. Files using .selector with affected Solidity versions should be reviewed and updated to mitigate potential issues, ensuring contracts function correctly and securely.
Num of instances: 4
Click to show findings
['47']
47: function safeTransfer(address token, address recipient, uint256 amount) internal {
48: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, recipient, amount)); // <= FOUND
49:
50: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
51: revert ERC20CallFailed(token, success, data);
52: }
53: }['63']
63: function safeApprove(address token, address spender, uint256 value) internal {
64: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, value)); // <= FOUND
65:
66: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
67: revert ERC20CallFailed(token, success, data);
68: }
69: }['80']
80: function safeTransferFrom(address token, address owner, address recipient, uint256 amount) internal {
81: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, owner, recipient, amount)); // <= FOUND
82:
83: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
84: revert ERC20CallFailed(token, success, data);
85: }
86: }['29']
29: function expectDecimals(address token) internal view returns (uint8) {
30: (bool success, bytes memory data) = token.staticcall(abi.encodeWithSelector(IERC20Metadata.decimals.selector)); // <= FOUND
31:
32: if (!success || data.length < 32) {
33: revert ERC20CallFailed(token, success, data);
34: }
35:
36: return abi.decode(data, (uint8));
37: }Chainlink oracles use data aggregators to provide asset prices, incorporating mechanisms like circuit breakers to handle extreme market conditions. These circuit breakers, including parameters like minAnswer, are designed to prevent erratic data or manipulation. However, if an asset's price plummets rapidly beyond these predefined bands (as seen in the LUNA crash), the oracle might freeze and keep returning the minPrice, not reflecting the asset's actual market value. This discrepancy can lead to incorrect borrowing rates in DeFi platforms, as was observed in the Venus incident on BSC, where users were able to borrow against devalued collateral, leading to significant platform losses. This scenario underscores the importance of robust oracle design and risk management strategies in DeFi protocols.
Num of instances: 2
Click to show findings
['21']
21: function ethToUSD(uint256 ethAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 usdAmount) {
22:
23: AggregatorV3Interface priceFeed = AggregatorV3Interface(usdPriceFeed);
24: (, int256 price,, uint256 updateTime,) = priceFeed.latestRoundData(); // <= FOUND
25:
26: if (price <= 0) {
27: revert ChainlinkMalfunction(usdPriceFeed, price);
28: }
29:
30: if (updateTime == 0) {
31: revert IncompleteRound(usdPriceFeed, updateTime);
32: }
33:
34: if (updateTime < block.timestamp - expectedUpdateTime) {
35: revert IncompleteRound(usdPriceFeed, updateTime);
36: }
37:
38:
39: return (ethAmount * uint256(price)) / 1e20;
40: }['48']
48: function usdToETH(uint256 usdAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 ethAmount) {
49:
50: AggregatorV3Interface priceFeed = AggregatorV3Interface(usdPriceFeed);
51: (, int256 price,, uint256 updateTime,) = priceFeed.latestRoundData(); // <= FOUND
52:
53: if (price <= 0) {
54: revert ChainlinkMalfunction(usdPriceFeed, price);
55: }
56:
57: if (updateTime == 0) {
58: revert IncompleteRound(usdPriceFeed, updateTime);
59: }
60:
61: if (updateTime < block.timestamp - expectedUpdateTime) {
62: revert IncompleteRound(usdPriceFeed, updateTime);
63: }
64:
65:
66:
67: return (usdAmount * 1e20) / uint256(price);
68: }In recent versions of the OpenZeppelin Ownable library, ownership has to be initialized within the constructor by adding Ownable(<owner_address>) to the constructor declaration. This is only a LOW issue as in older versions of OZ lib this will not cause an issue as the Ownable constructor automatically sets the owner to msg.sender.
Num of instances: 2
Click to show findings
['27']
27: constructor(address _weth, address _alchemist, address _owner) AbstractFeeVault(_weth, _alchemist, _owner) {}['41']
41: constructor() {}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: 14
Click to show findings
['37']
37: function withdraw(address recipient, uint256 amount) external override onlyAuthorized { // <= FOUND
38: _checkNonZeroAddress(recipient);
39: _checkNonZeroAmount(amount);
40:
41: IERC20(token).transfer(recipient, amount); // <= FOUND
42: emit Withdrawn(recipient, amount);
43: }['384']
384: function withdraw(uint256 amount, address recipient, uint256 tokenId) external returns (uint256) { // <= FOUND
385: _checkArgument(recipient != address(0));
386: _checkForValidAccountId(tokenId);
387: _checkArgument(amount > 0);
388: _checkAccountOwnership(IAlchemistV3Position(alchemistPositionNFT).ownerOf(tokenId), msg.sender);
389: _earmark();
390:
391: _sync(tokenId);
392:
393: _checkArgument(_accounts[tokenId].freeCollateral >= amount);
394:
395: _accounts[tokenId].collateralBalance -= amount;
396: _accounts[tokenId].freeCollateral -= amount;
397:
398:
399: _validate(tokenId);
400:
401:
402: TokenUtils.safeTransfer(yieldToken, recipient, amount); // <= FOUND
403:
404: emit Withdraw(amount, tokenId, recipient);
405:
406: return amount;
407: }['488']
488: function repay(uint256 amount, uint256 recipientTokenId) public returns (uint256) { // <= FOUND
489: _checkArgument(amount > 0);
490: _checkForValidAccountId(recipientTokenId);
491: Account storage account = _accounts[recipientTokenId];
492:
493:
494: if (block.number == account.lastMintBlock) revert CannotRepayOnMintBlock();
495:
496:
497: _earmark();
498:
499:
500: _sync(recipientTokenId);
501:
502: uint256 debt;
503:
504:
505: _checkState((debt = account.debt) > 0);
506:
507: uint256 yieldToDebt = convertYieldTokensToDebt(amount);
508: uint256 credit = yieldToDebt > debt ? debt : yieldToDebt;
509: uint256 creditToYield = convertDebtTokensToYield(credit);
510:
511:
512: uint256 earmarkToRemove = credit > account.earmarked ? account.earmarked : credit;
513: account.earmarked -= earmarkToRemove;
514:
515:
516: account.collateralBalance -= creditToYield * protocolFee / BPS;
517:
518: _subDebt(recipientTokenId, credit);
519:
520:
521: TokenUtils.safeTransferFrom(yieldToken, msg.sender, transmuter, creditToYield);
522: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, creditToYield); // <= FOUND
523:
524: emit Repay(msg.sender, amount, recipientTokenId, creditToYield);
525:
526: return creditToYield;
527: }['573']
573: function redeem(uint256 amount) external onlyTransmuter { // <= FOUND
574: _earmark();
575:
576: _redemptionWeight += PositionDecay.WeightIncrement(amount, cumulativeEarmarked);
577:
578:
579: uint256 collRedeemed = convertDebtTokensToYield(amount);
580: uint256 feeCollateral = collRedeemed * protocolFee / BPS;
581: uint256 totalOut = collRedeemed + feeCollateral;
582:
583:
584: uint256 old = _totalLocked;
585: _totalLocked = old - totalOut;
586: _collateralWeight += PositionDecay.WeightIncrement(totalOut, old);
587: cumulativeEarmarked -= amount;
588: totalDebt -= amount;
589: totalSyntheticsIssued -= amount;
590:
591: lastRedemptionBlock = block.number;
592:
593: _redemptions[block.number] = RedemptionInfo(cumulativeEarmarked, totalDebt, _earmarkWeight);
594:
595: TokenUtils.safeTransfer(yieldToken, transmuter, collRedeemed); // <= FOUND
596: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, feeCollateral);
597:
598: emit Redemption(amount);
599: }['735']
735: function _forceRepay(uint256 accountId, uint256 amount) internal returns (uint256) { // <= FOUND
736: _checkArgument(amount > 0);
737: _checkForValidAccountId(accountId);
738: Account storage account = _accounts[accountId];
739:
740:
741: _earmark();
742:
743:
744: _sync(accountId);
745:
746: uint256 debt;
747:
748:
749: _checkState((debt = account.debt) > 0);
750:
751: uint256 yieldToDebt = convertYieldTokensToDebt(amount);
752: uint256 credit = yieldToDebt > debt ? debt : yieldToDebt;
753: uint256 creditToYield = convertDebtTokensToYield(credit);
754: _subDebt(accountId, credit);
755:
756:
757: account.earmarked -= credit > account.earmarked ? account.earmarked : credit;
758:
759: account.collateralBalance -= creditToYield;
760:
761:
762: TokenUtils.safeTransfer(yieldToken, address(this), creditToYield); // <= FOUND
763: return creditToYield;
764: }['771']
771: function _liquidate(uint256 accountId) internal returns (uint256 debtAmount, uint256 feeInYield, uint256 feeInUnderlying) { // <= FOUND
772:
773: _earmark();
774:
775: _sync(accountId);
776:
777: Account storage account = _accounts[accountId];
778:
779:
780: if (account.debt == 0) {
781: return (0, 0, 0);
782: }
783:
784:
785: uint256 collateralInUnderlying = totalValue(accountId);
786: uint256 collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
787:
788:
789: if (collateralizationRatio > collateralizationLowerBound) {
790: return (0, 0, 0);
791: }
792:
793:
794: uint256 repaidAmountInYield = 0;
795: if (account.earmarked > 0) {
796: repaidAmountInYield = _forceRepay(accountId, convertDebtTokensToYield(account.earmarked));
797: }
798:
799: if (account.debt == 0) {
800: return (repaidAmountInYield, 0, 0);
801: }
802:
803:
804: collateralInUnderlying = totalValue(accountId);
805: collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
806:
807: if (collateralizationRatio <= collateralizationLowerBound) {
808: uint256 alchemistCurrentCollateralization = normalizeUnderlyingTokensToDebt(_getTotalUnderlyingValue()) * FIXED_POINT_SCALAR / totalDebt;
809:
810: (uint256 liquidationAmount, uint256 debtToBurn, uint256 baseFee) = calculateLiquidation(
811: collateralInUnderlying, account.debt, minimumCollateralization, alchemistCurrentCollateralization, globalMinimumCollateralization, liquidatorFee
812: );
813:
814: uint256 feeBonus = debtToBurn * liquidatorFee / BPS;
815: uint256 adjustedLiquidationAmount = convertDebtTokensToYield(liquidationAmount);
816: uint256 adjustedDebtToBurn = convertDebtTokensToYield(debtToBurn);
817: debtAmount = adjustedLiquidationAmount;
818: feeInYield = convertDebtTokensToYield(baseFee);
819:
820:
821: account.collateralBalance = account.collateralBalance > adjustedLiquidationAmount ? account.collateralBalance - adjustedLiquidationAmount : 0;
822:
823:
824: _subDebt(accountId, debtToBurn);
825:
826:
827: TokenUtils.safeTransfer(yieldToken, transmuter, adjustedDebtToBurn); // <= FOUND
828:
829: if (feeInYield > 0) {
830:
831: TokenUtils.safeTransfer(yieldToken, msg.sender, feeInYield);
832: }
833:
834:
835:
836: if (feeBonus > 0) {
837: uint256 vaultBalance = IFeeVault(alchemistFeeVault).totalDeposits();
838: if (vaultBalance > 0) {
839: feeInUnderlying = vaultBalance > feeBonus ? feeBonus : vaultBalance;
840: IFeeVault(alchemistFeeVault).withdraw(msg.sender, feeInUnderlying);
841: }
842: }
843: }
844:
845: return (debtAmount + repaidAmountInYield, feeInYield, feeInUnderlying);
846: }['200']
200: function claimRedemption(uint256 id) external { // <= FOUND
201: StakingPosition storage position = _positions[id];
202:
203: if (position.maturationBlock == 0) {
204: revert PositionNotFound();
205: }
206:
207: uint256 transmutationTime = position.maturationBlock - position.startBlock;
208: uint256 blocksLeft = position.maturationBlock > block.number ? position.maturationBlock - block.number : 0;
209: uint256 amountNottransmuted = blocksLeft > 0 ? position.amount * blocksLeft / transmutationTime : 0;
210: uint256 amountTransmuted = position.amount - amountNottransmuted;
211:
212: if (_requireOwned(id) != msg.sender) {
213: revert CallerNotOwner();
214: }
215:
216:
217: _burn(id);
218:
219:
220: uint256 yieldTokenBalance = TokenUtils.safeBalanceOf(alchemist.yieldToken(), address(this));
221: uint256 debtValue = alchemist.convertYieldTokensToDebt(yieldTokenBalance);
222: uint256 amountToRedeem = amountTransmuted > debtValue ? amountTransmuted - debtValue : 0;
223: if (amountToRedeem > 0) alchemist.redeem(amountToRedeem);
224:
225: uint256 feeAmount = amountTransmuted * transmutationFee / BPS;
226: uint256 claimAmount = amountTransmuted - feeAmount;
227:
228: uint256 syntheticFee = amountNottransmuted * exitFee / BPS;
229: uint256 syntheticReturned = amountNottransmuted - syntheticFee;
230:
231:
232: if (blocksLeft > 0) _updateStakingGraph(-position.amount.toInt256() * BLOCK_SCALING_FACTOR / transmutationTime.toInt256(), blocksLeft);
233:
234:
235:
236: uint256 badDebtRatio = alchemist.totalSyntheticsIssued() * 10**TokenUtils.expectDecimals(alchemist.yieldToken()) / alchemist.getTotalUnderlyingValue();
237:
238: if (badDebtRatio > 1e18) {
239: claimAmount = claimAmount * FIXED_POINT_SCALAR / badDebtRatio;
240: feeAmount = feeAmount * FIXED_POINT_SCALAR / badDebtRatio;
241: }
242:
243: TokenUtils.safeTransfer(alchemist.yieldToken(), msg.sender, alchemist.convertDebtTokensToYield(claimAmount));
244: TokenUtils.safeTransfer(alchemist.yieldToken(), protocolFeeReceiver, alchemist.convertDebtTokensToYield(feeAmount));
245:
246: TokenUtils.safeTransfer(syntheticToken, msg.sender, syntheticReturned);
247: TokenUtils.safeTransfer(syntheticToken, protocolFeeReceiver, syntheticFee);
248:
249:
250: TokenUtils.safeBurn(syntheticToken, amountTransmuted);
251:
252: totalLocked -= position.amount;
253:
254: emit PositionClaimed(msg.sender, claimAmount, syntheticReturned);
255:
256: delete _positions[id];
257: }['37']
37: function withdraw(address recipient, uint256 amount) external override onlyAuthorized {
38: _checkNonZeroAddress(recipient);
39: _checkNonZeroAmount(amount);
40:
41: IERC20(token).transfer(recipient, amount); // <= FOUND
42: emit Withdrawn(recipient, amount);
43: }['384']
384: function withdraw(uint256 amount, address recipient, uint256 tokenId) external returns (uint256) {
385: _checkArgument(recipient != address(0));
386: _checkForValidAccountId(tokenId);
387: _checkArgument(amount > 0);
388: _checkAccountOwnership(IAlchemistV3Position(alchemistPositionNFT).ownerOf(tokenId), msg.sender);
389: _earmark();
390:
391: _sync(tokenId);
392:
393: _checkArgument(_accounts[tokenId].freeCollateral >= amount);
394:
395: _accounts[tokenId].collateralBalance -= amount;
396: _accounts[tokenId].freeCollateral -= amount;
397:
398:
399: _validate(tokenId);
400:
401:
402: TokenUtils.safeTransfer(yieldToken, recipient, amount); // <= FOUND
403:
404: emit Withdraw(amount, tokenId, recipient);
405:
406: return amount;
407: }['488']
488: function repay(uint256 amount, uint256 recipientTokenId) public returns (uint256) {
489: _checkArgument(amount > 0);
490: _checkForValidAccountId(recipientTokenId);
491: Account storage account = _accounts[recipientTokenId];
492:
493:
494: if (block.number == account.lastMintBlock) revert CannotRepayOnMintBlock();
495:
496:
497: _earmark();
498:
499:
500: _sync(recipientTokenId);
501:
502: uint256 debt;
503:
504:
505: _checkState((debt = account.debt) > 0);
506:
507: uint256 yieldToDebt = convertYieldTokensToDebt(amount);
508: uint256 credit = yieldToDebt > debt ? debt : yieldToDebt;
509: uint256 creditToYield = convertDebtTokensToYield(credit);
510:
511:
512: uint256 earmarkToRemove = credit > account.earmarked ? account.earmarked : credit;
513: account.earmarked -= earmarkToRemove;
514:
515:
516: account.collateralBalance -= creditToYield * protocolFee / BPS;
517:
518: _subDebt(recipientTokenId, credit);
519:
520:
521: TokenUtils.safeTransferFrom(yieldToken, msg.sender, transmuter, creditToYield);
522: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, creditToYield); // <= FOUND
523:
524: emit Repay(msg.sender, amount, recipientTokenId, creditToYield);
525:
526: return creditToYield;
527: }['573']
573: function redeem(uint256 amount) external onlyTransmuter {
574: _earmark();
575:
576: _redemptionWeight += PositionDecay.WeightIncrement(amount, cumulativeEarmarked);
577:
578:
579: uint256 collRedeemed = convertDebtTokensToYield(amount);
580: uint256 feeCollateral = collRedeemed * protocolFee / BPS;
581: uint256 totalOut = collRedeemed + feeCollateral;
582:
583:
584: uint256 old = _totalLocked;
585: _totalLocked = old - totalOut;
586: _collateralWeight += PositionDecay.WeightIncrement(totalOut, old);
587: cumulativeEarmarked -= amount;
588: totalDebt -= amount;
589: totalSyntheticsIssued -= amount;
590:
591: lastRedemptionBlock = block.number;
592:
593: _redemptions[block.number] = RedemptionInfo(cumulativeEarmarked, totalDebt, _earmarkWeight);
594:
595: TokenUtils.safeTransfer(yieldToken, transmuter, collRedeemed); // <= FOUND
596: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, feeCollateral); // <= FOUND
597:
598: emit Redemption(amount);
599: }['735']
735: function _forceRepay(uint256 accountId, uint256 amount) internal returns (uint256) {
736: _checkArgument(amount > 0);
737: _checkForValidAccountId(accountId);
738: Account storage account = _accounts[accountId];
739:
740:
741: _earmark();
742:
743:
744: _sync(accountId);
745:
746: uint256 debt;
747:
748:
749: _checkState((debt = account.debt) > 0);
750:
751: uint256 yieldToDebt = convertYieldTokensToDebt(amount);
752: uint256 credit = yieldToDebt > debt ? debt : yieldToDebt;
753: uint256 creditToYield = convertDebtTokensToYield(credit);
754: _subDebt(accountId, credit);
755:
756:
757: account.earmarked -= credit > account.earmarked ? account.earmarked : credit;
758:
759: account.collateralBalance -= creditToYield;
760:
761:
762: TokenUtils.safeTransfer(yieldToken, address(this), creditToYield); // <= FOUND
763: return creditToYield;
764: }['771']
771: function _liquidate(uint256 accountId) internal returns (uint256 debtAmount, uint256 feeInYield, uint256 feeInUnderlying) {
772:
773: _earmark();
774:
775: _sync(accountId);
776:
777: Account storage account = _accounts[accountId];
778:
779:
780: if (account.debt == 0) {
781: return (0, 0, 0);
782: }
783:
784:
785: uint256 collateralInUnderlying = totalValue(accountId);
786: uint256 collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
787:
788:
789: if (collateralizationRatio > collateralizationLowerBound) {
790: return (0, 0, 0);
791: }
792:
793:
794: uint256 repaidAmountInYield = 0;
795: if (account.earmarked > 0) {
796: repaidAmountInYield = _forceRepay(accountId, convertDebtTokensToYield(account.earmarked));
797: }
798:
799: if (account.debt == 0) {
800: return (repaidAmountInYield, 0, 0);
801: }
802:
803:
804: collateralInUnderlying = totalValue(accountId);
805: collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
806:
807: if (collateralizationRatio <= collateralizationLowerBound) {
808: uint256 alchemistCurrentCollateralization = normalizeUnderlyingTokensToDebt(_getTotalUnderlyingValue()) * FIXED_POINT_SCALAR / totalDebt;
809:
810: (uint256 liquidationAmount, uint256 debtToBurn, uint256 baseFee) = calculateLiquidation(
811: collateralInUnderlying, account.debt, minimumCollateralization, alchemistCurrentCollateralization, globalMinimumCollateralization, liquidatorFee
812: );
813:
814: uint256 feeBonus = debtToBurn * liquidatorFee / BPS;
815: uint256 adjustedLiquidationAmount = convertDebtTokensToYield(liquidationAmount);
816: uint256 adjustedDebtToBurn = convertDebtTokensToYield(debtToBurn);
817: debtAmount = adjustedLiquidationAmount;
818: feeInYield = convertDebtTokensToYield(baseFee);
819:
820:
821: account.collateralBalance = account.collateralBalance > adjustedLiquidationAmount ? account.collateralBalance - adjustedLiquidationAmount : 0;
822:
823:
824: _subDebt(accountId, debtToBurn);
825:
826:
827: TokenUtils.safeTransfer(yieldToken, transmuter, adjustedDebtToBurn); // <= FOUND
828:
829: if (feeInYield > 0) {
830:
831: TokenUtils.safeTransfer(yieldToken, msg.sender, feeInYield); // <= FOUND
832: }
833:
834:
835:
836: if (feeBonus > 0) {
837: uint256 vaultBalance = IFeeVault(alchemistFeeVault).totalDeposits();
838: if (vaultBalance > 0) {
839: feeInUnderlying = vaultBalance > feeBonus ? feeBonus : vaultBalance;
840: IFeeVault(alchemistFeeVault).withdraw(msg.sender, feeInUnderlying);
841: }
842: }
843: }
844:
845: return (debtAmount + repaidAmountInYield, feeInYield, feeInUnderlying);
846: }['200']
200: function claimRedemption(uint256 id) external {
201: StakingPosition storage position = _positions[id];
202:
203: if (position.maturationBlock == 0) {
204: revert PositionNotFound();
205: }
206:
207: uint256 transmutationTime = position.maturationBlock - position.startBlock;
208: uint256 blocksLeft = position.maturationBlock > block.number ? position.maturationBlock - block.number : 0;
209: uint256 amountNottransmuted = blocksLeft > 0 ? position.amount * blocksLeft / transmutationTime : 0;
210: uint256 amountTransmuted = position.amount - amountNottransmuted;
211:
212: if (_requireOwned(id) != msg.sender) {
213: revert CallerNotOwner();
214: }
215:
216:
217: _burn(id);
218:
219:
220: uint256 yieldTokenBalance = TokenUtils.safeBalanceOf(alchemist.yieldToken(), address(this));
221: uint256 debtValue = alchemist.convertYieldTokensToDebt(yieldTokenBalance);
222: uint256 amountToRedeem = amountTransmuted > debtValue ? amountTransmuted - debtValue : 0;
223: if (amountToRedeem > 0) alchemist.redeem(amountToRedeem);
224:
225: uint256 feeAmount = amountTransmuted * transmutationFee / BPS;
226: uint256 claimAmount = amountTransmuted - feeAmount;
227:
228: uint256 syntheticFee = amountNottransmuted * exitFee / BPS;
229: uint256 syntheticReturned = amountNottransmuted - syntheticFee;
230:
231:
232: if (blocksLeft > 0) _updateStakingGraph(-position.amount.toInt256() * BLOCK_SCALING_FACTOR / transmutationTime.toInt256(), blocksLeft);
233:
234:
235:
236: uint256 badDebtRatio = alchemist.totalSyntheticsIssued() * 10**TokenUtils.expectDecimals(alchemist.yieldToken()) / alchemist.getTotalUnderlyingValue();
237:
238: if (badDebtRatio > 1e18) {
239: claimAmount = claimAmount * FIXED_POINT_SCALAR / badDebtRatio;
240: feeAmount = feeAmount * FIXED_POINT_SCALAR / badDebtRatio;
241: }
242:
243: TokenUtils.safeTransfer(alchemist.yieldToken(), msg.sender, alchemist.convertDebtTokensToYield(claimAmount)); // <= FOUND
244: TokenUtils.safeTransfer(alchemist.yieldToken(), protocolFeeReceiver, alchemist.convertDebtTokensToYield(feeAmount)); // <= FOUND
245:
246: TokenUtils.safeTransfer(syntheticToken, msg.sender, syntheticReturned); // <= FOUND
247: TokenUtils.safeTransfer(syntheticToken, protocolFeeReceiver, syntheticFee); // <= FOUND
248:
249:
250: TokenUtils.safeBurn(syntheticToken, amountTransmuted);
251:
252: totalLocked -= position.amount;
253:
254: emit PositionClaimed(msg.sender, claimAmount, syntheticReturned);
255:
256: delete _positions[id];
257: }[Low-46] Contract which utilized ERC20 defines a standard ERC20 function who's selector does not match the ERC20 specification thus potentially not making it callable by external contracts.
A contract implementing ERC20 that defines a standard ERC20 function but with a selector that deviates from the ERC20 specification introduces significant interoperability issues. Since selector values are derived from the function's signature, any mismatch—caused by incorrect naming, parameters, or order—will render the function incompatible with external contracts expecting the standard selector. This makes the function non-callable by external systems like wallets, DEXs, or DeFi protocols relying on standardized interfaces.
To prevent this, ensure all ERC20 function definitions strictly conform to the specification, including correct names, parameter types, and order, to maintain compatibility and usability across the ecosystem.
Num of instances: 1
Click to show findings
['713']
713: function _mint(uint256 tokenId, uint256 amount, address recipient) internal // <= FOUND[Low-47] Contract which utilized ERC721 defines a standard ERC721 function who's selector does not match the ERC721 specification thus potentially not making it callable by external contracts.
A contract implementing ERC721 that defines a standard ERC721 function but with a selector that deviates from the ERC721 specification introduces significant interoperability issues. Since selector values are derived from the function's signature, any mismatch—caused by incorrect naming, parameters, or order—will render the function incompatible with external contracts expecting the standard selector. This makes the function non-callable by external systems like wallets, DEXs, or DeFi protocols relying on standardized interfaces.
To prevent this, ensure all ERC721 function definitions strictly conform to the specification, including correct names, parameter types, and order, to maintain compatibility and usability across the ecosystem.
Num of instances: 1
Click to show findings
['713']
713: function _mint(uint256 tokenId, uint256 amount, address recipient) internal // <= FOUNDSolidity version 0.8.20 uses the new Shanghai EVM which introduces the PUSH0 opcode, this may not be implemented on all chains and L2 thus reducing the portability and compatability of the code. Consider using a earlier solidity version.
Num of instances: 6
Click to show findings
['2']
2: pragma solidity ^0.8.26; // <= FOUND['1']
1: pragma solidity ^0.8.23; // <= FOUND['4']
4: pragma solidity ^0.8.20; // <= FOUND['9']
9: pragma solidity ^0.8.7; // <= FOUND['1']
1: pragma solidity ^0.8.13; // <= FOUND['2']
2: pragma solidity >=0.8.4; // <= FOUNDUsing block.number can break compatibility with some L2s such as Optimism whos time between blocks differ from Ethereum and isn't constant. Consider using block.timestamp to prevent compatibility issues
Num of instances: 15
Click to show findings
['174']
174: lastEarmarkBlock = block.number; // <= FOUND['175']
175: lastRedemptionBlock = block.number; // <= FOUND['452']
452:
453:
454: if (block.number == _accounts[recipientId].lastMintBlock) revert CannotRepayOnMintBlock(); // <= FOUND['494']
494:
495:
496: if (block.number == account.lastMintBlock) revert CannotRepayOnMintBlock(); // <= FOUND['593']
593: _redemptions[block.number] = RedemptionInfo(cumulativeEarmarked, totalDebt, _earmarkWeight); // <= FOUND['721']
721: _accounts[tokenId].lastMintBlock = block.number; // <= FOUND['984']
984: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) { // <= FOUND['1010']
1010: if (block.number > lastEarmarkBlock) { // <= FOUND['1011']
1011: uint256 amount = ITransmuter(transmuter).queryGraph(lastEarmarkBlock + 1, block.number); // <= FOUND['1017']
1017: lastEarmarkBlock = block.number; // <= FOUND['1010']
1010:
1011: if (block.number > lastEarmarkBlock) { // <= FOUND['1037']
1037: amount = ITransmuter(transmuter).queryGraph(lastEarmarkBlock + 1, block.number); // <= FOUND['187']
187: _positions[++_nonce] = StakingPosition(syntheticDepositAmount, block.number, block.number + timeToTransmute); // <= FOUND['208']
208: uint256 blocksLeft = position.maturationBlock > block.number ? position.maturationBlock - block.number : 0; // <= FOUND['271']
271: _stakingGraph.addStake(amount, block.number, blocks); // <= FOUNDWhen 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: 4
Click to show findings
['573']
573: function redeem(uint256 amount) external onlyTransmuter { // <= FOUND
574: _earmark();
575:
576: _redemptionWeight += PositionDecay.WeightIncrement(amount, cumulativeEarmarked);
577:
578:
579: uint256 collRedeemed = convertDebtTokensToYield(amount);
580: uint256 feeCollateral = collRedeemed * protocolFee / BPS; // <= FOUND
581: uint256 totalOut = collRedeemed + feeCollateral; // <= FOUND
582:
583:
584: uint256 old = _totalLocked;
585: _totalLocked = old - totalOut;
586: _collateralWeight += PositionDecay.WeightIncrement(totalOut, old);
587: cumulativeEarmarked -= amount;
588: totalDebt -= amount;
589: totalSyntheticsIssued -= amount;
590:
591: lastRedemptionBlock = block.number;
592:
593: _redemptions[block.number] = RedemptionInfo(cumulativeEarmarked, totalDebt, _earmarkWeight);
594:
595: TokenUtils.safeTransfer(yieldToken, transmuter, collRedeemed);
596: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, feeCollateral); // <= FOUND
597:
598: emit Redemption(amount);
599: }['631']
631: function calculateLiquidation( // <= FOUND
632: uint256 collateral,
633: uint256 debt,
634: uint256 targetCollateralization,
635: uint256 alchemistCurrentCollateralization,
636: uint256 alchemistMinimumCollateralization,
637: uint256 feeBps // <= FOUND
638: ) public pure returns (uint256 grossCollateralToSeize, uint256 debtToBurn, uint256 fee) {
639: if (debt >= collateral) {
640:
641: return (debt, debt, 0);
642: }
643:
644: if (alchemistCurrentCollateralization < alchemistMinimumCollateralization) {
645:
646: return (debt, debt, 0);
647: }
648:
649:
650: uint256 surplus = collateral > debt ? collateral - debt : 0;
651: fee = (surplus * feeBps) / BPS; // <= FOUND
652:
653:
654: uint256 adjCollat = collateral - fee;
655:
656:
657: uint256 md = (targetCollateralization * debt) / FIXED_POINT_SCALAR;
658:
659:
660: if (md <= adjCollat) {
661: return (0, 0, fee);
662: }
663:
664:
665: uint256 num = md - adjCollat;
666:
667:
668: uint256 denom = targetCollateralization - FIXED_POINT_SCALAR;
669:
670:
671: debtToBurn = (num * FIXED_POINT_SCALAR) / denom;
672:
673:
674: grossCollateralToSeize = debtToBurn + fee;
675: }['771']
771: function _liquidate(uint256 accountId) internal returns (uint256 debtAmount, uint256 feeInYield, uint256 feeInUnderlying) { // <= FOUND
772:
773: _earmark();
774:
775: _sync(accountId);
776:
777: Account storage account = _accounts[accountId];
778:
779:
780: if (account.debt == 0) {
781: return (0, 0, 0);
782: }
783:
784:
785: uint256 collateralInUnderlying = totalValue(accountId);
786: uint256 collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
787:
788:
789: if (collateralizationRatio > collateralizationLowerBound) {
790: return (0, 0, 0);
791: }
792:
793:
794: uint256 repaidAmountInYield = 0;
795: if (account.earmarked > 0) {
796: repaidAmountInYield = _forceRepay(accountId, convertDebtTokensToYield(account.earmarked));
797: }
798:
799: if (account.debt == 0) {
800: return (repaidAmountInYield, 0, 0);
801: }
802:
803:
804: collateralInUnderlying = totalValue(accountId);
805: collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
806:
807: if (collateralizationRatio <= collateralizationLowerBound) {
808: uint256 alchemistCurrentCollateralization = normalizeUnderlyingTokensToDebt(_getTotalUnderlyingValue()) * FIXED_POINT_SCALAR / totalDebt;
809:
810: (uint256 liquidationAmount, uint256 debtToBurn, uint256 baseFee) = calculateLiquidation(
811: collateralInUnderlying, account.debt, minimumCollateralization, alchemistCurrentCollateralization, globalMinimumCollateralization, liquidatorFee
812: );
813:
814: uint256 feeBonus = debtToBurn * liquidatorFee / BPS; // <= FOUND
815: uint256 adjustedLiquidationAmount = convertDebtTokensToYield(liquidationAmount);
816: uint256 adjustedDebtToBurn = convertDebtTokensToYield(debtToBurn);
817: debtAmount = adjustedLiquidationAmount;
818: feeInYield = convertDebtTokensToYield(baseFee);
819:
820:
821: account.collateralBalance = account.collateralBalance > adjustedLiquidationAmount ? account.collateralBalance - adjustedLiquidationAmount : 0;
822:
823:
824: _subDebt(accountId, debtToBurn);
825:
826:
827: TokenUtils.safeTransfer(yieldToken, transmuter, adjustedDebtToBurn);
828:
829: if (feeInYield > 0) {
830:
831: TokenUtils.safeTransfer(yieldToken, msg.sender, feeInYield);
832: }
833:
834:
835:
836: if (feeBonus > 0) { // <= FOUND
837: uint256 vaultBalance = IFeeVault(alchemistFeeVault).totalDeposits();
838: if (vaultBalance > 0) {
839: feeInUnderlying = vaultBalance > feeBonus ? feeBonus : vaultBalance; // <= FOUND
840: IFeeVault(alchemistFeeVault).withdraw(msg.sender, feeInUnderlying);
841: }
842: }
843: }
844:
845: return (debtAmount + repaidAmountInYield, feeInYield, feeInUnderlying);
846: }['200']
200: function claimRedemption(uint256 id) external { // <= FOUND
201: StakingPosition storage position = _positions[id];
202:
203: if (position.maturationBlock == 0) {
204: revert PositionNotFound();
205: }
206:
207: uint256 transmutationTime = position.maturationBlock - position.startBlock;
208: uint256 blocksLeft = position.maturationBlock > block.number ? position.maturationBlock - block.number : 0;
209: uint256 amountNottransmuted = blocksLeft > 0 ? position.amount * blocksLeft / transmutationTime : 0;
210: uint256 amountTransmuted = position.amount - amountNottransmuted;
211:
212: if (_requireOwned(id) != msg.sender) {
213: revert CallerNotOwner();
214: }
215:
216:
217: _burn(id);
218:
219:
220: uint256 yieldTokenBalance = TokenUtils.safeBalanceOf(alchemist.yieldToken(), address(this));
221: uint256 debtValue = alchemist.convertYieldTokensToDebt(yieldTokenBalance);
222: uint256 amountToRedeem = amountTransmuted > debtValue ? amountTransmuted - debtValue : 0;
223: if (amountToRedeem > 0) alchemist.redeem(amountToRedeem);
224:
225: uint256 feeAmount = amountTransmuted * transmutationFee / BPS; // <= FOUND
226: uint256 claimAmount = amountTransmuted - feeAmount; // <= FOUND
227:
228: uint256 syntheticFee = amountNottransmuted * exitFee / BPS;
229: uint256 syntheticReturned = amountNottransmuted - syntheticFee;
230:
231:
232: if (blocksLeft > 0) _updateStakingGraph(-position.amount.toInt256() * BLOCK_SCALING_FACTOR / transmutationTime.toInt256(), blocksLeft);
233:
234:
235:
236: uint256 badDebtRatio = alchemist.totalSyntheticsIssued() * 10**TokenUtils.expectDecimals(alchemist.yieldToken()) / alchemist.getTotalUnderlyingValue();
237:
238: if (badDebtRatio > 1e18) {
239: claimAmount = claimAmount * FIXED_POINT_SCALAR / badDebtRatio;
240: feeAmount = feeAmount * FIXED_POINT_SCALAR / badDebtRatio; // <= FOUND
241: }
242:
243: TokenUtils.safeTransfer(alchemist.yieldToken(), msg.sender, alchemist.convertDebtTokensToYield(claimAmount));
244: TokenUtils.safeTransfer(alchemist.yieldToken(), protocolFeeReceiver, alchemist.convertDebtTokensToYield(feeAmount)); // <= FOUND
245:
246: TokenUtils.safeTransfer(syntheticToken, msg.sender, syntheticReturned);
247: TokenUtils.safeTransfer(syntheticToken, protocolFeeReceiver, syntheticFee);
248:
249:
250: TokenUtils.safeBurn(syntheticToken, amountTransmuted);
251:
252: totalLocked -= position.amount;
253:
254: emit PositionClaimed(msg.sender, claimAmount, syntheticReturned);
255:
256: delete _positions[id];
257: }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: 13
Click to show findings
['182']
182: function setAlchemistPositionNFT(address nft) external onlyAdmin ['65']
65: function setWhitelist(address _toWhitelist, bool _state) external ['81']
81: function setCeiling(address _toSetCeiling, uint256 _ceiling) external ['132']
132: function update(uint256[GRAPH_MAX + 1] storage graph, uint256 index, uint256 treeSize, int256 delta, int256 deltaProd) private ['100']
100: function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) ['270']
270: function _updateStakingGraph(int256 amount, uint256 blocks) private ['58']
58: function _onlyAdmin() internal view ['182']
182: function setAlchemistPositionNFT(address nft) external onlyAdmin { // <= FOUND
183: if (nft == address(0)) {
184: revert AlchemistV3NFTZeroAddressError();
185: }
186:
187: if (alchemistPositionNFT != address(0)) {
188: revert AlchemistV3NFTAlreadySetError();
189: }
190:
191: alchemistPositionNFT = nft;
192: }['65']
65: function setWhitelist(address _toWhitelist, bool _state) external { // <= FOUND
66: whiteList[_toWhitelist] = _state;
67: }['81']
81: function setCeiling(address _toSetCeiling, uint256 _ceiling) external { // <= FOUND
82: ceiling[_toSetCeiling] = _ceiling;
83: }['132']
132: function update(uint256[GRAPH_MAX + 1] storage graph, uint256 index, uint256 treeSize, int256 delta, int256 deltaProd) private { // <= FOUND
133: unchecked {
134: index += 1;
135: while (index <= treeSize) {
136:
137: uint256 packed = graph[index];
138: int256 ad;
139: int256 ap;
140:
141:
142: if ((packed&DELTA_SIGNBIT) != 0) {
143: ad = int256(packed | ~DELTA_MASK);
144: } else {
145: ad = int256(packed & DELTA_MASK);
146: }
147: ap = int256(packed)>>DELTA_BITS;
148:
149: ad+=delta;
150: ap+=deltaProd;
151:
152:
153: require(ad <= DELTA_MAX && ad >= DELTA_MIN);
154: require(ap <= PRODUCT_MAX && ap >= PRODUCT_MIN);
155: graph[index] = (uint256(ad)&DELTA_MASK)|uint256(ap<<DELTA_BITS);
156:
157: assembly {
158: index := add(index, and(index, sub(0, index)))
159: }
160: }
161: }
162: }['100']
100: function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) { // <= FOUND
101: address from = _ownerOf(tokenId);
102:
103: if (from != address(0)) {
104:
105: IAlchemistV3(alchemist).resetMintAllowances(tokenId);
106: }
107:
108: return super._update(to, tokenId, auth);
109: }['270']
270: function _updateStakingGraph(int256 amount, uint256 blocks) private { // <= FOUND
271: _stakingGraph.addStake(amount, block.number, blocks);
272: }SafeTransfer should be used in place of Transfer for Solidity contracts to ensure robust security and error handling. Unlike the basic Transfer function, SafeTransfer incorporates safeguards against potential smart contract vulnerabilities, such as reentrancy attacks and unexpected token loss. By automatically validating the recipient's ability to receive tokens and reverting transactions in case of failures,
Num of instances: 2
Click to show findings
['26']
26: function deposit(uint256 amount) external { // <= FOUND
27: _checkNonZeroAmount(amount);
28: IERC20(token).transferFrom(msg.sender, address(this), amount); // <= FOUND
29: emit Deposited(msg.sender, amount);
30: }['37']
37: function withdraw(address recipient, uint256 amount) external override onlyAuthorized { // <= FOUND
38: _checkNonZeroAddress(recipient);
39: _checkNonZeroAmount(amount);
40:
41: IERC20(token).transfer(recipient, amount); // <= FOUND
42: emit Withdrawn(recipient, amount);
43: }Indexed dynamic arrays in events can lead to unintended data loss. When indexed, dynamic arrays are hashed into a 32-byte value, rather than preserving the array's actual contents. This hashing obscures the data, making it inaccessible to off-chain monitoring services. Unlike static arrays or indexed primitive variables, the hashed representation of dynamic arrays prevents accurate data retrieval and monitoring, potentially complicating the tracking and analysis of event data. To maintain data transparency and usability, avoid indexing dynamic arrays in events and instead use non-indexed parameters for dynamic array data.
Num of instances: 1
Click to show findings
['563']
563:
570: event BatchLiquidated(uint256[] indexed accounts, address liquidator, uint256 amount, uint256 feeInYield, uint256 feeInETH); // <= FOUNDAvoid using floating pragmas in non-interface and non-library files to ensure contract compatibility and security. Floating pragmas like pragma solidity ^0.8.0; allow any compiler version that matches the specified range. While this can provide flexibility, it risks introducing unexpected behavior or vulnerabilities from future compiler versions. Instead, specify a fixed pragma version, such as pragma solidity 0.8.0;, to guarantee consistent behavior and security across deployments. This practice ensures that your contracts are tested and verified against a specific compiler version, reducing the risk of unforeseen issues and maintaining code reliability.
Num of instances: 4
Click to show findings
[]
10: contract EulerUSDCAdapter is ITokenAdapter []
18: contract AlEth is ERC20("Alchemix ETH", "alETH") []
21: contract Transmuter is ITransmuter, ERC721 []
10: contract Whitelist is IWhitelist, Ownable 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: 13
Click to show findings
['771']
771: function _liquidate(uint256 accountId) internal returns (uint256 debtAmount, uint256 feeInYield, uint256 feeInUnderlying) {
772:
773: _earmark();
774:
775: _sync(accountId);
776:
777: Account storage account = _accounts[accountId];
778:
779:
780: if (account.debt == 0) {
781: return (0, 0, 0);
782: }
783:
784:
785: uint256 collateralInUnderlying = totalValue(accountId);
786: uint256 collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
787:
788:
789: if (collateralizationRatio > collateralizationLowerBound) {
790: return (0, 0, 0);
791: }
792:
793:
794: uint256 repaidAmountInYield = 0;
795: if (account.earmarked > 0) {
796: repaidAmountInYield = _forceRepay(accountId, convertDebtTokensToYield(account.earmarked));
797: }
798:
799: if (account.debt == 0) {
800: return (repaidAmountInYield, 0, 0);
801: }
802:
803:
804: collateralInUnderlying = totalValue(accountId);
805: collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
806:
807: if (collateralizationRatio <= collateralizationLowerBound) {
808: uint256 alchemistCurrentCollateralization = normalizeUnderlyingTokensToDebt(_getTotalUnderlyingValue()) * FIXED_POINT_SCALAR / totalDebt;
809:
810: (uint256 liquidationAmount, uint256 debtToBurn, uint256 baseFee) = calculateLiquidation(
811: collateralInUnderlying, account.debt, minimumCollateralization, alchemistCurrentCollateralization, globalMinimumCollateralization, liquidatorFee
812: );
813:
814: uint256 feeBonus = debtToBurn * liquidatorFee / BPS;
815: uint256 adjustedLiquidationAmount = convertDebtTokensToYield(liquidationAmount);
816: uint256 adjustedDebtToBurn = convertDebtTokensToYield(debtToBurn);
817: debtAmount = adjustedLiquidationAmount;
818: feeInYield = convertDebtTokensToYield(baseFee);
819:
820:
821: account.collateralBalance = account.collateralBalance > adjustedLiquidationAmount ? account.collateralBalance - adjustedLiquidationAmount : 0;
822:
823:
824: _subDebt(accountId, debtToBurn);
825:
826:
827: TokenUtils.safeTransfer(yieldToken, transmuter, adjustedDebtToBurn);
828:
829: if (feeInYield > 0) {
830:
831: TokenUtils.safeTransfer(yieldToken, msg.sender, feeInYield);
832: }
833:
834:
835:
836: if (feeBonus > 0) {
837: uint256 vaultBalance = IFeeVault(alchemistFeeVault).totalDeposits(); // <= FOUND
838: if (vaultBalance > 0) {
839: feeInUnderlying = vaultBalance > feeBonus ? feeBonus : vaultBalance;
840: IFeeVault(alchemistFeeVault).withdraw(msg.sender, feeInUnderlying);
841: }
842: }
843: }
844:
845: return (debtAmount + repaidAmountInYield, feeInYield, feeInUnderlying);
846: }['200']
200: function claimRedemption(uint256 id) external {
201: StakingPosition storage position = _positions[id];
202:
203: if (position.maturationBlock == 0) {
204: revert PositionNotFound();
205: }
206:
207: uint256 transmutationTime = position.maturationBlock - position.startBlock;
208: uint256 blocksLeft = position.maturationBlock > block.number ? position.maturationBlock - block.number : 0;
209: uint256 amountNottransmuted = blocksLeft > 0 ? position.amount * blocksLeft / transmutationTime : 0;
210: uint256 amountTransmuted = position.amount - amountNottransmuted;
211:
212: if (_requireOwned(id) != msg.sender) {
213: revert CallerNotOwner();
214: }
215:
216:
217: _burn(id);
218:
219:
220: uint256 yieldTokenBalance = TokenUtils.safeBalanceOf(alchemist.yieldToken(), address(this)); // <= FOUND
221: uint256 debtValue = alchemist.convertYieldTokensToDebt(yieldTokenBalance);
222: uint256 amountToRedeem = amountTransmuted > debtValue ? amountTransmuted - debtValue : 0;
223: if (amountToRedeem > 0) alchemist.redeem(amountToRedeem);
224:
225: uint256 feeAmount = amountTransmuted * transmutationFee / BPS;
226: uint256 claimAmount = amountTransmuted - feeAmount;
227:
228: uint256 syntheticFee = amountNottransmuted * exitFee / BPS;
229: uint256 syntheticReturned = amountNottransmuted - syntheticFee;
230:
231:
232: if (blocksLeft > 0) _updateStakingGraph(-position.amount.toInt256() * BLOCK_SCALING_FACTOR / transmutationTime.toInt256(), blocksLeft);
233:
234:
235:
236: uint256 badDebtRatio = alchemist.totalSyntheticsIssued() * 10**TokenUtils.expectDecimals(alchemist.yieldToken()) / alchemist.getTotalUnderlyingValue(); // <= FOUND
237:
238: if (badDebtRatio > 1e18) {
239: claimAmount = claimAmount * FIXED_POINT_SCALAR / badDebtRatio;
240: feeAmount = feeAmount * FIXED_POINT_SCALAR / badDebtRatio;
241: }
242:
243: TokenUtils.safeTransfer(alchemist.yieldToken(), msg.sender, alchemist.convertDebtTokensToYield(claimAmount));
244: TokenUtils.safeTransfer(alchemist.yieldToken(), protocolFeeReceiver, alchemist.convertDebtTokensToYield(feeAmount));
245:
246: TokenUtils.safeTransfer(syntheticToken, msg.sender, syntheticReturned);
247: TokenUtils.safeTransfer(syntheticToken, protocolFeeReceiver, syntheticFee);
248:
249:
250: TokenUtils.safeBurn(syntheticToken, amountTransmuted);
251:
252: totalLocked -= position.amount;
253:
254: emit PositionClaimed(msg.sender, claimAmount, syntheticReturned);
255:
256: delete _positions[id];
257: }['155']
155: function initialize(AlchemistInitializationParams memory params) external initializer {
156: _checkArgument(params.protocolFee <= BPS);
157: _checkArgument(params.liquidatorFee <= BPS);
158:
159: debtToken = params.debtToken;
160: underlyingToken = params.underlyingToken;
161: underlyingConversionFactor = 10 ** (TokenUtils.expectDecimals(params.debtToken) - TokenUtils.expectDecimals(params.underlyingToken)); // <= FOUND
162: yieldToken = params.yieldToken;
163: depositCap = params.depositCap;
164: blocksPerYear = params.blocksPerYear;
165: minimumCollateralization = params.minimumCollateralization;
166: globalMinimumCollateralization = params.globalMinimumCollateralization;
167: collateralizationLowerBound = params.collateralizationLowerBound;
168: admin = params.admin;
169: tokenAdapter = params.tokenAdapter;
170: transmuter = params.transmuter;
171: protocolFee = params.protocolFee;
172: protocolFeeReceiver = params.protocolFeeReceiver;
173: liquidatorFee = params.liquidatorFee;
174: lastEarmarkBlock = block.number;
175: lastRedemptionBlock = block.number;
176: }['20']
20: function add(AddressSet storage self, address value) internal returns (bool) {
21: if (self.contains(value)) { // <= FOUND
22: return false;
23: }
24: self.values.push(value);
25: self.indexes[value] = self.values.length;
26: return true;
27: }['266']
266: function setTransmuter(address value) external onlyAdmin {
267: _checkArgument(value != address(0));
268:
269:
270: require(convertYieldTokensToDebt(TokenUtils.safeBalanceOf(yieldToken, transmuter)) >= ITransmuter(transmuter).totalLocked()); // <= FOUND
271:
272: transmuter = value;
273: emit TransmuterUpdated(value);
274: }['1007']
1007: function _earmark() internal {
1008: if (totalDebt == 0) return;
1009:
1010: if (block.number > lastEarmarkBlock) {
1011: uint256 amount = ITransmuter(transmuter).queryGraph(lastEarmarkBlock + 1, block.number); // <= FOUND
1012: if (amount > 0) {
1013: _earmarkWeight += PositionDecay.WeightIncrement(amount, totalDebt - cumulativeEarmarked); // <= FOUND
1014: cumulativeEarmarked += amount;
1015: }
1016:
1017: lastEarmarkBlock = block.number;
1018: }
1019: }['1028']
1028: function _calculateUnrealizedDebt(uint256 tokenId) internal view returns (uint256, uint256, uint256) {
1029: Account storage account = _accounts[tokenId];
1030: RedemptionInfo memory previousRedemption = _redemptions[lastRedemptionBlock];
1031:
1032: uint256 amount;
1033: uint256 earmarkWeightCopy = _earmarkWeight;
1034:
1035:
1036: if (block.number > lastEarmarkBlock) {
1037: amount = ITransmuter(transmuter).queryGraph(lastEarmarkBlock + 1, block.number); // <= FOUND
1038: if (amount > 0) {
1039: earmarkWeightCopy += PositionDecay.WeightIncrement(amount, totalDebt - cumulativeEarmarked); // <= FOUND
1040: }
1041: }
1042:
1043: uint256 debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, earmarkWeightCopy - account.lastAccruedEarmarkWeight); // <= FOUND
1044: uint256 earmarkedState = account.earmarked + debtToEarmark;
1045:
1046:
1047: uint256 earmarkedPreviousState;
1048: uint256 earmarkToRedeem;
1049: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) {
1050: debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, previousRedemption.earmarkWeight - account.lastAccruedEarmarkWeight); // <= FOUND
1051:
1052: earmarkedPreviousState = account.earmarked + debtToEarmark;
1053: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight); // <= FOUND
1054: } else {
1055: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedState, _redemptionWeight - account.lastAccruedRedemptionWeight); // <= FOUND
1056: }
1057:
1058: uint256 collateralToRemove = PositionDecay.ScaleByWeightDelta(account.rawLocked, _collateralWeight - account.lastCollateralWeight); // <= FOUND
1059:
1060: return (account.debt - earmarkToRedeem, earmarkedState - earmarkToRedeem, account.collateralBalance - collateralToRemove);
1061: }['573']
573: function redeem(uint256 amount) external onlyTransmuter {
574: _earmark();
575:
576: _redemptionWeight += PositionDecay.WeightIncrement(amount, cumulativeEarmarked); // <= FOUND
577:
578:
579: uint256 collRedeemed = convertDebtTokensToYield(amount);
580: uint256 feeCollateral = collRedeemed * protocolFee / BPS;
581: uint256 totalOut = collRedeemed + feeCollateral;
582:
583:
584: uint256 old = _totalLocked;
585: _totalLocked = old - totalOut;
586: _collateralWeight += PositionDecay.WeightIncrement(totalOut, old); // <= FOUND
587: cumulativeEarmarked -= amount;
588: totalDebt -= amount;
589: totalSyntheticsIssued -= amount;
590:
591: lastRedemptionBlock = block.number;
592:
593: _redemptions[block.number] = RedemptionInfo(cumulativeEarmarked, totalDebt, _earmarkWeight);
594:
595: TokenUtils.safeTransfer(yieldToken, transmuter, collRedeemed);
596: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, feeCollateral);
597:
598: emit Redemption(amount);
599: }['974']
974: function _sync(uint256 tokenId) internal {
975: Account storage account = _accounts[tokenId];
976: RedemptionInfo memory previousRedemption = _redemptions[lastRedemptionBlock];
977:
978: uint256 debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, _earmarkWeight - account.lastAccruedEarmarkWeight); // <= FOUND
979: uint256 earmarkedState = account.earmarked + debtToEarmark;
980:
981:
982: uint256 earmarkToRedeem;
983: uint256 earmarkPreviousState;
984: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) {
985: debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, previousRedemption.earmarkWeight - account.lastAccruedEarmarkWeight); // <= FOUND
986:
987: earmarkPreviousState = account.earmarked + debtToEarmark;
988: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight); // <= FOUND
989: } else {
990: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedState, _redemptionWeight - account.lastAccruedRedemptionWeight); // <= FOUND
991: }
992:
993: uint256 collateralToRemove = PositionDecay.ScaleByWeightDelta(account.rawLocked, _collateralWeight - account.lastCollateralWeight); // <= FOUND
994:
995:
996: account.earmarked = earmarkedState - earmarkToRedeem;
997: account.debt -= earmarkToRedeem;
998: account.lastAccruedRedemptionWeight = _redemptionWeight;
999: account.lastAccruedEarmarkWeight = _earmarkWeight;
1000:
1001: account.collateralBalance -= collateralToRemove;
1002: account.rawLocked -= collateralToRemove;
1003: account.lastCollateralWeight = _collateralWeight;
1004: }['123']
123: function setDepositCap(uint256 cap) external onlyAdmin {
124: _checkArgument(cap <= type(int256).max.toUint256()); // <= FOUND
125:
126: depositCap = cap;
127: emit DepositCapUpdated(cap);
128: }['172']
172: function createRedemption(uint256 syntheticDepositAmount) external {
173: if (syntheticDepositAmount == 0) {
174: revert DepositZeroAmount();
175: }
176:
177: if (totalLocked + syntheticDepositAmount > depositCap) {
178: revert DepositCapReached();
179: }
180:
181: if (totalLocked + syntheticDepositAmount > alchemist.totalSyntheticsIssued()) {
182: revert DepositCapReached();
183: }
184:
185: TokenUtils.safeTransferFrom(syntheticToken, msg.sender, address(this), syntheticDepositAmount); // <= FOUND
186:
187: _positions[++_nonce] = StakingPosition(syntheticDepositAmount, block.number, block.number + timeToTransmute);
188:
189:
190: _updateStakingGraph(syntheticDepositAmount.toInt256() * BLOCK_SCALING_FACTOR / timeToTransmute.toInt256(), timeToTransmute);
191:
192: totalLocked += syntheticDepositAmount;
193:
194: _mint(msg.sender, _nonce);
195:
196: emit PositionCreated(msg.sender, syntheticDepositAmount, _nonce);
197: }['357']
357: function deposit(uint256 amount, address recipient, uint256 recipientId) external returns (uint256) {
358: _checkArgument(recipient != address(0));
359: _checkArgument(amount > 0);
360: _checkState(!depositsPaused);
361: _checkState(IERC20(yieldToken).balanceOf(address(this)) + amount <= depositCap);
362: uint256 tokenId = recipientId;
363:
364:
365: if (tokenId == 0) {
366: tokenId = IAlchemistV3Position(alchemistPositionNFT).mint(recipient); // <= FOUND
367: emit AlchemistV3PositionNFTMinted(recipient, tokenId);
368: } else {
369: _checkForValidAccountId(tokenId);
370: }
371:
372: _accounts[tokenId].collateralBalance += amount;
373: _accounts[tokenId].freeCollateral += amount;
374:
375:
376: TokenUtils.safeTransferFrom(yieldToken, msg.sender, address(this), amount); // <= FOUND
377:
378: emit Deposit(amount, tokenId);
379:
380: return convertYieldTokensToDebt(amount);
381: }['447']
447: function burn(uint256 amount, uint256 recipientId) external returns (uint256) {
448: _checkArgument(amount > 0);
449: _checkForValidAccountId(recipientId);
450:
451:
452: if (block.number == _accounts[recipientId].lastMintBlock) revert CannotRepayOnMintBlock();
453:
454:
455: _earmark();
456:
457:
458: _sync(recipientId);
459:
460: uint256 debt;
461:
462: _checkState((debt = _accounts[recipientId].debt - _accounts[recipientId].earmarked) > 0);
463:
464: uint256 credit = amount > debt ? debt : amount;
465:
466:
467: if (credit > totalDebt - ITransmuter(transmuter).totalLocked()) {
468: revert BurnLimitExceeded(credit, totalDebt - ITransmuter(transmuter).totalLocked());
469: }
470:
471:
472: TokenUtils.safeBurnFrom(debtToken, msg.sender, credit); // <= FOUND
473:
474:
475: _accounts[recipientId].collateralBalance -= convertDebtTokensToYield(credit) * protocolFee / BPS;
476:
477:
478: _subDebt(recipientId, credit);
479:
480: totalSyntheticsIssued -= credit;
481:
482: emit Burn(msg.sender, credit, recipientId);
483:
484: return credit;
485: }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: 10
Click to show findings
['27']
27: contract ETHUSDPriceFeedAdapter is IPriceFeedAdapter // <= FOUND['10']
10: contract EulerUSDCAdapter is ITokenAdapter // <= FOUND['16']
16: contract AlchemistETHVault is AbstractFeeVault, ReentrancyGuard // <= FOUND['13']
13: contract AlchemistTokenVault is AbstractFeeVault // <= FOUND['16']
16: contract AlchemistV3Position is ERC721Enumerable // <= FOUND['23']
23: contract AlchemistV3 is IAlchemistV3, Initializable // <= FOUND['18']
18: contract AlEth is ERC20("Alchemix ETH", "alETH") // <= FOUND['21']
21: contract Transmuter is ITransmuter, ERC721 // <= FOUND['10']
10: contract Whitelist is IWhitelist, Ownable // <= FOUND['12']
12: abstract contract AbstractFeeVault is IFeeVault, Ownable // <= FOUNDIn arithmetic operations involving subtraction and multiplication, an underflow may occur if a subtraction result is negative, or if a multiplication result exceeds the maximum value representable in the data type. For instance, if a large multiplication precedes a subtraction, it may create a value too large to subtract from, causing an underflow. This could lead to unexpected and incorrect results in the calculation.
Num of instances: 3
Click to show findings
['488']
488: function repay(uint256 amount, uint256 recipientTokenId) public returns (uint256) { // <= FOUND
489: _checkArgument(amount > 0);
490: _checkForValidAccountId(recipientTokenId);
491: Account storage account = _accounts[recipientTokenId];
492:
493:
494: if (block.number == account.lastMintBlock) revert CannotRepayOnMintBlock();
495:
496:
497: _earmark();
498:
499:
500: _sync(recipientTokenId);
501:
502: uint256 debt;
503:
504:
505: _checkState((debt = account.debt) > 0);
506:
507: uint256 yieldToDebt = convertYieldTokensToDebt(amount);
508: uint256 credit = yieldToDebt > debt ? debt : yieldToDebt;
509: uint256 creditToYield = convertDebtTokensToYield(credit);
510:
511:
512: uint256 earmarkToRemove = credit > account.earmarked ? account.earmarked : credit;
513: account.earmarked -= earmarkToRemove;
514:
515:
516: account.collateralBalance -= creditToYield * protocolFee / BPS; // <= FOUND
517:
518: _subDebt(recipientTokenId, credit);
519:
520:
521: TokenUtils.safeTransferFrom(yieldToken, msg.sender, transmuter, creditToYield);
522: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, creditToYield);
523:
524: emit Repay(msg.sender, amount, recipientTokenId, creditToYield);
525:
526: return creditToYield;
527: }['66']
66: function ScaleByWeightDelta(uint256 value, uint256 weightDelta) internal pure returns (uint256) { // <= FOUND
67: unchecked {
68: require(value <= type(uint128).max);
69:
70: if (weightDelta == 0) {
71:
72: return 0;
73: }
74:
75:
76:
77:
78:
79:
80:
81: return value - ((value * Exp2NegFrac(weightDelta)) >> 128); // <= FOUND
82: }
83: }['108']
108: function queryStake(Graph storage g, uint256 start, uint256 end) internal view returns (int256) { // <= FOUND
109: int256 begDelta;
110: int256 begProd;
111: int256 endDelta;
112: int256 endProd;
113: unchecked {
114: require (end <= GRAPH_MAX);
115:
116: start--;
117: require (start <= GRAPH_MAX);
118:
119: (begDelta,begProd) = query(g.g, start);
120: (endDelta,endProd) = query(g.g, end);
121:
122: return ((int256(end) * endDelta) - endProd) - ((int256(start) * begDelta) - begProd); // <= FOUND
123: }
124: }Num of instances: 1
Click to show findings
['583']
583: interface IAlchemistV3State {
584:
587: function admin() external view returns (address admin);
588:
589: function depositCap() external view returns (uint256 cap);
590:
591: function guardians(address guardian) external view returns (bool isActive);
592:
593: function blocksPerYear() external view returns (uint256 blocks);
594:
595: function cumulativeEarmarked() external view returns (uint256 earmarked);
596:
597: function lastEarmarkBlock() external view returns (uint256 block);
598:
599: function lastRedemptionBlock() external view returns (uint256 block);
600:
601: function totalDebt() external view returns (uint256 debt);
602:
603: function totalSyntheticsIssued() external view returns (uint256 syntheticAmount);
604:
605: function protocolFee() external view returns (uint256 fee);
606:
607: function liquidatorFee() external view returns (uint256 fee);
608:
609: function underlyingConversionFactor() external view returns (uint256 factor);
610:
611: function protocolFeeReceiver() external view returns (address receiver);
612:
613: function underlyingToken() external view returns (address token);
614:
615: function yieldToken() external view returns (address token);
616:
617: function depositsPaused() external view returns (bool isPaused);
618:
619: function loansPaused() external view returns (bool isPaused);
620:
621: function alchemistPositionNFT() external view returns (address nftContract);
622:
626: function pendingAdmin() external view returns (address pendingAdmin);
627:
631: function tokenAdapter() external returns (address adapter);
632:
636: function alchemistFeeVault() external view returns (address vault);
637:
641: function transmuter() external view returns (address transmuter);
642:
650: function minimumCollateralization() external view returns (uint256 minimumCollateralization);
651:
659: function globalMinimumCollateralization() external view returns (uint256 globalMinimumCollateralization);
660:
662: function collateralizationLowerBound() external view returns (uint256 ratio);
663:
667: function convertYieldTokensToDebt(uint256 amount) external view returns (uint256);
668:
672: function convertYieldTokensToUnderlying(uint256 amount) external view returns (uint256);
673:
677: function convertDebtTokensToYield(uint256 amount) external view returns (uint256);
678:
682: function convertUnderlyingTokensToYield(uint256 amount) external view returns (uint256);
683:
695: function calculateLiquidation( // <= FOUND
696: uint256 collateral,
697: uint256 debt,
698: uint256 targetCollateralization,
699: uint256 alchemistCurrentCollateralization,
700: uint256 alchemistMinimumCollateralization,
701: uint256 feeBps
702: ) external view returns (uint256 grossCollateralToSeize, uint256 debtToBurn, uint256 fee);
703:
708: function normalizeUnderlyingTokensToDebt(uint256 amount) external view returns (uint256);
709:
714: function normalizeDebtTokensToUnderlying(uint256 amount) external view returns (uint256);
715:
723: function getCDP(uint256 tokenId) external view returns (uint256 collateral, uint256 debt, uint256 earmarked);
724:
730: function totalValue(uint256 tokenId) external view returns (uint256 value);
731:
735: function getTotalDeposited() external view returns (uint256 amount);
736:
742: function getMaxBorrowable(uint256 tokenId) external view returns (uint256 maxDebt);
743:
747: function getTotalUnderlyingValue() external view returns (uint256 TVL);
748:
755: function mintAllowance(uint256 ownerTokenId, address spender) external view returns (uint256 allowance);
756: }[NonCritical-3] Contracts with multiple onlyXYZ modifiers where XYZ is a role can introduce complexities when managing privileges
In smart contracts, using multiple onlyXYZ modifiers for different roles can complicate privilege management. OpenZeppelin's AccessControl offers a streamlined solution, enabling easier and more flexible role-based permission handling. It simplifies the assignment and revocation of roles compared to multiple individual modifiers, reducing potential errors and improving contract maintainability. This modular approach to access management makes it more straightforward to define, manage, and audit roles and permissions within a contract.
Num of instances: 1
Click to show findings
['23']
23: contract AlchemistV3 is IAlchemistV3, Initializable {
24: using SafeCast for int256;
25: using SafeCast for uint256;
132: modifier onlyAdmin() { // <= FOUND
133: if (msg.sender != admin) {
134: revert Unauthorized();
135: }
136: _;
139: modifier onlyAdminOrGuardian() { // <= FOUND
140: if (msg.sender != admin && !guardians[msg.sender]) {
141: revert Unauthorized();
142: }
143: _;
146: modifier onlyTransmuter() { // <= FOUND
147: if (msg.sender != transmuter) {
148: revert Unauthorized();
149: }
150: _;
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: 11
Click to show findings
['50']
50: function addStake(Graph storage g, int256 amount, uint256 start, uint256 duration) internal {
51: unchecked {
52: require(amount <= DELTA_MAX && amount >= DELTA_MIN);
53: require(start < GRAPH_MAX-1);
54:
55: uint256 expiration = start + duration;
56: require(expiration < GRAPH_MAX-1);
57:
58: uint256 graphSize = g.size; // <= FOUND
59:
60:
61:
62: uint256 newSize = expiration + 2;
63: if (newSize >= graphSize) {
64:
65: newSize |= newSize >> 1;
66: newSize |= newSize >> 2;
67: newSize |= newSize >> 4;
68: newSize |= newSize >> 8;
69: newSize |= newSize >> 16;
70: if (GRAPH_MAX > 2**32) {
71: newSize |= newSize >> 32;
72: newSize |= newSize >> 64;
73: newSize |= newSize >> 128;
74: }
75: newSize++;
76:
77:
78:
79:
80: require (newSize <= GRAPH_MAX);
81:
82: if (graphSize != 0) {
83:
84: uint256 copy = g.g[graphSize];
85: while (graphSize <= newSize) {
86: g.g[graphSize] = copy;
87: graphSize += graphSize;
88: }
89: }
90: graphSize = newSize;
91: g.size = newSize;
92: }
93:
94:
95: update(g.g, start + 1, graphSize, amount, amount * int256(start));
96: update(g.g, expiration + 1, graphSize, -amount, -amount * int256(expiration));
97: }
98: }['47']
47: function safeTransfer(address token, address recipient, uint256 amount) internal { // <= FOUND
48: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, recipient, amount));
49:
50: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
51: revert ERC20CallFailed(token, success, data);
52: }
53: }['63']
63: function safeApprove(address token, address spender, uint256 value) internal { // <= FOUND
64: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, value));
65:
66: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
67: revert ERC20CallFailed(token, success, data);
68: }
69: }['80']
80: function safeTransferFrom(address token, address owner, address recipient, uint256 amount) internal { // <= FOUND
81: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, owner, recipient, amount));
82:
83: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
84: revert ERC20CallFailed(token, success, data);
85: }
86: }['44']
44: function safeBalanceOf(address token, address account) internal view returns (uint256) { // <= FOUND
45: (bool success, bytes memory data) = token.staticcall(abi.encodeWithSelector(IERC20.balanceOf.selector, account));
46:
47: if (token.code.length == 0 || !success || data.length < 32) {
48: revert ERC20CallFailed(token, success, data);
49: }
50:
51: return abi.decode(data, (uint256));
52: }['61']
61: function safeTransfer(address token, address recipient, uint256 amount) internal { // <= FOUND
62: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, recipient, amount));
63:
64: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
65: revert ERC20CallFailed(token, success, data);
66: }
67: }['76']
76: function safeApprove(address token, address spender, uint256 value) internal { // <= FOUND
77: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, value));
78:
79: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
80: revert ERC20CallFailed(token, success, data);
81: }
82: }['92']
92: function safeTransferFrom(address token, address owner, address recipient, uint256 amount) internal { // <= FOUND
93: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, owner, recipient, amount));
94:
95: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
96: revert ERC20CallFailed(token, success, data);
97: }
98: }['107']
107: function safeMint(address token, address recipient, uint256 amount) internal { // <= FOUND
108: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Mintable.mint.selector, recipient, amount));
109:
110: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
111: revert ERC20CallFailed(token, success, data);
112: }
113: }['121']
121: function safeBurn(address token, uint256 amount) internal { // <= FOUND
122: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Burnable.burn.selector, amount));
123:
124: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
125: revert ERC20CallFailed(token, success, data);
126: }
127: }['136']
136: function safeBurnFrom(address token, address owner, uint256 amount) internal { // <= FOUND
137: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Burnable.burnFrom.selector, owner, amount));
138:
139: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
140: revert ERC20CallFailed(token, success, data);
141: }
142: }When state variables (uints) that can be set to their maximum value (type(uint256).max for example) are used in "greater than" comparisons, it introduces a risk of logic errors. If the state variable ever reaches this max value, any comparison expecting it to increment further will fail. This can halt or disrupt contract functionality. To avoid this, implement checks to ensure that the state variable doesn't exceed a certain threshold below the max value.
Num of instances: 12
Click to show findings
['293']
293: function setGlobalMinimumCollateralization(uint256 value) external onlyAdmin {
294: _checkArgument(value >= minimumCollateralization); // <= FOUND
295: globalMinimumCollateralization = value;
296: emit GlobalMinimumCollateralizationUpdated(value);
297: }['172']
172: function createRedemption(uint256 syntheticDepositAmount) external {
173: if (syntheticDepositAmount == 0) {
174: revert DepositZeroAmount();
175: }
176:
177: if (totalLocked + syntheticDepositAmount > depositCap) { // <= FOUND
178: revert DepositCapReached();
179: }
180:
181: if (totalLocked + syntheticDepositAmount > alchemist.totalSyntheticsIssued()) {
182: revert DepositCapReached();
183: }
184:
185: TokenUtils.safeTransferFrom(syntheticToken, msg.sender, address(this), syntheticDepositAmount);
186:
187: _positions[++_nonce] = StakingPosition(syntheticDepositAmount, block.number, block.number + timeToTransmute);
188:
189:
190: _updateStakingGraph(syntheticDepositAmount.toInt256() * BLOCK_SCALING_FACTOR / timeToTransmute.toInt256(), timeToTransmute);
191:
192: totalLocked += syntheticDepositAmount;
193:
194: _mint(msg.sender, _nonce);
195:
196: emit PositionCreated(msg.sender, syntheticDepositAmount, _nonce);
197: }['1007']
1007: function _earmark() internal {
1008: if (totalDebt == 0) return;
1009:
1010: if (block.number > lastEarmarkBlock) { // <= FOUND
1011: uint256 amount = ITransmuter(transmuter).queryGraph(lastEarmarkBlock + 1, block.number);
1012: if (amount > 0) {
1013: _earmarkWeight += PositionDecay.WeightIncrement(amount, totalDebt - cumulativeEarmarked);
1014: cumulativeEarmarked += amount;
1015: }
1016:
1017: lastEarmarkBlock = block.number;
1018: }
1019: }['1028']
1028: function _calculateUnrealizedDebt(uint256 tokenId) internal view returns (uint256, uint256, uint256) {
1029: Account storage account = _accounts[tokenId];
1030: RedemptionInfo memory previousRedemption = _redemptions[lastRedemptionBlock];
1031:
1032: uint256 amount;
1033: uint256 earmarkWeightCopy = _earmarkWeight;
1034:
1035:
1036: if (block.number > lastEarmarkBlock) { // <= FOUND
1037: amount = ITransmuter(transmuter).queryGraph(lastEarmarkBlock + 1, block.number);
1038: if (amount > 0) {
1039: earmarkWeightCopy += PositionDecay.WeightIncrement(amount, totalDebt - cumulativeEarmarked);
1040: }
1041: }
1042:
1043: uint256 debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, earmarkWeightCopy - account.lastAccruedEarmarkWeight);
1044: uint256 earmarkedState = account.earmarked + debtToEarmark;
1045:
1046:
1047: uint256 earmarkedPreviousState;
1048: uint256 earmarkToRedeem;
1049: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) { // <= FOUND
1050: debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, previousRedemption.earmarkWeight - account.lastAccruedEarmarkWeight);
1051:
1052: earmarkedPreviousState = account.earmarked + debtToEarmark;
1053: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight);
1054: } else {
1055: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedState, _redemptionWeight - account.lastAccruedRedemptionWeight);
1056: }
1057:
1058: uint256 collateralToRemove = PositionDecay.ScaleByWeightDelta(account.rawLocked, _collateralWeight - account.lastCollateralWeight);
1059:
1060: return (account.debt - earmarkToRedeem, earmarkedState - earmarkToRedeem, account.collateralBalance - collateralToRemove);
1061: }['974']
974: function _sync(uint256 tokenId) internal {
975: Account storage account = _accounts[tokenId];
976: RedemptionInfo memory previousRedemption = _redemptions[lastRedemptionBlock];
977:
978: uint256 debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, _earmarkWeight - account.lastAccruedEarmarkWeight);
979: uint256 earmarkedState = account.earmarked + debtToEarmark;
980:
981:
982: uint256 earmarkToRedeem;
983: uint256 earmarkPreviousState;
984: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) { // <= FOUND
985: debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, previousRedemption.earmarkWeight - account.lastAccruedEarmarkWeight);
986:
987: earmarkPreviousState = account.earmarked + debtToEarmark;
988: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight);
989: } else {
990: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedState, _redemptionWeight - account.lastAccruedRedemptionWeight);
991: }
992:
993: uint256 collateralToRemove = PositionDecay.ScaleByWeightDelta(account.rawLocked, _collateralWeight - account.lastCollateralWeight);
994:
995:
996: account.earmarked = earmarkedState - earmarkToRedeem;
997: account.debt -= earmarkToRedeem;
998: account.lastAccruedRedemptionWeight = _redemptionWeight;
999: account.lastAccruedEarmarkWeight = _earmarkWeight;
1000:
1001: account.collateralBalance -= collateralToRemove;
1002: account.rawLocked -= collateralToRemove;
1003: account.lastCollateralWeight = _collateralWeight;
1004: }['771']
771: function _liquidate(uint256 accountId) internal returns (uint256 debtAmount, uint256 feeInYield, uint256 feeInUnderlying) {
772:
773: _earmark();
774:
775: _sync(accountId);
776:
777: Account storage account = _accounts[accountId];
778:
779:
780: if (account.debt == 0) {
781: return (0, 0, 0);
782: }
783:
784:
785: uint256 collateralInUnderlying = totalValue(accountId);
786: uint256 collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
787:
788:
789: if (collateralizationRatio > collateralizationLowerBound) { // <= FOUND
790: return (0, 0, 0);
791: }
792:
793:
794: uint256 repaidAmountInYield = 0;
795: if (account.earmarked > 0) {
796: repaidAmountInYield = _forceRepay(accountId, convertDebtTokensToYield(account.earmarked));
797: }
798:
799: if (account.debt == 0) {
800: return (repaidAmountInYield, 0, 0);
801: }
802:
803:
804: collateralInUnderlying = totalValue(accountId);
805: collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
806:
807: if (collateralizationRatio <= collateralizationLowerBound) {
808: uint256 alchemistCurrentCollateralization = normalizeUnderlyingTokensToDebt(_getTotalUnderlyingValue()) * FIXED_POINT_SCALAR / totalDebt;
809:
810: (uint256 liquidationAmount, uint256 debtToBurn, uint256 baseFee) = calculateLiquidation(
811: collateralInUnderlying, account.debt, minimumCollateralization, alchemistCurrentCollateralization, globalMinimumCollateralization, liquidatorFee
812: );
813:
814: uint256 feeBonus = debtToBurn * liquidatorFee / BPS;
815: uint256 adjustedLiquidationAmount = convertDebtTokensToYield(liquidationAmount);
816: uint256 adjustedDebtToBurn = convertDebtTokensToYield(debtToBurn);
817: debtAmount = adjustedLiquidationAmount;
818: feeInYield = convertDebtTokensToYield(baseFee);
819:
820:
821: account.collateralBalance = account.collateralBalance > adjustedLiquidationAmount ? account.collateralBalance - adjustedLiquidationAmount : 0;
822:
823:
824: _subDebt(accountId, debtToBurn);
825:
826:
827: TokenUtils.safeTransfer(yieldToken, transmuter, adjustedDebtToBurn);
828:
829: if (feeInYield > 0) {
830:
831: TokenUtils.safeTransfer(yieldToken, msg.sender, feeInYield);
832: }
833:
834:
835:
836: if (feeBonus > 0) {
837: uint256 vaultBalance = IFeeVault(alchemistFeeVault).totalDeposits();
838: if (vaultBalance > 0) {
839: feeInUnderlying = vaultBalance > feeBonus ? feeBonus : vaultBalance;
840: IFeeVault(alchemistFeeVault).withdraw(msg.sender, feeInUnderlying);
841: }
842: }
843: }
844:
845: return (debtAmount + repaidAmountInYield, feeInYield, feeInUnderlying);
846: }['155']
155: function initialize(AlchemistInitializationParams memory params) external initializer {
156: _checkArgument(params.protocolFee <= BPS);
157: _checkArgument(params.liquidatorFee <= BPS);
158:
159: debtToken = params.debtToken;
160: underlyingToken = params.underlyingToken;
161: underlyingConversionFactor = 10 ** (TokenUtils.expectDecimals(params.debtToken) - TokenUtils.expectDecimals(params.underlyingToken));
162: yieldToken = params.yieldToken;
163: depositCap = params.depositCap; // <= FOUND
164: blocksPerYear = params.blocksPerYear;
165: minimumCollateralization = params.minimumCollateralization; // <= FOUND
166: globalMinimumCollateralization = params.globalMinimumCollateralization;
167: collateralizationLowerBound = params.collateralizationLowerBound; // <= FOUND
168: admin = params.admin;
169: tokenAdapter = params.tokenAdapter;
170: transmuter = params.transmuter;
171: protocolFee = params.protocolFee;
172: protocolFeeReceiver = params.protocolFeeReceiver;
173: liquidatorFee = params.liquidatorFee;
174: lastEarmarkBlock = block.number;
175: lastRedemptionBlock = block.number; // <= FOUND
176: }['226']
226: function setDepositCap(uint256 value) external onlyAdmin {
227: _checkArgument(value >= IERC20(yieldToken).balanceOf(address(this)));
228:
229: depositCap = value; // <= FOUND
230: emit DepositCapUpdated(value);
231: }['123']
123: function setDepositCap(uint256 cap) external onlyAdmin {
124: _checkArgument(cap <= type(int256).max.toUint256());
125:
126: depositCap = cap; // <= FOUND
127: emit DepositCapUpdated(cap);
128: }['573']
573: function redeem(uint256 amount) external onlyTransmuter {
574: _earmark();
575:
576: _redemptionWeight += PositionDecay.WeightIncrement(amount, cumulativeEarmarked);
577:
578:
579: uint256 collRedeemed = convertDebtTokensToYield(amount);
580: uint256 feeCollateral = collRedeemed * protocolFee / BPS;
581: uint256 totalOut = collRedeemed + feeCollateral;
582:
583:
584: uint256 old = _totalLocked;
585: _totalLocked = old - totalOut;
586: _collateralWeight += PositionDecay.WeightIncrement(totalOut, old);
587: cumulativeEarmarked -= amount;
588: totalDebt -= amount;
589: totalSyntheticsIssued -= amount;
590:
591: lastRedemptionBlock = block.number; // <= FOUND
592:
593: _redemptions[block.number] = RedemptionInfo(cumulativeEarmarked, totalDebt, _earmarkWeight);
594:
595: TokenUtils.safeTransfer(yieldToken, transmuter, collRedeemed);
596: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, feeCollateral);
597:
598: emit Redemption(amount);
599: }['285']
285: function setMinimumCollateralization(uint256 value) external onlyAdmin {
286: _checkArgument(value >= FIXED_POINT_SCALAR);
287: minimumCollateralization = value; // <= FOUND
288:
289: emit MinimumCollateralizationUpdated(value);
290: }['300']
300: function setCollateralizationLowerBound(uint256 value) external onlyAdmin {
301: _checkArgument(value <= minimumCollateralization);
302: _checkArgument(value >= FIXED_POINT_SCALAR);
303: collateralizationLowerBound = value; // <= FOUND
304: emit CollateralizationLowerBoundUpdated(value);
305: }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: 1
Click to show findings
['113']
113: unchecked {
114: require (end <= GRAPH_MAX);
115:
116: start--;
117: require (start <= GRAPH_MAX);
118:
119: (begDelta,begProd) = query(g.g, start);
120: (endDelta,endProd) = query(g.g, end);
121:
122: return ((int256(end) * endDelta) - endProd) - ((int256(start) * begDelta) - begProd);
123: }Num of instances: 7
Click to show findings
['2']
2: pragma solidity ^0.8.26; // <= FOUND['1']
1: pragma solidity ^0.8.23; // <= FOUND['4']
4: pragma solidity ^0.8.20; // <= FOUND['9']
9: pragma solidity ^0.8.7; // <= FOUND['1']
1: pragma solidity ^0.8.13; // <= FOUND['2']
2: pragma solidity >=0.8.4; // <= FOUND['1']
1: pragma solidity >=0.5.0; // <= FOUNDIt is general standard to declare interfaces on files separate from regular contract declarations
Num of instances: 1
Adding error strings to require statements in Solidity contracts, although not mandatory, enhances error handling, debugging, and overall contract maintainability. Providing a descriptive error message with each require statement helps identify the specific reason for a transaction failure, making it easier for developers to troubleshoot issues and for users to understand the cause of a revert. Including error strings improves code readability and fosters transparency, as the logic and conditions behind each requirement are clearly communicated
Num of instances: 9
Click to show findings
['40']
40: require(increment <= total);['41']
41: require(total <= type(uint128).max);['68']
68: require(value <= type(uint128).max);['96']
96: require(x > 0);['52']
52: require(amount <= DELTA_MAX && amount >= DELTA_MIN);['53']
53: require(start < GRAPH_MAX-1);['56']
56: require(expiration < GRAPH_MAX-1);['153']
153: require(ad <= DELTA_MAX && ad >= DELTA_MIN);['154']
154: require(ap <= PRODUCT_MAX && ap >= PRODUCT_MIN);[NonCritical-10] 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
[NonCritical-11] 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: 37
Click to show findings
['21']
21: function ethToUSD(uint256 ethAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 usdAmount) ['48']
48: function usdToETH(uint256 usdAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 ethAmount) ['72']
72: function _deposit(address depositor, uint256 amount) internal ['81']
81: function withdraw(address recipient, uint256 amount) external override onlyAuthorized nonReentrant ['195']
195: function setAlchemistFeeVault(address value) external onlyAdmin ['204']
204: function setPendingAdmin(address value) external onlyAdmin ['338']
338: function mintAllowance(uint256 ownerTokenId, address spender) external view returns (uint256) ['609']
609: function approveMint(uint256 tokenId, address spender, uint256 amount) external ['713']
713: function _mint(uint256 tokenId, uint256 amount, address recipient) internal ['892']
892: function _approveMint(uint256 ownerTokenId, address spender, uint256 amount) internal ['903']
903: function _decreaseMintAllowance(uint256 ownerTokenId, address spender, uint256 amount) internal ['921']
921: function _checkAccountOwnership(address owner, address user) internal pure ['942']
942: function _tokenExists(address nft, uint256 tokenId) internal view returns (bool exists) ['55']
55: function mint(address _recipient, uint256 _amount) external onlyWhitelisted ['65']
65: function setWhitelist(address _toWhitelist, bool _state) external ['73']
73: function pauseAlchemist(address _toPause, bool _state) external ['81']
81: function setCeiling(address _toSetCeiling, uint256 _ceiling) external ['105']
105: function burnFrom(address account, uint256 amount) public virtual ['26']
26: function expectDecimals(address token) internal view returns (uint8) ['61']
61: function safeTransfer(address token, address recipient, uint256 amount) internal ['76']
76: function safeApprove(address token, address spender, uint256 value) internal ['92']
92: function safeTransferFrom(address token, address owner, address recipient, uint256 amount) internal ['20']
20: function add(AddressSet storage self, address value) internal returns (bool) ['35']
35: function remove(AddressSet storage self, address value) internal returns (bool) ['65']
65: function contains(AddressSet storage self, address value) internal view returns (bool) ['26']
26: function expectDecimals(address token) internal view returns (uint8) ['44']
44: function safeBalanceOf(address token, address account) internal view returns (uint256) ['61']
61: function safeTransfer(address token, address recipient, uint256 amount) internal ['76']
76: function safeApprove(address token, address spender, uint256 value) internal ['92']
92: function safeTransferFrom(address token, address owner, address recipient, uint256 amount) internal ['107']
107: function safeMint(address token, address recipient, uint256 amount) internal ['121']
121: function safeBurn(address token, uint256 amount) internal ['136']
136: function safeBurnFrom(address token, address owner, uint256 amount) internal ['116']
116: function setAlchemist(address value) external onlyAdmin ['26']
26: function add(address caller) external override ['36']
36: function remove(address caller) external override ['53']
53: function isWhitelisted(address account) external view override returns (bool) In instances where a new variable is defined, there is no need to set it to it's default value.
Num of instances: 2
Click to show findings
['794']
794:
795: uint256 repaidAmountInYield = 0; // <= FOUND['553']
553: for (uint256 i = 0; i < accountIds.length; i++) { // <= FOUNDOwnable2Step further prevents risks posed by centralised privileges as there is a smaller likelihood of the owner being wrongfully changed
Num of instances: 2
Click to show findings
['10']
10: contract Whitelist is IWhitelist, Ownable // <= FOUND['12']
12: abstract contract AbstractFeeVault is IFeeVault, Ownable // <= FOUND[NonCritical-14] Revert statements within external and public functions can be used to perform DOS attacks
In Solidity, 'revert' statements are used to undo changes and throw an exception when certain conditions are not met. However, in public and external functions, improper use of revert can be exploited for Denial of Service (DoS) attacks. An attacker can intentionally trigger these 'revert' conditions, causing legitimate transactions to consistently fail. For example, if a function relies on specific conditions from user input or contract state, an attacker could manipulate these to continually force reverts, blocking the function's execution. Therefore, it's crucial to design contract logic to handle exceptions properly and avoid scenarios where revert can be predictably triggered by malicious actors. This includes careful input validation and considering alternative design patterns that are less susceptible to such abuses.
Num of instances: 16
Click to show findings
['40']
40: function deposit() external payable nonReentrant {
41: if (msg.value == 0) revert ZeroAmount(); // <= FOUND
42: _deposit(msg.sender, msg.value);
43: }['54']
54: function depositWETH(uint256 amount) external nonReentrant {
55: if (amount == 0) revert ZeroAmount(); // <= FOUND
56:
57:
58: IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
59:
60:
61: IWETH(token).withdraw(amount);
62:
63:
64: _deposit(msg.sender, amount);
65: }['81']
81: function withdraw(address recipient, uint256 amount) external override onlyAuthorized nonReentrant {
82: if (amount == 0) revert ZeroAmount(); // <= FOUND
83:
84:
85: if (amount > address(this).balance) revert InsufficientBalance(); // <= FOUND
86:
87:
88: (bool success,) = recipient.call{value: amount}("");
89: if (!success) revert TransferFailed(); // <= FOUND
90:
91: emit Withdrawn(recipient, amount);
92: }['65']
65: function mint(address to) external onlyAlchemist returns (uint256) {
66: if (to == address(0)) {
67: revert MintToZeroAddressError(); // <= FOUND
68: }
69: _currentTokenId++;
70: uint256 tokenId = _currentTokenId;
71: _mint(to, tokenId);
72: return tokenId;
73: }['182']
182: function setAlchemistPositionNFT(address nft) external onlyAdmin {
183: if (nft == address(0)) {
184: revert AlchemistV3NFTZeroAddressError(); // <= FOUND
185: }
186:
187: if (alchemistPositionNFT != address(0)) {
188: revert AlchemistV3NFTAlreadySetError(); // <= FOUND
189: }
190:
191: alchemistPositionNFT = nft;
192: }['195']
195: function setAlchemistFeeVault(address value) external onlyAdmin {
196: if (IFeeVault(value).token() != underlyingToken) {
197: revert AlchemistVaultTokenMismatchError(); // <= FOUND
198: }
199: alchemistFeeVault = value;
200: emit AlchemistFeeVaultUpdated(value);
201: }['211']
211: function acceptAdmin() external {
212: _checkState(pendingAdmin != address(0));
213:
214: if (msg.sender != pendingAdmin) {
215: revert Unauthorized(); // <= FOUND
216: }
217:
218: admin = pendingAdmin;
219: pendingAdmin = address(0);
220:
221: emit AdminUpdated(admin);
222: emit PendingAdminUpdated(address(0));
223: }['447']
447: function burn(uint256 amount, uint256 recipientId) external returns (uint256) {
448: _checkArgument(amount > 0);
449: _checkForValidAccountId(recipientId);
450:
451:
452: if (block.number == _accounts[recipientId].lastMintBlock) revert CannotRepayOnMintBlock(); // <= FOUND
453:
454:
455: _earmark();
456:
457:
458: _sync(recipientId);
459:
460: uint256 debt;
461:
462: _checkState((debt = _accounts[recipientId].debt - _accounts[recipientId].earmarked) > 0);
463:
464: uint256 credit = amount > debt ? debt : amount;
465:
466:
467: if (credit > totalDebt - ITransmuter(transmuter).totalLocked()) {
468: revert BurnLimitExceeded(credit, totalDebt - ITransmuter(transmuter).totalLocked()); // <= FOUND
469: }
470:
471:
472: TokenUtils.safeBurnFrom(debtToken, msg.sender, credit);
473:
474:
475: _accounts[recipientId].collateralBalance -= convertDebtTokensToYield(credit) * protocolFee / BPS;
476:
477:
478: _subDebt(recipientId, credit);
479:
480: totalSyntheticsIssued -= credit;
481:
482: emit Burn(msg.sender, credit, recipientId);
483:
484: return credit;
485: }['530']
530: function liquidate(uint256 accountId) external override returns (uint256 yieldAmount, uint256 feeInYield, uint256 feeInUnderlying) {
531: _checkForValidAccountId(accountId);
532: (yieldAmount, feeInYield, feeInUnderlying) = _liquidate(accountId);
533: if (yieldAmount > 0) {
534: emit Liquidated(accountId, msg.sender, yieldAmount, feeInYield, feeInUnderlying);
535: return (yieldAmount, feeInYield, feeInUnderlying);
536: } else {
537:
538: revert LiquidationError(); // <= FOUND
539: }
540: }['543']
543: function batchLiquidate(uint256[] memory accountIds)
544: external
545: returns (uint256 totalAmountLiquidated, uint256 totalFeesInYield, uint256 totalFeesInUnderlying)
546: {
547:
548:
549: if (accountIds.length == 0) {
550: revert MissingInputData(); // <= FOUND
551: }
552:
553: for (uint256 i = 0; i < accountIds.length; i++) {
554: uint256 accountId = accountIds[i];
555: if (accountId == 0 || !_tokenExists(alchemistPositionNFT, accountId)) {
556: continue;
557: }
558: (uint256 underlyingAmount, uint256 feeInYield, uint256 feeInUnderlying) = _liquidate(accountId);
559: totalAmountLiquidated += underlyingAmount;
560: totalFeesInYield += feeInYield;
561: totalFeesInUnderlying += feeInUnderlying;
562: }
563:
564: if (totalAmountLiquidated > 0) {
565: return (totalAmountLiquidated, totalFeesInYield, totalFeesInUnderlying);
566: } else {
567:
568: revert LiquidationError(); // <= FOUND
569: }
570: }['615']
615: function resetMintAllowances(uint256 tokenId) external {
616:
617: if (msg.sender != address(alchemistPositionNFT)) {
618:
619: address tokenOwner = IERC721(alchemistPositionNFT).ownerOf(tokenId);
620: if (msg.sender != tokenOwner) {
621: revert Unauthorized(); // <= FOUND
622: }
623: }
624:
625: _accounts[tokenId].allowancesVersion += 1;
626:
627: emit MintAllowancesReset(tokenId);
628: }['172']
172: function createRedemption(uint256 syntheticDepositAmount) external {
173: if (syntheticDepositAmount == 0) {
174: revert DepositZeroAmount(); // <= FOUND
175: }
176:
177: if (totalLocked + syntheticDepositAmount > depositCap) {
178: revert DepositCapReached(); // <= FOUND
179: }
180:
181: if (totalLocked + syntheticDepositAmount > alchemist.totalSyntheticsIssued()) {
182: revert DepositCapReached(); // <= FOUND
183: }
184:
185: TokenUtils.safeTransferFrom(syntheticToken, msg.sender, address(this), syntheticDepositAmount);
186:
187: _positions[++_nonce] = StakingPosition(syntheticDepositAmount, block.number, block.number + timeToTransmute);
188:
189:
190: _updateStakingGraph(syntheticDepositAmount.toInt256() * BLOCK_SCALING_FACTOR / timeToTransmute.toInt256(), timeToTransmute);
191:
192: totalLocked += syntheticDepositAmount;
193:
194: _mint(msg.sender, _nonce);
195:
196: emit PositionCreated(msg.sender, syntheticDepositAmount, _nonce);
197: }['200']
200: function claimRedemption(uint256 id) external {
201: StakingPosition storage position = _positions[id];
202:
203: if (position.maturationBlock == 0) {
204: revert PositionNotFound(); // <= FOUND
205: }
206:
207: uint256 transmutationTime = position.maturationBlock - position.startBlock;
208: uint256 blocksLeft = position.maturationBlock > block.number ? position.maturationBlock - block.number : 0;
209: uint256 amountNottransmuted = blocksLeft > 0 ? position.amount * blocksLeft / transmutationTime : 0;
210: uint256 amountTransmuted = position.amount - amountNottransmuted;
211:
212: if (_requireOwned(id) != msg.sender) {
213: revert CallerNotOwner(); // <= FOUND
214: }
215:
216:
217: _burn(id);
218:
219:
220: uint256 yieldTokenBalance = TokenUtils.safeBalanceOf(alchemist.yieldToken(), address(this));
221: uint256 debtValue = alchemist.convertYieldTokensToDebt(yieldTokenBalance);
222: uint256 amountToRedeem = amountTransmuted > debtValue ? amountTransmuted - debtValue : 0;
223: if (amountToRedeem > 0) alchemist.redeem(amountToRedeem);
224:
225: uint256 feeAmount = amountTransmuted * transmutationFee / BPS;
226: uint256 claimAmount = amountTransmuted - feeAmount;
227:
228: uint256 syntheticFee = amountNottransmuted * exitFee / BPS;
229: uint256 syntheticReturned = amountNottransmuted - syntheticFee;
230:
231:
232: if (blocksLeft > 0) _updateStakingGraph(-position.amount.toInt256() * BLOCK_SCALING_FACTOR / transmutationTime.toInt256(), blocksLeft);
233:
234:
235:
236: uint256 badDebtRatio = alchemist.totalSyntheticsIssued() * 10**TokenUtils.expectDecimals(alchemist.yieldToken()) / alchemist.getTotalUnderlyingValue();
237:
238: if (badDebtRatio > 1e18) {
239: claimAmount = claimAmount * FIXED_POINT_SCALAR / badDebtRatio;
240: feeAmount = feeAmount * FIXED_POINT_SCALAR / badDebtRatio;
241: }
242:
243: TokenUtils.safeTransfer(alchemist.yieldToken(), msg.sender, alchemist.convertDebtTokensToYield(claimAmount));
244: TokenUtils.safeTransfer(alchemist.yieldToken(), protocolFeeReceiver, alchemist.convertDebtTokensToYield(feeAmount));
245:
246: TokenUtils.safeTransfer(syntheticToken, msg.sender, syntheticReturned);
247: TokenUtils.safeTransfer(syntheticToken, protocolFeeReceiver, syntheticFee);
248:
249:
250: TokenUtils.safeBurn(syntheticToken, amountTransmuted);
251:
252: totalLocked -= position.amount;
253:
254: emit PositionClaimed(msg.sender, claimAmount, syntheticReturned);
255:
256: delete _positions[id];
257: }['26']
26: function add(address caller) external override {
27: _onlyAdmin();
28: if (disabled) {
29: revert IllegalState(); // <= FOUND
30: }
31: addresses.add(caller);
32: emit AccountAdded(caller);
33: }['36']
36: function remove(address caller) external override {
37: _onlyAdmin();
38: if (disabled) {
39: revert IllegalState(); // <= FOUND
40: }
41: addresses.remove(caller);
42: emit AccountRemoved(caller);
43: }['488']
488: function repay(uint256 amount, uint256 recipientTokenId) public returns (uint256) {
489: _checkArgument(amount > 0);
490: _checkForValidAccountId(recipientTokenId);
491: Account storage account = _accounts[recipientTokenId];
492:
493:
494: if (block.number == account.lastMintBlock) revert CannotRepayOnMintBlock(); // <= FOUND
495:
496:
497: _earmark();
498:
499:
500: _sync(recipientTokenId);
501:
502: uint256 debt;
503:
504:
505: _checkState((debt = account.debt) > 0);
506:
507: uint256 yieldToDebt = convertYieldTokensToDebt(amount);
508: uint256 credit = yieldToDebt > debt ? debt : yieldToDebt;
509: uint256 creditToYield = convertDebtTokensToYield(credit);
510:
511:
512: uint256 earmarkToRemove = credit > account.earmarked ? account.earmarked : credit;
513: account.earmarked -= earmarkToRemove;
514:
515:
516: account.collateralBalance -= creditToYield * protocolFee / BPS;
517:
518: _subDebt(recipientTokenId, credit);
519:
520:
521: TokenUtils.safeTransferFrom(yieldToken, msg.sender, transmuter, creditToYield);
522: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, creditToYield);
523:
524: emit Repay(msg.sender, amount, recipientTokenId, creditToYield);
525:
526: return creditToYield;
527: }[NonCritical-15] 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: 47
Click to show findings
['21']
21: function ethToUSD(uint256 ethAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 usdAmount) ['48']
48: function usdToETH(uint256 usdAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 ethAmount) ['75']
75: function underlyingTokenToUSD(uint256 underlyingTokenAmount, uint256 underlyingTokenDecimals) internal pure returns (uint256 usdAmount) ['27']
27: function encode(uint256 value) internal pure returns (Number memory) ['38']
38: function encodeRaw(uint256 value) internal pure returns (uint256) ['48']
48: function max() internal pure returns (Number memory) ['59']
59: function rational(uint256 n, uint256 d) internal pure returns (Number memory) ['71']
71: function add(Number memory self, Number memory value) internal pure returns (Number memory) ['82']
82: function add(Number memory self, uint256 value) internal pure returns (Number memory) ['93']
93: function sub(Number memory self, Number memory value) internal pure returns (Number memory) ['104']
104: function sub(Number memory self, uint256 value) internal pure returns (Number memory) ['115']
115: function mul(Number memory self, Number memory number) internal pure returns (Number memory) ['126']
126: function mul(Number memory self, uint256 value) internal pure returns (Number memory) ['137']
137: function div(Number memory self, uint256 value) internal pure returns (Number memory) ['150']
150: function cmp(Number memory self, Number memory value) internal pure returns (int256) ['170']
170: function equals(Number memory self, Number memory value) internal pure returns (bool) ['179']
179: function truncate(Number memory self) internal pure returns (uint256) ['19']
19: function generateSVG(uint256 tokenId, string memory title) internal pure returns (string memory) ['53']
53: function generateJSONString(uint256 tokenId, string memory svg) internal pure returns (string memory) ['74']
74: function generateTokenURI(uint256 tokenId, string memory title) internal pure returns (string memory) ['38']
38: function WeightIncrement(uint256 increment, uint256 total) internal pure returns (uint256) ['66']
66: function ScaleByWeightDelta(uint256 value, uint256 weightDelta) internal pure returns (uint256) ['12']
12: function toInt256(uint256 y) internal pure returns (int256 z) ['22']
22: function toUint256(int256 y) internal pure returns (uint256 z) ['32']
32: function uint256ToUint128(uint256 y) internal pure returns (uint128 z) ['42']
42: function uint128ToUint256(uint128 y) internal pure returns (uint256 z) ['26']
26: function expectDecimals(address token) internal view returns (uint8) ['61']
61: function safeTransfer(address token, address recipient, uint256 amount) internal ['76']
76: function safeApprove(address token, address spender, uint256 value) internal ['92']
92: function safeTransferFrom(address token, address owner, address recipient, uint256 amount) internal ['20']
20: function add(AddressSet storage self, address value) internal returns (bool) ['35']
35: function remove(AddressSet storage self, address value) internal returns (bool) ['65']
65: function contains(AddressSet storage self, address value) internal view returns (bool) ['50']
50: function addStake(Graph storage g, int256 amount, uint256 start, uint256 duration) internal ['108']
108: function queryStake(Graph storage g, uint256 start, uint256 end) internal view returns (int256) ['26']
26: function expectDecimals(address token) internal view returns (uint8) ['44']
44: function safeBalanceOf(address token, address account) internal view returns (uint256) ['61']
61: function safeTransfer(address token, address recipient, uint256 amount) internal ['76']
76: function safeApprove(address token, address spender, uint256 value) internal ['92']
92: function safeTransferFrom(address token, address owner, address recipient, uint256 amount) internal ['107']
107: function safeMint(address token, address recipient, uint256 amount) internal ['121']
121: function safeBurn(address token, uint256 amount) internal ['136']
136: function safeBurnFrom(address token, address owner, uint256 amount) internal ['92']
92: function Log2NegFrac(uint256 x) private pure returns (uint256) ['127']
127: function Exp2NegFrac(uint256 x) private pure returns (uint256) ['132']
132: function update(uint256[GRAPH_MAX + 1] storage graph, uint256 index, uint256 treeSize, int256 delta, int256 deltaProd) private ['170']
170: function query(uint256[GRAPH_MAX + 1] storage graph, uint256 index) private view returns (int256 sum, int256 sumProd) [NonCritical-16] 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: 4
Click to show findings
['28']
28: address immutable priceFeed;['29']
29: uint256 immutable updateTimeInSeconds;['30']
30: uint256 immutable underlyingTokenDecimals;['13']
13: Sets.AddressSet addresses;Consider spreading these lines over multiple lines to aid in readability and the support of VIM users everywhere.
Num of instances: 71
Click to show findings
['161']
161: underlyingConversionFactor = 10 ** (TokenUtils.expectDecimals(params.debtToken) - TokenUtils.expectDecimals(params.underlyingToken)); // <= FOUND['270']
270: require(convertYieldTokensToDebt(TokenUtils.safeBalanceOf(yieldToken, transmuter)) >= ITransmuter(transmuter).totalLocked()); // <= FOUND['530']
530: function liquidate(uint256 accountId) external override returns (uint256 yieldAmount, uint256 feeInYield, uint256 feeInUnderlying) { // <= FOUND['766']
766: /// @dev Fetches and applies the liquidation amount to account `tokenId` if the account collateral ratio touches `collateralizationLowerBound`. // <= FOUND['770']
770: /// @return feeInUnderlying The additional fee as a % of the liquidation amount, denominated in underlying token, to be sent to the liquidator // <= FOUND['771']
771: function _liquidate(uint256 accountId) internal returns (uint256 debtAmount, uint256 feeInYield, uint256 feeInUnderlying) { // <= FOUND['808']
808: uint256 alchemistCurrentCollateralization = normalizeUnderlyingTokensToDebt(_getTotalUnderlyingValue()) * FIXED_POINT_SCALAR / totalDebt; // <= FOUND['811']
811: collateralInUnderlying, account.debt, minimumCollateralization, alchemistCurrentCollateralization, globalMinimumCollateralization, liquidatorFee // <= FOUND['821']
821: account.collateralBalance = account.collateralBalance > adjustedLiquidationAmount ? account.collateralBalance - adjustedLiquidationAmount : 0; // <= FOUND['917']
917: /// @dev Checks if owner == sender and reverts with an {UnauthorizedAccountAccessError} error if the result is {false}. // <= FOUND['978']
978: uint256 debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, _earmarkWeight - account.lastAccruedEarmarkWeight); // <= FOUND['985']
985: debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, previousRedemption.earmarkWeight - account.lastAccruedEarmarkWeight); // <= FOUND['988']
988: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight); // <= FOUND['990']
990: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedState, _redemptionWeight - account.lastAccruedRedemptionWeight); // <= FOUND['993']
993: uint256 collateralToRemove = PositionDecay.ScaleByWeightDelta(account.rawLocked, _collateralWeight - account.lastCollateralWeight); // <= FOUND['1035']
1035: // If earmark was not this block then simulate, earmark, and store temporary variables for proper debt calculation // <= FOUND['1043']
1043: uint256 debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, earmarkWeightCopy - account.lastAccruedEarmarkWeight); // <= FOUND['1053']
1053: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight); // <= FOUND['1060']
1060: return (account.debt - earmarkToRedeem, earmarkedState - earmarkToRedeem, account.collateralBalance - collateralToRemove); // <= FOUND['1075']
1075: /// @dev Calculates the amount required to reduce an accounts debt and collateral by to achieve the target `minimumCollateralization` ratio. // <= FOUND['1080']
1080: function _getLiquidationAmount(uint256 collateral, uint256 debt, uint256 globalRatio) internal view returns (uint256 liquidationAmount) { // <= FOUND['1091']
1091: // otherwise, partially liquidate using formula : (collateral - amount)/(debt - amount) = minimumCollateralization // <= FOUND['190']
190: _updateStakingGraph(syntheticDepositAmount.toInt256() * BLOCK_SCALING_FACTOR / timeToTransmute.toInt256(), timeToTransmute); // <= FOUND['219']
219: // If the contract has a balance of yield tokens from alchemist repayments then we only need to redeem partial or none from Alchemist earmarked // <= FOUND['232']
232: if (blocksLeft > 0) _updateStakingGraph(-position.amount.toInt256() * BLOCK_SCALING_FACTOR / transmutationTime.toInt256(), blocksLeft); // <= FOUND['235']
235: // If the system experiences bad debt we use this ratio to scale back the amount of yield tokens that are transmuted // <= FOUND['236']
236: uint256 badDebtRatio = alchemist.totalSyntheticsIssued() * 10**TokenUtils.expectDecimals(alchemist.yieldToken()) / alchemist.getTotalUnderlyingValue(); // <= FOUND['244']
244: TokenUtils.safeTransfer(alchemist.yieldToken(), protocolFeeReceiver, alchemist.convertDebtTokensToYield(feeAmount)); // <= FOUND['132']
132: function update(uint256[GRAPH_MAX + 1] storage graph, uint256 index, uint256 treeSize, int256 delta, int256 deltaProd) private { // <= FOUND['170']
170: function query(uint256[GRAPH_MAX + 1] storage graph, uint256 index) private view returns (int256 sum, int256 sumProd) { // <= FOUND['45']
45: (bool success, bytes memory data) = token.staticcall(abi.encodeWithSelector(IERC20.balanceOf.selector, account)); // <= FOUND['62']
62: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, recipient, amount)); // <= FOUND['93']
93: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, owner, recipient, amount)); // <= FOUND['108']
108: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Mintable.mint.selector, recipient, amount)); // <= FOUND['137']
137: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Burnable.burnFrom.selector, owner, amount)); // <= FOUND['27']
27: function permit(address owner, address spender, uint256 amount, uint256 expiry, bytes calldata signature) external returns (bool); // <= FOUND['69']
69: /// @notice This is the main contact point where the Strategy interacts with the Vault. It is critical that this call // <= FOUND['74']
74: /// @notice This function should only be used in the scenario where the Strategy is being retired but no migration of // <= FOUND['76']
76: /// "Emergency Exit" mode in order for it to exit as quickly as possible. The latter scenario could be for any // <= FOUND['81']
81: /// @notice View the governance address of the Vault to assert privileged functions can only be called by governance. // <= FOUND['85']
85: /// @notice View the management address of the Vault to assert privileged functions can only be called by management. // <= FOUND['89']
89: /// @notice View the guardian address of the Vault to assert privileged functions can only be called by guardian. The // <= FOUND['64']
64: /// @notice This redemption struct is included in the main contract, AlchemistV3.sol, to aid in calculating user debt from historic redemptions. // <= FOUND['96']
96: /// @notice **_NOTE:_** When depositing, the `AlchemistV3` contract must have **allowance()** to spend funds on behalf of **msg.sender** for at least **amount** of the **yieldToken** being deposited. This can be done via the standard `ERC20.approve()` method. // <= FOUND['161']
161: /// @notice **_NOTE:_** The caller of `mintFrom()` must have **mintAllowance()** to mint debt from the `Account` controlled by **owner** for at least the amount of **yieldTokens** that **shares** will be converted to. This can be done via the `approveMint()` or `permitMint()` methods. // <= FOUND['218']
218: * @notice Liquidates `owner` if the debt for account `owner` is greater than the underlying value of their collateral * LTV. // <= FOUND['235']
235: function liquidate(uint256 accountId) external returns (uint256 yieldAmount, uint256 feeInYield, uint256 feeInUnderlying); // <= FOUND['237']
237: /// @notice Liquidates `owners` if the debt for account `owner` is greater than the underlying value of their collateral * LTV. // <= FOUND['282']
282: /// @dev This is the first step in the two-step process of setting a new administrator. After this function is called, the pending administrator will then need to call {acceptAdmin} to complete the process. // <= FOUND['300']
300: /// @notice The current pending administrator must be non-zero or this call will revert with an {IllegalState} error. // <= FOUND['302']
302: /// @dev This is the second step in the two-step process of setting a new administrator. After this function is successfully called, this pending administrator will be reset and the new administrator will be set. // <= FOUND['553']
553: /// @param feeInUnderlying The liquidation fee sent to 'liquidator' in ETH (if needed i.e. if there isn't enough remaining collateral to cover the fee). // <= FOUND['554']
554: event Liquidated(uint256 indexed accountId, address liquidator, uint256 amount, uint256 feeInYield, uint256 feeInUnderlying); // <= FOUND['562']
562: /// @param feeInETH The liquidation fee sent to 'liquidator' in ETH (if needed i.e. if there isn't enough remaining collateral to cover the fee). // <= FOUND['563']
563: event BatchLiquidated(uint256[] indexed accounts, address liquidator, uint256 amount, uint256 feeInYield, uint256 feeInETH); // <= FOUND['645']
645: /// @notice Collateralization is determined by taking the total value of collateral that a user has deposited into their account and dividing it their debt. // <= FOUND['654']
654: /// @notice Collateralization is determined by taking the total value of collateral deposited in the alchemist and dividing it by the total debt. // <= FOUND['690']
690: /// @param alchemistMinimumCollateralization Minimum collateralization ratio of the alchemist to trigger full liquidation // <= FOUND['759']
759: /// @notice An error which is used to indicate that an operation failed because an account became undercollateralized. // <= FOUND['762']
762: /// @notice An error which is used to indicate that a liquidate operation failed because an account is sufficiaenly collateralized. // <= FOUND['765']
765: /// @notice An error which is used to indicate that a user is performing an action on an account that requires account ownership // <= FOUND['768']
768: /// @notice An error which is used to indicate that a burn operation failed because the transmuter requires more debt in the system. // <= FOUND['783']
783: /// @notice An error which is used to indicate that the token address for the AlchemistTokenVault does not match the underlyingToken // <= FOUND['792']
792: interface IAlchemistV3 is IAlchemistV3Actions, IAlchemistV3AdminActions, IAlchemistV3Errors, IAlchemistV3Immutables, IAlchemistV3Events, IAlchemistV3State {} // <= FOUND['158']
158: /// @notice End block of position must be <= to current block or this call will revert with a {PrematureClaim} error. // <= FOUND['60']
60: /// @notice Transfers `amount` tokens from `owner` to `recipient` using an approval that `owner` gave to `msg.sender`. // <= FOUND['21']
21: function ethToUSD(uint256 ethAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 usdAmount) { // <= FOUND['48']
48: function usdToETH(uint256 usdAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 ethAmount) { // <= FOUND['75']
75: function underlyingTokenToUSD(uint256 underlyingTokenAmount, uint256 underlyingTokenDecimals) internal pure returns (uint256 usdAmount) { // <= FOUND['15']
15: /// @notice An error used to indicate that an action could not be completed because the required amount of allowance has not // <= FOUND['30']
30: /// @notice An error which is used to indicate that the functioin call failed becasue the caller is not the alchemist // <= FOUNDIn Solidity, manipulating contract storage comes with significant gas costs. One can optimize gas usage by preventing unnecessary storage updates when the new value is the same as the existing one. If an existing value is the same as the new one, not reassigning it to the storage could potentially save substantial amounts of gas, notably 2900 gas for a 'Gsreset'. This saving may come at the expense of a cold storage load operation ('Gcoldsload'), which costs 2100 gas, or a warm storage access operation ('Gwarmaccess'), which costs 100 gas. Therefore, the gas efficiency of your contract can be significantly improved by adding a check that compares the new value with the current one before any storage update operation. If the values are the same, you can bypass the storage operation, thereby saving gas.
Num of instances: 13
Click to show findings
['57']
57: function setAuthorization(address account, bool status) external onlyOwner {
58: _checkNonZeroAddress(account);
59: authorized[account] = status;
60: emit AuthorizationUpdated(account, status);
61: }['204']
204: function setPendingAdmin(address value) external onlyAdmin {
205: pendingAdmin = value;
206:
207: emit PendingAdminUpdated(value);
208: }['226']
226: function setDepositCap(uint256 value) external onlyAdmin {
227: _checkArgument(value >= IERC20(yieldToken).balanceOf(address(this)));
228:
229: depositCap = value;
230: emit DepositCapUpdated(value);
231: }['242']
242: function setProtocolFee(uint256 fee) external onlyAdmin {
243: _checkArgument(fee <= BPS);
244:
245: protocolFee = fee;
246: emit ProtocolFeeUpdated(fee);
247: }['250']
250: function setLiquidatorFee(uint256 fee) external onlyAdmin {
251: _checkArgument(fee <= BPS);
252:
253: liquidatorFee = fee;
254: emit LiquidatorFeeUpdated(fee);
255: }['285']
285: function setMinimumCollateralization(uint256 value) external onlyAdmin {
286: _checkArgument(value >= FIXED_POINT_SCALAR);
287: minimumCollateralization = value;
288:
289: emit MinimumCollateralizationUpdated(value);
290: }['293']
293: function setGlobalMinimumCollateralization(uint256 value) external onlyAdmin {
294: _checkArgument(value >= minimumCollateralization);
295: globalMinimumCollateralization = value;
296: emit GlobalMinimumCollateralizationUpdated(value);
297: }['300']
300: function setCollateralizationLowerBound(uint256 value) external onlyAdmin {
301: _checkArgument(value <= minimumCollateralization);
302: _checkArgument(value >= FIXED_POINT_SCALAR);
303: collateralizationLowerBound = value;
304: emit CollateralizationLowerBoundUpdated(value);
305: }['116']
116: function setAlchemist(address value) external onlyAdmin {
117: alchemist = IAlchemistV3(value);
118:
119: emit AlchemistUpdated(value);
120: }['123']
123: function setDepositCap(uint256 cap) external onlyAdmin {
124: _checkArgument(cap <= type(int256).max.toUint256());
125:
126: depositCap = cap;
127: emit DepositCapUpdated(cap);
128: }['131']
131: function setTransmutationFee(uint256 fee) external onlyAdmin {
132: _checkArgument(fee <= BPS);
133:
134: transmutationFee = fee;
135: emit TransmutationFeeUpdated(fee);
136: }['139']
139: function setExitFee(uint256 fee) external onlyAdmin {
140: _checkArgument(fee <= BPS);
141:
142: exitFee = fee;
143: emit ExitFeeUpdated(fee);
144: }['147']
147: function setTransmutationTime(uint256 time) external onlyAdmin {
148: timeToTransmute = time;
149:
150: emit TransmutationTimeUpdated(time);
151: }In many cases only some functionality is used from an import. In such cases it makes more sense to use {} to specify what to import and thus save gas whilst improving readability
Num of instances: 20
Click to show findings
['4']
4: import "@openzeppelin/contracts/access/Ownable.sol";['4']
4: import "../libraries/TokenUtils.sol";['4']
4: import "@openzeppelin/contracts/token/ERC20/IERC20.sol";['6']
6: import '../interfaces/ITokenAdapter.sol';['7']
7: import "@openzeppelin/contracts/interfaces/IERC4626.sol";['5']
5: import "./base/Errors.sol";['6']
6: import "./adapters/AbstractFeeVault.sol";['4']
4: import "./interfaces/IAlchemistV3.sol";['11']
11: import "./libraries/PositionDecay.sol";['3']
3: import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";['4']
4: import "./IAlchemistV3.sol";['4']
4: import "../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol";['3']
3: import "../../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";['5']
5: import "../interfaces/IERC20Burnable.sol";['6']
6: import "../interfaces/IERC20Mintable.sol";['16']
16: import "./base/TransmuterErrors.sol";['3']
3: import "../base/Errors.sol";['4']
4: import "../interfaces/IWhitelist.sol";['5']
5: import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol";['6']
6: import "../libraries/Sets.sol";Using outdated Solidity versions can lead to security risks and inefficiencies. It's recommended to adopt newer versions, ideally the latest, which as of now is 0.8.24. This ensures access to the latest bug fixes, features, and optimizations, particularly crucial for Layer 2 deployments. Regular updates to versions like 0.8.19 or later, up to 0.8.24, enhance contract security and performance.
Num of instances: 4
Click to show findings
['2']
2: pragma solidity >=0.8.4;['1']
1: pragma solidity >=0.5.0;['9']
9: pragma solidity ^0.8.7;['1']
1: pragma solidity ^0.8.13;Try to index as much as three variables in event declarations as this is more gas efficient when done on value type variables (uint, address etc) however not for bytes and string variables
Num of instances: 4
Click to show findings
['39']
39: event AlchemistPaused(address alchemistAddress, bool isPaused); // <= FOUND['59']
59: event Deposited(address indexed from, uint256 amount); // <= FOUND['64']
64: event Withdrawn(address indexed to, uint256 amount); // <= FOUND['27']
27: event AuthorizationUpdated(address indexed account, bool status); // <= FOUND[NonCritical-22] Explicitly define visibility of state variables to prevent misconceptions on what can access the variable
Such state variables should be marked as private as this is the default visibility
Num of instances: 4
Click to show findings
['29']
29: uint256 immutable updateTimeInSeconds; // <= FOUND['30']
30: uint256 immutable underlyingTokenDecimals; // <= FOUND['28']
28: address immutable priceFeed; // <= FOUND['13']
13: Sets.AddressSet addresses; // <= FOUNDContracts 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: 67
Click to show findings
['57']
57: function setAuthorization(address account, bool status) external onlyOwner ['44']
44: function usdToETH(uint256 usdAmount) external view returns (uint256 ethAmount) ['49']
49: function ethToUSD(uint256 ethAmount) external view returns (uint256 usdAmount) ['54']
54: function underlyingTokenToUSD(uint256 underlyingTokenAmount) external view returns (uint256 usdAmount) ['22']
22: function price() external view returns (uint256) ['40']
40: function deposit() external payable nonReentrant ['54']
54: function depositWETH(uint256 amount) external nonReentrant ['32']
32: function deposit(uint256 amount) external ['65']
65: function mint(address to) external onlyAlchemist returns (uint256) ['155']
155: function initialize(AlchemistInitializationParams memory params) external initializer ['182']
182: function setAlchemistPositionNFT(address nft) external onlyAdmin ['195']
195: function setAlchemistFeeVault(address value) external onlyAdmin ['204']
204: function setPendingAdmin(address value) external onlyAdmin ['211']
211: function acceptAdmin() external ['226']
226: function setDepositCap(uint256 value) external onlyAdmin ['234']
234: function setProtocolFeeReceiver(address value) external onlyAdmin ['242']
242: function setProtocolFee(uint256 fee) external onlyAdmin ['250']
250: function setLiquidatorFee(uint256 fee) external onlyAdmin ['258']
258: function setTokenAdapter(address value) external onlyAdmin ['266']
266: function setTransmuter(address value) external onlyAdmin ['277']
277: function setGuardian(address guardian, bool isActive) external onlyAdmin ['285']
285: function setMinimumCollateralization(uint256 value) external onlyAdmin ['293']
293: function setGlobalMinimumCollateralization(uint256 value) external onlyAdmin ['300']
300: function setCollateralizationLowerBound(uint256 value) external onlyAdmin ['308']
308: function pauseDeposits(bool isPaused) external onlyAdminOrGuardian ['314']
314: function pauseLoans(bool isPaused) external onlyAdminOrGuardian ['320']
320: function getCDP(uint256 tokenId) external view returns (uint256, uint256, uint256) ['326']
326: function getTotalDeposited() external view returns (uint256) ['331']
331: function getMaxBorrowable(uint256 tokenId) external view returns (uint256) ['338']
338: function mintAllowance(uint256 ownerTokenId, address spender) external view returns (uint256) ['344']
344: function getTotalUnderlyingValue() external view returns (uint256) ['357']
357: function deposit(uint256 amount, address recipient, uint256 recipientId) external returns (uint256) ['384']
384: function withdraw(uint256 amount, address recipient, uint256 tokenId) external returns (uint256) ['410']
410: function mint(uint256 tokenId, uint256 amount, address recipient) external ['428']
428: function mintFrom(uint256 tokenId, uint256 amount, address recipient) external ['447']
447: function burn(uint256 amount, uint256 recipientId) external returns (uint256) ['543']
543: function batchLiquidate(uint256[] memory accountIds)
544: external
545: returns (uint256 totalAmountLiquidated, uint256 totalFeesInYield, uint256 totalFeesInUnderlying)
546: ['573']
573: function redeem(uint256 amount) external onlyTransmuter ['602']
602: function poke(uint256 tokenId) external ['609']
609: function approveMint(uint256 tokenId, address spender, uint256 amount) external ['615']
615: function resetMintAllowances(uint256 tokenId) external ['55']
55: function mint(address _recipient, uint256 _amount) external onlyWhitelisted ['65']
65: function setWhitelist(address _toWhitelist, bool _state) external ['73']
73: function pauseAlchemist(address _toPause, bool _state) external ['81']
81: function setCeiling(address _toSetCeiling, uint256 _ceiling) external ['116']
116: function setAlchemist(address value) external onlyAdmin ['123']
123: function setDepositCap(uint256 cap) external onlyAdmin ['131']
131: function setTransmutationFee(uint256 fee) external onlyAdmin ['139']
139: function setExitFee(uint256 fee) external onlyAdmin ['147']
147: function setTransmutationTime(uint256 time) external onlyAdmin ['234']
234: function setProtocolFeeReceiver(address value) external onlyAdmin ['167']
167: function getPosition(uint256 id) external view returns (StakingPosition memory) ['172']
172: function createRedemption(uint256 syntheticDepositAmount) external ['200']
200: function claimRedemption(uint256 id) external ['260']
260: function queryGraph(uint256 startBlock, uint256 endBlock) external view returns (uint256) ['21']
21: function getAddresses() external view returns (address[] memory) ['75']
75: function burn(uint256 tokenId) public onlyAlchemist ['349']
349: function totalValue(uint256 tokenId) public view returns (uint256) ['488']
488: function repay(uint256 amount, uint256 recipientTokenId) public returns (uint256) ['631']
631: function calculateLiquidation(
632: uint256 collateral,
633: uint256 debt,
634: uint256 targetCollateralization,
635: uint256 alchemistCurrentCollateralization,
636: uint256 alchemistMinimumCollateralization,
637: uint256 feeBps
638: ) public pure returns (uint256 grossCollateralToSeize, uint256 debtToBurn, uint256 fee) ['678']
678: function convertYieldTokensToDebt(uint256 amount) public view returns (uint256) ['683']
683: function convertDebtTokensToYield(uint256 amount) public view returns (uint256) ['688']
688: function convertYieldTokensToUnderlying(uint256 amount) public view returns (uint256) ['694']
694: function convertUnderlyingTokensToYield(uint256 amount) public view returns (uint256) ['700']
700: function normalizeUnderlyingTokensToDebt(uint256 amount) public view returns (uint256) ['705']
705: function normalizeDebtTokensToUnderlying(uint256 amount) public view returns (uint256) ['116']
116: function lowerHasMinted(uint256 amount) public onlyWhitelisted 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: 3
Click to show findings
['16']
16: contract AlchemistETHVault is AbstractFeeVault, ReentrancyGuard // <= FOUND['23']
23: contract AlchemistV3 is IAlchemistV3, Initializable // <= FOUND['21']
21: contract Transmuter is ITransmuter, ERC721 // <= FOUNDIn 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: 4
Click to show findings
['211']
211: function acceptAdmin() external {
212: _checkState(pendingAdmin != address(0));
213:
214: if (msg.sender != pendingAdmin) { // <= FOUND
215: revert Unauthorized();
216: }
217:
218: admin = pendingAdmin;
219: pendingAdmin = address(0);
220:
221: emit AdminUpdated(admin);
222: emit PendingAdminUpdated(address(0));
223: }['357']
357: function deposit(uint256 amount, address recipient, uint256 recipientId) external returns (uint256) {
358: _checkArgument(recipient != address(0));
359: _checkArgument(amount > 0);
360: _checkState(!depositsPaused);
361: _checkState(IERC20(yieldToken).balanceOf(address(this)) + amount <= depositCap);
362: uint256 tokenId = recipientId;
363:
364:
365: if (tokenId == 0) {
366: tokenId = IAlchemistV3Position(alchemistPositionNFT).mint(recipient);
367: emit AlchemistV3PositionNFTMinted(recipient, tokenId);
368: } else {
369: _checkForValidAccountId(tokenId);
370: }
371:
372: _accounts[tokenId].collateralBalance += amount;
373: _accounts[tokenId].freeCollateral += amount;
374:
375:
376: TokenUtils.safeTransferFrom(yieldToken, msg.sender, address(this), amount); // <= FOUND
377:
378: emit Deposit(amount, tokenId);
379:
380: return convertYieldTokensToDebt(amount);
381: }['384']
384: function withdraw(uint256 amount, address recipient, uint256 tokenId) external returns (uint256) {
385: _checkArgument(recipient != address(0));
386: _checkForValidAccountId(tokenId);
387: _checkArgument(amount > 0);
388: _checkAccountOwnership(IAlchemistV3Position(alchemistPositionNFT).ownerOf(tokenId), msg.sender); // <= FOUND
389: _earmark();
390:
391: _sync(tokenId);
392:
393: _checkArgument(_accounts[tokenId].freeCollateral >= amount);
394:
395: _accounts[tokenId].collateralBalance -= amount;
396: _accounts[tokenId].freeCollateral -= amount;
397:
398:
399: _validate(tokenId);
400:
401:
402: TokenUtils.safeTransfer(yieldToken, recipient, amount);
403:
404: emit Withdraw(amount, tokenId, recipient);
405:
406: return amount;
407: }['615']
615: function resetMintAllowances(uint256 tokenId) external {
616:
617: if (msg.sender != address(alchemistPositionNFT)) { // <= FOUND
618:
619: address tokenOwner = IERC721(alchemistPositionNFT).ownerOf(tokenId);
620: if (msg.sender != tokenOwner) { // <= FOUND
621: revert Unauthorized();
622: }
623: }
624:
625: _accounts[tokenId].allowancesVersion += 1;
626:
627: emit MintAllowancesReset(tokenId);
628: }Num of instances: 5
Click to show findings
['7']
7: interface ITestYieldToken is IERC20 // <= FOUND['8']
8: interface IYearnVaultV2 is IERC20Metadata // <= FOUND['5']
5: interface IERC20Minimal // <= FOUND['3']
3: interface IYieldToken // <= FOUND['8']
8: interface IAlchemicToken is IERC20 // <= FOUND[NonCritical-27] 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: 12
Click to show findings
['21']
21: function ethToUSD(uint256 ethAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 usdAmount) {
22:
23: AggregatorV3Interface priceFeed = AggregatorV3Interface(usdPriceFeed);
24: (, int256 price,, uint256 updateTime,) = priceFeed.latestRoundData();
25:
26: if (price <= 0) {
27: revert ChainlinkMalfunction(usdPriceFeed, price);
28: }
29:
30: if (updateTime == 0) {
31: revert IncompleteRound(usdPriceFeed, updateTime);
32: }
33:
34: if (updateTime < block.timestamp - expectedUpdateTime) {
35: revert IncompleteRound(usdPriceFeed, updateTime);
36: }
37:
38:
39: return (ethAmount * uint256(price)) / 1e20; // <= FOUND
40: }['48']
48: function usdToETH(uint256 usdAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 ethAmount) {
49:
50: AggregatorV3Interface priceFeed = AggregatorV3Interface(usdPriceFeed);
51: (, int256 price,, uint256 updateTime,) = priceFeed.latestRoundData();
52:
53: if (price <= 0) {
54: revert ChainlinkMalfunction(usdPriceFeed, price);
55: }
56:
57: if (updateTime == 0) {
58: revert IncompleteRound(usdPriceFeed, updateTime);
59: }
60:
61: if (updateTime < block.timestamp - expectedUpdateTime) {
62: revert IncompleteRound(usdPriceFeed, updateTime);
63: }
64:
65:
66:
67: return (usdAmount * 1e20) / uint256(price); // <= FOUND
68: }['75']
75: function underlyingTokenToUSD(uint256 underlyingTokenAmount, uint256 underlyingTokenDecimals) internal pure returns (uint256 usdAmount) {
76: uint256 usdConversionFactor = 10 ** (underlyingTokenDecimals - USDC_DECIMALS);
77: return underlyingTokenAmount / usdConversionFactor; // <= FOUND
78: }['44']
44: function usdToETH(uint256 usdAmount) external view returns (uint256 ethAmount) {
45: return ETHUSDConverter.usdToETH(usdAmount, priceFeed, updateTimeInSeconds); // <= FOUND
46: }['49']
49: function ethToUSD(uint256 ethAmount) external view returns (uint256 usdAmount) {
50: return ETHUSDConverter.ethToUSD(ethAmount, priceFeed, updateTimeInSeconds); // <= FOUND
51: }['54']
54: function underlyingTokenToUSD(uint256 underlyingTokenAmount) external view returns (uint256 usdAmount) {
55: return ETHUSDConverter.underlyingTokenToUSD(underlyingTokenAmount, underlyingTokenDecimals); // <= FOUND
56: }['530']
530: function liquidate(uint256 accountId) external override returns (uint256 yieldAmount, uint256 feeInYield, uint256 feeInUnderlying) {
531: _checkForValidAccountId(accountId);
532: (yieldAmount, feeInYield, feeInUnderlying) = _liquidate(accountId);
533: if (yieldAmount > 0) {
534: emit Liquidated(accountId, msg.sender, yieldAmount, feeInYield, feeInUnderlying);
535: return (yieldAmount, feeInYield, feeInUnderlying); // <= FOUND
536: } else {
537:
538: revert LiquidationError();
539: }
540: }['543']
543: function batchLiquidate(uint256[] memory accountIds)
544: external
545: returns (uint256 totalAmountLiquidated, uint256 totalFeesInYield, uint256 totalFeesInUnderlying)
546: {
547:
548:
549: if (accountIds.length == 0) {
550: revert MissingInputData();
551: }
552:
553: for (uint256 i = 0; i < accountIds.length; i++) {
554: uint256 accountId = accountIds[i];
555: if (accountId == 0 || !_tokenExists(alchemistPositionNFT, accountId)) {
556: continue;
557: }
558: (uint256 underlyingAmount, uint256 feeInYield, uint256 feeInUnderlying) = _liquidate(accountId);
559: totalAmountLiquidated += underlyingAmount;
560: totalFeesInYield += feeInYield;
561: totalFeesInUnderlying += feeInUnderlying;
562: }
563:
564: if (totalAmountLiquidated > 0) {
565: return (totalAmountLiquidated, totalFeesInYield, totalFeesInUnderlying); // <= FOUND
566: } else {
567:
568: revert LiquidationError();
569: }
570: }['631']
631: function calculateLiquidation(
632: uint256 collateral,
633: uint256 debt,
634: uint256 targetCollateralization,
635: uint256 alchemistCurrentCollateralization,
636: uint256 alchemistMinimumCollateralization,
637: uint256 feeBps
638: ) public pure returns (uint256 grossCollateralToSeize, uint256 debtToBurn, uint256 fee) {
639: if (debt >= collateral) {
640:
641: return (debt, debt, 0); // <= FOUND
642: }
643:
644: if (alchemistCurrentCollateralization < alchemistMinimumCollateralization) {
645:
646: return (debt, debt, 0); // <= FOUND
647: }
648:
649:
650: uint256 surplus = collateral > debt ? collateral - debt : 0;
651: fee = (surplus * feeBps) / BPS;
652:
653:
654: uint256 adjCollat = collateral - fee;
655:
656:
657: uint256 md = (targetCollateralization * debt) / FIXED_POINT_SCALAR;
658:
659:
660: if (md <= adjCollat) {
661: return (0, 0, fee); // <= FOUND
662: }
663:
664:
665: uint256 num = md - adjCollat;
666:
667:
668: uint256 denom = targetCollateralization - FIXED_POINT_SCALAR;
669:
670:
671: debtToBurn = (num * FIXED_POINT_SCALAR) / denom;
672:
673:
674: grossCollateralToSeize = debtToBurn + fee;
675: }['771']
771: function _liquidate(uint256 accountId) internal returns (uint256 debtAmount, uint256 feeInYield, uint256 feeInUnderlying) {
772:
773: _earmark();
774:
775: _sync(accountId);
776:
777: Account storage account = _accounts[accountId];
778:
779:
780: if (account.debt == 0) {
781: return (0, 0, 0); // <= FOUND
782: }
783:
784:
785: uint256 collateralInUnderlying = totalValue(accountId);
786: uint256 collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
787:
788:
789: if (collateralizationRatio > collateralizationLowerBound) {
790: return (0, 0, 0); // <= FOUND
791: }
792:
793:
794: uint256 repaidAmountInYield = 0;
795: if (account.earmarked > 0) {
796: repaidAmountInYield = _forceRepay(accountId, convertDebtTokensToYield(account.earmarked));
797: }
798:
799: if (account.debt == 0) {
800: return (repaidAmountInYield, 0, 0); // <= FOUND
801: }
802:
803:
804: collateralInUnderlying = totalValue(accountId);
805: collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
806:
807: if (collateralizationRatio <= collateralizationLowerBound) {
808: uint256 alchemistCurrentCollateralization = normalizeUnderlyingTokensToDebt(_getTotalUnderlyingValue()) * FIXED_POINT_SCALAR / totalDebt;
809:
810: (uint256 liquidationAmount, uint256 debtToBurn, uint256 baseFee) = calculateLiquidation(
811: collateralInUnderlying, account.debt, minimumCollateralization, alchemistCurrentCollateralization, globalMinimumCollateralization, liquidatorFee
812: );
813:
814: uint256 feeBonus = debtToBurn * liquidatorFee / BPS;
815: uint256 adjustedLiquidationAmount = convertDebtTokensToYield(liquidationAmount);
816: uint256 adjustedDebtToBurn = convertDebtTokensToYield(debtToBurn);
817: debtAmount = adjustedLiquidationAmount;
818: feeInYield = convertDebtTokensToYield(baseFee);
819:
820:
821: account.collateralBalance = account.collateralBalance > adjustedLiquidationAmount ? account.collateralBalance - adjustedLiquidationAmount : 0;
822:
823:
824: _subDebt(accountId, debtToBurn);
825:
826:
827: TokenUtils.safeTransfer(yieldToken, transmuter, adjustedDebtToBurn);
828:
829: if (feeInYield > 0) {
830:
831: TokenUtils.safeTransfer(yieldToken, msg.sender, feeInYield);
832: }
833:
834:
835:
836: if (feeBonus > 0) {
837: uint256 vaultBalance = IFeeVault(alchemistFeeVault).totalDeposits();
838: if (vaultBalance > 0) {
839: feeInUnderlying = vaultBalance > feeBonus ? feeBonus : vaultBalance;
840: IFeeVault(alchemistFeeVault).withdraw(msg.sender, feeInUnderlying);
841: }
842: }
843: }
844:
845: return (debtAmount + repaidAmountInYield, feeInYield, feeInUnderlying); // <= FOUND
846: }['942']
942: function _tokenExists(address nft, uint256 tokenId) internal view returns (bool exists) {
943: if (tokenId == 0) {
944:
945: return false; // <= FOUND
946: }
947: try IERC721(nft).ownerOf(tokenId) {
948:
949: exists = true;
950: } catch {
951:
952: exists = false;
953: }
954: }['1080']
1080: function _getLiquidationAmount(uint256 collateral, uint256 debt, uint256 globalRatio) internal view returns (uint256 liquidationAmount) {
1081: _checkState(minimumCollateralization > FIXED_POINT_SCALAR);
1082: if (debt >= collateral) {
1083:
1084: return debt; // <= FOUND
1085: }
1086:
1087: if (globalRatio < globalMinimumCollateralization) {
1088:
1089: return debt; // <= FOUND
1090: }
1091:
1092: uint256 expectedCollateralForCurrentDebt = (debt * minimumCollateralization) / FIXED_POINT_SCALAR;
1093: uint256 collateralDiff = expectedCollateralForCurrentDebt - collateral;
1094: uint256 ratioDiff = minimumCollateralization - FIXED_POINT_SCALAR;
1095: liquidationAmount = collateralDiff * FIXED_POINT_SCALAR / ratioDiff;
1096: return liquidationAmount; // <= FOUND
1097: }If these serve no purpose, they should be safely removed
Num of instances: 3
Click to show findings
['22']
22: bytes32 public constant ADMIN_ROLE = keccak256("ADMIN"); // <= FOUND['25']
25: bytes32 public constant SENTINEL_ROLE = keccak256("SENTINEL"); // <= FOUND['67']
67: address[] public alchemists; // <= FOUNDPutting constants on the left side of a comparison operator like == or < is a best practice known as "Yoda conditions", which can help prevent accidental assignment instead of comparison. In some programming languages, if a variable is mistakenly put on the left with a single = instead of ==, it assigns the constant's value to the variable without any compiler error. However, doing this with the constant on the left would generate an error, as constants cannot be assigned values. Although Solidity's static typing system prevents accidental assignments within conditionals, adopting this practice enhances code readability and consistency, especially when developers are working across multiple languages that support this convention.
Num of instances: 40
Click to show findings
['26']
26: if (price <= 0) // <= FOUND['30']
30: if (updateTime == 0) // <= FOUND['365']
365: if (tokenId == 0) // <= FOUND['549']
549: if (accountIds.length == 0) // <= FOUND['780']
780: if (account.debt == 0) // <= FOUND['780']
780: if (account.debt == 0) // <= FOUND['365']
365: if (tokenId == 0) // <= FOUND['984']
984: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) // <= FOUND['984']
984: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) // <= FOUND['50']
50: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) // <= FOUND['37']
37: if (index == 0) // <= FOUND['29']
29: if (token.code.length == 0 || !success || data.length < 32) // <= FOUND['64']
64: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) // <= FOUND['173']
173: if (syntheticDepositAmount == 0) // <= FOUND['203']
203: if (position.maturationBlock == 0) // <= FOUND['44']
44: if (increment == 0) // <= FOUND['50']
50: if (ratio == 0) // <= FOUND['70']
70: if (weightDelta == 0) // <= FOUND['100']
100: if (xc >= 0x10000000000000000) // <= FOUND['101']
101: if (xc >= 0x100000000) // <= FOUND['102']
102: if (xc >= 0x10000) // <= FOUND['103']
103: if (xc >= 0x100) // <= FOUND['104']
104: if (xc >= 0x10) // <= FOUND['105']
105: if (xc >= 0x4) // <= FOUND['82']
82: if (graphSize != 0) // <= FOUND['142']
142: if ((packed&DELTA_SIGNBIT) != 0) // <= FOUND['180']
180: if ((packed&(2**(DELTA_BITS-1))) != 0) // <= FOUND['13']
13: if (y >= 2 ** 255) // <= FOUND['533']
533: if (yieldAmount > 0) // <= FOUND['564']
564: if (totalAmountLiquidated > 0) // <= FOUND['795']
795: if (account.earmarked > 0) // <= FOUND['829']
829: if (feeInYield > 0) // <= FOUND['836']
836: if (feeBonus > 0) // <= FOUND['1012']
1012: if (amount > 0) // <= FOUND['1012']
1012: if (amount > 0) // <= FOUND['836']
836: if (feeBonus > 0) // <= FOUND['238']
238: if (badDebtRatio > 1e18) // <= FOUND['70']
70: if (GRAPH_MAX > 2**32) // <= FOUND['23']
23: if (y < 0) // <= FOUND['32']
32: if (!success || data.length < 32) // <= FOUNDSuch instances can be replaced with unnamed returns
Num of instances: 6
Click to show findings
['21']
21: function ethToUSD(uint256 ethAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 usdAmount) { // <= FOUND
22:
23: AggregatorV3Interface priceFeed = AggregatorV3Interface(usdPriceFeed);
24: (, int256 price,, uint256 updateTime,) = priceFeed.latestRoundData();
25:
26: if (price <= 0) {
27: revert ChainlinkMalfunction(usdPriceFeed, price);
28: }
29:
30: if (updateTime == 0) {
31: revert IncompleteRound(usdPriceFeed, updateTime);
32: }
33:
34: if (updateTime < block.timestamp - expectedUpdateTime) {
35: revert IncompleteRound(usdPriceFeed, updateTime);
36: }
37:
38:
39: return (ethAmount * uint256(price)) / 1e20;
40: }['49']
49: function ethToUSD(uint256 ethAmount) external view returns (uint256 usdAmount) { // <= FOUND
50: return ETHUSDConverter.ethToUSD(ethAmount, priceFeed, updateTimeInSeconds);
51: }['48']
48: function usdToETH(uint256 usdAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 ethAmount) { // <= FOUND
49:
50: AggregatorV3Interface priceFeed = AggregatorV3Interface(usdPriceFeed);
51: (, int256 price,, uint256 updateTime,) = priceFeed.latestRoundData();
52:
53: if (price <= 0) {
54: revert ChainlinkMalfunction(usdPriceFeed, price);
55: }
56:
57: if (updateTime == 0) {
58: revert IncompleteRound(usdPriceFeed, updateTime);
59: }
60:
61: if (updateTime < block.timestamp - expectedUpdateTime) {
62: revert IncompleteRound(usdPriceFeed, updateTime);
63: }
64:
65:
66:
67: return (usdAmount * 1e20) / uint256(price);
68: }['44']
44: function usdToETH(uint256 usdAmount) external view returns (uint256 ethAmount) { // <= FOUND
45: return ETHUSDConverter.usdToETH(usdAmount, priceFeed, updateTimeInSeconds);
46: }['75']
75: function underlyingTokenToUSD(uint256 underlyingTokenAmount, uint256 underlyingTokenDecimals) internal pure returns (uint256 usdAmount) { // <= FOUND
76: uint256 usdConversionFactor = 10 ** (underlyingTokenDecimals - USDC_DECIMALS);
77: return underlyingTokenAmount / usdConversionFactor;
78: }['54']
54: function underlyingTokenToUSD(uint256 underlyingTokenAmount) external view returns (uint256 usdAmount) { // <= FOUND
55: return ETHUSDConverter.underlyingTokenToUSD(underlyingTokenAmount, underlyingTokenDecimals);
56: }Emitting an event within initializer functions in Solidity is a best practice for providing transparency and traceability. Initializer functions set the initial state and values of an upgradeable contract. Emitting an event during initialization allows anyone to verify and audit the initial state of the contract via the transaction logs. This can be particularly useful for verifying the parameters set during initialization, tracking the contract's deployment, and troubleshooting or debugging. Therefore, developers should include an event emission in their initializer functions, providing a clear record of the contract's initialization and enhancing the contract's transparency and security.
Num of instances: 1
Click to show findings
['155']
155: function initialize(AlchemistInitializationParams memory params) external initializer { // <= FOUND
156: _checkArgument(params.protocolFee <= BPS);
157: _checkArgument(params.liquidatorFee <= BPS);
158:
159: debtToken = params.debtToken;
160: underlyingToken = params.underlyingToken;
161: underlyingConversionFactor = 10 ** (TokenUtils.expectDecimals(params.debtToken) - TokenUtils.expectDecimals(params.underlyingToken));
162: yieldToken = params.yieldToken;
163: depositCap = params.depositCap;
164: blocksPerYear = params.blocksPerYear;
165: minimumCollateralization = params.minimumCollateralization;
166: globalMinimumCollateralization = params.globalMinimumCollateralization;
167: collateralizationLowerBound = params.collateralizationLowerBound;
168: admin = params.admin;
169: tokenAdapter = params.tokenAdapter;
170: transmuter = params.transmuter;
171: protocolFee = params.protocolFee;
172: protocolFeeReceiver = params.protocolFeeReceiver;
173: liquidatorFee = params.liquidatorFee;
174: lastEarmarkBlock = block.number;
175: lastRedemptionBlock = block.number;
176: }Make found instants CAPITAL_CASE
Num of instances: 7
Click to show findings
['29']
29: uint256 immutable updateTimeInSeconds; // <= FOUND['30']
30: uint256 immutable underlyingTokenDecimals; // <= FOUND['28']
28: address immutable priceFeed; // <= FOUND['15']
15: address public immutable underlyingToken; // <= FOUND['11']
11: string public constant version = "1.0.0"; // <= FOUND['33']
33: string public constant version = "3.0.0"; // <= FOUND['13']
13: address public immutable token; // <= FOUNDTo maintain readability in code, particularly in Solidity which can involve complex mathematical operations, it is often recommended to limit the number of arithmetic operations to a maximum of 2-3 per line. Too many operations in a single line can make the code difficult to read and understand, increase the likelihood of mistakes, and complicate the process of debugging and reviewing the code. Consider splitting such operations over more than one line, take special care when dealing with division however. Try to limit the number of arithmetic operations to a maximum of 3 per line.
Num of instances: 3
Click to show findings
['384']
384:
385: result >>= uint256 (int256 (-1 - (nx >> 120))); // <= FOUND['96']
96: update(g.g, expiration + 1, graphSize, -amount, -amount * int256(expiration)); // <= FOUND['122']
122: return ((int256(end) * endDelta) - endProd) - ((int256(start) * begDelta) - begProd); // <= FOUNDMagic numbers should be avoided in Solidity code to enhance readability, maintainability, and reduce the likelihood of errors. Magic numbers are hard-coded values with no clear meaning or context, which can create confusion and make the code harder to understand for developers. Using well-defined constants or variables with descriptive names instead of magic numbers not only clarifies the purpose and significance of the value but also simplifies code updates and modifications.
Num of instances: 148
Click to show findings
['690']
690: return (amount * ITokenAdapter(tokenAdapter).price()) / 10 ** decimals;['696']
696: return amount * 10 ** decimals / ITokenAdapter(tokenAdapter).price();['49']
49: uint256 ratio = ((total - increment) << 128) / total;['81']
81:
82:
83:
84:
85:
86:
87: return value - ((value * Exp2NegFrac(weightDelta)) >> 128);['102']
102: if (xc >= 0x10000) { xc >>= 16; msb += 16; }['108']
108: int256 result = (msb - 128) << 120;['113']
113: ux >>= 127 + b;['142']
142:
143: if (nx & 2**119 > 0)
144: result += result * 0xd413cccfe779921165f626cdd52afa89 >> 129;['144']
144: if (nx & 2**118 > 0)
145: result += result * 0xc1bf828c6dc54b7a356918c17217b7bf >> 130;['148']
148: if (nx & 2**116 > 0)
149: result += result * 0xb5586cf9890f6298b92b71842a98364f >> 132;['150']
150: if (nx & 2**115 > 0)
151: result += result * 0xb361a62b0ae875cf8a91d6d19482ffdf >> 133;['152']
152: if (nx & 2**114 > 0)
153: result += result * 0x59347cef00c1dcdef95949ef4537bd3f >> 133;['154']
154: if (nx & 2**113 > 0)
155: result += result * 0x2c7b53f6666adb094cd5c66db9bf481f >> 133;['156']
156: if (nx & 2**112 > 0)
157: result += result * 0x1635f4b5797dac2535627d823b92a89f >> 133;['158']
158: if (nx & 2**111 > 0)
159: result += result * 0xb190db43813d43fe33a5299e5ecf39f >> 133;['160']
160: if (nx & 2**110 > 0)
161: result += result * 0x58c0bc5d19d8a0da437f9134474021f >> 133;['162']
162: if (nx & 2**109 > 0)
163: result += result * 0x2c5e72080a3f425179538ab863cbc1f >> 133;['164']
164: if (nx & 2**108 > 0)
165: result += result * 0x162ebdffb8ed7471c62ce395272e4df >> 133;['166']
166: if (nx & 2**107 > 0)
167: result += result * 0xb17403f73f2dad959a9630122f87df >> 133;['168']
168: if (nx & 2**106 > 0)
169: result += result * 0x58b986fb52923a130b86918d1cf67f >> 133;['170']
170: if (nx & 2**105 > 0)
171: result += result * 0x2c5ca4bdc0a8ea88afab32a52404df >> 133;['172']
172: if (nx & 2**104 > 0)
173: result += result * 0x162e4aaeeb8080c317e9495bd07f9f >> 133;['174']
174: if (nx & 2**103 > 0)
175: result += result * 0xb17236b7935c5ddb03d36fa99f59f >> 133;['176']
176: if (nx & 2**102 > 0)
177: result += result * 0x58b913abd8d949af9159802f6f93f >> 133;['178']
178: if (nx & 2**101 > 0)
179: result += result * 0x2c5c87e9f0620c1c05b07353a2dbf >> 133;['180']
180: if (nx & 2**100 > 0)
181: result += result * 0x162e4379f933b3f121d40d222ec7f >> 133;['182']
182: if (nx & 2**99 > 0)
183: result += result * 0xb17219e3cdb2ff39429b7982c51f >> 133;['188']
188: if (nx & 2**96 > 0)
189: result += result * 0x162e4306aa2970dcdb2ddfa37fff >> 133;['190']
190: if (nx & 2**95 > 0)
191: result += result * 0xb1721816918d7cbbf08d8b65e1f >> 133;['192']
192: if (nx & 2**94 > 0)
193: result += result * 0x58b90c0398d73d284278e4e6f5f >> 133;['194']
194: if (nx & 2**93 > 0)
195: result += result * 0x2c5c85ffe06fbe715456433e0df >> 133;['196']
196: if (nx & 2**92 > 0)
197: result += result * 0x162e42ff7538e7354b033e89f3f >> 133;['198']
198: if (nx & 2**91 > 0)
199: result += result * 0xb17217f9bdcb59a7839db9047f >> 133;['200']
200: if (nx & 2**90 > 0)
201: result += result * 0x58b90bfc63e6b4d461b437ca1f >> 133;['202']
202: if (nx & 2**89 > 0)
203: result += result * 0x2c5c85fe13339c6a8373fff9ff >> 133;['206']
206: if (nx & 2**87 > 0)
207: result += result * 0xb17217f7f08f37ab5036a35bf >> 133;['208']
208: if (nx & 2**86 > 0)
209: result += result * 0x58b90bfbf097ac55c614e999f >> 133;['210']
210: if (nx & 2**85 > 0)
211: result += result * 0x2c5c85fdf65fda4aeab37b55f >> 133;['212']
212: if (nx & 2**84 > 0)
213: result += result * 0x162e42fefab4ee2d7749535ff >> 133;['214']
214: if (nx & 2**83 > 0)
215: result += result * 0xb17217f7d3bb758bc21399ff >> 133;['216']
216: if (nx & 2**82 > 0)
217: result += result * 0x58b90bfbe962bbcde2fd61bf >> 133;['218']
218: if (nx & 2**81 > 0)
219: result += result * 0x2c5c85fdf4929e28f1fbc0bf >> 133;['220']
220: if (nx & 2**80 > 0)
221: result += result * 0x162e42fefa419f24f91d299f >> 133;['224']
224: if (nx & 2**78 > 0)
225: result += result * 0x58b90bfbe8ef6cc564d28bf >> 133;['226']
226: if (nx & 2**77 > 0)
227: result += result * 0x2c5c85fdf475ca66d27119f >> 133;['228']
228: if (nx & 2**76 > 0)
229: result += result * 0x162e42fefa3a6a34713a81f >> 133;['230']
230: if (nx & 2**75 > 0)
231: result += result * 0xb17217f7d1d165a7a9dbff >> 133;['232']
232: if (nx & 2**74 > 0)
233: result += result * 0x58b90bfbe8e837d4dcefff >> 133;['234']
234: if (nx & 2**73 > 0)
235: result += result * 0x2c5c85fdf473fd2ab0787f >> 133;['236']
236: if (nx & 2**72 > 0)
237: result += result * 0x162e42fefa39f6e568bc5f >> 133;['238']
238: if (nx & 2**71 > 0)
239: result += result * 0xb17217f7d1cf986b87e3f >> 133;['240']
240: if (nx & 2**70 > 0)
241: result += result * 0x58b90bfbe8e7c485d471f >> 133;['242']
242: if (nx & 2**69 > 0)
243: result += result * 0x2c5c85fdf473e056ee59f >> 133;['244']
244: if (nx & 2**68 > 0)
245: result += result * 0x162e42fefa39efb07835f >> 133;['246']
246: if (nx & 2**67 > 0)
247: result += result * 0xb17217f7d1cf7b97c5df >> 133;['248']
248: if (nx & 2**66 > 0)
249: result += result * 0x58b90bfbe8e7bd50e3ff >> 133;['250']
250: if (nx & 2**65 > 0)
251: result += result * 0x2c5c85fdf473de89b23f >> 133;['252']
252: if (nx & 2**64 > 0)
253: result += result * 0x162e42fefa39ef3d293f >> 133;['254']
254: if (nx & 2**63 > 0)
255: result += result * 0xb17217f7d1cf79ca89f >> 133;['256']
256: if (nx & 2**62 > 0)
257: result += result * 0x58b90bfbe8e7bcdd95f >> 133;['258']
258: if (nx & 2**61 > 0)
259: result += result * 0x2c5c85fdf473de6cdff >> 133;['260']
260: if (nx & 2**60 > 0)
261: result += result * 0x162e42fefa39ef35f5f >> 133;['262']
262: if (nx & 2**59 > 0)
263: result += result * 0xb17217f7d1cf79adbf >> 133;['264']
264: if (nx & 2**58 > 0)
265: result += result * 0x58b90bfbe8e7bcd65f >> 133;['266']
266: if (nx & 2**57 > 0)
267: result += result * 0x2c5c85fdf473de6b1f >> 133;['268']
268: if (nx & 2**56 > 0)
269: result += result * 0x162e42fefa39ef359f >> 133;['270']
270: if (nx & 2**55 > 0)
271: result += result * 0xb17217f7d1cf79abf >> 133;['272']
272: if (nx & 2**54 > 0)
273: result += result * 0x58b90bfbe8e7bcd5f >> 133;['274']
274: if (nx & 2**53 > 0)
275: result += result * 0x2c5c85fdf473de6bf >> 133;['276']
276: if (nx & 2**52 > 0)
277: result += result * 0x162e42fefa39ef35f >> 133;['278']
278: if (nx & 2**51 > 0)
279: result += result * 0xb17217f7d1cf79bf >> 133;['280']
280: if (nx & 2**50 > 0)
281: result += result * 0x58b90bfbe8e7bcdf >> 133;['282']
282: if (nx & 2**49 > 0)
283: result += result * 0x2c5c85fdf473de7f >> 133;['284']
284: if (nx & 2**48 > 0)
285: result += result * 0x162e42fefa39ef3f >> 133;['286']
286: if (nx & 2**47 > 0)
287: result += result * 0xb17217f7d1cf79f >> 133;['288']
288: if (nx & 2**46 > 0)
289: result += result * 0x58b90bfbe8e7bdf >> 133;['290']
290: if (nx & 2**45 > 0)
291: result += result * 0x2c5c85fdf473dff >> 133;['292']
292: if (nx & 2**44 > 0)
293: result += result * 0x162e42fefa39eff >> 133;['294']
294: if (nx & 2**43 > 0)
295: result += result * 0xb17217f7d1cf7f >> 133;['296']
296: if (nx & 2**42 > 0)
297: result += result * 0x58b90bfbe8e7bf >> 133;['298']
298: if (nx & 2**41 > 0)
299: result += result * 0x2c5c85fdf473df >> 133;['300']
300: if (nx & 2**40 > 0)
301: result += result * 0x162e42fefa39ff >> 133;['302']
302: if (nx & 2**39 > 0)
303: result += result * 0xb17217f7d1cff >> 133;['304']
304: if (nx & 2**38 > 0)
305: result += result * 0x58b90bfbe8e7f >> 133;['306']
306: if (nx & 2**37 > 0)
307: result += result * 0x2c5c85fdf473f >> 133;['308']
308: if (nx & 2**36 > 0)
309: result += result * 0x162e42fefa39f >> 133;['310']
310: if (nx & 2**35 > 0)
311: result += result * 0xb17217f7d1df >> 133;['312']
312: if (nx & 2**34 > 0)
313: result += result * 0x58b90bfbe8ff >> 133;['314']
314: if (nx & 2**33 > 0)
315: result += result * 0x2c5c85fdf47f >> 133;['316']
316: if (nx & 2**32 > 0)
317: result += result * 0x162e42fefa3f >> 133;['318']
318: if (nx & 2**31 > 0)
319: result += result * 0xb17217f7d1f >> 133;['320']
320: if (nx & 2**30 > 0)
321: result += result * 0x58b90bfbe9f >> 133;['322']
322: if (nx & 2**29 > 0)
323: result += result * 0x2c5c85fdf5f >> 133;['324']
324: if (nx & 2**28 > 0)
325: result += result * 0x162e42fefbf >> 133;['326']
326: if (nx & 2**27 > 0)
327: result += result * 0xb17217f7df >> 133;['328']
328: if (nx & 2**26 > 0)
329: result += result * 0x58b90bfbff >> 133;['330']
330: if (nx & 2**25 > 0)
331: result += result * 0x2c5c85fdff >> 133;['332']
332: if (nx & 2**24 > 0)
333: result += result * 0x162e42feff >> 133;['334']
334: if (nx & 2**23 > 0)
335: result += result * 0xb17217f7f >> 133;['336']
336: if (nx & 2**22 > 0)
337: result += result * 0x58b90bfbf >> 133;['338']
338: if (nx & 2**21 > 0)
339: result += result * 0x2c5c85fdf >> 133;['340']
340: if (nx & 2**20 > 0)
341: result += result * 0x162e42fff >> 133;['342']
342: if (nx & 2**19 > 0)
343: result += result * 0xb17217ff >> 133;['344']
344: if (nx & 2**18 > 0)
345: result += result * 0x58b90bff >> 133;['346']
346: if (nx & 2**17 > 0)
347: result += result * 0x2c5c85ff >> 133;['348']
348: if (nx & 2**16 > 0)
349: result += result * 0x162e42ff >> 133;['350']
350: if (nx & 2**15 > 0)
351: result += result * 0xb17217f >> 133;['352']
352: if (nx & 2**14 > 0)
353: result += result * 0x58b90bf >> 133;['354']
354: if (nx & 2**13 > 0)
355: result += result * 0x2c5c85f >> 133;['356']
356: if (nx & 2**12 > 0)
357: result += result * 0x162e43f >> 133;['358']
358: if (nx & 2**11 > 0)
359: result += result * 0xb1721f >> 133;['360']
360: if (nx & 2**10 > 0)
361: result += result * 0x58b91f >> 133;['362']
362: if (nx & 2**9 > 0)
363: result += result * 0x2c5c9f >> 133;['364']
364: if (nx & 2**8 > 0)
365: result += result * 0x162e5f >> 133;['366']
366: if (nx & 2**7 > 0)
367: result += result * 0xb173f >> 133;['368']
368: if (nx & 2**6 > 0)
369: result += result * 0x58b9f >> 133;['370']
370: if (nx & 2**5 > 0)
371: result += result * 0x2c5df >> 133;['372']
372: if (nx & 2**4 > 0)
373: result += result * 0x162ff >> 133;['374']
374: if (nx & 2**3 > 0)
375: result += result * 0xb17f >> 133;['376']
376: if (nx & 2**2 > 0)
377: result += result * 0x58bf >> 133;['378']
378: if (nx & 2**1 > 0)
379: result += result * 0x2c5f >> 133;['380']
380: if (nx & 2**0 > 0)
381: result += result * 0x163f >> 133;['384']
384:
385: result >>= uint256 (int256 (-1 - (nx >> 120)));['69']
69: newSize |= newSize >> 16;['73']
73: newSize |= newSize >> 128;['236']
236:
237:
238: uint256 badDebtRatio = alchemist.totalSyntheticsIssued() * 10**TokenUtils.expectDecimals(alchemist.yieldToken()) / alchemist.getTotalUnderlyingValue();['94']
94: if (x >= 2**128) return 0;['105']
105: if (xc >= 0x4) { xc >>= 2; msb += 2; }['112']
112: uint256 b = ux >> 255;['146']
146: if (nx & 2**117 > 0)
147: result += result * 0xb95c1e3ea8bd6e6fbe4628758a53c90f >> 131;['184']
184: if (nx & 2**98 > 0)
185: result += result * 0x58b90c76e7e02c8d1ed758b9459f >> 133;['186']
186: if (nx & 2**97 > 0)
187: result += result * 0x2c5c861cb431ec233c78045054bf >> 133;['204']
204: if (nx & 2**88 > 0)
205: result += result * 0x162e42ff01e9deb55bb48aaa9f >> 133;['222']
222: if (nx & 2**79 > 0)
223: result += result * 0xb17217f7d1ee3969c9667df >> 133;['13']
13: if (y >= 2 ** 255) {['62']
62:
63:
64:
65: uint256 newSize = expiration + 2;['66']
66: newSize |= newSize >> 2;['70']
70: if (GRAPH_MAX > 2**32) {['101']
101: if (xc >= 0x100000000) { xc >>= 32; msb += 32; }['32']
32: if (!success || data.length < 32) {['71']
71: newSize |= newSize >> 32;['29']
29: if (token.code.length == 0 || !success || data.length < 32) {['104']
104: if (xc >= 0x10) { xc >>= 4; msb += 4; }['67']
67: newSize |= newSize >> 4;['100']
100: if (xc >= 0x10000000000000000) { xc >>= 64; msb += 64; }['72']
72: newSize |= newSize >> 64;['103']
103: if (xc >= 0x100) { xc >>= 8; msb += 8; }['68']
68: newSize |= newSize >> 8;Num of instances: 2
Click to show findings
['530']
530: function liquidate(uint256 accountId) external override returns (uint256 yieldAmount, uint256 feeInYield, uint256 feeInUnderlying) { // <= FOUND
531: _checkForValidAccountId(accountId);
532: (yieldAmount, feeInYield, feeInUnderlying) = _liquidate(accountId);
533: if (yieldAmount > 0) {
534: emit Liquidated(accountId, msg.sender, yieldAmount, feeInYield, feeInUnderlying);
535: return (yieldAmount, feeInYield, feeInUnderlying);
536: } else {
537:
538: revert LiquidationError();
539: }
540: }[]
543: function batchLiquidate(uint256[] memory accountIds)
544: external
545: returns (uint256 totalAmountLiquidated, uint256 totalFeesInYield, uint256 totalFeesInUnderlying)
546: {
547:
548:
549: if (accountIds.length == 0) {
550: revert MissingInputData();
551: }
552:
553: for (uint256 i = 0; i < accountIds.length; i++) {
554: uint256 accountId = accountIds[i];
555: if (accountId == 0 || !_tokenExists(alchemistPositionNFT, accountId)) {
556: continue;
557: }
558: (uint256 underlyingAmount, uint256 feeInYield, uint256 feeInUnderlying) = _liquidate(accountId);
559: totalAmountLiquidated += underlyingAmount;
560: totalFeesInYield += feeInYield;
561: totalFeesInUnderlying += feeInUnderlying;
562: }
563:
564: if (totalAmountLiquidated > 0) {
565: return (totalAmountLiquidated, totalFeesInYield, totalFeesInUnderlying);
566: } else {
567:
568: revert LiquidationError();
569: }
570: }It's crucial to leverage the right features for the appropriate contexts in Solidity, despite the compiler's ability to correct common developer mistakes. Both constant and immutable variables have distinct uses. Constant variables are best suited for hard-coded, literal values within your contract, where the value doesn't need to be computed or modified.
On the other hand, immutable variables are ideal for situations where the value might be the result of an expression or received from a constructor. While both serve to define unchanging variables, they function differently and their proper utilization contributes to clearer, more efficient code. Remember, even if the compiler can correct certain mistakes, best practices dictate using the correct feature for the task at hand.
Num of instances: 2
Click to show findings
['22']
22: bytes32 public constant ADMIN_ROLE = keccak256("ADMIN"); // <= FOUND['25']
25: bytes32 public constant SENTINEL_ROLE = keccak256("SENTINEL"); // <= FOUNDEvents in Solidity offer valuable insight into the contract's execution as they log specific instances of state changes or value transfers. However, if events do not include any parameters, their usefulness can be significantly reduced. Events without parameters can provide limited information, mainly signaling that a specific operation occurred, but lacking the context of what exactly changed. It's generally recommended to include relevant parameters, such as state changes or value modifications, in the emitted events.
Num of instances: 1
If these serve no purpose, they should be safely removed
Num of instances: 1
Click to show findings
['9']
9: struct StrategyParams { // <= FOUND
10: uint256 performanceFee;
11: uint256 activation;
12: uint256 debtRatio;
13: uint256 minDebtPerHarvest;
14: uint256 maxDebtPerHarvest;
15: uint256 lastReport;
16: uint256 totalDebt;
17: uint256 totalGain;
18: uint256 totalLoss;
19: bool enforceChangeLimit;
20: uint256 profitLimitRatio;
21: uint256 lossLimitRatio;
22: address customCheck;
23: }In Solidity programming, duplicate code in multiple functions introduces potential security risks and maintainability issues. It magnifies the impact of any bugs or vulnerabilities, since developers must fix these in every location where the code is replicated. Overlooking any instance of replicated code could leave vulnerabilities intact. Furthermore, code duplication leads to contract bloating, diminishing the readability and manageability of the code. Future logic changes would need to be applied in every location where the code is replicated, increasing the complexity of updates. To resolve this, it is recommended to consolidate duplicate code into a single internal function, and replace the duplicate instances with calls to this new function. This approach streamlines the code, reducing the attack surface and making it easier to maintain and update.
Num of instances: 1
Click to show findings
['211']
211: function acceptAdmin() external {
212: _checkState(pendingAdmin != address(0)); // <= FOUND
213:
214: if (msg.sender != pendingAdmin) {
215: revert Unauthorized();
216: }
217:
218: admin = pendingAdmin;
219: pendingAdmin = address(0);
220:
221: emit AdminUpdated(admin);
222: emit PendingAdminUpdated(address(0));
223: }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: 1
Click to show findings
['93']
93: function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721Enumerable) returns (bool) {
94: return super.supportsInterface(interfaceId);
95: }In Solidity, when designing functions that return boolean values, it's crucial for clarity and maintainability to explicitly handle both true and false return scenarios. If a function is intended to return true under certain conditions, it should also explicitly return false when these conditions are not met, and vice versa. This approach eliminates ambiguity and makes the code's intent more transparent. Explicitly handling all possible outcomes of a boolean function ensures that future modifications or extensions of the contract do not unintentionally alter its logic. It contributes to better readability, easier debugging, and reduces the risk of bugs related to unintended fall-through cases.
Num of instances: 1
Click to show findings
['942']
942: function _tokenExists(address nft, uint256 tokenId) internal view returns (bool exists) {
943: if (tokenId == 0) {
944:
945: return false; // <= FOUND
946: }
947: try IERC721(nft).ownerOf(tokenId) {
948:
949: exists = true;
950: } catch {
951:
952: exists = false;
953: }
954: }Using the .call method in Solidity enables direct communication with an address, bypassing function existence checks, type checking, and argument packing. While this can save gas and provide flexibility, it can also introduce security risks and potential errors. The absence of these checks can lead to unexpected behavior if the callee contract's interface changes or if the input parameters are not crafted with care. The resolution to these issues is to use Solidity's high-level interface for calling functions when possible, as it automatically manages these aspects. If using .call is necessary, ensure that the inputs are carefully validated and that awareness of the called contract's behavior is maintained.
Num of instances: 6
Click to show findings
['62']
62: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, recipient, amount)); // <= FOUND['77']
77: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, value)); // <= FOUND['93']
93: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, owner, recipient, amount)); // <= FOUND['108']
108: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Mintable.mint.selector, recipient, amount)); // <= FOUND['122']
122: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Burnable.burn.selector, amount)); // <= FOUND['137']
137: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Burnable.burnFrom.selector, owner, amount)); // <= FOUNDCyclomatic 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: 10
Click to show findings
['21']
21: function ethToUSD(uint256 ethAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 usdAmount) { // <= FOUND
22:
23: AggregatorV3Interface priceFeed = AggregatorV3Interface(usdPriceFeed);
24: (, int256 price,, uint256 updateTime,) = priceFeed.latestRoundData();
25:
26: if (price <= 0) {
27: revert ChainlinkMalfunction(usdPriceFeed, price);
28: }
29:
30: if (updateTime == 0) {
31: revert IncompleteRound(usdPriceFeed, updateTime);
32: }
33:
34: if (updateTime < block.timestamp - expectedUpdateTime) {
35: revert IncompleteRound(usdPriceFeed, updateTime);
36: }
37:
38:
39: return (ethAmount * uint256(price)) / 1e20;
40: }['48']
48: function usdToETH(uint256 usdAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 ethAmount) { // <= FOUND
49:
50: AggregatorV3Interface priceFeed = AggregatorV3Interface(usdPriceFeed);
51: (, int256 price,, uint256 updateTime,) = priceFeed.latestRoundData();
52:
53: if (price <= 0) {
54: revert ChainlinkMalfunction(usdPriceFeed, price);
55: }
56:
57: if (updateTime == 0) {
58: revert IncompleteRound(usdPriceFeed, updateTime);
59: }
60:
61: if (updateTime < block.timestamp - expectedUpdateTime) {
62: revert IncompleteRound(usdPriceFeed, updateTime);
63: }
64:
65:
66:
67: return (usdAmount * 1e20) / uint256(price);
68: }[]
543: function batchLiquidate(uint256[] memory accountIds)
544: external
545: returns (uint256 totalAmountLiquidated, uint256 totalFeesInYield, uint256 totalFeesInUnderlying)
546: {
547:
548:
549: if (accountIds.length == 0) {
550: revert MissingInputData();
551: }
552:
553: for (uint256 i = 0; i < accountIds.length; i++) {
554: uint256 accountId = accountIds[i];
555: if (accountId == 0 || !_tokenExists(alchemistPositionNFT, accountId)) {
556: continue;
557: }
558: (uint256 underlyingAmount, uint256 feeInYield, uint256 feeInUnderlying) = _liquidate(accountId);
559: totalAmountLiquidated += underlyingAmount;
560: totalFeesInYield += feeInYield;
561: totalFeesInUnderlying += feeInUnderlying;
562: }
563:
564: if (totalAmountLiquidated > 0) {
565: return (totalAmountLiquidated, totalFeesInYield, totalFeesInUnderlying);
566: } else {
567:
568: revert LiquidationError();
569: }
570: }[]
631: function calculateLiquidation(
632: uint256 collateral,
633: uint256 debt,
634: uint256 targetCollateralization,
635: uint256 alchemistCurrentCollateralization,
636: uint256 alchemistMinimumCollateralization,
637: uint256 feeBps
638: ) public pure returns (uint256 grossCollateralToSeize, uint256 debtToBurn, uint256 fee) {
639: if (debt >= collateral) {
640:
641: return (debt, debt, 0);
642: }
643:
644: if (alchemistCurrentCollateralization < alchemistMinimumCollateralization) {
645:
646: return (debt, debt, 0);
647: }
648:
649:
650: uint256 surplus = collateral > debt ? collateral - debt : 0;
651: fee = (surplus * feeBps) / BPS;
652:
653:
654: uint256 adjCollat = collateral - fee;
655:
656:
657: uint256 md = (targetCollateralization * debt) / FIXED_POINT_SCALAR;
658:
659:
660: if (md <= adjCollat) {
661: return (0, 0, fee);
662: }
663:
664:
665: uint256 num = md - adjCollat;
666:
667:
668: uint256 denom = targetCollateralization - FIXED_POINT_SCALAR;
669:
670:
671: debtToBurn = (num * FIXED_POINT_SCALAR) / denom;
672:
673:
674: grossCollateralToSeize = debtToBurn + fee;
675: }['771']
771: function _liquidate(uint256 accountId) internal returns (uint256 debtAmount, uint256 feeInYield, uint256 feeInUnderlying) { // <= FOUND
772:
773: _earmark();
774:
775: _sync(accountId);
776:
777: Account storage account = _accounts[accountId];
778:
779:
780: if (account.debt == 0) {
781: return (0, 0, 0);
782: }
783:
784:
785: uint256 collateralInUnderlying = totalValue(accountId);
786: uint256 collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
787:
788:
789: if (collateralizationRatio > collateralizationLowerBound) {
790: return (0, 0, 0);
791: }
792:
793:
794: uint256 repaidAmountInYield = 0;
795: if (account.earmarked > 0) {
796: repaidAmountInYield = _forceRepay(accountId, convertDebtTokensToYield(account.earmarked));
797: }
798:
799: if (account.debt == 0) {
800: return (repaidAmountInYield, 0, 0);
801: }
802:
803:
804: collateralInUnderlying = totalValue(accountId);
805: collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
806:
807: if (collateralizationRatio <= collateralizationLowerBound) {
808: uint256 alchemistCurrentCollateralization = normalizeUnderlyingTokensToDebt(_getTotalUnderlyingValue()) * FIXED_POINT_SCALAR / totalDebt;
809:
810: (uint256 liquidationAmount, uint256 debtToBurn, uint256 baseFee) = calculateLiquidation(
811: collateralInUnderlying, account.debt, minimumCollateralization, alchemistCurrentCollateralization, globalMinimumCollateralization, liquidatorFee
812: );
813:
814: uint256 feeBonus = debtToBurn * liquidatorFee / BPS;
815: uint256 adjustedLiquidationAmount = convertDebtTokensToYield(liquidationAmount);
816: uint256 adjustedDebtToBurn = convertDebtTokensToYield(debtToBurn);
817: debtAmount = adjustedLiquidationAmount;
818: feeInYield = convertDebtTokensToYield(baseFee);
819:
820:
821: account.collateralBalance = account.collateralBalance > adjustedLiquidationAmount ? account.collateralBalance - adjustedLiquidationAmount : 0;
822:
823:
824: _subDebt(accountId, debtToBurn);
825:
826:
827: TokenUtils.safeTransfer(yieldToken, transmuter, adjustedDebtToBurn);
828:
829: if (feeInYield > 0) {
830:
831: TokenUtils.safeTransfer(yieldToken, msg.sender, feeInYield);
832: }
833:
834:
835:
836: if (feeBonus > 0) {
837: uint256 vaultBalance = IFeeVault(alchemistFeeVault).totalDeposits();
838: if (vaultBalance > 0) {
839: feeInUnderlying = vaultBalance > feeBonus ? feeBonus : vaultBalance;
840: IFeeVault(alchemistFeeVault).withdraw(msg.sender, feeInUnderlying);
841: }
842: }
843: }
844:
845: return (debtAmount + repaidAmountInYield, feeInYield, feeInUnderlying);
846: }['1028']
1028: function _calculateUnrealizedDebt(uint256 tokenId) internal view returns (uint256, uint256, uint256) { // <= FOUND
1029: Account storage account = _accounts[tokenId];
1030: RedemptionInfo memory previousRedemption = _redemptions[lastRedemptionBlock];
1031:
1032: uint256 amount;
1033: uint256 earmarkWeightCopy = _earmarkWeight;
1034:
1035:
1036: if (block.number > lastEarmarkBlock) {
1037: amount = ITransmuter(transmuter).queryGraph(lastEarmarkBlock + 1, block.number);
1038: if (amount > 0) {
1039: earmarkWeightCopy += PositionDecay.WeightIncrement(amount, totalDebt - cumulativeEarmarked);
1040: }
1041: }
1042:
1043: uint256 debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, earmarkWeightCopy - account.lastAccruedEarmarkWeight);
1044: uint256 earmarkedState = account.earmarked + debtToEarmark;
1045:
1046:
1047: uint256 earmarkedPreviousState;
1048: uint256 earmarkToRedeem;
1049: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) {
1050: debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, previousRedemption.earmarkWeight - account.lastAccruedEarmarkWeight);
1051:
1052: earmarkedPreviousState = account.earmarked + debtToEarmark;
1053: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight);
1054: } else {
1055: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedState, _redemptionWeight - account.lastAccruedRedemptionWeight);
1056: }
1057:
1058: uint256 collateralToRemove = PositionDecay.ScaleByWeightDelta(account.rawLocked, _collateralWeight - account.lastCollateralWeight);
1059:
1060: return (account.debt - earmarkToRedeem, earmarkedState - earmarkToRedeem, account.collateralBalance - collateralToRemove);
1061: }['92']
92: function Log2NegFrac(uint256 x) private pure returns (uint256) { // <= FOUND
93: unchecked {
94: if (x >= 2**128) return 0;
95:
96: require(x > 0);
97:
98: int256 msb = 0;
99: uint256 xc = x;
100: if (xc >= 0x10000000000000000) { xc >>= 64; msb += 64; }
101: if (xc >= 0x100000000) { xc >>= 32; msb += 32; }
102: if (xc >= 0x10000) { xc >>= 16; msb += 16; }
103: if (xc >= 0x100) { xc >>= 8; msb += 8; }
104: if (xc >= 0x10) { xc >>= 4; msb += 4; }
105: if (xc >= 0x4) { xc >>= 2; msb += 2; }
106: if (xc >= 0x2) msb += 1;
107:
108: int256 result = (msb - 128) << 120;
109: uint256 ux = uint256(x) << uint256(127 - msb);
110: for (int256 bit = 0x800000000000000000000000000000; bit > 0; bit >>= 1) {
111: ux *= ux;
112: uint256 b = ux >> 255;
113: ux >>= 127 + b;
114: result += bit * int256 (b);
115: }
116:
117: return uint256(-result);
118: }
119: }['50']
50: function addStake(Graph storage g, int256 amount, uint256 start, uint256 duration) internal { // <= FOUND
51: unchecked {
52: require(amount <= DELTA_MAX && amount >= DELTA_MIN);
53: require(start < GRAPH_MAX-1);
54:
55: uint256 expiration = start + duration;
56: require(expiration < GRAPH_MAX-1);
57:
58: uint256 graphSize = g.size;
59:
60:
61:
62: uint256 newSize = expiration + 2;
63: if (newSize >= graphSize) {
64:
65: newSize |= newSize >> 1;
66: newSize |= newSize >> 2;
67: newSize |= newSize >> 4;
68: newSize |= newSize >> 8;
69: newSize |= newSize >> 16;
70: if (GRAPH_MAX > 2**32) {
71: newSize |= newSize >> 32;
72: newSize |= newSize >> 64;
73: newSize |= newSize >> 128;
74: }
75: newSize++;
76:
77:
78:
79:
80: require (newSize <= GRAPH_MAX);
81:
82: if (graphSize != 0) {
83:
84: uint256 copy = g.g[graphSize];
85: while (graphSize <= newSize) {
86: g.g[graphSize] = copy;
87: graphSize += graphSize;
88: }
89: }
90: graphSize = newSize;
91: g.size = newSize;
92: }
93:
94:
95: update(g.g, start + 1, graphSize, amount, amount * int256(start));
96: update(g.g, expiration + 1, graphSize, -amount, -amount * int256(expiration));
97: }
98: }['172']
172: function createRedemption(uint256 syntheticDepositAmount) external { // <= FOUND
173: if (syntheticDepositAmount == 0) {
174: revert DepositZeroAmount();
175: }
176:
177: if (totalLocked + syntheticDepositAmount > depositCap) {
178: revert DepositCapReached();
179: }
180:
181: if (totalLocked + syntheticDepositAmount > alchemist.totalSyntheticsIssued()) {
182: revert DepositCapReached();
183: }
184:
185: TokenUtils.safeTransferFrom(syntheticToken, msg.sender, address(this), syntheticDepositAmount);
186:
187: _positions[++_nonce] = StakingPosition(syntheticDepositAmount, block.number, block.number + timeToTransmute);
188:
189:
190: _updateStakingGraph(syntheticDepositAmount.toInt256() * BLOCK_SCALING_FACTOR / timeToTransmute.toInt256(), timeToTransmute);
191:
192: totalLocked += syntheticDepositAmount;
193:
194: _mint(msg.sender, _nonce);
195:
196: emit PositionCreated(msg.sender, syntheticDepositAmount, _nonce);
197: }['200']
200: function claimRedemption(uint256 id) external { // <= FOUND
201: StakingPosition storage position = _positions[id];
202:
203: if (position.maturationBlock == 0) {
204: revert PositionNotFound();
205: }
206:
207: uint256 transmutationTime = position.maturationBlock - position.startBlock;
208: uint256 blocksLeft = position.maturationBlock > block.number ? position.maturationBlock - block.number : 0;
209: uint256 amountNottransmuted = blocksLeft > 0 ? position.amount * blocksLeft / transmutationTime : 0;
210: uint256 amountTransmuted = position.amount - amountNottransmuted;
211:
212: if (_requireOwned(id) != msg.sender) {
213: revert CallerNotOwner();
214: }
215:
216:
217: _burn(id);
218:
219:
220: uint256 yieldTokenBalance = TokenUtils.safeBalanceOf(alchemist.yieldToken(), address(this));
221: uint256 debtValue = alchemist.convertYieldTokensToDebt(yieldTokenBalance);
222: uint256 amountToRedeem = amountTransmuted > debtValue ? amountTransmuted - debtValue : 0;
223: if (amountToRedeem > 0) alchemist.redeem(amountToRedeem);
224:
225: uint256 feeAmount = amountTransmuted * transmutationFee / BPS;
226: uint256 claimAmount = amountTransmuted - feeAmount;
227:
228: uint256 syntheticFee = amountNottransmuted * exitFee / BPS;
229: uint256 syntheticReturned = amountNottransmuted - syntheticFee;
230:
231:
232: if (blocksLeft > 0) _updateStakingGraph(-position.amount.toInt256() * BLOCK_SCALING_FACTOR / transmutationTime.toInt256(), blocksLeft);
233:
234:
235:
236: uint256 badDebtRatio = alchemist.totalSyntheticsIssued() * 10**TokenUtils.expectDecimals(alchemist.yieldToken()) / alchemist.getTotalUnderlyingValue();
237:
238: if (badDebtRatio > 1e18) {
239: claimAmount = claimAmount * FIXED_POINT_SCALAR / badDebtRatio;
240: feeAmount = feeAmount * FIXED_POINT_SCALAR / badDebtRatio;
241: }
242:
243: TokenUtils.safeTransfer(alchemist.yieldToken(), msg.sender, alchemist.convertDebtTokensToYield(claimAmount));
244: TokenUtils.safeTransfer(alchemist.yieldToken(), protocolFeeReceiver, alchemist.convertDebtTokensToYield(feeAmount));
245:
246: TokenUtils.safeTransfer(syntheticToken, msg.sender, syntheticReturned);
247: TokenUtils.safeTransfer(syntheticToken, protocolFeeReceiver, syntheticFee);
248:
249:
250: TokenUtils.safeBurn(syntheticToken, amountTransmuted);
251:
252: totalLocked -= position.amount;
253:
254: emit PositionClaimed(msg.sender, claimAmount, syntheticReturned);
255:
256: delete _positions[id];
257: }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: 2
Click to show findings
['81']
81: function withdraw(address recipient, uint256 amount) external override onlyAuthorized nonReentrant // <= FOUND['37']
37: function withdraw(address recipient, uint256 amount) external override onlyAuthorized // <= FOUNDIf these serve no purpose, they should be safely removed
Num of instances: 1
Click to show findings
['39']
39: event AlchemistPaused(address alchemistAddress, bool isPaused); // <= FOUNDIf these serve no purpose, they should be safely removed
Num of instances: 5
Click to show findings
['8']
8: import {IAlchemistETHVault} from "./interfaces/IAlchemistETHVault.sol"; // <= FOUND['19']
19: import {IAlchemistTokenVault} from "./interfaces/IAlchemistTokenVault.sol"; // <= FOUND['8']
8: import {IDetailedERC20} from "./interfaces/IDetailedERC20.sol"; // <= FOUND['4']
4: import {IERC721Enumerable} from "../interfaces/IERC721Enumerable.sol"; // <= FOUND['15']
15: import {Unauthorized, IllegalArgument, IllegalState, InsufficientAllowance} from "./base/Errors.sol"; // <= FOUNDUnchecked 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: 3
Click to show findings
['51']
51: unchecked {
52: require(amount <= DELTA_MAX && amount >= DELTA_MIN);
53: require(start < GRAPH_MAX-1);
54:
55: uint256 expiration = start + duration;
56: require(expiration < GRAPH_MAX-1);
57:
58: uint256 graphSize = g.size;
59:
60:
61:
62: uint256 newSize = expiration + 2;
63: if (newSize >= graphSize) {
64:
65: newSize |= newSize >> 1;
66: newSize |= newSize >> 2;
67: newSize |= newSize >> 4;
68: newSize |= newSize >> 8;
69: newSize |= newSize >> 16;
70: if (GRAPH_MAX > 2**32) {
71: newSize |= newSize >> 32;
72: newSize |= newSize >> 64;
73: newSize |= newSize >> 128;
74: }
75: newSize++;
76:
77:
78:
79:
80: require (newSize <= GRAPH_MAX);
81:
82: if (graphSize != 0) {
83:
84: uint256 copy = g.g[graphSize];
85: while (graphSize <= newSize) {
86: g.g[graphSize] = copy;
87: graphSize += graphSize;
88: }
89: }
90: graphSize = newSize;
91: g.size = newSize;
92: }
93:
94:
95: update(g.g, start + 1, graphSize, amount, amount * int256(start));
96: update(g.g, expiration + 1, graphSize, -amount, -amount * int256(expiration));
97: }['133']
133: unchecked {
134: index += 1; // <= FOUND
135: while (index <= treeSize) {
136:
137: uint256 packed = graph[index];
138: int256 ad;
139: int256 ap;
140:
141:
142: if ((packed&DELTA_SIGNBIT) != 0) {
143: ad = int256(packed | ~DELTA_MASK);
144: } else {
145: ad = int256(packed & DELTA_MASK);
146: }
147: ap = int256(packed)>>DELTA_BITS;
148:
149: ad+=delta;
150: ap+=deltaProd;
151:
152:
153: require(ad <= DELTA_MAX && ad >= DELTA_MIN);
154: require(ap <= PRODUCT_MAX && ap >= PRODUCT_MIN);
155: graph[index] = (uint256(ad)&DELTA_MASK)|uint256(ap<<DELTA_BITS);
156:
157: assembly {
158: index := add(index, and(index, sub(0, index)))
159: }
160: }
161: }['171']
171: unchecked {
172: index += 1; // <= FOUND
173: while (index > 0) {
174:
175: uint256 packed = graph[index];
176: int256 ad;
177: int256 ap;
178:
179:
180: if ((packed&(2**(DELTA_BITS-1))) != 0) {
181: ad = int256(packed | ~DELTA_MASK);
182: } else {
183: ad = int256(packed & DELTA_MASK);
184: }
185: ap = int256(packed)>>DELTA_BITS;
186:
187: sum += ad;
188: sumProd += ap;
189:
190: assembly {
191: index := sub(index, and(index, sub(0, index)))
192: }
193: }
194: }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
Click to show findings
['113']
113: unchecked {
114: require (end <= GRAPH_MAX);
115:
116: start--;
117: require (start <= GRAPH_MAX);
118:
119: (begDelta,begProd) = query(g.g, start);
120: (endDelta,endProd) = query(g.g, end);
121:
122: return ((int256(end) * endDelta) - endProd) - ((int256(start) * begDelta) - begProd);
123: }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: 11
Click to show findings
['516']
516:
517: account.collateralBalance -= creditToYield * protocolFee / BPS;['580']
580: uint256 feeCollateral = collRedeemed * protocolFee / BPS;['786']
786: uint256 collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;['805']
805: collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;['814']
814: uint256 feeBonus = debtToBurn * liquidatorFee / BPS;['1095']
1095: liquidationAmount = collateralDiff * FIXED_POINT_SCALAR / ratioDiff;['209']
209: uint256 amountNottransmuted = blocksLeft > 0 ? position.amount * blocksLeft / transmutationTime : 0;['225']
225: uint256 feeAmount = amountTransmuted * transmutationFee / BPS;['228']
228: uint256 syntheticFee = amountNottransmuted * exitFee / BPS;['239']
239: claimAmount = claimAmount * FIXED_POINT_SCALAR / badDebtRatio;['240']
240: feeAmount = feeAmount * FIXED_POINT_SCALAR / badDebtRatio;[NonCritical-50] A event should be emitted if a non immutable state variable is set in a constructor
Num of instances: 3
Click to show findings
['52']
52: constructor(address alchemist_) ERC721("AlchemistV3Position", "ALCV3") {
53: if (alchemist_ == address(0)) {
54: revert AlchemistZeroAddressError();
55: }
56: alchemist = alchemist_; // <= FOUND
57: }['83']
83: constructor(ITransmuter.TransmuterInitializationParams memory params) ERC721("Alchemix V3 Transmuter", "TRNSMTR") {
84: syntheticToken = params.syntheticToken;
85: timeToTransmute = params.timeToTransmute;
86: transmutationFee = params.transmutationFee;
87: exitFee = params.exitFee;
88: protocolFeeReceiver = params.feeReceiver;
89: admin = msg.sender; // <= FOUND
90: graphSize = params.graphSize;
91: }['17']
17: constructor(address _token, address _underlyingToken) {
18: token = _token;
19: underlyingToken = _underlyingToken; // <= FOUND
20: }Non-constant or non-immutable state variables lacking a setter function can create inflexibility in contract operations. If there's no way to update these variables post-deployment, the contract might not adapt to changing conditions or requirements, which can be a significant drawback, especially in upgradable or long-lived contracts. To resolve this, implement setter functions guarded by appropriate access controls, like onlyOwner or similar modifiers, so that these variables can be updated as required while maintaining security. This enables smoother contract maintenance and feature upgrades.
Num of instances: 2
Click to show findings
['61']
61: address public syntheticToken;['13']
13: Sets.AddressSet addresses;For consistency consider only using one of these incrementing methods.
Num of instances: 1
Click to show findings
['23']
23: contract AlchemistV3 is IAlchemistV3, Initializable {
24: using SafeCast for int256;
25: using SafeCast for uint256;
625: _accounts[tokenId].allowancesVersion += 1; // <= FOUND
Num of instances: 2
Click to show findings
['51']
51: unchecked { // <= FOUND
52: require(amount <= DELTA_MAX && amount >= DELTA_MIN);
53: require(start < GRAPH_MAX-1);
54:
55: uint256 expiration = start + duration; // <= FOUND
56: require(expiration < GRAPH_MAX-1);
57:
58: uint256 graphSize = g.size;
59:
60:
61:
62: uint256 newSize = expiration + 2;
63: if (newSize >= graphSize) {
64:
65: newSize |= newSize >> 1;
66: newSize |= newSize >> 2;
67: newSize |= newSize >> 4;
68: newSize |= newSize >> 8;
69: newSize |= newSize >> 16;
70: if (GRAPH_MAX > 2**32) {
71: newSize |= newSize >> 32;
72: newSize |= newSize >> 64;
73: newSize |= newSize >> 128;
74: }
75: newSize++;
76:
77:
78:
79:
80: require (newSize <= GRAPH_MAX);
81:
82: if (graphSize != 0) {
83:
84: uint256 copy = g.g[graphSize];
85: while (graphSize <= newSize) {
86: g.g[graphSize] = copy;
87: graphSize += graphSize;
88: }
89: }
90: graphSize = newSize;
91: g.size = newSize;
92: }
93:
94:
95: update(g.g, start + 1, graphSize, amount, amount * int256(start));
96: update(g.g, expiration + 1, graphSize, -amount, -amount * int256(expiration));
97: }['113']
113: unchecked { // <= FOUND
114: require (end <= GRAPH_MAX);
115:
116: start--;
117: require (start <= GRAPH_MAX);
118:
119: (begDelta,begProd) = query(g.g, start);
120: (endDelta,endProd) = query(g.g, end);
121:
122: return ((int256(end) * endDelta) - endProd) - ((int256(start) * begDelta) - begProd); // <= FOUND
123: }There is an inbuilt functionality to get the max value of a uint, it makes more sense to use this to improve readability
Num of instances: 24
Click to show findings
['202']
202: if (nx & 2**89 > 0) // <= FOUND
203: result += result * 0x2c5c85fe13339c6a8373fff9ff >> 133;['204']
204: if (nx & 2**88 > 0) // <= FOUND
205: result += result * 0x162e42ff01e9deb55bb48aaa9f >> 133;['206']
206: if (nx & 2**87 > 0) // <= FOUND
207: result += result * 0xb17217f7f08f37ab5036a35bf >> 133;['208']
208: if (nx & 2**86 > 0) // <= FOUND
209: result += result * 0x58b90bfbf097ac55c614e999f >> 133;['210']
210: if (nx & 2**85 > 0) // <= FOUND
211: result += result * 0x2c5c85fdf65fda4aeab37b55f >> 133;['212']
212: if (nx & 2**84 > 0) // <= FOUND
213: result += result * 0x162e42fefab4ee2d7749535ff >> 133;['214']
214: if (nx & 2**83 > 0) // <= FOUND
215: result += result * 0xb17217f7d3bb758bc21399ff >> 133;['216']
216: if (nx & 2**82 > 0) // <= FOUND
217: result += result * 0x58b90bfbe962bbcde2fd61bf >> 133;['218']
218: if (nx & 2**81 > 0) // <= FOUND
219: result += result * 0x2c5c85fdf4929e28f1fbc0bf >> 133;['220']
220: if (nx & 2**80 > 0) // <= FOUND
221: result += result * 0x162e42fefa419f24f91d299f >> 133;['364']
364: if (nx & 2**8 > 0) // <= FOUND
365: result += result * 0x162e5f >> 133;['348']
348: if (nx & 2**16 > 0) // <= FOUND
349: result += result * 0x162e42ff >> 133;['332']
332: if (nx & 2**24 > 0) // <= FOUND
333: result += result * 0x162e42feff >> 133;['316']
316: if (nx & 2**32 > 0) // <= FOUND
317: result += result * 0x162e42fefa3f >> 133;['70']
70: if (GRAPH_MAX > 2**32) { // <= FOUND['300']
300: if (nx & 2**40 > 0) // <= FOUND
301: result += result * 0x162e42fefa39ff >> 133;['284']
284: if (nx & 2**48 > 0) // <= FOUND
285: result += result * 0x162e42fefa39ef3f >> 133;['268']
268: if (nx & 2**56 > 0) // <= FOUND
269: result += result * 0x162e42fefa39ef359f >> 133;['252']
252: if (nx & 2**64 > 0) // <= FOUND
253: result += result * 0x162e42fefa39ef3d293f >> 133;['236']
236: if (nx & 2**72 > 0) // <= FOUND
237: result += result * 0x162e42fefa39f6e568bc5f >> 133;['188']
188: if (nx & 2**96 > 0) // <= FOUND
189: result += result * 0x162e4306aa2970dcdb2ddfa37fff >> 133;['172']
172: if (nx & 2**104 > 0) // <= FOUND
173: result += result * 0x162e4aaeeb8080c317e9495bd07f9f >> 133;['156']
156: if (nx & 2**112 > 0) // <= FOUND
157: result += result * 0x1635f4b5797dac2535627d823b92a89f >> 133;['94']
94: if (x >= 2**128) return 0; // <= FOUNDUsing -= for mappings instead of explicitly subtracting a value ensures cleaner code and potential gas savings. When manipulating numerical values within a mapping, it's more gas-efficient and clearer to use compound assignment operators like -=. For instance, instead of writing mappingA[xyz] = mappingA[xyz] - 1;, you can simply use mappingA[xyz] -= 1;.
Resolution: Replace explicit subtraction assignments with the -= operator for mappings. This not only makes the code more concise and readable but also potentially reduces gas costs. Utilize linters or static analysis tools to identify instances where the code can be optimized in this manner, ensuring consistency and efficiency across the codebase.
Num of instances: 1
Click to show findings
['120']
120: hasMinted[msg.sender] = hasMinted[msg.sender] - amount; // <= FOUNDUsing 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: 2
Click to show findings
['88']
88:
89: (bool success,) = recipient.call{value: amount}(""); // <= FOUND['88']
88:
89: (bool success,) = recipient.call{value: amount}(""); // <= FOUNDInconsistent use of floating pragma declarations, like ^ for a specific version or >=/<= for a range, can lead to version ambiguities and potential compatibility issues. Consistency in specifying compiler versions is crucial for ensuring predictable behavior across different environments. Using one consistent method, either fixed or ranged, throughout the project is recommended for clarity and safety.
Num of instances: 9
Click to show findings
['2']
2: pragma solidity ^0.8.26; // <= FOUND['1']
1: pragma solidity ^0.8.23; // <= FOUND['4']
4: pragma solidity ^0.8.20; // <= FOUND['9']
9: pragma solidity ^0.8.7; // <= FOUND[]
}pragma solidity ^0.8.23; // <= FOUND['1']
1: pragma solidity ^0.8.13; // <= FOUND['2']
2: pragma solidity >=0.8.4; // <= FOUND['1']
1: pragma solidity >=0.5.0; // <= FOUND[]
}pragma solidity >=0.5.0; // <= FOUND[NonCritical-58] 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: 42
Click to show findings
['45']
45: return ETHUSDConverter.usdToETH(usdAmount, priceFeed, updateTimeInSeconds); // <= FOUND 'ETHUSDConverter.'['50']
50: return ETHUSDConverter.ethToUSD(ethAmount, priceFeed, updateTimeInSeconds); // <= FOUND 'ETHUSDConverter.'['55']
55: return ETHUSDConverter.underlyingTokenToUSD(underlyingTokenAmount, underlyingTokenDecimals); // <= FOUND 'ETHUSDConverter.'['28']
28: return Number(FixedPointMath.encodeRaw(value)); // <= FOUND 'FixedPointMath.'['61']
61: return FixedPointMath.div(numerator, d); // <= FOUND 'FixedPointMath.'['83']
83: return add(self, FixedPointMath.encode(value)); // <= FOUND 'FixedPointMath.'['105']
105: return sub(self, FixedPointMath.encode(value)); // <= FOUND 'FixedPointMath.'['576']
576: _redemptionWeight += PositionDecay.WeightIncrement(amount, cumulativeEarmarked); // <= FOUND 'PositionDecay.'['586']
586: _collateralWeight += PositionDecay.WeightIncrement(totalOut, old); // <= FOUND 'PositionDecay.'['978']
978: uint256 debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, _earmarkWeight - account.lastAccruedEarmarkWeight); // <= FOUND 'PositionDecay.'['985']
985: debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, previousRedemption.earmarkWeight - account.lastAccruedEarmarkWeight); // <= FOUND 'PositionDecay.'['988']
988: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight); // <= FOUND 'PositionDecay.'['990']
990: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedState, _redemptionWeight - account.lastAccruedRedemptionWeight); // <= FOUND 'PositionDecay.'['993']
993: uint256 collateralToRemove = PositionDecay.ScaleByWeightDelta(account.rawLocked, _collateralWeight - account.lastCollateralWeight); // <= FOUND 'PositionDecay.'['1013']
1013: _earmarkWeight += PositionDecay.WeightIncrement(amount, totalDebt - cumulativeEarmarked); // <= FOUND 'PositionDecay.'['1039']
1039: earmarkWeightCopy += PositionDecay.WeightIncrement(amount, totalDebt - cumulativeEarmarked); // <= FOUND 'PositionDecay.'['1043']
1043: uint256 debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, earmarkWeightCopy - account.lastAccruedEarmarkWeight); // <= FOUND 'PositionDecay.'['1053']
1053: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight); // <= FOUND 'PositionDecay.'['73']
73:
74: StakingGraph.Graph private _stakingGraph; // <= FOUND 'StakingGraph.'['23']
23: return IERC4626(token).convertToAssets(10**TokenUtils.expectDecimals(token)); // <= FOUND 'TokenUtils.'['161']
161: underlyingConversionFactor = 10 ** (TokenUtils.expectDecimals(params.debtToken) - TokenUtils.expectDecimals(params.underlyingToken)); // <= FOUND 'TokenUtils.'['270']
270:
271: require(convertYieldTokensToDebt(TokenUtils.safeBalanceOf(yieldToken, transmuter)) >= ITransmuter(transmuter).totalLocked()); // <= FOUND 'TokenUtils.'['376']
376:
377: TokenUtils.safeTransferFrom(yieldToken, msg.sender, address(this), amount); // <= FOUND 'TokenUtils.'['402']
402:
403: TokenUtils.safeTransfer(yieldToken, recipient, amount); // <= FOUND 'TokenUtils.'['472']
472:
473: TokenUtils.safeBurnFrom(debtToken, msg.sender, credit); // <= FOUND 'TokenUtils.'['521']
521:
522: TokenUtils.safeTransferFrom(yieldToken, msg.sender, transmuter, creditToYield); // <= FOUND 'TokenUtils.'['522']
522: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, creditToYield); // <= FOUND 'TokenUtils.'['595']
595: TokenUtils.safeTransfer(yieldToken, transmuter, collRedeemed); // <= FOUND 'TokenUtils.'['596']
596: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, feeCollateral); // <= FOUND 'TokenUtils.'['689']
689: uint8 decimals = TokenUtils.expectDecimals(yieldToken); // <= FOUND 'TokenUtils.'['724']
724:
725: TokenUtils.safeMint(debtToken, recipient, amount); // <= FOUND 'TokenUtils.'['762']
762:
763: TokenUtils.safeTransfer(yieldToken, address(this), creditToYield); // <= FOUND 'TokenUtils.'['827']
827:
828: TokenUtils.safeTransfer(yieldToken, transmuter, adjustedDebtToBurn); // <= FOUND 'TokenUtils.'['831']
831:
832: TokenUtils.safeTransfer(yieldToken, msg.sender, feeInYield); // <= FOUND 'TokenUtils.'['185']
185: TokenUtils.safeTransferFrom(syntheticToken, msg.sender, address(this), syntheticDepositAmount); // <= FOUND 'TokenUtils.'['220']
220:
221: uint256 yieldTokenBalance = TokenUtils.safeBalanceOf(alchemist.yieldToken(), address(this)); // <= FOUND 'TokenUtils.'['236']
236:
237:
238: uint256 badDebtRatio = alchemist.totalSyntheticsIssued() * 10**TokenUtils.expectDecimals(alchemist.yieldToken()) / alchemist.getTotalUnderlyingValue(); // <= FOUND 'TokenUtils.'['243']
243: TokenUtils.safeTransfer(alchemist.yieldToken(), msg.sender, alchemist.convertDebtTokensToYield(claimAmount)); // <= FOUND 'TokenUtils.'['244']
244: TokenUtils.safeTransfer(alchemist.yieldToken(), protocolFeeReceiver, alchemist.convertDebtTokensToYield(feeAmount)); // <= FOUND 'TokenUtils.'['246']
246: TokenUtils.safeTransfer(syntheticToken, msg.sender, syntheticReturned); // <= FOUND 'TokenUtils.'['247']
247: TokenUtils.safeTransfer(syntheticToken, protocolFeeReceiver, syntheticFee); // <= FOUND 'TokenUtils.'['250']
250:
251: TokenUtils.safeBurn(syntheticToken, amountTransmuted); // <= FOUND 'TokenUtils.'In Solidity, the try-catch statement is used for handling exceptions in external function calls and contract creation. However, when a try-catch block doesn't include a catch for specific human-readable errors (using catch Error(string memory reason)), it can miss catching exceptions that provide explanatory error messages. This lack of detailed error handling could hinder debugging and obscure the reasons behind transaction failures. To address this, it's recommended to include a catch block specifically for Error to capture and handle these descriptive error messages effectively. This practice enhances the contract's robustness by providing clearer insights into why certain operations fail, thereby improving maintainability and troubleshooting.
Num of instances: 1
Only some address parameters are checked against address(0), to ensure consistency ensure all address parameters are checked.
Num of instances: 1
Click to show findings
['100']
100: function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) { // <= FOUND
101: address from = _ownerOf(tokenId);
102:
103: if (from != address(0)) {
104:
105: IAlchemistV3(alchemist).resetMintAllowances(tokenId);
106: }
107:
108: return super._update(to, tokenId, auth);
109: }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: 1
Click to show findings
['171']
171:
177: function queryGraph(uint256 startBlock, uint256 endBlock) external view returns (uint256 totalValue); // <= FOUNDEmitting an event in a constructor of a smart contract provides transparency and traceability in blockchain applications. This event logs the contract’s creation, aiding in monitoring and verifying contract deployment. Although constructors are executed only once, the emitted event ensures the contract's initialization is recorded on the blockchain.
Num of instances: 4
Click to show findings
['37']
37: constructor(address _priceFeed, uint256 _updateTimeInSeconds, uint256 _underlyingTokenDecimals) { // <= FOUND
38: priceFeed = _priceFeed;
39: updateTimeInSeconds = _updateTimeInSeconds;
40: underlyingTokenDecimals = _underlyingTokenDecimals;
41: }['17']
17: constructor(address _token, address _underlyingToken) { // <= FOUND
18: token = _token;
19: underlyingToken = _underlyingToken;
20: }['52']
52: constructor(address alchemist_) ERC721("AlchemistV3Position", "ALCV3") { // <= FOUND
53: if (alchemist_ == address(0)) {
54: revert AlchemistZeroAddressError();
55: }
56: alchemist = alchemist_;
57: }['83']
83: constructor(ITransmuter.TransmuterInitializationParams memory params) ERC721("Alchemix V3 Transmuter", "TRNSMTR") { // <= FOUND
84: syntheticToken = params.syntheticToken;
85: timeToTransmute = params.timeToTransmute;
86: transmutationFee = params.transmutationFee;
87: exitFee = params.exitFee;
88: protocolFeeReceiver = params.feeReceiver;
89: admin = msg.sender;
90: graphSize = params.graphSize;
91: }Using a fixed compiler version in Solidity contract and abstract files ensures consistency and predictability in smart contract behavior. A fixed version prevents unforeseen issues arising from compiler updates, which might introduce breaking changes or new bugs. It guarantees that the contract's behavior remains stable and consistent with the version used during its development and testing.
Num of instances: 10
Click to show findings
[]
10: contract EulerUSDCAdapter is ITokenAdapter []
18: contract AlEth is ERC20("Alchemix ETH", "alETH") []
21: contract Transmuter is ITransmuter, ERC721 []
10: contract Whitelist is IWhitelist, Ownable ['1']
1: pragma solidity >=0.5.0; // <= FOUND['2']
2: pragma solidity >=0.8.4; // <= FOUND['9']
9: pragma solidity ^0.8.7; // <= FOUND['1']
1: pragma solidity ^0.8.13; // <= FOUND['4']
4: pragma solidity ^0.8.20; // <= FOUND['1']
1: pragma solidity ^0.8.23; // <= FOUNDNot 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: 1
Click to show findings
[]
function generateSVG(uint256 tokenId, string memory title) internal pure returns (string memory) { // <= FOUND
return string( // <= FOUND
abi.encodePacked(
'<svg xmlns="http:www.w3.org/2000/svg" viewBox="0 0 300 484" width="300" height="484">',
'<rect width="300" height="484" fill="',
SVG_BG_COLOR,
'" />',
'<circle cx="150" cy="150" r="120" fill="none" stroke="',
SVG_ACCENT_COLOR,
'" stroke-width="4" />',
'<text x="150" y="120" font-family="Arial" font-size="24" fill="',
SVG_TEXT_COLOR,
'" text-anchor="middle">',
title,
"</text>",
'<text x="150" y="150" font-family="Arial" font-size="28" fill="',
SVG_ACCENT_COLOR,
'" text-anchor="middle" font-weight="bold">Position</text>',
'<text x="150" y="190" font-family="monospace" font-size="24" fill="',
SVG_TEXT_COLOR,
'" text-anchor="middle">#',
tokenId.toString(),
"</text>",
"</svg>"
)
);
}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: 1
Click to show findings
['78']
78: modifier onlyAdmin() { // <= FOUND
79: _checkArgument(msg.sender == admin); // <= FOUND
80: _;
81: }In Solidity, events are crucial for logging and informing external consumers about smart contract activities. Using parameters in events is essential for conveying detailed information about these activities. Parameters enable developers to specify what data is emitted, facilitating efficient and targeted data retrieval by front-end applications or off-chain monitoring tools. Well-defined event parameters improve transparency, enable better data analysis, and enhance the contract's interface with the external world, making interactions with the blockchain more informative and accessible.
Num of instances: 1
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: 24
Click to show findings
['16']
16:
18: error Unauthorized(); // <= FOUND['17']
17: error ZeroAddress(); // <= FOUND['18']
18: error ZeroAmount(); // <= FOUND['19']
19: error InsufficientBalance(); // <= FOUND['20']
20:
22: error TransferFailed(); // <= FOUND['31']
31:
32: error CallerNotAlchemist(); // <= FOUND['34']
34:
35: error AlchemistZeroAddressError(); // <= FOUND['37']
37:
38: error MintToZeroAddressError(); // <= FOUND['760']
760:
761: error Undercollateralized(); // <= FOUND['763']
763:
764: error LiquidationError(); // <= FOUND['766']
766:
767: error UnauthorizedAccountAccessError(); // <= FOUND['775']
775:
776: error UnknownAccountOwnerIDError(); // <= FOUND['778']
778:
779: error AlchemistV3NFTZeroAddressError(); // <= FOUND['781']
781:
782: error AlchemistV3NFTAlreadySetError(); // <= FOUND['784']
784:
785: error AlchemistVaultTokenMismatchError(); // <= FOUND['787']
787:
788: error CannotRepayOnMintBlock(); // <= FOUND['20']
20: error TransferFailed(); // <= FOUND['31']
31: error CallerNotAlchemist(); // <= FOUND['34']
34: error AlchemistZeroAddressError(); // <= FOUND['37']
37: error MintToZeroAddressError(); // <= FOUND['16']
16: error Unauthorized(); // <= FOUND['17']
17: error ZeroAddress(); // <= FOUND['18']
18: error ZeroAmount(); // <= FOUND['19']
19: error InsufficientBalance(); // <= FOUND[NonCritical-68] 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: 10
Click to show findings
['81']
81: function withdraw(address recipient, uint256 amount) external override onlyAuthorized nonReentrant { // <= FOUND
82: if (amount == 0) revert ZeroAmount();
83:
84:
85: if (amount > address(this).balance) revert InsufficientBalance();
86:
87:
88: (bool success,) = recipient.call{value: amount}(""); // <= FOUND
89: if (!success) revert TransferFailed();
90:
91: emit Withdrawn(recipient, amount);
92: }['47']
47: function safeTransfer(address token, address recipient, uint256 amount) internal { // <= FOUND
48: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, recipient, amount)); // <= FOUND
49:
50: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
51: revert ERC20CallFailed(token, success, data);
52: }
53: }['61']
61: function safeTransfer(address token, address recipient, uint256 amount) internal { // <= FOUND
62: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, recipient, amount)); // <= FOUND
63:
64: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
65: revert ERC20CallFailed(token, success, data);
66: }
67: }['63']
63: function safeApprove(address token, address spender, uint256 value) internal { // <= FOUND
64: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, value)); // <= FOUND
65:
66: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
67: revert ERC20CallFailed(token, success, data);
68: }
69: }['76']
76: function safeApprove(address token, address spender, uint256 value) internal { // <= FOUND
77: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, value)); // <= FOUND
78:
79: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
80: revert ERC20CallFailed(token, success, data);
81: }
82: }['80']
80: function safeTransferFrom(address token, address owner, address recipient, uint256 amount) internal { // <= FOUND
81: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, owner, recipient, amount)); // <= FOUND
82:
83: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
84: revert ERC20CallFailed(token, success, data);
85: }
86: }['92']
92: function safeTransferFrom(address token, address owner, address recipient, uint256 amount) internal { // <= FOUND
93: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, owner, recipient, amount)); // <= FOUND
94:
95: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
96: revert ERC20CallFailed(token, success, data);
97: }
98: }['107']
107: function safeMint(address token, address recipient, uint256 amount) internal { // <= FOUND
108: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Mintable.mint.selector, recipient, amount)); // <= FOUND
109:
110: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
111: revert ERC20CallFailed(token, success, data);
112: }
113: }['121']
121: function safeBurn(address token, uint256 amount) internal { // <= FOUND
122: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Burnable.burn.selector, amount)); // <= FOUND
123:
124: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
125: revert ERC20CallFailed(token, success, data);
126: }
127: }['136']
136: function safeBurnFrom(address token, address owner, uint256 amount) internal { // <= FOUND
137: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Burnable.burnFrom.selector, owner, amount)); // <= FOUND
138:
139: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
140: revert ERC20CallFailed(token, success, data);
141: }
142: }Using 'owner' or '_owner' as a parameter name in functions, especially in contracts inheriting from or interacting with OpenZeppelin's Ownable contract, can lead to confusion and potential bugs. These contracts often have a state variable named owner, managed by the Ownable pattern for access control. Using the same name for function parameters may obscure the contract's owner state variable, complicating code readability and maintenance. Prefer alternative descriptive names for parameters to maintain clarity and prevent conflicts with established ownership patterns.
Num of instances: 3
Click to show findings
['92']
92: function safeTransferFrom(address token, address owner, address recipient, uint256 amount) internal // <= FOUND['92']
92: function safeTransferFrom(address token, address owner, address recipient, uint256 amount) internal // <= FOUND['136']
136: function safeBurnFrom(address token, address owner, uint256 amount) internal // <= FOUNDAvoiding arithmetic directly within array indices, like test[i + 2], is recommended to prevent errors such as index out of bounds or incorrect data access. This practice enhances code readability and maintainability. Instead, calculate the index beforehand, store it in a variable, and then use that variable to access the array element. This approach reduces mistakes, especially in complex loops or when handling dynamic data, ensuring that each access is clear and verifiable. It's about keeping code clean and understandable, minimizing potential bugs.
Num of instances: 4
Click to show findings
['38']
38: uint256[GRAPH_MAX + 1] g; ['132']
132:
138: function update(uint256[GRAPH_MAX + 1] storage graph, uint256 index, uint256 treeSize, int256 delta, int256 deltaProd) private {['170']
170:
176: function query(uint256[GRAPH_MAX + 1] storage graph, uint256 index) private view returns (int256 sum, int256 sumProd) {['187']
187: _positions[++_nonce] = StakingPosition(syntheticDepositAmount, block.number, block.number + timeToTransmute);Tagging assembly blocks as "memory safe" in Solidity helps maintainers and auditors quickly identify areas of the code less likely to introduce vulnerabilities due to improper memory access. This practice promotes safer contract development by clearly communicating assumptions about memory handling, aiding in the review process, and reducing the risk of introducing memory-related bugs. It's a part of best coding practices, enhancing code clarity and security, especially in complex, low-level operations where Solidity's safety checks are bypassed.
Num of instances: 2
Click to show findings
['157']
157: assembly {
158: index := add(index, and(index, sub(0, index)))
159: }['190']
190: assembly {
191: index := sub(index, and(index, sub(0, index)))
192: }Rather than redefining state variable constant, consider utilising a library to store all constants as this will prevent data redundancy
Num of instances: 6
Click to show findings
['10']
10: string private constant SVG_BG_COLOR = "#d4c3b7"; // <= FOUND['11']
11: string private constant SVG_TEXT_COLOR = "#0a3a60"; // <= FOUND['12']
12: string private constant SVG_ACCENT_COLOR = "#0a3a60"; // <= FOUND['29']
29: uint256 public constant BPS = 10_000; // <= FOUND['30']
30: uint256 public constant FIXED_POINT_SCALAR = 1e18; // <= FOUND['33']
33: string public constant version = "3.0.0"; // <= FOUNDIn struct definitions, using redundant prefixes for attributes is unnecessary. For instance, in a struct named Employee, attributes like employeeName, employeeID, and employeeEmail can be simplified to name, ID, and email respectively, since they are already inherently associated with Employee. By removing these repetitive prefixes, the code becomes more concise and easier to read, maintaining its contextual clarity.
Num of instances: 1
Click to show findings
['9']
9: struct AddressSet { // <= FOUND
10: address[] values; // <= FOUND
11: mapping(address => uint256) indexes;
12: }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
['26']
26: function deposit(uint256 amount) external { // <= FOUND
27: _checkNonZeroAmount(amount);
28: IERC20(token).transferFrom(msg.sender, address(this), amount); // <= FOUND
29: emit Deposited(msg.sender, amount);
30: }['357']
357: function deposit(uint256 amount, address recipient, uint256 recipientId) external returns (uint256) { // <= FOUND
358: _checkArgument(recipient != address(0));
359: _checkArgument(amount > 0);
360: _checkState(!depositsPaused);
361: _checkState(IERC20(yieldToken).balanceOf(address(this)) + amount <= depositCap);
362: uint256 tokenId = recipientId;
363:
364:
365: if (tokenId == 0) {
366: tokenId = IAlchemistV3Position(alchemistPositionNFT).mint(recipient);
367: emit AlchemistV3PositionNFTMinted(recipient, tokenId);
368: } else {
369: _checkForValidAccountId(tokenId);
370: }
371:
372: _accounts[tokenId].collateralBalance += amount;
373: _accounts[tokenId].freeCollateral += amount;
374:
375:
376: TokenUtils.safeTransferFrom(yieldToken, msg.sender, address(this), amount); // <= FOUND
377:
378: emit Deposit(amount, tokenId);
379:
380: return convertYieldTokensToDebt(amount);
381: }['488']
488: function repay(uint256 amount, uint256 recipientTokenId) public returns (uint256) { // <= FOUND
489: _checkArgument(amount > 0);
490: _checkForValidAccountId(recipientTokenId);
491: Account storage account = _accounts[recipientTokenId];
492:
493:
494: if (block.number == account.lastMintBlock) revert CannotRepayOnMintBlock();
495:
496:
497: _earmark();
498:
499:
500: _sync(recipientTokenId);
501:
502: uint256 debt;
503:
504:
505: _checkState((debt = account.debt) > 0);
506:
507: uint256 yieldToDebt = convertYieldTokensToDebt(amount);
508: uint256 credit = yieldToDebt > debt ? debt : yieldToDebt;
509: uint256 creditToYield = convertDebtTokensToYield(credit);
510:
511:
512: uint256 earmarkToRemove = credit > account.earmarked ? account.earmarked : credit;
513: account.earmarked -= earmarkToRemove;
514:
515:
516: account.collateralBalance -= creditToYield * protocolFee / BPS;
517:
518: _subDebt(recipientTokenId, credit);
519:
520:
521: TokenUtils.safeTransferFrom(yieldToken, msg.sender, transmuter, creditToYield); // <= FOUND
522: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, creditToYield);
523:
524: emit Repay(msg.sender, amount, recipientTokenId, creditToYield);
525:
526: return creditToYield;
527: }['80']
80: function safeTransferFrom(address token, address owner, address recipient, uint256 amount) internal { // <= FOUND
81: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, owner, recipient, amount));
82:
83: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
84: revert ERC20CallFailed(token, success, data);
85: }
86: }['92']
92: function safeTransferFrom(address token, address owner, address recipient, uint256 amount) internal { // <= FOUND
93: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, owner, recipient, amount));
94:
95: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {
96: revert ERC20CallFailed(token, success, data);
97: }
98: }['172']
172: function createRedemption(uint256 syntheticDepositAmount) external { // <= FOUND
173: if (syntheticDepositAmount == 0) {
174: revert DepositZeroAmount();
175: }
176:
177: if (totalLocked + syntheticDepositAmount > depositCap) {
178: revert DepositCapReached();
179: }
180:
181: if (totalLocked + syntheticDepositAmount > alchemist.totalSyntheticsIssued()) {
182: revert DepositCapReached();
183: }
184:
185: TokenUtils.safeTransferFrom(syntheticToken, msg.sender, address(this), syntheticDepositAmount); // <= FOUND
186:
187: _positions[++_nonce] = StakingPosition(syntheticDepositAmount, block.number, block.number + timeToTransmute);
188:
189:
190: _updateStakingGraph(syntheticDepositAmount.toInt256() * BLOCK_SCALING_FACTOR / timeToTransmute.toInt256(), timeToTransmute);
191:
192: totalLocked += syntheticDepositAmount;
193:
194: _mint(msg.sender, _nonce);
195:
196: emit PositionCreated(msg.sender, syntheticDepositAmount, _nonce);
197: }Using assembly in view or pure functions is risky because it bypasses Solidity's safeguards, potentially allowing unintended state reads or writes when interacting with storage. view and pure functions are meant to ensure no state changes occur, but improper use of assembly can violate this, leading to logical errors or undefined behavior.
For instance, directly accessing storage slots in assembly within a view function could inadvertently modify state due to mismanagement of pointers or memory allocation. This undermines the trust and guarantees associated with view and pure functions. To ensure integrity, use high-level Solidity constructs for state access, as they enforce read/write restrictions and maintain consistency.
Num of instances: 3
Click to show findings
[]
108: function queryStake(Graph storage g, uint256 start, uint256 end) internal view returns (int256) {
109: int256 begDelta;
110: int256 begProd;
111: int256 endDelta;
112: int256 endProd;
113: unchecked {
114: require (end <= GRAPH_MAX);
115:
116: start--;
117: require (start <= GRAPH_MAX);
118:
119: (begDelta,begProd) = query(g.g, start);
120: (endDelta,endProd) = query(g.g, end);
121:
122: return ((int256(end) * endDelta) - endProd) - ((int256(start) * begDelta) - begProd);
123: }
124: }['170']
170: function query(uint256[GRAPH_MAX + 1] storage graph, uint256 index) private view returns (int256 sum, int256 sumProd) {
171: unchecked {
172: index += 1;
173: while (index > 0) {
174:
175: uint256 packed = graph[index];
176: int256 ad;
177: int256 ap;
178:
179:
180: if ((packed&(2**(DELTA_BITS-1))) != 0) {
181: ad = int256(packed | ~DELTA_MASK);
182: } else {
183: ad = int256(packed & DELTA_MASK);
184: }
185: ap = int256(packed)>>DELTA_BITS;
186:
187: sum += ad;
188: sumProd += ap;
189:
190: assembly { // <= FOUND
191: index := sub(index, and(index, sub(0, index)))
192: }
193: }
194: }
195: }[]
260: function queryGraph(uint256 startBlock, uint256 endBlock) external view returns (uint256) {
261: int256 queried = _stakingGraph.queryStake(startBlock, endBlock);
262:
263: if (queried == 0) return 0;
264:
265: return (queried / BLOCK_SCALING_FACTOR).toUint256() + 1;
266:
267: }Num of instances: 11
Click to show findings
['349']
349: function totalValue(uint256 tokenId) public view returns (uint256) {
350: uint256 totalUnderlying; // <= FOUND
351: (,, uint256 collateral) = _calculateUnrealizedDebt(tokenId);
352: if (collateral > 0) totalUnderlying += convertYieldTokensToUnderlying(collateral);
353: return normalizeUnderlyingTokensToDebt(totalUnderlying);
354: }['447']
447: function burn(uint256 amount, uint256 recipientId) external returns (uint256) {
448: _checkArgument(amount > 0);
449: _checkForValidAccountId(recipientId);
450:
451:
452: if (block.number == _accounts[recipientId].lastMintBlock) revert CannotRepayOnMintBlock();
453:
454:
455: _earmark();
456:
457:
458: _sync(recipientId);
459:
460: uint256 debt; // <= FOUND
461:
462: _checkState((debt = _accounts[recipientId].debt - _accounts[recipientId].earmarked) > 0);
463:
464: uint256 credit = amount > debt ? debt : amount; // <= FOUND
465:
466:
467: if (credit > totalDebt - ITransmuter(transmuter).totalLocked()) {
468: revert BurnLimitExceeded(credit, totalDebt - ITransmuter(transmuter).totalLocked());
469: }
470:
471:
472: TokenUtils.safeBurnFrom(debtToken, msg.sender, credit);
473:
474:
475: _accounts[recipientId].collateralBalance -= convertDebtTokensToYield(credit) * protocolFee / BPS;
476:
477:
478: _subDebt(recipientId, credit);
479:
480: totalSyntheticsIssued -= credit;
481:
482: emit Burn(msg.sender, credit, recipientId);
483:
484: return credit;
485: }['488']
488: function repay(uint256 amount, uint256 recipientTokenId) public returns (uint256) {
489: _checkArgument(amount > 0);
490: _checkForValidAccountId(recipientTokenId);
491: Account storage account = _accounts[recipientTokenId];
492:
493:
494: if (block.number == account.lastMintBlock) revert CannotRepayOnMintBlock();
495:
496:
497: _earmark();
498:
499:
500: _sync(recipientTokenId);
501:
502: uint256 debt; // <= FOUND
503:
504:
505: _checkState((debt = account.debt) > 0);
506:
507: uint256 yieldToDebt = convertYieldTokensToDebt(amount);
508: uint256 credit = yieldToDebt > debt ? debt : yieldToDebt;
509: uint256 creditToYield = convertDebtTokensToYield(credit);
510:
511:
512: uint256 earmarkToRemove = credit > account.earmarked ? account.earmarked : credit;
513: account.earmarked -= earmarkToRemove;
514:
515:
516: account.collateralBalance -= creditToYield * protocolFee / BPS;
517:
518: _subDebt(recipientTokenId, credit);
519:
520:
521: TokenUtils.safeTransferFrom(yieldToken, msg.sender, transmuter, creditToYield);
522: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, creditToYield);
523:
524: emit Repay(msg.sender, amount, recipientTokenId, creditToYield);
525:
526: return creditToYield;
527: }['735']
735: function _forceRepay(uint256 accountId, uint256 amount) internal returns (uint256) {
736: _checkArgument(amount > 0);
737: _checkForValidAccountId(accountId);
738: Account storage account = _accounts[accountId];
739:
740:
741: _earmark();
742:
743:
744: _sync(accountId);
745:
746: uint256 debt; // <= FOUND
747:
748:
749: _checkState((debt = account.debt) > 0);
750:
751: uint256 yieldToDebt = convertYieldTokensToDebt(amount);
752: uint256 credit = yieldToDebt > debt ? debt : yieldToDebt;
753: uint256 creditToYield = convertDebtTokensToYield(credit);
754: _subDebt(accountId, credit);
755:
756:
757: account.earmarked -= credit > account.earmarked ? account.earmarked : credit;
758:
759: account.collateralBalance -= creditToYield;
760:
761:
762: TokenUtils.safeTransfer(yieldToken, address(this), creditToYield);
763: return creditToYield;
764: }['974']
974: function _sync(uint256 tokenId) internal {
975: Account storage account = _accounts[tokenId];
976: RedemptionInfo memory previousRedemption = _redemptions[lastRedemptionBlock];
977:
978: uint256 debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, _earmarkWeight - account.lastAccruedEarmarkWeight);
979: uint256 earmarkedState = account.earmarked + debtToEarmark;
980:
981:
982: uint256 earmarkToRedeem; // <= FOUND
983: uint256 earmarkPreviousState;
984: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) {
985: debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, previousRedemption.earmarkWeight - account.lastAccruedEarmarkWeight);
986:
987: earmarkPreviousState = account.earmarked + debtToEarmark;
988: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight);
989: } else {
990: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedState, _redemptionWeight - account.lastAccruedRedemptionWeight);
991: }
992:
993: uint256 collateralToRemove = PositionDecay.ScaleByWeightDelta(account.rawLocked, _collateralWeight - account.lastCollateralWeight);
994:
995:
996: account.earmarked = earmarkedState - earmarkToRedeem; // <= FOUND
997: account.debt -= earmarkToRedeem; // <= FOUND
998: account.lastAccruedRedemptionWeight = _redemptionWeight;
999: account.lastAccruedEarmarkWeight = _earmarkWeight;
1000:
1001: account.collateralBalance -= collateralToRemove;
1002: account.rawLocked -= collateralToRemove;
1003: account.lastCollateralWeight = _collateralWeight;
1004: }['1028']
1028: function _calculateUnrealizedDebt(uint256 tokenId) internal view returns (uint256, uint256, uint256) {
1029: Account storage account = _accounts[tokenId];
1030: RedemptionInfo memory previousRedemption = _redemptions[lastRedemptionBlock];
1031:
1032: uint256 amount; // <= FOUND
1033: uint256 earmarkWeightCopy = _earmarkWeight;
1034:
1035:
1036: if (block.number > lastEarmarkBlock) {
1037: amount = ITransmuter(transmuter).queryGraph(lastEarmarkBlock + 1, block.number);
1038: if (amount > 0) {
1039: earmarkWeightCopy += PositionDecay.WeightIncrement(amount, totalDebt - cumulativeEarmarked);
1040: }
1041: }
1042:
1043: uint256 debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, earmarkWeightCopy - account.lastAccruedEarmarkWeight);
1044: uint256 earmarkedState = account.earmarked + debtToEarmark;
1045:
1046:
1047: uint256 earmarkedPreviousState;
1048: uint256 earmarkToRedeem; // <= FOUND
1049: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) {
1050: debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, previousRedemption.earmarkWeight - account.lastAccruedEarmarkWeight);
1051:
1052: earmarkedPreviousState = account.earmarked + debtToEarmark;
1053: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight);
1054: } else {
1055: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedState, _redemptionWeight - account.lastAccruedRedemptionWeight);
1056: }
1057:
1058: uint256 collateralToRemove = PositionDecay.ScaleByWeightDelta(account.rawLocked, _collateralWeight - account.lastCollateralWeight);
1059:
1060: return (account.debt - earmarkToRedeem, earmarkedState - earmarkToRedeem, account.collateralBalance - collateralToRemove);
1061: }['92']
92: function Log2NegFrac(uint256 x) private pure returns (uint256) {
93: unchecked {
94: if (x >= 2**128) return 0;
95:
96: require(x > 0);
97:
98: int256 msb = 0;
99: uint256 xc = x;
100: if (xc >= 0x10000000000000000) { xc >>= 64; msb += 64; }
101: if (xc >= 0x100000000) { xc >>= 32; msb += 32; }
102: if (xc >= 0x10000) { xc >>= 16; msb += 16; }
103: if (xc >= 0x100) { xc >>= 8; msb += 8; }
104: if (xc >= 0x10) { xc >>= 4; msb += 4; }
105: if (xc >= 0x4) { xc >>= 2; msb += 2; }
106: if (xc >= 0x2) msb += 1;
107:
108: int256 result = (msb - 128) << 120;
109: uint256 ux = uint256(x) << uint256(127 - msb);
110: for (int256 bit = 0x800000000000000000000000000000; bit > 0; bit >>= 1) {
111: ux *= ux;
112: uint256 b = ux >> 255;
113: ux >>= 127 + b;
114: result += bit * int256 (b); // <= FOUND
115: }
116:
117: return uint256(-result);
118: }
119: }['127']
127: function Exp2NegFrac(uint256 x) private pure returns (uint256) {
128: unchecked {
129: if (x > LOG2NEGFRAC_1) return 0;
130:
131: int256 nx = -int256(x);
132:
133: require (nx < 0);
134:
135:
136:
137:
138:
139: uint256 result = 0x80000000000000000000000000000051;
140:
141:
142: if (nx & 2**119 > 0)
143: result += result * 0xd413cccfe779921165f626cdd52afa89 >> 129;
144: if (nx & 2**118 > 0)
145: result += result * 0xc1bf828c6dc54b7a356918c17217b7bf >> 130;
146: if (nx & 2**117 > 0)
147: result += result * 0xb95c1e3ea8bd6e6fbe4628758a53c90f >> 131;
148: if (nx & 2**116 > 0)
149: result += result * 0xb5586cf9890f6298b92b71842a98364f >> 132;
150: if (nx & 2**115 > 0)
151: result += result * 0xb361a62b0ae875cf8a91d6d19482ffdf >> 133;
152: if (nx & 2**114 > 0)
153: result += result * 0x59347cef00c1dcdef95949ef4537bd3f >> 133;
154: if (nx & 2**113 > 0)
155: result += result * 0x2c7b53f6666adb094cd5c66db9bf481f >> 133;
156: if (nx & 2**112 > 0)
157: result += result * 0x1635f4b5797dac2535627d823b92a89f >> 133;
158: if (nx & 2**111 > 0)
159: result += result * 0xb190db43813d43fe33a5299e5ecf39f >> 133;
160: if (nx & 2**110 > 0)
161: result += result * 0x58c0bc5d19d8a0da437f9134474021f >> 133;
162: if (nx & 2**109 > 0)
163: result += result * 0x2c5e72080a3f425179538ab863cbc1f >> 133;
164: if (nx & 2**108 > 0)
165: result += result * 0x162ebdffb8ed7471c62ce395272e4df >> 133;
166: if (nx & 2**107 > 0)
167: result += result * 0xb17403f73f2dad959a9630122f87df >> 133;
168: if (nx & 2**106 > 0)
169: result += result * 0x58b986fb52923a130b86918d1cf67f >> 133;
170: if (nx & 2**105 > 0)
171: result += result * 0x2c5ca4bdc0a8ea88afab32a52404df >> 133;
172: if (nx & 2**104 > 0)
173: result += result * 0x162e4aaeeb8080c317e9495bd07f9f >> 133;
174: if (nx & 2**103 > 0)
175: result += result * 0xb17236b7935c5ddb03d36fa99f59f >> 133;
176: if (nx & 2**102 > 0)
177: result += result * 0x58b913abd8d949af9159802f6f93f >> 133;
178: if (nx & 2**101 > 0)
179: result += result * 0x2c5c87e9f0620c1c05b07353a2dbf >> 133;
180: if (nx & 2**100 > 0)
181: result += result * 0x162e4379f933b3f121d40d222ec7f >> 133;
182: if (nx & 2**99 > 0)
183: result += result * 0xb17219e3cdb2ff39429b7982c51f >> 133;
184: if (nx & 2**98 > 0)
185: result += result * 0x58b90c76e7e02c8d1ed758b9459f >> 133;
186: if (nx & 2**97 > 0)
187: result += result * 0x2c5c861cb431ec233c78045054bf >> 133;
188: if (nx & 2**96 > 0)
189: result += result * 0x162e4306aa2970dcdb2ddfa37fff >> 133;
190: if (nx & 2**95 > 0)
191: result += result * 0xb1721816918d7cbbf08d8b65e1f >> 133;
192: if (nx & 2**94 > 0)
193: result += result * 0x58b90c0398d73d284278e4e6f5f >> 133;
194: if (nx & 2**93 > 0)
195: result += result * 0x2c5c85ffe06fbe715456433e0df >> 133;
196: if (nx & 2**92 > 0)
197: result += result * 0x162e42ff7538e7354b033e89f3f >> 133;
198: if (nx & 2**91 > 0)
199: result += result * 0xb17217f9bdcb59a7839db9047f >> 133;
200: if (nx & 2**90 > 0)
201: result += result * 0x58b90bfc63e6b4d461b437ca1f >> 133;
202: if (nx & 2**89 > 0)
203: result += result * 0x2c5c85fe13339c6a8373fff9ff >> 133;
204: if (nx & 2**88 > 0)
205: result += result * 0x162e42ff01e9deb55bb48aaa9f >> 133;
206: if (nx & 2**87 > 0)
207: result += result * 0xb17217f7f08f37ab5036a35bf >> 133;
208: if (nx & 2**86 > 0)
209: result += result * 0x58b90bfbf097ac55c614e999f >> 133;
210: if (nx & 2**85 > 0)
211: result += result * 0x2c5c85fdf65fda4aeab37b55f >> 133;
212: if (nx & 2**84 > 0)
213: result += result * 0x162e42fefab4ee2d7749535ff >> 133;
214: if (nx & 2**83 > 0)
215: result += result * 0xb17217f7d3bb758bc21399ff >> 133;
216: if (nx & 2**82 > 0)
217: result += result * 0x58b90bfbe962bbcde2fd61bf >> 133;
218: if (nx & 2**81 > 0)
219: result += result * 0x2c5c85fdf4929e28f1fbc0bf >> 133;
220: if (nx & 2**80 > 0)
221: result += result * 0x162e42fefa419f24f91d299f >> 133;
222: if (nx & 2**79 > 0)
223: result += result * 0xb17217f7d1ee3969c9667df >> 133;
224: if (nx & 2**78 > 0)
225: result += result * 0x58b90bfbe8ef6cc564d28bf >> 133;
226: if (nx & 2**77 > 0)
227: result += result * 0x2c5c85fdf475ca66d27119f >> 133;
228: if (nx & 2**76 > 0)
229: result += result * 0x162e42fefa3a6a34713a81f >> 133;
230: if (nx & 2**75 > 0)
231: result += result * 0xb17217f7d1d165a7a9dbff >> 133;
232: if (nx & 2**74 > 0)
233: result += result * 0x58b90bfbe8e837d4dcefff >> 133;
234: if (nx & 2**73 > 0)
235: result += result * 0x2c5c85fdf473fd2ab0787f >> 133;
236: if (nx & 2**72 > 0)
237: result += result * 0x162e42fefa39f6e568bc5f >> 133;
238: if (nx & 2**71 > 0)
239: result += result * 0xb17217f7d1cf986b87e3f >> 133;
240: if (nx & 2**70 > 0)
241: result += result * 0x58b90bfbe8e7c485d471f >> 133;
242: if (nx & 2**69 > 0)
243: result += result * 0x2c5c85fdf473e056ee59f >> 133;
244: if (nx & 2**68 > 0)
245: result += result * 0x162e42fefa39efb07835f >> 133;
246: if (nx & 2**67 > 0)
247: result += result * 0xb17217f7d1cf7b97c5df >> 133;
248: if (nx & 2**66 > 0)
249: result += result * 0x58b90bfbe8e7bd50e3ff >> 133;
250: if (nx & 2**65 > 0)
251: result += result * 0x2c5c85fdf473de89b23f >> 133;
252: if (nx & 2**64 > 0)
253: result += result * 0x162e42fefa39ef3d293f >> 133;
254: if (nx & 2**63 > 0)
255: result += result * 0xb17217f7d1cf79ca89f >> 133;
256: if (nx & 2**62 > 0)
257: result += result * 0x58b90bfbe8e7bcdd95f >> 133;
258: if (nx & 2**61 > 0)
259: result += result * 0x2c5c85fdf473de6cdff >> 133;
260: if (nx & 2**60 > 0)
261: result += result * 0x162e42fefa39ef35f5f >> 133;
262: if (nx & 2**59 > 0)
263: result += result * 0xb17217f7d1cf79adbf >> 133;
264: if (nx & 2**58 > 0)
265: result += result * 0x58b90bfbe8e7bcd65f >> 133;
266: if (nx & 2**57 > 0)
267: result += result * 0x2c5c85fdf473de6b1f >> 133;
268: if (nx & 2**56 > 0)
269: result += result * 0x162e42fefa39ef359f >> 133;
270: if (nx & 2**55 > 0)
271: result += result * 0xb17217f7d1cf79abf >> 133;
272: if (nx & 2**54 > 0)
273: result += result * 0x58b90bfbe8e7bcd5f >> 133;
274: if (nx & 2**53 > 0)
275: result += result * 0x2c5c85fdf473de6bf >> 133;
276: if (nx & 2**52 > 0)
277: result += result * 0x162e42fefa39ef35f >> 133;
278: if (nx & 2**51 > 0)
279: result += result * 0xb17217f7d1cf79bf >> 133;
280: if (nx & 2**50 > 0)
281: result += result * 0x58b90bfbe8e7bcdf >> 133;
282: if (nx & 2**49 > 0)
283: result += result * 0x2c5c85fdf473de7f >> 133;
284: if (nx & 2**48 > 0)
285: result += result * 0x162e42fefa39ef3f >> 133;
286: if (nx & 2**47 > 0)
287: result += result * 0xb17217f7d1cf79f >> 133;
288: if (nx & 2**46 > 0)
289: result += result * 0x58b90bfbe8e7bdf >> 133;
290: if (nx & 2**45 > 0)
291: result += result * 0x2c5c85fdf473dff >> 133;
292: if (nx & 2**44 > 0)
293: result += result * 0x162e42fefa39eff >> 133;
294: if (nx & 2**43 > 0)
295: result += result * 0xb17217f7d1cf7f >> 133;
296: if (nx & 2**42 > 0)
297: result += result * 0x58b90bfbe8e7bf >> 133;
298: if (nx & 2**41 > 0)
299: result += result * 0x2c5c85fdf473df >> 133;
300: if (nx & 2**40 > 0)
301: result += result * 0x162e42fefa39ff >> 133;
302: if (nx & 2**39 > 0)
303: result += result * 0xb17217f7d1cff >> 133;
304: if (nx & 2**38 > 0)
305: result += result * 0x58b90bfbe8e7f >> 133;
306: if (nx & 2**37 > 0)
307: result += result * 0x2c5c85fdf473f >> 133;
308: if (nx & 2**36 > 0)
309: result += result * 0x162e42fefa39f >> 133;
310: if (nx & 2**35 > 0)
311: result += result * 0xb17217f7d1df >> 133;
312: if (nx & 2**34 > 0)
313: result += result * 0x58b90bfbe8ff >> 133;
314: if (nx & 2**33 > 0)
315: result += result * 0x2c5c85fdf47f >> 133;
316: if (nx & 2**32 > 0)
317: result += result * 0x162e42fefa3f >> 133;
318: if (nx & 2**31 > 0)
319: result += result * 0xb17217f7d1f >> 133;
320: if (nx & 2**30 > 0)
321: result += result * 0x58b90bfbe9f >> 133;
322: if (nx & 2**29 > 0)
323: result += result * 0x2c5c85fdf5f >> 133;
324: if (nx & 2**28 > 0)
325: result += result * 0x162e42fefbf >> 133;
326: if (nx & 2**27 > 0)
327: result += result * 0xb17217f7df >> 133;
328: if (nx & 2**26 > 0)
329: result += result * 0x58b90bfbff >> 133;
330: if (nx & 2**25 > 0)
331: result += result * 0x2c5c85fdff >> 133;
332: if (nx & 2**24 > 0)
333: result += result * 0x162e42feff >> 133;
334: if (nx & 2**23 > 0)
335: result += result * 0xb17217f7f >> 133;
336: if (nx & 2**22 > 0)
337: result += result * 0x58b90bfbf >> 133;
338: if (nx & 2**21 > 0)
339: result += result * 0x2c5c85fdf >> 133;
340: if (nx & 2**20 > 0)
341: result += result * 0x162e42fff >> 133;
342: if (nx & 2**19 > 0)
343: result += result * 0xb17217ff >> 133;
344: if (nx & 2**18 > 0)
345: result += result * 0x58b90bff >> 133;
346: if (nx & 2**17 > 0)
347: result += result * 0x2c5c85ff >> 133;
348: if (nx & 2**16 > 0)
349: result += result * 0x162e42ff >> 133;
350: if (nx & 2**15 > 0)
351: result += result * 0xb17217f >> 133;
352: if (nx & 2**14 > 0)
353: result += result * 0x58b90bf >> 133;
354: if (nx & 2**13 > 0)
355: result += result * 0x2c5c85f >> 133;
356: if (nx & 2**12 > 0)
357: result += result * 0x162e43f >> 133;
358: if (nx & 2**11 > 0)
359: result += result * 0xb1721f >> 133;
360: if (nx & 2**10 > 0)
361: result += result * 0x58b91f >> 133;
362: if (nx & 2**9 > 0)
363: result += result * 0x2c5c9f >> 133;
364: if (nx & 2**8 > 0)
365: result += result * 0x162e5f >> 133;
366: if (nx & 2**7 > 0)
367: result += result * 0xb173f >> 133;
368: if (nx & 2**6 > 0)
369: result += result * 0x58b9f >> 133;
370: if (nx & 2**5 > 0)
371: result += result * 0x2c5df >> 133;
372: if (nx & 2**4 > 0)
373: result += result * 0x162ff >> 133;
374: if (nx & 2**3 > 0)
375: result += result * 0xb17f >> 133;
376: if (nx & 2**2 > 0)
377: result += result * 0x58bf >> 133;
378: if (nx & 2**1 > 0)
379: result += result * 0x2c5f >> 133;
380: if (nx & 2**0 > 0)
381: result += result * 0x163f >> 133;
382:
383:
384: result >>= uint256 (int256 (-1 - (nx >> 120))); // <= FOUND
385: require (result <= uint256 (type(uint128).max));
386:
387: return result;
388: }
389: }['108']
108: function queryStake(Graph storage g, uint256 start, uint256 end) internal view returns (int256) {
109: int256 begDelta; // <= FOUND
110: int256 begProd;
111: int256 endDelta;
112: int256 endProd;
113: unchecked {
114: require (end <= GRAPH_MAX);
115:
116: start--;
117: require (start <= GRAPH_MAX);
118:
119: (begDelta,begProd) = query(g.g, start);
120: (endDelta,endProd) = query(g.g, end);
121:
122: return ((int256(end) * endDelta) - endProd) - ((int256(start) * begDelta) - begProd);
123: }
124: }['132']
132: function update(uint256[GRAPH_MAX + 1] storage graph, uint256 index, uint256 treeSize, int256 delta, int256 deltaProd) private {
133: unchecked {
134: index += 1;
135: while (index <= treeSize) {
136:
137: uint256 packed = graph[index];
138: int256 ad; // <= FOUND
139: int256 ap;
140:
141:
142: if ((packed&DELTA_SIGNBIT) != 0) {
143: ad = int256(packed | ~DELTA_MASK);
144: } else {
145: ad = int256(packed & DELTA_MASK);
146: }
147: ap = int256(packed)>>DELTA_BITS;
148:
149: ad+=delta;
150: ap+=deltaProd;
151:
152:
153: require(ad <= DELTA_MAX && ad >= DELTA_MIN);
154: require(ap <= PRODUCT_MAX && ap >= PRODUCT_MIN);
155: graph[index] = (uint256(ad)&DELTA_MASK)|uint256(ap<<DELTA_BITS);
156:
157: assembly {
158: index := add(index, and(index, sub(0, index)))
159: }
160: }
161: }
162: }['170']
170: function query(uint256[GRAPH_MAX + 1] storage graph, uint256 index) private view returns (int256 sum, int256 sumProd) {
171: unchecked {
172: index += 1;
173: while (index > 0) {
174:
175: uint256 packed = graph[index];
176: int256 ad; // <= FOUND
177: int256 ap;
178:
179:
180: if ((packed&(2**(DELTA_BITS-1))) != 0) {
181: ad = int256(packed | ~DELTA_MASK);
182: } else {
183: ad = int256(packed & DELTA_MASK);
184: }
185: ap = int256(packed)>>DELTA_BITS;
186:
187: sum += ad; // <= FOUND
188: sumProd += ap;
189:
190: assembly {
191: index := sub(index, and(index, sub(0, index)))
192: }
193: }
194: }
195: }[NonCritical-77] Local variables declared without ever initializing them. If these are meant to be the default values, make this clear by initializing them as such.
Num of instances: 3
Click to show findings
['92']
92: function Log2NegFrac(uint256 x) private pure returns (uint256) {
93: unchecked {
94: if (x >= 2**128) return 0;
95:
96: require(x > 0);
97:
98: int256 msb = 0;
99: uint256 xc = x;
100: if (xc >= 0x10000000000000000) { xc >>= 64; msb += 64; }
101: if (xc >= 0x100000000) { xc >>= 32; msb += 32; }
102: if (xc >= 0x10000) { xc >>= 16; msb += 16; }
103: if (xc >= 0x100) { xc >>= 8; msb += 8; }
104: if (xc >= 0x10) { xc >>= 4; msb += 4; }
105: if (xc >= 0x4) { xc >>= 2; msb += 2; }
106: if (xc >= 0x2) msb += 1;
107:
108: int256 result = (msb - 128) << 120;
109: uint256 ux = uint256(x) << uint256(127 - msb);
110: for (int256 bit = 0x800000000000000000000000000000; bit > 0; bit >>= 1) {
111: ux *= ux;
112: uint256 b = ux >> 255;
113: ux >>= 127 + b;
114: result += bit * int256 (b); // <= FOUND
115: }
116:
117: return uint256(-result);
118: }
119: }['127']
127: function Exp2NegFrac(uint256 x) private pure returns (uint256) {
128: unchecked {
129: if (x > LOG2NEGFRAC_1) return 0;
130:
131: int256 nx = -int256(x);
132:
133: require (nx < 0);
134:
135:
136:
137:
138:
139: uint256 result = 0x80000000000000000000000000000051;
140:
141:
142: if (nx & 2**119 > 0)
143: result += result * 0xd413cccfe779921165f626cdd52afa89 >> 129;
144: if (nx & 2**118 > 0)
145: result += result * 0xc1bf828c6dc54b7a356918c17217b7bf >> 130;
146: if (nx & 2**117 > 0)
147: result += result * 0xb95c1e3ea8bd6e6fbe4628758a53c90f >> 131;
148: if (nx & 2**116 > 0)
149: result += result * 0xb5586cf9890f6298b92b71842a98364f >> 132;
150: if (nx & 2**115 > 0)
151: result += result * 0xb361a62b0ae875cf8a91d6d19482ffdf >> 133;
152: if (nx & 2**114 > 0)
153: result += result * 0x59347cef00c1dcdef95949ef4537bd3f >> 133;
154: if (nx & 2**113 > 0)
155: result += result * 0x2c7b53f6666adb094cd5c66db9bf481f >> 133;
156: if (nx & 2**112 > 0)
157: result += result * 0x1635f4b5797dac2535627d823b92a89f >> 133;
158: if (nx & 2**111 > 0)
159: result += result * 0xb190db43813d43fe33a5299e5ecf39f >> 133;
160: if (nx & 2**110 > 0)
161: result += result * 0x58c0bc5d19d8a0da437f9134474021f >> 133;
162: if (nx & 2**109 > 0)
163: result += result * 0x2c5e72080a3f425179538ab863cbc1f >> 133;
164: if (nx & 2**108 > 0)
165: result += result * 0x162ebdffb8ed7471c62ce395272e4df >> 133;
166: if (nx & 2**107 > 0)
167: result += result * 0xb17403f73f2dad959a9630122f87df >> 133;
168: if (nx & 2**106 > 0)
169: result += result * 0x58b986fb52923a130b86918d1cf67f >> 133;
170: if (nx & 2**105 > 0)
171: result += result * 0x2c5ca4bdc0a8ea88afab32a52404df >> 133;
172: if (nx & 2**104 > 0)
173: result += result * 0x162e4aaeeb8080c317e9495bd07f9f >> 133;
174: if (nx & 2**103 > 0)
175: result += result * 0xb17236b7935c5ddb03d36fa99f59f >> 133;
176: if (nx & 2**102 > 0)
177: result += result * 0x58b913abd8d949af9159802f6f93f >> 133;
178: if (nx & 2**101 > 0)
179: result += result * 0x2c5c87e9f0620c1c05b07353a2dbf >> 133;
180: if (nx & 2**100 > 0)
181: result += result * 0x162e4379f933b3f121d40d222ec7f >> 133;
182: if (nx & 2**99 > 0)
183: result += result * 0xb17219e3cdb2ff39429b7982c51f >> 133;
184: if (nx & 2**98 > 0)
185: result += result * 0x58b90c76e7e02c8d1ed758b9459f >> 133;
186: if (nx & 2**97 > 0)
187: result += result * 0x2c5c861cb431ec233c78045054bf >> 133;
188: if (nx & 2**96 > 0)
189: result += result * 0x162e4306aa2970dcdb2ddfa37fff >> 133;
190: if (nx & 2**95 > 0)
191: result += result * 0xb1721816918d7cbbf08d8b65e1f >> 133;
192: if (nx & 2**94 > 0)
193: result += result * 0x58b90c0398d73d284278e4e6f5f >> 133;
194: if (nx & 2**93 > 0)
195: result += result * 0x2c5c85ffe06fbe715456433e0df >> 133;
196: if (nx & 2**92 > 0)
197: result += result * 0x162e42ff7538e7354b033e89f3f >> 133;
198: if (nx & 2**91 > 0)
199: result += result * 0xb17217f9bdcb59a7839db9047f >> 133;
200: if (nx & 2**90 > 0)
201: result += result * 0x58b90bfc63e6b4d461b437ca1f >> 133;
202: if (nx & 2**89 > 0)
203: result += result * 0x2c5c85fe13339c6a8373fff9ff >> 133;
204: if (nx & 2**88 > 0)
205: result += result * 0x162e42ff01e9deb55bb48aaa9f >> 133;
206: if (nx & 2**87 > 0)
207: result += result * 0xb17217f7f08f37ab5036a35bf >> 133;
208: if (nx & 2**86 > 0)
209: result += result * 0x58b90bfbf097ac55c614e999f >> 133;
210: if (nx & 2**85 > 0)
211: result += result * 0x2c5c85fdf65fda4aeab37b55f >> 133;
212: if (nx & 2**84 > 0)
213: result += result * 0x162e42fefab4ee2d7749535ff >> 133;
214: if (nx & 2**83 > 0)
215: result += result * 0xb17217f7d3bb758bc21399ff >> 133;
216: if (nx & 2**82 > 0)
217: result += result * 0x58b90bfbe962bbcde2fd61bf >> 133;
218: if (nx & 2**81 > 0)
219: result += result * 0x2c5c85fdf4929e28f1fbc0bf >> 133;
220: if (nx & 2**80 > 0)
221: result += result * 0x162e42fefa419f24f91d299f >> 133;
222: if (nx & 2**79 > 0)
223: result += result * 0xb17217f7d1ee3969c9667df >> 133;
224: if (nx & 2**78 > 0)
225: result += result * 0x58b90bfbe8ef6cc564d28bf >> 133;
226: if (nx & 2**77 > 0)
227: result += result * 0x2c5c85fdf475ca66d27119f >> 133;
228: if (nx & 2**76 > 0)
229: result += result * 0x162e42fefa3a6a34713a81f >> 133;
230: if (nx & 2**75 > 0)
231: result += result * 0xb17217f7d1d165a7a9dbff >> 133;
232: if (nx & 2**74 > 0)
233: result += result * 0x58b90bfbe8e837d4dcefff >> 133;
234: if (nx & 2**73 > 0)
235: result += result * 0x2c5c85fdf473fd2ab0787f >> 133;
236: if (nx & 2**72 > 0)
237: result += result * 0x162e42fefa39f6e568bc5f >> 133;
238: if (nx & 2**71 > 0)
239: result += result * 0xb17217f7d1cf986b87e3f >> 133;
240: if (nx & 2**70 > 0)
241: result += result * 0x58b90bfbe8e7c485d471f >> 133;
242: if (nx & 2**69 > 0)
243: result += result * 0x2c5c85fdf473e056ee59f >> 133;
244: if (nx & 2**68 > 0)
245: result += result * 0x162e42fefa39efb07835f >> 133;
246: if (nx & 2**67 > 0)
247: result += result * 0xb17217f7d1cf7b97c5df >> 133;
248: if (nx & 2**66 > 0)
249: result += result * 0x58b90bfbe8e7bd50e3ff >> 133;
250: if (nx & 2**65 > 0)
251: result += result * 0x2c5c85fdf473de89b23f >> 133;
252: if (nx & 2**64 > 0)
253: result += result * 0x162e42fefa39ef3d293f >> 133;
254: if (nx & 2**63 > 0)
255: result += result * 0xb17217f7d1cf79ca89f >> 133;
256: if (nx & 2**62 > 0)
257: result += result * 0x58b90bfbe8e7bcdd95f >> 133;
258: if (nx & 2**61 > 0)
259: result += result * 0x2c5c85fdf473de6cdff >> 133;
260: if (nx & 2**60 > 0)
261: result += result * 0x162e42fefa39ef35f5f >> 133;
262: if (nx & 2**59 > 0)
263: result += result * 0xb17217f7d1cf79adbf >> 133;
264: if (nx & 2**58 > 0)
265: result += result * 0x58b90bfbe8e7bcd65f >> 133;
266: if (nx & 2**57 > 0)
267: result += result * 0x2c5c85fdf473de6b1f >> 133;
268: if (nx & 2**56 > 0)
269: result += result * 0x162e42fefa39ef359f >> 133;
270: if (nx & 2**55 > 0)
271: result += result * 0xb17217f7d1cf79abf >> 133;
272: if (nx & 2**54 > 0)
273: result += result * 0x58b90bfbe8e7bcd5f >> 133;
274: if (nx & 2**53 > 0)
275: result += result * 0x2c5c85fdf473de6bf >> 133;
276: if (nx & 2**52 > 0)
277: result += result * 0x162e42fefa39ef35f >> 133;
278: if (nx & 2**51 > 0)
279: result += result * 0xb17217f7d1cf79bf >> 133;
280: if (nx & 2**50 > 0)
281: result += result * 0x58b90bfbe8e7bcdf >> 133;
282: if (nx & 2**49 > 0)
283: result += result * 0x2c5c85fdf473de7f >> 133;
284: if (nx & 2**48 > 0)
285: result += result * 0x162e42fefa39ef3f >> 133;
286: if (nx & 2**47 > 0)
287: result += result * 0xb17217f7d1cf79f >> 133;
288: if (nx & 2**46 > 0)
289: result += result * 0x58b90bfbe8e7bdf >> 133;
290: if (nx & 2**45 > 0)
291: result += result * 0x2c5c85fdf473dff >> 133;
292: if (nx & 2**44 > 0)
293: result += result * 0x162e42fefa39eff >> 133;
294: if (nx & 2**43 > 0)
295: result += result * 0xb17217f7d1cf7f >> 133;
296: if (nx & 2**42 > 0)
297: result += result * 0x58b90bfbe8e7bf >> 133;
298: if (nx & 2**41 > 0)
299: result += result * 0x2c5c85fdf473df >> 133;
300: if (nx & 2**40 > 0)
301: result += result * 0x162e42fefa39ff >> 133;
302: if (nx & 2**39 > 0)
303: result += result * 0xb17217f7d1cff >> 133;
304: if (nx & 2**38 > 0)
305: result += result * 0x58b90bfbe8e7f >> 133;
306: if (nx & 2**37 > 0)
307: result += result * 0x2c5c85fdf473f >> 133;
308: if (nx & 2**36 > 0)
309: result += result * 0x162e42fefa39f >> 133;
310: if (nx & 2**35 > 0)
311: result += result * 0xb17217f7d1df >> 133;
312: if (nx & 2**34 > 0)
313: result += result * 0x58b90bfbe8ff >> 133;
314: if (nx & 2**33 > 0)
315: result += result * 0x2c5c85fdf47f >> 133;
316: if (nx & 2**32 > 0)
317: result += result * 0x162e42fefa3f >> 133;
318: if (nx & 2**31 > 0)
319: result += result * 0xb17217f7d1f >> 133;
320: if (nx & 2**30 > 0)
321: result += result * 0x58b90bfbe9f >> 133;
322: if (nx & 2**29 > 0)
323: result += result * 0x2c5c85fdf5f >> 133;
324: if (nx & 2**28 > 0)
325: result += result * 0x162e42fefbf >> 133;
326: if (nx & 2**27 > 0)
327: result += result * 0xb17217f7df >> 133;
328: if (nx & 2**26 > 0)
329: result += result * 0x58b90bfbff >> 133;
330: if (nx & 2**25 > 0)
331: result += result * 0x2c5c85fdff >> 133;
332: if (nx & 2**24 > 0)
333: result += result * 0x162e42feff >> 133;
334: if (nx & 2**23 > 0)
335: result += result * 0xb17217f7f >> 133;
336: if (nx & 2**22 > 0)
337: result += result * 0x58b90bfbf >> 133;
338: if (nx & 2**21 > 0)
339: result += result * 0x2c5c85fdf >> 133;
340: if (nx & 2**20 > 0)
341: result += result * 0x162e42fff >> 133;
342: if (nx & 2**19 > 0)
343: result += result * 0xb17217ff >> 133;
344: if (nx & 2**18 > 0)
345: result += result * 0x58b90bff >> 133;
346: if (nx & 2**17 > 0)
347: result += result * 0x2c5c85ff >> 133;
348: if (nx & 2**16 > 0)
349: result += result * 0x162e42ff >> 133;
350: if (nx & 2**15 > 0)
351: result += result * 0xb17217f >> 133;
352: if (nx & 2**14 > 0)
353: result += result * 0x58b90bf >> 133;
354: if (nx & 2**13 > 0)
355: result += result * 0x2c5c85f >> 133;
356: if (nx & 2**12 > 0)
357: result += result * 0x162e43f >> 133;
358: if (nx & 2**11 > 0)
359: result += result * 0xb1721f >> 133;
360: if (nx & 2**10 > 0)
361: result += result * 0x58b91f >> 133;
362: if (nx & 2**9 > 0)
363: result += result * 0x2c5c9f >> 133;
364: if (nx & 2**8 > 0)
365: result += result * 0x162e5f >> 133;
366: if (nx & 2**7 > 0)
367: result += result * 0xb173f >> 133;
368: if (nx & 2**6 > 0)
369: result += result * 0x58b9f >> 133;
370: if (nx & 2**5 > 0)
371: result += result * 0x2c5df >> 133;
372: if (nx & 2**4 > 0)
373: result += result * 0x162ff >> 133;
374: if (nx & 2**3 > 0)
375: result += result * 0xb17f >> 133;
376: if (nx & 2**2 > 0)
377: result += result * 0x58bf >> 133;
378: if (nx & 2**1 > 0)
379: result += result * 0x2c5f >> 133;
380: if (nx & 2**0 > 0)
381: result += result * 0x163f >> 133;
382:
383:
384: result >>= uint256 (int256 (-1 - (nx >> 120))); // <= FOUND
385: require (result <= uint256 (type(uint128).max));
386:
387: return result;
388: }
389: }['108']
108: function queryStake(Graph storage g, uint256 start, uint256 end) internal view returns (int256) {
109: int256 begDelta;
110: int256 begProd; // <= FOUND
111: int256 endDelta;
112: int256 endProd;
113: unchecked {
114: require (end <= GRAPH_MAX);
115:
116: start--;
117: require (start <= GRAPH_MAX);
118:
119: (begDelta,begProd) = query(g.g, start);
120: (endDelta,endProd) = query(g.g, end);
121:
122: return ((int256(end) * endDelta) - endProd) - ((int256(start) * begDelta) - begProd);
123: }
124: }[NonCritical-78] Contract doesn't follow upgradeable paradigm despite importing Upgradeable OpenZeppelin libraries
The following contracts do not implement upgradeable OpenZeppelin library variants despite the solidity file importing them. This has been flagged to help detect wrong library variants being used accidentally.
Num of instances: 1
A custom implementation of a roundUp operation can introduce errors or inefficiencies, especially in cases involving large numbers, precision requirements, or edge cases. Incorrect rounding logic can lead to subtle yet significant inaccuracies in calculations like pricing, rewards, or token distributions. To ensure correctness and gas efficiency, consider using the mulDivUp function, which performs multiplication and division with proper rounding towards the next integer. This approach leverages well-tested, standardized libraries, ensuring reliability and consistency in results. Adopting mulDivUp reduces the risk of faulty arithmetic while optimizing performance, protecting the protocol from unintended financial discrepancies or potential exploits.
Num of instances: 1
Click to show findings
['265']
265: // <= FOUND
266: return (queried / BLOCK_SCALING_FACTOR).toUint256() + 1; // <= FOUNDThe ERC20 Permit feature, as detailed in EIP-2612, is vulnerable to frontrunning because transactions can be observed in the mempool, allowing malicious actors to duplicate and preempt the transaction. This can be particularly harmful when the permit() is part of a larger contract call sequence, leading to Denial of Service (DOS) by making the subsequent intended contract functionality fail. A workaround includes attempting the permit() call and, if it fails due to frontrunning, continuing with the intended logic if the allowance check passes. Source trust-security
Num of instances: 1
Click to show findings
['27']
27: function permit(address owner, address spender, uint256 amount, uint256 expiry, bytes calldata signature) external returns (bool); // <= FOUNDCache such variables and perform operations on them, if operations include modifications to the state variable(s) then remember to equate the state variable to it's cached counterpart at the end
Num of instances: 4
Click to show findings
['771']
771: function _liquidate(uint256 accountId) internal returns (uint256 debtAmount, uint256 feeInYield, uint256 feeInUnderlying) { // <= FOUND 'function _liquidate'
772:
773: _earmark();
774:
775: _sync(accountId);
776:
777: Account storage account = _accounts[accountId];
778:
779:
780: if (account.debt == 0) {
781: return (0, 0, 0);
782: }
783:
784:
785: uint256 collateralInUnderlying = totalValue(accountId);
786: uint256 collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
787:
788:
789: if (collateralizationRatio > collateralizationLowerBound) { // <= FOUND 'collateralizationLowerBound'
790: return (0, 0, 0);
791: }
792:
793:
794: uint256 repaidAmountInYield = 0;
795: if (account.earmarked > 0) {
796: repaidAmountInYield = _forceRepay(accountId, convertDebtTokensToYield(account.earmarked));
797: }
798:
799: if (account.debt == 0) {
800: return (repaidAmountInYield, 0, 0);
801: }
802:
803:
804: collateralInUnderlying = totalValue(accountId);
805: collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / account.debt;
806:
807: if (collateralizationRatio <= collateralizationLowerBound) { // <= FOUND 'collateralizationLowerBound'
808: uint256 alchemistCurrentCollateralization = normalizeUnderlyingTokensToDebt(_getTotalUnderlyingValue()) * FIXED_POINT_SCALAR / totalDebt;
809:
810: (uint256 liquidationAmount, uint256 debtToBurn, uint256 baseFee) = calculateLiquidation(
811: collateralInUnderlying, account.debt, minimumCollateralization, alchemistCurrentCollateralization, globalMinimumCollateralization, liquidatorFee
812: );
813:
814: uint256 feeBonus = debtToBurn * liquidatorFee / BPS;
815: uint256 adjustedLiquidationAmount = convertDebtTokensToYield(liquidationAmount);
816: uint256 adjustedDebtToBurn = convertDebtTokensToYield(debtToBurn);
817: debtAmount = adjustedLiquidationAmount;
818: feeInYield = convertDebtTokensToYield(baseFee);
819:
820:
821: account.collateralBalance = account.collateralBalance > adjustedLiquidationAmount ? account.collateralBalance - adjustedLiquidationAmount : 0;
822:
823:
824: _subDebt(accountId, debtToBurn);
825:
826:
827: TokenUtils.safeTransfer(yieldToken, transmuter, adjustedDebtToBurn);
828:
829: if (feeInYield > 0) {
830:
831: TokenUtils.safeTransfer(yieldToken, msg.sender, feeInYield);
832: }
833:
834:
835:
836: if (feeBonus > 0) {
837: uint256 vaultBalance = IFeeVault(alchemistFeeVault).totalDeposits();
838: if (vaultBalance > 0) {
839: feeInUnderlying = vaultBalance > feeBonus ? feeBonus : vaultBalance;
840: IFeeVault(alchemistFeeVault).withdraw(msg.sender, feeInUnderlying);
841: }
842: }
843: }
844:
845: return (debtAmount + repaidAmountInYield, feeInYield, feeInUnderlying);
846: }['200']
200: function claimRedemption(uint256 id) external { // <= FOUND 'function claimRedemption'
201: StakingPosition storage position = _positions[id];
202:
203: if (position.maturationBlock == 0) {
204: revert PositionNotFound();
205: }
206:
207: uint256 transmutationTime = position.maturationBlock - position.startBlock;
208: uint256 blocksLeft = position.maturationBlock > block.number ? position.maturationBlock - block.number : 0;
209: uint256 amountNottransmuted = blocksLeft > 0 ? position.amount * blocksLeft / transmutationTime : 0;
210: uint256 amountTransmuted = position.amount - amountNottransmuted;
211:
212: if (_requireOwned(id) != msg.sender) {
213: revert CallerNotOwner();
214: }
215:
216:
217: _burn(id);
218:
219:
220: uint256 yieldTokenBalance = TokenUtils.safeBalanceOf(alchemist.yieldToken(), address(this));
221: uint256 debtValue = alchemist.convertYieldTokensToDebt(yieldTokenBalance);
222: uint256 amountToRedeem = amountTransmuted > debtValue ? amountTransmuted - debtValue : 0;
223: if (amountToRedeem > 0) alchemist.redeem(amountToRedeem);
224:
225: uint256 feeAmount = amountTransmuted * transmutationFee / BPS;
226: uint256 claimAmount = amountTransmuted - feeAmount;
227:
228: uint256 syntheticFee = amountNottransmuted * exitFee / BPS;
229: uint256 syntheticReturned = amountNottransmuted - syntheticFee;
230:
231:
232: if (blocksLeft > 0) _updateStakingGraph(-position.amount.toInt256() * BLOCK_SCALING_FACTOR / transmutationTime.toInt256(), blocksLeft);
233:
234:
235:
236: uint256 badDebtRatio = alchemist.totalSyntheticsIssued() * 10**TokenUtils.expectDecimals(alchemist.yieldToken()) / alchemist.getTotalUnderlyingValue();
237:
238: if (badDebtRatio > 1e18) {
239: claimAmount = claimAmount * FIXED_POINT_SCALAR / badDebtRatio;
240: feeAmount = feeAmount * FIXED_POINT_SCALAR / badDebtRatio;
241: }
242:
243: TokenUtils.safeTransfer(alchemist.yieldToken(), msg.sender, alchemist.convertDebtTokensToYield(claimAmount));
244: TokenUtils.safeTransfer(alchemist.yieldToken(), protocolFeeReceiver, alchemist.convertDebtTokensToYield(feeAmount)); // <= FOUND 'protocolFeeReceiver'
245:
246: TokenUtils.safeTransfer(syntheticToken, msg.sender, syntheticReturned);
247: TokenUtils.safeTransfer(syntheticToken, protocolFeeReceiver, syntheticFee); // <= FOUND 'protocolFeeReceiver'
248:
249:
250: TokenUtils.safeBurn(syntheticToken, amountTransmuted);
251:
252: totalLocked -= position.amount;
253:
254: emit PositionClaimed(msg.sender, claimAmount, syntheticReturned);
255:
256: delete _positions[id];
257: }['974']
974: function _sync(uint256 tokenId) internal { // <= FOUND 'function _sync'
975: Account storage account = _accounts[tokenId];
976: RedemptionInfo memory previousRedemption = _redemptions[lastRedemptionBlock];
977:
978: uint256 debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, _earmarkWeight - account.lastAccruedEarmarkWeight); // <= FOUND '_earmarkWeight'
979: uint256 earmarkedState = account.earmarked + debtToEarmark;
980:
981:
982: uint256 earmarkToRedeem;
983: uint256 earmarkPreviousState;
984: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) {
985: debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, previousRedemption.earmarkWeight - account.lastAccruedEarmarkWeight);
986:
987: earmarkPreviousState = account.earmarked + debtToEarmark;
988: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight);
989: } else {
990: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedState, _redemptionWeight - account.lastAccruedRedemptionWeight);
991: }
992:
993: uint256 collateralToRemove = PositionDecay.ScaleByWeightDelta(account.rawLocked, _collateralWeight - account.lastCollateralWeight);
994:
995:
996: account.earmarked = earmarkedState - earmarkToRedeem;
997: account.debt -= earmarkToRedeem;
998: account.lastAccruedRedemptionWeight = _redemptionWeight;
999: account.lastAccruedEarmarkWeight = _earmarkWeight; // <= FOUND '_earmarkWeight'
1000:
1001: account.collateralBalance -= collateralToRemove;
1002: account.rawLocked -= collateralToRemove;
1003: account.lastCollateralWeight = _collateralWeight;
1004: }['172']
172: function createRedemption(uint256 syntheticDepositAmount) external { // <= FOUND 'function createRedemption'
173: if (syntheticDepositAmount == 0) {
174: revert DepositZeroAmount();
175: }
176:
177: if (totalLocked + syntheticDepositAmount > depositCap) { // <= FOUND 'totalLocked'
178: revert DepositCapReached();
179: }
180:
181: if (totalLocked + syntheticDepositAmount > alchemist.totalSyntheticsIssued()) { // <= FOUND 'totalLocked'
182: revert DepositCapReached();
183: }
184:
185: TokenUtils.safeTransferFrom(syntheticToken, msg.sender, address(this), syntheticDepositAmount);
186:
187: _positions[++_nonce] = StakingPosition(syntheticDepositAmount, block.number, block.number + timeToTransmute);
188:
189:
190: _updateStakingGraph(syntheticDepositAmount.toInt256() * BLOCK_SCALING_FACTOR / timeToTransmute.toInt256(), timeToTransmute);
191:
192: totalLocked += syntheticDepositAmount; // <= FOUND 'totalLocked'
193:
194: _mint(msg.sender, _nonce);
195:
196: emit PositionCreated(msg.sender, syntheticDepositAmount, _nonce);
197: }Reading state variables takes alot of gas to do, so if you have a conditional statement regarding a state varaible alongide others which are not related to state vars, it is advisable to have conditions which do not involve state vars be declared first in the overall conditional so unnecessary state variable lookups can be avoided.
Num of instances: 2
Click to show findings
['29']
29: if (token.code.length == 0 || !success || data.length < 32) {['64']
64: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) {In instances found where either += or -= are used against state variables use x = x + y instead
Num of instances: 9
Click to show findings
['1007']
1007: function _earmark() internal {
1008: if (totalDebt == 0) return;
1009:
1010: if (block.number > lastEarmarkBlock) {
1011: uint256 amount = ITransmuter(transmuter).queryGraph(lastEarmarkBlock + 1, block.number);
1012: if (amount > 0) {
1013: _earmarkWeight += PositionDecay.WeightIncrement(amount, totalDebt - cumulativeEarmarked);
1014: cumulativeEarmarked += amount; // <= FOUND
1015: }
1016:
1017: lastEarmarkBlock = block.number;
1018: }
1019: }['573']
573: function redeem(uint256 amount) external onlyTransmuter {
574: _earmark();
575:
576: _redemptionWeight += PositionDecay.WeightIncrement(amount, cumulativeEarmarked);
577:
578:
579: uint256 collRedeemed = convertDebtTokensToYield(amount);
580: uint256 feeCollateral = collRedeemed * protocolFee / BPS;
581: uint256 totalOut = collRedeemed + feeCollateral;
582:
583:
584: uint256 old = _totalLocked;
585: _totalLocked = old - totalOut;
586: _collateralWeight += PositionDecay.WeightIncrement(totalOut, old);
587: cumulativeEarmarked -= amount; // <= FOUND
588: totalDebt -= amount; // <= FOUND
589: totalSyntheticsIssued -= amount; // <= FOUND
590:
591: lastRedemptionBlock = block.number;
592:
593: _redemptions[block.number] = RedemptionInfo(cumulativeEarmarked, totalDebt, _earmarkWeight);
594:
595: TokenUtils.safeTransfer(yieldToken, transmuter, collRedeemed);
596: TokenUtils.safeTransfer(yieldToken, protocolFeeReceiver, feeCollateral);
597:
598: emit Redemption(amount);
599: }['852']
852: function _addDebt(uint256 tokenId, uint256 amount) internal {
853: Account storage account = _accounts[tokenId];
854: account.debt += amount;
855: totalDebt += amount; // <= FOUND
856:
857:
858: uint256 toLock = convertDebtTokensToYield(amount) * minimumCollateralization / FIXED_POINT_SCALAR;
859:
860: if (account.freeCollateral < toLock) revert Undercollateralized();
861: account.freeCollateral -= toLock;
862: account.rawLocked += toLock;
863: _totalLocked += toLock; // <= FOUND
864: }['869']
869: function _subDebt(uint256 tokenId, uint256 amount) internal {
870: Account storage account = _accounts[tokenId];
871: account.debt -= amount;
872: totalDebt -= amount; // <= FOUND
873:
874:
875: uint256 toFree = convertDebtTokensToYield(amount) * minimumCollateralization / FIXED_POINT_SCALAR;
876:
877:
878: if (toFree > _totalLocked) {
879: toFree = _totalLocked;
880: }
881:
882: _totalLocked -= toFree; // <= FOUND
883: account.rawLocked -= toFree;
884: account.freeCollateral += toFree;
885: }['713']
713: function _mint(uint256 tokenId, uint256 amount, address recipient) internal {
714: _addDebt(tokenId, amount);
715:
716: totalSyntheticsIssued += amount; // <= FOUND
717:
718:
719: _validate(tokenId);
720:
721: _accounts[tokenId].lastMintBlock = block.number;
722:
723:
724: TokenUtils.safeMint(debtToken, recipient, amount);
725:
726: emit Mint(tokenId, amount, recipient);
727: }['447']
447: function burn(uint256 amount, uint256 recipientId) external returns (uint256) {
448: _checkArgument(amount > 0);
449: _checkForValidAccountId(recipientId);
450:
451:
452: if (block.number == _accounts[recipientId].lastMintBlock) revert CannotRepayOnMintBlock();
453:
454:
455: _earmark();
456:
457:
458: _sync(recipientId);
459:
460: uint256 debt;
461:
462: _checkState((debt = _accounts[recipientId].debt - _accounts[recipientId].earmarked) > 0);
463:
464: uint256 credit = amount > debt ? debt : amount;
465:
466:
467: if (credit > totalDebt - ITransmuter(transmuter).totalLocked()) {
468: revert BurnLimitExceeded(credit, totalDebt - ITransmuter(transmuter).totalLocked());
469: }
470:
471:
472: TokenUtils.safeBurnFrom(debtToken, msg.sender, credit);
473:
474:
475: _accounts[recipientId].collateralBalance -= convertDebtTokensToYield(credit) * protocolFee / BPS;
476:
477:
478: _subDebt(recipientId, credit);
479:
480: totalSyntheticsIssued -= credit; // <= FOUND
481:
482: emit Burn(msg.sender, credit, recipientId);
483:
484: return credit;
485: }['50']
50: function addStake(Graph storage g, int256 amount, uint256 start, uint256 duration) internal {
51: unchecked {
52: require(amount <= DELTA_MAX && amount >= DELTA_MIN);
53: require(start < GRAPH_MAX-1);
54:
55: uint256 expiration = start + duration;
56: require(expiration < GRAPH_MAX-1);
57:
58: uint256 graphSize = g.size;
59:
60:
61:
62: uint256 newSize = expiration + 2;
63: if (newSize >= graphSize) {
64:
65: newSize |= newSize >> 1;
66: newSize |= newSize >> 2;
67: newSize |= newSize >> 4;
68: newSize |= newSize >> 8;
69: newSize |= newSize >> 16;
70: if (GRAPH_MAX > 2**32) {
71: newSize |= newSize >> 32;
72: newSize |= newSize >> 64;
73: newSize |= newSize >> 128;
74: }
75: newSize++;
76:
77:
78:
79:
80: require (newSize <= GRAPH_MAX);
81:
82: if (graphSize != 0) {
83:
84: uint256 copy = g.g[graphSize];
85: while (graphSize <= newSize) {
86: g.g[graphSize] = copy;
87: graphSize += graphSize; // <= FOUND
88: }
89: }
90: graphSize = newSize;
91: g.size = newSize;
92: }
93:
94:
95: update(g.g, start + 1, graphSize, amount, amount * int256(start));
96: update(g.g, expiration + 1, graphSize, -amount, -amount * int256(expiration));
97: }
98: }['172']
172: function createRedemption(uint256 syntheticDepositAmount) external {
173: if (syntheticDepositAmount == 0) {
174: revert DepositZeroAmount();
175: }
176:
177: if (totalLocked + syntheticDepositAmount > depositCap) {
178: revert DepositCapReached();
179: }
180:
181: if (totalLocked + syntheticDepositAmount > alchemist.totalSyntheticsIssued()) {
182: revert DepositCapReached();
183: }
184:
185: TokenUtils.safeTransferFrom(syntheticToken, msg.sender, address(this), syntheticDepositAmount);
186:
187: _positions[++_nonce] = StakingPosition(syntheticDepositAmount, block.number, block.number + timeToTransmute);
188:
189:
190: _updateStakingGraph(syntheticDepositAmount.toInt256() * BLOCK_SCALING_FACTOR / timeToTransmute.toInt256(), timeToTransmute);
191:
192: totalLocked += syntheticDepositAmount; // <= FOUND
193:
194: _mint(msg.sender, _nonce);
195:
196: emit PositionCreated(msg.sender, syntheticDepositAmount, _nonce);
197: }['200']
200: function claimRedemption(uint256 id) external {
201: StakingPosition storage position = _positions[id];
202:
203: if (position.maturationBlock == 0) {
204: revert PositionNotFound();
205: }
206:
207: uint256 transmutationTime = position.maturationBlock - position.startBlock;
208: uint256 blocksLeft = position.maturationBlock > block.number ? position.maturationBlock - block.number : 0;
209: uint256 amountNottransmuted = blocksLeft > 0 ? position.amount * blocksLeft / transmutationTime : 0;
210: uint256 amountTransmuted = position.amount - amountNottransmuted;
211:
212: if (_requireOwned(id) != msg.sender) {
213: revert CallerNotOwner();
214: }
215:
216:
217: _burn(id);
218:
219:
220: uint256 yieldTokenBalance = TokenUtils.safeBalanceOf(alchemist.yieldToken(), address(this));
221: uint256 debtValue = alchemist.convertYieldTokensToDebt(yieldTokenBalance);
222: uint256 amountToRedeem = amountTransmuted > debtValue ? amountTransmuted - debtValue : 0;
223: if (amountToRedeem > 0) alchemist.redeem(amountToRedeem);
224:
225: uint256 feeAmount = amountTransmuted * transmutationFee / BPS;
226: uint256 claimAmount = amountTransmuted - feeAmount;
227:
228: uint256 syntheticFee = amountNottransmuted * exitFee / BPS;
229: uint256 syntheticReturned = amountNottransmuted - syntheticFee;
230:
231:
232: if (blocksLeft > 0) _updateStakingGraph(-position.amount.toInt256() * BLOCK_SCALING_FACTOR / transmutationTime.toInt256(), blocksLeft);
233:
234:
235:
236: uint256 badDebtRatio = alchemist.totalSyntheticsIssued() * 10**TokenUtils.expectDecimals(alchemist.yieldToken()) / alchemist.getTotalUnderlyingValue();
237:
238: if (badDebtRatio > 1e18) {
239: claimAmount = claimAmount * FIXED_POINT_SCALAR / badDebtRatio;
240: feeAmount = feeAmount * FIXED_POINT_SCALAR / badDebtRatio;
241: }
242:
243: TokenUtils.safeTransfer(alchemist.yieldToken(), msg.sender, alchemist.convertDebtTokensToYield(claimAmount));
244: TokenUtils.safeTransfer(alchemist.yieldToken(), protocolFeeReceiver, alchemist.convertDebtTokensToYield(feeAmount));
245:
246: TokenUtils.safeTransfer(syntheticToken, msg.sender, syntheticReturned);
247: TokenUtils.safeTransfer(syntheticToken, protocolFeeReceiver, syntheticFee);
248:
249:
250: TokenUtils.safeBurn(syntheticToken, amountTransmuted);
251:
252: totalLocked -= position.amount; // <= FOUND
253:
254: emit PositionClaimed(msg.sender, claimAmount, syntheticReturned);
255:
256: delete _positions[id];
257: }[Gas-4] There is a 32 byte length threshold for error strings, strings longer than this consume more gas
In require statements found which are longer than 32 characters, shorten these to 32 character or less
Num of instances: 1
Click to show findings
['55']
55: function mint(address _recipient, uint256 _amount) external onlyWhitelisted {
56: require(!paused[msg.sender], "AlETH: Alchemist is currently paused."); // <= FOUND
57: hasMinted[msg.sender] = hasMinted[msg.sender] + _amount;
58: _mint(_recipient, _amount);
59: }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: 4
Click to show findings
['84']
84: function tokenURI(uint256 tokenId) public view override returns (string memory) ['160']
160: function tokenURI(uint256 id) public view override returns (string memory) ['488']
488: function repay(uint256 amount, uint256 recipientTokenId) public returns (uint256) ['116']
116: function lowerHasMinted(uint256 amount) public onlyWhitelisted 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: 3
Click to show findings
['689']
689: uint8 decimals = TokenUtils.expectDecimals(yieldToken); // <= FOUND['32']
32:
35: function uint256ToUint128(uint256 y) internal pure returns (uint128 z) { // <= FOUND['42']
42:
45: function uint128ToUint256(uint128 y) internal pure returns (uint256 z) { // <= FOUNDReplace spotted instances with != 0 for uints as this uses less gas
Num of instances: 150
Click to show findings
['352']
352: if (collateral > 0) totalUnderlying += convertYieldTokensToUnderlying(collateral); // <= FOUND['359']
359: _checkArgument(amount > 0); // <= FOUND['462']
462:
463: _checkState((debt = _accounts[recipientId].debt - _accounts[recipientId].earmarked) > 0); // <= FOUND['505']
505:
506: _checkState((debt = account.debt) > 0); // <= FOUND['533']
533: if (yieldAmount > 0) { // <= FOUND['564']
564: if (totalAmountLiquidated > 0) { // <= FOUND['795']
795: if (account.earmarked > 0) { // <= FOUND['829']
829: if (feeInYield > 0) { // <= FOUND['836']
836:
837:
838: if (feeBonus > 0) { // <= FOUND['838']
838: if (vaultBalance > 0) { // <= FOUND['1012']
1012: if (amount > 0) { // <= FOUND['96']
96: require(x > 0); // <= FOUND['110']
110: for (int256 bit = 0x800000000000000000000000000000; bit > 0; bit >>= 1) { // <= FOUND['142']
142:
143: if (nx & 2**119 > 0) // <= FOUND
144: result += result * 0xd413cccfe779921165f626cdd52afa89 >> 129;['144']
144: if (nx & 2**118 > 0) // <= FOUND
145: result += result * 0xc1bf828c6dc54b7a356918c17217b7bf >> 130;['146']
146: if (nx & 2**117 > 0) // <= FOUND
147: result += result * 0xb95c1e3ea8bd6e6fbe4628758a53c90f >> 131;['148']
148: if (nx & 2**116 > 0) // <= FOUND
149: result += result * 0xb5586cf9890f6298b92b71842a98364f >> 132;['150']
150: if (nx & 2**115 > 0) // <= FOUND
151: result += result * 0xb361a62b0ae875cf8a91d6d19482ffdf >> 133;['152']
152: if (nx & 2**114 > 0) // <= FOUND
153: result += result * 0x59347cef00c1dcdef95949ef4537bd3f >> 133;['154']
154: if (nx & 2**113 > 0) // <= FOUND
155: result += result * 0x2c7b53f6666adb094cd5c66db9bf481f >> 133;['156']
156: if (nx & 2**112 > 0) // <= FOUND
157: result += result * 0x1635f4b5797dac2535627d823b92a89f >> 133;['158']
158: if (nx & 2**111 > 0) // <= FOUND
159: result += result * 0xb190db43813d43fe33a5299e5ecf39f >> 133;['160']
160: if (nx & 2**110 > 0) // <= FOUND
161: result += result * 0x58c0bc5d19d8a0da437f9134474021f >> 133;['162']
162: if (nx & 2**109 > 0) // <= FOUND
163: result += result * 0x2c5e72080a3f425179538ab863cbc1f >> 133;['164']
164: if (nx & 2**108 > 0) // <= FOUND
165: result += result * 0x162ebdffb8ed7471c62ce395272e4df >> 133;['166']
166: if (nx & 2**107 > 0) // <= FOUND
167: result += result * 0xb17403f73f2dad959a9630122f87df >> 133;['168']
168: if (nx & 2**106 > 0) // <= FOUND
169: result += result * 0x58b986fb52923a130b86918d1cf67f >> 133;['170']
170: if (nx & 2**105 > 0) // <= FOUND
171: result += result * 0x2c5ca4bdc0a8ea88afab32a52404df >> 133;['172']
172: if (nx & 2**104 > 0) // <= FOUND
173: result += result * 0x162e4aaeeb8080c317e9495bd07f9f >> 133;['174']
174: if (nx & 2**103 > 0) // <= FOUND
175: result += result * 0xb17236b7935c5ddb03d36fa99f59f >> 133;['176']
176: if (nx & 2**102 > 0) // <= FOUND
177: result += result * 0x58b913abd8d949af9159802f6f93f >> 133;['178']
178: if (nx & 2**101 > 0) // <= FOUND
179: result += result * 0x2c5c87e9f0620c1c05b07353a2dbf >> 133;['180']
180: if (nx & 2**100 > 0) // <= FOUND
181: result += result * 0x162e4379f933b3f121d40d222ec7f >> 133;['182']
182: if (nx & 2**99 > 0) // <= FOUND
183: result += result * 0xb17219e3cdb2ff39429b7982c51f >> 133;['184']
184: if (nx & 2**98 > 0) // <= FOUND
185: result += result * 0x58b90c76e7e02c8d1ed758b9459f >> 133;['186']
186: if (nx & 2**97 > 0) // <= FOUND
187: result += result * 0x2c5c861cb431ec233c78045054bf >> 133;['188']
188: if (nx & 2**96 > 0) // <= FOUND
189: result += result * 0x162e4306aa2970dcdb2ddfa37fff >> 133;['190']
190: if (nx & 2**95 > 0) // <= FOUND
191: result += result * 0xb1721816918d7cbbf08d8b65e1f >> 133;['192']
192: if (nx & 2**94 > 0) // <= FOUND
193: result += result * 0x58b90c0398d73d284278e4e6f5f >> 133;['194']
194: if (nx & 2**93 > 0) // <= FOUND
195: result += result * 0x2c5c85ffe06fbe715456433e0df >> 133;['196']
196: if (nx & 2**92 > 0) // <= FOUND
197: result += result * 0x162e42ff7538e7354b033e89f3f >> 133;['198']
198: if (nx & 2**91 > 0) // <= FOUND
199: result += result * 0xb17217f9bdcb59a7839db9047f >> 133;['200']
200: if (nx & 2**90 > 0) // <= FOUND
201: result += result * 0x58b90bfc63e6b4d461b437ca1f >> 133;['202']
202: if (nx & 2**89 > 0) // <= FOUND
203: result += result * 0x2c5c85fe13339c6a8373fff9ff >> 133;['204']
204: if (nx & 2**88 > 0) // <= FOUND
205: result += result * 0x162e42ff01e9deb55bb48aaa9f >> 133;['206']
206: if (nx & 2**87 > 0) // <= FOUND
207: result += result * 0xb17217f7f08f37ab5036a35bf >> 133;['208']
208: if (nx & 2**86 > 0) // <= FOUND
209: result += result * 0x58b90bfbf097ac55c614e999f >> 133;['210']
210: if (nx & 2**85 > 0) // <= FOUND
211: result += result * 0x2c5c85fdf65fda4aeab37b55f >> 133;['212']
212: if (nx & 2**84 > 0) // <= FOUND
213: result += result * 0x162e42fefab4ee2d7749535ff >> 133;['214']
214: if (nx & 2**83 > 0) // <= FOUND
215: result += result * 0xb17217f7d3bb758bc21399ff >> 133;['216']
216: if (nx & 2**82 > 0) // <= FOUND
217: result += result * 0x58b90bfbe962bbcde2fd61bf >> 133;['218']
218: if (nx & 2**81 > 0) // <= FOUND
219: result += result * 0x2c5c85fdf4929e28f1fbc0bf >> 133;['220']
220: if (nx & 2**80 > 0) // <= FOUND
221: result += result * 0x162e42fefa419f24f91d299f >> 133;['222']
222: if (nx & 2**79 > 0) // <= FOUND
223: result += result * 0xb17217f7d1ee3969c9667df >> 133;['224']
224: if (nx & 2**78 > 0) // <= FOUND
225: result += result * 0x58b90bfbe8ef6cc564d28bf >> 133;['226']
226: if (nx & 2**77 > 0) // <= FOUND
227: result += result * 0x2c5c85fdf475ca66d27119f >> 133;['228']
228: if (nx & 2**76 > 0) // <= FOUND
229: result += result * 0x162e42fefa3a6a34713a81f >> 133;['230']
230: if (nx & 2**75 > 0) // <= FOUND
231: result += result * 0xb17217f7d1d165a7a9dbff >> 133;['232']
232: if (nx & 2**74 > 0) // <= FOUND
233: result += result * 0x58b90bfbe8e837d4dcefff >> 133;['234']
234: if (nx & 2**73 > 0) // <= FOUND
235: result += result * 0x2c5c85fdf473fd2ab0787f >> 133;['236']
236: if (nx & 2**72 > 0) // <= FOUND
237: result += result * 0x162e42fefa39f6e568bc5f >> 133;['238']
238: if (nx & 2**71 > 0) // <= FOUND
239: result += result * 0xb17217f7d1cf986b87e3f >> 133;['240']
240: if (nx & 2**70 > 0) // <= FOUND
241: result += result * 0x58b90bfbe8e7c485d471f >> 133;['242']
242: if (nx & 2**69 > 0) // <= FOUND
243: result += result * 0x2c5c85fdf473e056ee59f >> 133;['244']
244: if (nx & 2**68 > 0) // <= FOUND
245: result += result * 0x162e42fefa39efb07835f >> 133;['246']
246: if (nx & 2**67 > 0) // <= FOUND
247: result += result * 0xb17217f7d1cf7b97c5df >> 133;['248']
248: if (nx & 2**66 > 0) // <= FOUND
249: result += result * 0x58b90bfbe8e7bd50e3ff >> 133;['250']
250: if (nx & 2**65 > 0) // <= FOUND
251: result += result * 0x2c5c85fdf473de89b23f >> 133;['252']
252: if (nx & 2**64 > 0) // <= FOUND
253: result += result * 0x162e42fefa39ef3d293f >> 133;['254']
254: if (nx & 2**63 > 0) // <= FOUND
255: result += result * 0xb17217f7d1cf79ca89f >> 133;['256']
256: if (nx & 2**62 > 0) // <= FOUND
257: result += result * 0x58b90bfbe8e7bcdd95f >> 133;['258']
258: if (nx & 2**61 > 0) // <= FOUND
259: result += result * 0x2c5c85fdf473de6cdff >> 133;['260']
260: if (nx & 2**60 > 0) // <= FOUND
261: result += result * 0x162e42fefa39ef35f5f >> 133;['262']
262: if (nx & 2**59 > 0) // <= FOUND
263: result += result * 0xb17217f7d1cf79adbf >> 133;['264']
264: if (nx & 2**58 > 0) // <= FOUND
265: result += result * 0x58b90bfbe8e7bcd65f >> 133;['266']
266: if (nx & 2**57 > 0) // <= FOUND
267: result += result * 0x2c5c85fdf473de6b1f >> 133;['268']
268: if (nx & 2**56 > 0) // <= FOUND
269: result += result * 0x162e42fefa39ef359f >> 133;['270']
270: if (nx & 2**55 > 0) // <= FOUND
271: result += result * 0xb17217f7d1cf79abf >> 133;['272']
272: if (nx & 2**54 > 0) // <= FOUND
273: result += result * 0x58b90bfbe8e7bcd5f >> 133;['274']
274: if (nx & 2**53 > 0) // <= FOUND
275: result += result * 0x2c5c85fdf473de6bf >> 133;['276']
276: if (nx & 2**52 > 0) // <= FOUND
277: result += result * 0x162e42fefa39ef35f >> 133;['278']
278: if (nx & 2**51 > 0) // <= FOUND
279: result += result * 0xb17217f7d1cf79bf >> 133;['280']
280: if (nx & 2**50 > 0) // <= FOUND
281: result += result * 0x58b90bfbe8e7bcdf >> 133;['282']
282: if (nx & 2**49 > 0) // <= FOUND
283: result += result * 0x2c5c85fdf473de7f >> 133;['284']
284: if (nx & 2**48 > 0) // <= FOUND
285: result += result * 0x162e42fefa39ef3f >> 133;['286']
286: if (nx & 2**47 > 0) // <= FOUND
287: result += result * 0xb17217f7d1cf79f >> 133;['288']
288: if (nx & 2**46 > 0) // <= FOUND
289: result += result * 0x58b90bfbe8e7bdf >> 133;['290']
290: if (nx & 2**45 > 0) // <= FOUND
291: result += result * 0x2c5c85fdf473dff >> 133;['292']
292: if (nx & 2**44 > 0) // <= FOUND
293: result += result * 0x162e42fefa39eff >> 133;['294']
294: if (nx & 2**43 > 0) // <= FOUND
295: result += result * 0xb17217f7d1cf7f >> 133;['296']
296: if (nx & 2**42 > 0) // <= FOUND
297: result += result * 0x58b90bfbe8e7bf >> 133;['298']
298: if (nx & 2**41 > 0) // <= FOUND
299: result += result * 0x2c5c85fdf473df >> 133;['300']
300: if (nx & 2**40 > 0) // <= FOUND
301: result += result * 0x162e42fefa39ff >> 133;['302']
302: if (nx & 2**39 > 0) // <= FOUND
303: result += result * 0xb17217f7d1cff >> 133;['304']
304: if (nx & 2**38 > 0) // <= FOUND
305: result += result * 0x58b90bfbe8e7f >> 133;['306']
306: if (nx & 2**37 > 0) // <= FOUND
307: result += result * 0x2c5c85fdf473f >> 133;['308']
308: if (nx & 2**36 > 0) // <= FOUND
309: result += result * 0x162e42fefa39f >> 133;['310']
310: if (nx & 2**35 > 0) // <= FOUND
311: result += result * 0xb17217f7d1df >> 133;['312']
312: if (nx & 2**34 > 0) // <= FOUND
313: result += result * 0x58b90bfbe8ff >> 133;['314']
314: if (nx & 2**33 > 0) // <= FOUND
315: result += result * 0x2c5c85fdf47f >> 133;['316']
316: if (nx & 2**32 > 0) // <= FOUND
317: result += result * 0x162e42fefa3f >> 133;['318']
318: if (nx & 2**31 > 0) // <= FOUND
319: result += result * 0xb17217f7d1f >> 133;['320']
320: if (nx & 2**30 > 0) // <= FOUND
321: result += result * 0x58b90bfbe9f >> 133;['322']
322: if (nx & 2**29 > 0) // <= FOUND
323: result += result * 0x2c5c85fdf5f >> 133;['324']
324: if (nx & 2**28 > 0) // <= FOUND
325: result += result * 0x162e42fefbf >> 133;['326']
326: if (nx & 2**27 > 0) // <= FOUND
327: result += result * 0xb17217f7df >> 133;['328']
328: if (nx & 2**26 > 0) // <= FOUND
329: result += result * 0x58b90bfbff >> 133;['330']
330: if (nx & 2**25 > 0) // <= FOUND
331: result += result * 0x2c5c85fdff >> 133;['332']
332: if (nx & 2**24 > 0) // <= FOUND
333: result += result * 0x162e42feff >> 133;['334']
334: if (nx & 2**23 > 0) // <= FOUND
335: result += result * 0xb17217f7f >> 133;['336']
336: if (nx & 2**22 > 0) // <= FOUND
337: result += result * 0x58b90bfbf >> 133;['338']
338: if (nx & 2**21 > 0) // <= FOUND
339: result += result * 0x2c5c85fdf >> 133;['340']
340: if (nx & 2**20 > 0) // <= FOUND
341: result += result * 0x162e42fff >> 133;['342']
342: if (nx & 2**19 > 0) // <= FOUND
343: result += result * 0xb17217ff >> 133;['344']
344: if (nx & 2**18 > 0) // <= FOUND
345: result += result * 0x58b90bff >> 133;['346']
346: if (nx & 2**17 > 0) // <= FOUND
347: result += result * 0x2c5c85ff >> 133;['348']
348: if (nx & 2**16 > 0) // <= FOUND
349: result += result * 0x162e42ff >> 133;['350']
350: if (nx & 2**15 > 0) // <= FOUND
351: result += result * 0xb17217f >> 133;['352']
352: if (nx & 2**14 > 0) // <= FOUND
353: result += result * 0x58b90bf >> 133;['354']
354: if (nx & 2**13 > 0) // <= FOUND
355: result += result * 0x2c5c85f >> 133;['356']
356: if (nx & 2**12 > 0) // <= FOUND
357: result += result * 0x162e43f >> 133;['358']
358: if (nx & 2**11 > 0) // <= FOUND
359: result += result * 0xb1721f >> 133;['360']
360: if (nx & 2**10 > 0) // <= FOUND
361: result += result * 0x58b91f >> 133;['362']
362: if (nx & 2**9 > 0) // <= FOUND
363: result += result * 0x2c5c9f >> 133;['364']
364: if (nx & 2**8 > 0) // <= FOUND
365: result += result * 0x162e5f >> 133;['366']
366: if (nx & 2**7 > 0) // <= FOUND
367: result += result * 0xb173f >> 133;['368']
368: if (nx & 2**6 > 0) // <= FOUND
369: result += result * 0x58b9f >> 133;['370']
370: if (nx & 2**5 > 0) // <= FOUND
371: result += result * 0x2c5df >> 133;['372']
372: if (nx & 2**4 > 0) // <= FOUND
373: result += result * 0x162ff >> 133;['374']
374: if (nx & 2**3 > 0) // <= FOUND
375: result += result * 0xb17f >> 133;['376']
376: if (nx & 2**2 > 0) // <= FOUND
377: result += result * 0x58bf >> 133;['378']
378: if (nx & 2**1 > 0) // <= FOUND
379: result += result * 0x2c5f >> 133;['380']
380: if (nx & 2**0 > 0) // <= FOUND
381: result += result * 0x163f >> 133;['173']
173: while (index > 0) { // <= FOUND['209']
209: uint256 amountNottransmuted = blocksLeft > 0 ? position.amount * blocksLeft / transmutationTime : 0; // <= FOUND['223']
223: if (amountToRedeem > 0) alchemist.redeem(amountToRedeem); // <= FOUND['232']
232:
233: if (blocksLeft > 0) _updateStakingGraph(-position.amount.toInt256() * BLOCK_SCALING_FACTOR / transmutationTime.toInt256(), blocksLeft); // <= FOUND['100']
100: if (xc >= 0x10000000000000000) { xc >>= 64; msb += 64; } // <= FOUND['101']
101: if (xc >= 0x100000000) { xc >>= 32; msb += 32; } // <= FOUND['102']
102: if (xc >= 0x10000) { xc >>= 16; msb += 16; } // <= FOUND['103']
103: if (xc >= 0x100) { xc >>= 8; msb += 8; } // <= FOUND['104']
104: if (xc >= 0x10) { xc >>= 4; msb += 4; } // <= FOUND['105']
105: if (xc >= 0x4) { xc >>= 2; msb += 2; } // <= FOUND['106']
106: if (xc >= 0x2) msb += 1; // <= FOUND[]
}
pragma solidity >=0.8.4; // <= FOUND[]
}
pragma solidity >=0.5.0; // <= FOUND['1']
1: pragma solidity >=0.5.0; // <= FOUND[]
}pragma solidity >=0.5.0; // <= FOUND[]
}
pragma solidity >=0.5.0; // <= FOUND[]
}
pragma solidity >=0.8.4; // <= FOUNDUsing unchecked increments in Solidity can save on gas fees by bypassing built-in overflow checks, thus optimizing gas usage, but requires careful assessment of potential risks and edge cases to avoid unintended consequences.
Num of instances: 2
Click to show findings
['65']
65: function mint(address to) external onlyAlchemist returns (uint256) { // <= FOUND
66: if (to == address(0)) {
67: revert MintToZeroAddressError();
68: }
69: _currentTokenId++; // <= FOUND
70: uint256 tokenId = _currentTokenId;
71: _mint(to, tokenId);
72: return tokenId;
73: }['172']
172: function createRedemption(uint256 syntheticDepositAmount) external { // <= FOUND
173: if (syntheticDepositAmount == 0) {
174: revert DepositZeroAmount();
175: }
176:
177: if (totalLocked + syntheticDepositAmount > depositCap) {
178: revert DepositCapReached();
179: }
180:
181: if (totalLocked + syntheticDepositAmount > alchemist.totalSyntheticsIssued()) {
182: revert DepositCapReached();
183: }
184:
185: TokenUtils.safeTransferFrom(syntheticToken, msg.sender, address(this), syntheticDepositAmount);
186:
187: _positions[++_nonce] = StakingPosition(syntheticDepositAmount, block.number, block.number + timeToTransmute); // <= FOUND
188:
189:
190: _updateStakingGraph(syntheticDepositAmount.toInt256() * BLOCK_SCALING_FACTOR / timeToTransmute.toInt256(), timeToTransmute);
191:
192: totalLocked += syntheticDepositAmount;
193:
194: _mint(msg.sender, _nonce);
195:
196: emit PositionCreated(msg.sender, syntheticDepositAmount, _nonce);
197: }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: 1
Click to show findings
['942']
942: function _tokenExists(address nft, uint256 tokenId) internal view returns (bool exists) { // <= FOUND
943: if (tokenId == 0) {
944:
945: return false;
946: }
947: try IERC721(nft).ownerOf(tokenId) {
948:
949: exists = true;
950: } catch {
951:
952: exists = false; // <= FOUND
953: }
954: }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: 1
Click to show findings
['553']
553: for (uint256 i = 0; i < accountIds.length; i++) {
554: uint256 accountId = accountIds[i];
555: if (accountId == 0 || !_tokenExists(alchemistPositionNFT, accountId)) {
556: continue;
557: }
558: (uint256 underlyingAmount, uint256 feeInYield, uint256 feeInUnderlying) = _liquidate(accountId); // <= FOUND
559: totalAmountLiquidated += underlyingAmount;
560: totalFeesInYield += feeInYield;
561: totalFeesInUnderlying += feeInUnderlying;
562: }[Gas-11] 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: 1
Click to show findings
['543']
543: function batchLiquidate(uint256[] memory accountIds)
544: external
545: returns (uint256 totalAmountLiquidated, uint256 totalFeesInYield, uint256 totalFeesInUnderlying)
546: {
547:
548:
549: if (accountIds.length == 0) {
550: revert MissingInputData();
551: }
552:
553: for (uint256 i = 0; i < accountIds.length; i++) { // <= FOUND
554: uint256 accountId = accountIds[i];
555: if (accountId == 0 || !_tokenExists(alchemistPositionNFT, accountId)) {
556: continue;
557: }
558: (uint256 underlyingAmount, uint256 feeInYield, uint256 feeInUnderlying) = _liquidate(accountId);
559: totalAmountLiquidated += underlyingAmount;
560: totalFeesInYield += feeInYield;
561: totalFeesInUnderlying += feeInUnderlying;
562: }
563:
564: if (totalAmountLiquidated > 0) {
565: return (totalAmountLiquidated, totalFeesInYield, totalFeesInUnderlying);
566: } else {
567:
568: revert LiquidationError();
569: }
570: }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: 14
Click to show findings
['67']
67: function _checkNonZeroAddress(address account) internal pure {
68: if (account == address(0)) revert ZeroAddress(); // <= FOUND
69: }['65']
65: function mint(address to) external onlyAlchemist returns (uint256) {
66: if (to == address(0)) { // <= FOUND
67: revert MintToZeroAddressError();
68: }
69: _currentTokenId++;
70: uint256 tokenId = _currentTokenId;
71: _mint(to, tokenId);
72: return tokenId;
73: }['182']
182: function setAlchemistPositionNFT(address nft) external onlyAdmin {
183: if (nft == address(0)) { // <= FOUND
184: revert AlchemistV3NFTZeroAddressError();
185: }
186:
187: if (alchemistPositionNFT != address(0)) { // <= FOUND
188: revert AlchemistV3NFTAlreadySetError();
189: }
190:
191: alchemistPositionNFT = nft;
192: }['100']
100: function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) {
101: address from = _ownerOf(tokenId);
102:
103: if (from != address(0)) { // <= FOUND
104:
105: IAlchemistV3(alchemist).resetMintAllowances(tokenId);
106: }
107:
108: return super._update(to, tokenId, auth);
109: }['211']
211: function acceptAdmin() external {
212: _checkState(pendingAdmin != address(0)); // <= FOUND
213:
214: if (msg.sender != pendingAdmin) {
215: revert Unauthorized();
216: }
217:
218: admin = pendingAdmin;
219: pendingAdmin = address(0);
220:
221: emit AdminUpdated(admin);
222: emit PendingAdminUpdated(address(0));
223: }['234']
234: function setProtocolFeeReceiver(address value) external onlyAdmin {
235: _checkArgument(value != address(0)); // <= FOUND
236:
237: protocolFeeReceiver = value;
238: emit ProtocolFeeReceiverUpdated(value);
239: }['258']
258: function setTokenAdapter(address value) external onlyAdmin {
259: _checkArgument(value != address(0)); // <= FOUND
260:
261: tokenAdapter = value;
262: emit TokenAdapterUpdated(value);
263: }['266']
266: function setTransmuter(address value) external onlyAdmin {
267: _checkArgument(value != address(0)); // <= FOUND
268:
269:
270: require(convertYieldTokensToDebt(TokenUtils.safeBalanceOf(yieldToken, transmuter)) >= ITransmuter(transmuter).totalLocked());
271:
272: transmuter = value;
273: emit TransmuterUpdated(value);
274: }['277']
277: function setGuardian(address guardian, bool isActive) external onlyAdmin {
278: _checkArgument(guardian != address(0)); // <= FOUND
279:
280: guardians[guardian] = isActive;
281: emit GuardianSet(guardian, isActive);
282: }['357']
357: function deposit(uint256 amount, address recipient, uint256 recipientId) external returns (uint256) {
358: _checkArgument(recipient != address(0)); // <= FOUND
359: _checkArgument(amount > 0);
360: _checkState(!depositsPaused);
361: _checkState(IERC20(yieldToken).balanceOf(address(this)) + amount <= depositCap);
362: uint256 tokenId = recipientId;
363:
364:
365: if (tokenId == 0) {
366: tokenId = IAlchemistV3Position(alchemistPositionNFT).mint(recipient);
367: emit AlchemistV3PositionNFTMinted(recipient, tokenId);
368: } else {
369: _checkForValidAccountId(tokenId);
370: }
371:
372: _accounts[tokenId].collateralBalance += amount;
373: _accounts[tokenId].freeCollateral += amount;
374:
375:
376: TokenUtils.safeTransferFrom(yieldToken, msg.sender, address(this), amount);
377:
378: emit Deposit(amount, tokenId);
379:
380: return convertYieldTokensToDebt(amount);
381: }['384']
384: function withdraw(uint256 amount, address recipient, uint256 tokenId) external returns (uint256) {
385: _checkArgument(recipient != address(0)); // <= FOUND
386: _checkForValidAccountId(tokenId);
387: _checkArgument(amount > 0);
388: _checkAccountOwnership(IAlchemistV3Position(alchemistPositionNFT).ownerOf(tokenId), msg.sender);
389: _earmark();
390:
391: _sync(tokenId);
392:
393: _checkArgument(_accounts[tokenId].freeCollateral >= amount);
394:
395: _accounts[tokenId].collateralBalance -= amount;
396: _accounts[tokenId].freeCollateral -= amount;
397:
398:
399: _validate(tokenId);
400:
401:
402: TokenUtils.safeTransfer(yieldToken, recipient, amount);
403:
404: emit Withdraw(amount, tokenId, recipient);
405:
406: return amount;
407: }['410']
410: function mint(uint256 tokenId, uint256 amount, address recipient) external {
411: _checkArgument(recipient != address(0)); // <= FOUND
412: _checkForValidAccountId(tokenId);
413: _checkArgument(amount > 0);
414: _checkState(!loansPaused);
415: _checkAccountOwnership(IAlchemistV3Position(alchemistPositionNFT).ownerOf(tokenId), msg.sender);
416:
417:
418: _earmark();
419:
420:
421: _sync(tokenId);
422:
423:
424: _mint(tokenId, amount, recipient);
425: }['428']
428: function mintFrom(uint256 tokenId, uint256 amount, address recipient) external {
429: _checkArgument(amount > 0);
430: _checkForValidAccountId(tokenId);
431: _checkArgument(recipient != address(0)); // <= FOUND
432: _checkState(!loansPaused);
433:
434: _decreaseMintAllowance(tokenId, msg.sender, amount);
435:
436:
437: _earmark();
438:
439:
440: _sync(tokenId);
441:
442:
443: _mint(tokenId, amount, recipient);
444: }['154']
154: function setProtocolFeeReceiver(address value) external onlyAdmin {
155: _checkArgument(value != address(0)); // <= FOUND
156: protocolFeeReceiver = value;
157: emit ProtocolFeeReceiverUpdated(value);
158: }Consider adding more detail to these error strings
Num of instances: 6
Click to show findings
['266']
266: function setTransmuter(address value) external onlyAdmin {
267: _checkArgument(value != address(0));
268:
269:
270: require(convertYieldTokensToDebt(TokenUtils.safeBalanceOf(yieldToken, transmuter)) >= ITransmuter(transmuter).totalLocked()); // <= FOUND
271:
272: transmuter = value;
273: emit TransmuterUpdated(value);
274: }['38']
38: function WeightIncrement(uint256 increment, uint256 total) internal pure returns (uint256) {
39: unchecked {
40: require(increment <= total); // <= FOUND
41: require(total <= type(uint128).max);
42:
43:
44: if (increment == 0) {
45:
46: return 0;
47: }
48:
49: uint256 ratio = ((total - increment) << 128) / total;
50: if (ratio == 0) {
51:
52: return LOG2NEGFRAC_1+1;
53: }
54: return Log2NegFrac(ratio);
55: }
56: }['66']
66: function ScaleByWeightDelta(uint256 value, uint256 weightDelta) internal pure returns (uint256) {
67: unchecked {
68: require(value <= type(uint128).max); // <= FOUND
69:
70: if (weightDelta == 0) {
71:
72: return 0;
73: }
74:
75:
76:
77:
78:
79:
80:
81: return value - ((value * Exp2NegFrac(weightDelta)) >> 128);
82: }
83: }['92']
92: function Log2NegFrac(uint256 x) private pure returns (uint256) {
93: unchecked {
94: if (x >= 2**128) return 0;
95:
96: require(x > 0); // <= FOUND
97:
98: int256 msb = 0;
99: uint256 xc = x;
100: if (xc >= 0x10000000000000000) { xc >>= 64; msb += 64; }
101: if (xc >= 0x100000000) { xc >>= 32; msb += 32; }
102: if (xc >= 0x10000) { xc >>= 16; msb += 16; }
103: if (xc >= 0x100) { xc >>= 8; msb += 8; }
104: if (xc >= 0x10) { xc >>= 4; msb += 4; }
105: if (xc >= 0x4) { xc >>= 2; msb += 2; }
106: if (xc >= 0x2) msb += 1;
107:
108: int256 result = (msb - 128) << 120;
109: uint256 ux = uint256(x) << uint256(127 - msb);
110: for (int256 bit = 0x800000000000000000000000000000; bit > 0; bit >>= 1) {
111: ux *= ux;
112: uint256 b = ux >> 255;
113: ux >>= 127 + b;
114: result += bit * int256 (b);
115: }
116:
117: return uint256(-result);
118: }
119: }['50']
50: function addStake(Graph storage g, int256 amount, uint256 start, uint256 duration) internal {
51: unchecked {
52: require(amount <= DELTA_MAX && amount >= DELTA_MIN); // <= FOUND
53: require(start < GRAPH_MAX-1);
54:
55: uint256 expiration = start + duration;
56: require(expiration < GRAPH_MAX-1);
57:
58: uint256 graphSize = g.size;
59:
60:
61:
62: uint256 newSize = expiration + 2;
63: if (newSize >= graphSize) {
64:
65: newSize |= newSize >> 1;
66: newSize |= newSize >> 2;
67: newSize |= newSize >> 4;
68: newSize |= newSize >> 8;
69: newSize |= newSize >> 16;
70: if (GRAPH_MAX > 2**32) {
71: newSize |= newSize >> 32;
72: newSize |= newSize >> 64;
73: newSize |= newSize >> 128;
74: }
75: newSize++;
76:
77:
78:
79:
80: require (newSize <= GRAPH_MAX);
81:
82: if (graphSize != 0) {
83:
84: uint256 copy = g.g[graphSize];
85: while (graphSize <= newSize) {
86: g.g[graphSize] = copy;
87: graphSize += graphSize;
88: }
89: }
90: graphSize = newSize;
91: g.size = newSize;
92: }
93:
94:
95: update(g.g, start + 1, graphSize, amount, amount * int256(start));
96: update(g.g, expiration + 1, graphSize, -amount, -amount * int256(expiration));
97: }
98: }['132']
132: function update(uint256[GRAPH_MAX + 1] storage graph, uint256 index, uint256 treeSize, int256 delta, int256 deltaProd) private {
133: unchecked {
134: index += 1;
135: while (index <= treeSize) {
136:
137: uint256 packed = graph[index];
138: int256 ad;
139: int256 ap;
140:
141:
142: if ((packed&DELTA_SIGNBIT) != 0) {
143: ad = int256(packed | ~DELTA_MASK);
144: } else {
145: ad = int256(packed & DELTA_MASK);
146: }
147: ap = int256(packed)>>DELTA_BITS;
148:
149: ad+=delta;
150: ap+=deltaProd;
151:
152:
153: require(ad <= DELTA_MAX && ad >= DELTA_MIN); // <= FOUND
154: require(ap <= PRODUCT_MAX && ap >= PRODUCT_MIN);
155: graph[index] = (uint256(ad)&DELTA_MASK)|uint256(ap<<DELTA_BITS);
156:
157: assembly {
158: index := add(index, and(index, sub(0, index)))
159: }
160: }
161: }
162: }[Gas-14] 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: 1
Click to show findings
['179']
179: function truncate(Number memory self) internal pure returns (uint256) { // <= FOUND
180: return self.n / ONE; // <= FOUND
181: }In Solidity, performing unnecessary operations can consume more gas than needed, leading to cost inefficiencies. For instance, if a transfer function doesn't have a zero amount check and someone calls it with a zero amount, unnecessary gas will be consumed in executing the function, even though the state of the contract remains the same. By implementing a zero amount check, such unnecessary function calls can be avoided, thereby saving gas and making the contract more efficient.
Num of instances: 1
Click to show findings
['37']
37: function withdraw(address recipient, uint256 amount) external override onlyAuthorized {
38: _checkNonZeroAddress(recipient);
39: _checkNonZeroAmount(amount);
40:
41: IERC20(token).transfer(recipient, amount); // <= FOUND
42: emit Withdrawn(recipient, amount);
43: }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: 2
Click to show findings
['70']
70: if (GRAPH_MAX > 2**32) { // <= FOUND['94']
94: if (x >= 2**128) return 0; // <= FOUNDIn 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: 5
Click to show findings
['5']
5: struct AlchemistInitializationParams { // <= FOUND
6:
7: address admin;
8:
9: address debtToken;
10:
11: address underlyingToken;
12:
13: address yieldToken;
14:
15: uint256 depositCap; // <= FOUND
16:
17: uint256 blocksPerYear; // <= FOUND
18:
19: uint256 minimumCollateralization; // <= FOUND
20:
21: uint256 globalMinimumCollateralization; // <= FOUND
22:
23: uint256 collateralizationLowerBound; // <= FOUND
24:
25: address tokenAdapter;
26:
27: address transmuter;
28:
29: uint256 protocolFee; // <= FOUND
30:
31: address protocolFeeReceiver;
32:
33: uint256 liquidatorFee; // <= FOUND
34: }['65']
65: struct RedemptionInfo { // <= FOUND
66: uint256 earmarked; // <= FOUND
67: uint256 debt; // <= FOUND
68: uint256 earmarkWeight; // <= FOUND
69: }['7']
7: struct StakingPosition { // <= FOUND
8:
9: uint256 amount; // <= FOUND
10:
11: uint256 startBlock; // <= FOUND
12:
13: uint256 maturationBlock; // <= FOUND
14: }['16']
16: struct TransmuterInitializationParams { // <= FOUND
17: address syntheticToken;
18: address feeReceiver;
19: uint256 timeToTransmute; // <= FOUND
20: uint256 transmutationFee; // <= FOUND
21: uint256 exitFee; // <= FOUND
22: uint256 graphSize; // <= FOUND
23: }['9']
9: struct StrategyParams { // <= FOUND
10: uint256 performanceFee; // <= FOUND
11: uint256 activation; // <= FOUND
12: uint256 debtRatio; // <= FOUND
13: uint256 minDebtPerHarvest; // <= FOUND
14: uint256 maxDebtPerHarvest; // <= FOUND
15: uint256 lastReport; // <= FOUND
16: uint256 totalDebt; // <= FOUND
17: uint256 totalGain; // <= FOUND
18: uint256 totalLoss; // <= FOUND
19: bool enforceChangeLimit;
20: uint256 profitLimitRatio; // <= FOUND
21: uint256 lossLimitRatio; // <= FOUND
22: address customCheck;
23: }From a gas efficiency perspective, using _msgSender() in a contract not intended to support EIP-2771 could add unnecessary overhead. The _msgSender() function includes checks to determine if the transaction was forwarded, which involves extra function calls that consume more gas than a simple msg.sender.
If a contract doesn't require EIP-2771 meta-transaction support, using msg.sender directly is more gas efficient. msg.sender is a globally accessible variable in Solidity that doesn't require an extra function call, making it a less costly choice.
In the context of Ethereum, where every operation has a gas cost, it's crucial to eliminate unnecessary computations to optimize contract execution and minimize transaction fees. Therefore, if EIP-2771 support isn't necessary, it's recommended to use msg.sender instead of _msgSender().
Num of instances: 3
Click to show findings
['91']
91: _burn(_msgSender(), amount); // <= FOUND['106']
106: uint256 decreasedAllowance = allowance(account, _msgSender()) - amount; // <= FOUND['108']
108: _approve(account, _msgSender(), decreasedAllowance); // <= FOUNDPrivate functions which are only called once can be inlined to save GAS.
Num of instances: 2
Click to show findings
['92']
92: function Log2NegFrac(uint256 x) private pure returns (uint256) // <= FOUND['127']
127: function Exp2NegFrac(uint256 x) private pure returns (uint256) // <= FOUNDERC721A is an enhanced variant of the original ERC721 standard, introducing new features that increase efficiency and broaden functionality. It preserves the features of the original ERC721 standard while incorporating support for batch minting, allowing for multiple NFTs to be minted in a single transaction. This reduces the amount of gas required compared to minting each token individually. Additionally, ERC721A introduces mechanisms to track the ownership of numerous NFTs concurrently, providing an optimized way to manage multiple NFT assets for a single owner. This can result in streamlined ownership tracking and potentially more efficient contract interactions.
Num of instances: 2
Click to show findings
['16']
16: contract AlchemistV3Position is ERC721Enumerable // <= FOUND['21']
21: contract Transmuter is ITransmuter, ERC721 // <= FOUNDThere is no need to emit an event for a safeMint as this function already contains a event emit
Num of instances: 1
Click to show findings
['713']
713: function _mint(uint256 tokenId, uint256 amount, address recipient) internal {
714: _addDebt(tokenId, amount);
715:
716: totalSyntheticsIssued += amount;
717:
718:
719: _validate(tokenId);
720:
721: _accounts[tokenId].lastMintBlock = block.number;
722:
723:
724: TokenUtils.safeMint(debtToken, recipient, amount); // <= FOUND
725:
726: emit Mint(tokenId, amount, recipient); // <= FOUND
727: }In Solidity, using selfBalance instead of address(this).balance is advantageous for gas optimization. address(this).balance performs an external call to retrieve the contract's balance, which costs extra gas. As a resolution, defining a selfBalance state variable that's updated accordingly provides a more gas-efficient approach. Using selfBalance instead of address(this).balance can save around 800 gas per call. This is because address(this).balance makes an EXTBAL operation, which costs 700 gas, and additionally, the operation is a SLOAD, which costs around 800 gas. Please note that these estimates are based on the Ethereum gas schedule and might vary depending on the Ethereum network state and its future upgrades.
Num of instances: 2
Click to show findings
['34']
34: return address(this).balance; // <= FOUND['85']
85:
86: if (amount > address(this).balance) revert InsufficientBalance(); // <= FOUNDWith the use of inline assembly in Solidity, we can take advantage of low-level features like scratch space and the free memory pointer, offering more gas-efficient ways of emitting events. The scratch space is a certain area of memory where we can temporarily store data, and the free memory pointer indicates the next available memory slot. Using these, we can efficiently assemble event data without incurring additional memory expansion costs. However, safety is paramount: to avoid overwriting or leakage, we must cache the free memory pointer before use and restore it afterward, ensuring that it points to the correct memory location post-operation.
Num of instances: 41
Click to show findings
['49']
49: emit AuthorizationUpdated(_alchemist, true); // <= FOUND['60']
60: emit AuthorizationUpdated(account, status); // <= FOUND['73']
73: emit Deposited(depositor, amount); // <= FOUND['42']
42: emit Withdrawn(recipient, amount); // <= FOUND['29']
29: emit Deposited(msg.sender, amount); // <= FOUND['200']
200: emit AlchemistFeeVaultUpdated(value); // <= FOUND['207']
207: emit PendingAdminUpdated(value); // <= FOUND['221']
221: emit AdminUpdated(admin); // <= FOUND['222']
222: emit PendingAdminUpdated(address(0)); // <= FOUND['230']
230: emit DepositCapUpdated(value); // <= FOUND['238']
238: emit ProtocolFeeReceiverUpdated(value); // <= FOUND['246']
246: emit ProtocolFeeUpdated(fee); // <= FOUND['254']
254: emit LiquidatorFeeUpdated(fee); // <= FOUND['262']
262: emit TokenAdapterUpdated(value); // <= FOUND['273']
273: emit TransmuterUpdated(value); // <= FOUND['281']
281: emit GuardianSet(guardian, isActive); // <= FOUND['289']
289: emit MinimumCollateralizationUpdated(value); // <= FOUND['296']
296: emit GlobalMinimumCollateralizationUpdated(value); // <= FOUND['304']
304: emit CollateralizationLowerBoundUpdated(value); // <= FOUND['310']
310: emit DepositsPaused(isPaused); // <= FOUND['316']
316: emit LoansPaused(isPaused); // <= FOUND['367']
367: emit AlchemistV3PositionNFTMinted(recipient, tokenId); // <= FOUND['378']
378: emit Deposit(amount, tokenId); // <= FOUND['404']
404: emit Withdraw(amount, tokenId, recipient); // <= FOUND['482']
482: emit Burn(msg.sender, credit, recipientId); // <= FOUND['524']
524: emit Repay(msg.sender, amount, recipientTokenId, creditToYield); // <= FOUND['534']
534: emit Liquidated(accountId, msg.sender, yieldAmount, feeInYield, feeInUnderlying); // <= FOUND['598']
598: emit Redemption(amount); // <= FOUND['627']
627:
628: emit MintAllowancesReset(tokenId); // <= FOUND['726']
726: emit Mint(tokenId, amount, recipient); // <= FOUND['895']
895: emit ApproveMint(ownerTokenId, spender, amount); // <= FOUND['119']
119: emit AlchemistUpdated(value); // <= FOUND['127']
127: emit DepositCapUpdated(cap); // <= FOUND['135']
135: emit TransmutationFeeUpdated(fee); // <= FOUND['143']
143: emit ExitFeeUpdated(fee); // <= FOUND['150']
150: emit TransmutationTimeUpdated(time); // <= FOUND['196']
196: emit PositionCreated(msg.sender, syntheticDepositAmount, _nonce); // <= FOUND['254']
254: emit PositionClaimed(msg.sender, claimAmount, syntheticReturned); // <= FOUND['32']
32: emit AccountAdded(caller); // <= FOUND['42']
42: emit AccountRemoved(caller); // <= FOUND['49']
49: emit WhitelistDisabled(); // <= FOUNDThe following OpenZeppelin imports have a Solady equivalent, as such they can be used to save GAS as Solady modules have been specifically designed to be as GAS efficient as possible
Num of instances: 15
Click to show findings
['4']
4: import "@openzeppelin/contracts/access/Ownable.sol"; // <= FOUND['6']
6: import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; // <= FOUND['5']
5: import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; // <= FOUND['4']
4: import {ERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; // <= FOUND['6']
6: import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; // <= FOUND['15']
15: import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; // <= FOUND['4']
4: import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; // <= FOUND['7']
7: import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; // <= FOUND['5']
5: import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; // <= FOUND['17']
17: import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; // <= FOUND['5']
5: import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; // <= FOUND['4']
4: import {IERC20} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; // <= FOUND['3']
3: import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; // <= FOUND['4']
4: import "../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; // <= FOUND['3']
3: import "../../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; // <= FOUNDUsing inline assembly to extract calldata values can be more gas-efficient than using abi.decode in Solidity. Inline assembly gives more direct access to EVM operations, enabling optimized usage of calldata. However, assembly should be used judiciously as it's more prone to errors. Opt for this approach when performance is critical and the complexity it introduces is manageable.
Num of instances: 4
Click to show findings
['33']
33: return abi.decode(data, (uint8)); // <= FOUND['50']
50: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) { // <= FOUND['51']
51: return abi.decode(data, (uint256)); // <= FOUND['64']
64: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) { // <= FOUNDStruct 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: 1
Click to show findings
['16']
16: struct TransmuterInitializationParams { // <= FOUND
17: address syntheticToken;
18: address feeReceiver;
19: uint256 timeToTransmute; // <= FOUND
20: uint256 transmutationFee;
21: uint256 exitFee;
22: uint256 graphSize;
23: }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: 7
Click to show findings
['29']
29: uint256 public constant BPS = 10_000; // <= FOUND['30']
30: uint256 public constant FIXED_POINT_SCALAR = 1e18; // <= FOUND['22']
22: bytes32 public constant ADMIN_ROLE = keccak256("ADMIN"); // <= FOUND['25']
25: bytes32 public constant SENTINEL_ROLE = keccak256("SENTINEL"); // <= FOUND['28']
28: int256 public constant BLOCK_SCALING_FACTOR = 1e8; // <= FOUND['11']
11: uint256 public constant DECIMALS = 18; // <= FOUND['12']
12: uint256 public constant ONE = 10 ** DECIMALS; // <= FOUNDIn 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: 26
Click to show findings
['57']
57: function setAuthorization(address account, bool status) external onlyOwner {
58: _checkNonZeroAddress(account);
59: authorized[account] = status;
60: emit AuthorizationUpdated(account, status);
61: }['182']
182: function setAlchemistPositionNFT(address nft) external onlyAdmin {
183: if (nft == address(0)) {
184: revert AlchemistV3NFTZeroAddressError();
185: }
186:
187: if (alchemistPositionNFT != address(0)) {
188: revert AlchemistV3NFTAlreadySetError();
189: }
190:
191: alchemistPositionNFT = nft;
192: }['195']
195: function setAlchemistFeeVault(address value) external onlyAdmin {
196: if (IFeeVault(value).token() != underlyingToken) {
197: revert AlchemistVaultTokenMismatchError();
198: }
199: alchemistFeeVault = value;
200: emit AlchemistFeeVaultUpdated(value);
201: }['204']
204: function setPendingAdmin(address value) external onlyAdmin {
205: pendingAdmin = value;
206:
207: emit PendingAdminUpdated(value);
208: }['211']
211: function acceptAdmin() external {
212: _checkState(pendingAdmin != address(0));
213:
214: if (msg.sender != pendingAdmin) {
215: revert Unauthorized();
216: }
217:
218: admin = pendingAdmin;
219: pendingAdmin = address(0);
220:
221: emit AdminUpdated(admin);
222: emit PendingAdminUpdated(address(0));
223: }['226']
226: function setDepositCap(uint256 value) external onlyAdmin {
227: _checkArgument(value >= IERC20(yieldToken).balanceOf(address(this)));
228:
229: depositCap = value;
230: emit DepositCapUpdated(value);
231: }['234']
234: function setProtocolFeeReceiver(address value) external onlyAdmin {
235: _checkArgument(value != address(0));
236:
237: protocolFeeReceiver = value;
238: emit ProtocolFeeReceiverUpdated(value);
239: }['242']
242: function setProtocolFee(uint256 fee) external onlyAdmin {
243: _checkArgument(fee <= BPS);
244:
245: protocolFee = fee;
246: emit ProtocolFeeUpdated(fee);
247: }['250']
250: function setLiquidatorFee(uint256 fee) external onlyAdmin {
251: _checkArgument(fee <= BPS);
252:
253: liquidatorFee = fee;
254: emit LiquidatorFeeUpdated(fee);
255: }['258']
258: function setTokenAdapter(address value) external onlyAdmin {
259: _checkArgument(value != address(0));
260:
261: tokenAdapter = value;
262: emit TokenAdapterUpdated(value);
263: }['266']
266: function setTransmuter(address value) external onlyAdmin {
267: _checkArgument(value != address(0));
268:
269:
270: require(convertYieldTokensToDebt(TokenUtils.safeBalanceOf(yieldToken, transmuter)) >= ITransmuter(transmuter).totalLocked());
271:
272: transmuter = value;
273: emit TransmuterUpdated(value);
274: }['277']
277: function setGuardian(address guardian, bool isActive) external onlyAdmin {
278: _checkArgument(guardian != address(0));
279:
280: guardians[guardian] = isActive;
281: emit GuardianSet(guardian, isActive);
282: }['285']
285: function setMinimumCollateralization(uint256 value) external onlyAdmin {
286: _checkArgument(value >= FIXED_POINT_SCALAR);
287: minimumCollateralization = value;
288:
289: emit MinimumCollateralizationUpdated(value);
290: }['293']
293: function setGlobalMinimumCollateralization(uint256 value) external onlyAdmin {
294: _checkArgument(value >= minimumCollateralization);
295: globalMinimumCollateralization = value;
296: emit GlobalMinimumCollateralizationUpdated(value);
297: }['300']
300: function setCollateralizationLowerBound(uint256 value) external onlyAdmin {
301: _checkArgument(value <= minimumCollateralization);
302: _checkArgument(value >= FIXED_POINT_SCALAR);
303: collateralizationLowerBound = value;
304: emit CollateralizationLowerBoundUpdated(value);
305: }['308']
308: function pauseDeposits(bool isPaused) external onlyAdminOrGuardian {
309: depositsPaused = isPaused;
310: emit DepositsPaused(isPaused);
311: }['314']
314: function pauseLoans(bool isPaused) external onlyAdminOrGuardian {
315: loansPaused = isPaused;
316: emit LoansPaused(isPaused);
317: }['116']
116: function setAlchemist(address value) external onlyAdmin {
117: alchemist = IAlchemistV3(value);
118:
119: emit AlchemistUpdated(value);
120: }['123']
123: function setDepositCap(uint256 cap) external onlyAdmin {
124: _checkArgument(cap <= type(int256).max.toUint256());
125:
126: depositCap = cap;
127: emit DepositCapUpdated(cap);
128: }['131']
131: function setTransmutationFee(uint256 fee) external onlyAdmin {
132: _checkArgument(fee <= BPS);
133:
134: transmutationFee = fee;
135: emit TransmutationFeeUpdated(fee);
136: }['139']
139: function setExitFee(uint256 fee) external onlyAdmin {
140: _checkArgument(fee <= BPS);
141:
142: exitFee = fee;
143: emit ExitFeeUpdated(fee);
144: }['147']
147: function setTransmutationTime(uint256 time) external onlyAdmin {
148: timeToTransmute = time;
149:
150: emit TransmutationTimeUpdated(time);
151: }['154']
154: function setProtocolFeeReceiver(address value) external onlyAdmin {
155: _checkArgument(value != address(0));
156: protocolFeeReceiver = value;
157: emit ProtocolFeeReceiverUpdated(value);
158: }['58']
58: function _onlyAdmin() internal view {
59: if (msg.sender != owner()) {
60: revert Unauthorized();
61: }
62: }['81']
81: function withdraw(address recipient, uint256 amount) external override onlyAuthorized nonReentrant {
82: if (amount == 0) revert ZeroAmount();
83:
84:
85: if (amount > address(this).balance) revert InsufficientBalance();
86:
87:
88: (bool success,) = recipient.call{value: amount}("");
89: if (!success) revert TransferFailed();
90:
91: emit Withdrawn(recipient, amount);
92: }['37']
37: function withdraw(address recipient, uint256 amount) external override onlyAuthorized {
38: _checkNonZeroAddress(recipient);
39: _checkNonZeroAmount(amount);
40:
41: IERC20(token).transfer(recipient, amount);
42: emit Withdrawn(recipient, amount);
43: }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: 2
Click to show findings
['553']
553: for (uint256 i = 0; i < accountIds.length; i++) {
554: uint256 accountId = accountIds[i];
555: if (accountId == 0 || !_tokenExists(alchemistPositionNFT, accountId)) {
556: continue;
557: }
558: (uint256 underlyingAmount, uint256 feeInYield, uint256 feeInUnderlying) = _liquidate(accountId);
559: totalAmountLiquidated += underlyingAmount;
560: totalFeesInYield += feeInYield;
561: totalFeesInUnderlying += feeInUnderlying;
562: }['110']
110: for (int256 bit = 0x800000000000000000000000000000; bit > 0; bit >>= 1) {
111: ux *= ux;
112: uint256 b = ux >> 255;
113: ux >>= 127 + b;
114: result += bit * int256 (b);
115: }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: 1
Using += for mappings simplifies code and can potentially save gas. When you need to increment a value within a mapping, writing mappingA[xyz] = mappingA[xyz] + 1; can be made more concise with mappingA[xyz] += 1;.
Resolution: To enhance code clarity and potentially reduce gas costs, replace explicit addition assignments with the += operator when dealing with mappings. This not only shortens the code but also makes it easier to read and understand. Employ tools like linters or static analysis to systematically identify and correct such instances, ensuring optimized and cleaner code throughout your smart contract.
Num of instances: 1
Click to show findings
['57']
57: hasMinted[msg.sender] = hasMinted[msg.sender] + _amount; // <= FOUNDUsing 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: 131
Click to show findings
['76']
76: if (amount == 0) revert ZeroAmount(); // <= FOUND['1008']
1008: if (totalDebt == 0) return; // <= FOUND['1069']
1069: if (debt == 0) return false; // <= FOUND['263']
263: if (queried == 0) return 0; // <= FOUND['106']
106: if (xc >= 0x2) msb += 1; // <= FOUND['352']
352: if (collateral > 0) totalUnderlying += convertYieldTokensToUnderlying(collateral);['359']
359: _checkArgument(amount > 0);['96']
96: require(x > 0);['142']
142:
143: if (nx & 2**119 > 0)
144: result += result * 0xd413cccfe779921165f626cdd52afa89 >> 129;['144']
144: if (nx & 2**118 > 0)
145: result += result * 0xc1bf828c6dc54b7a356918c17217b7bf >> 130;['146']
146: if (nx & 2**117 > 0)
147: result += result * 0xb95c1e3ea8bd6e6fbe4628758a53c90f >> 131;['148']
148: if (nx & 2**116 > 0)
149: result += result * 0xb5586cf9890f6298b92b71842a98364f >> 132;['150']
150: if (nx & 2**115 > 0)
151: result += result * 0xb361a62b0ae875cf8a91d6d19482ffdf >> 133;['152']
152: if (nx & 2**114 > 0)
153: result += result * 0x59347cef00c1dcdef95949ef4537bd3f >> 133;['154']
154: if (nx & 2**113 > 0)
155: result += result * 0x2c7b53f6666adb094cd5c66db9bf481f >> 133;['156']
156: if (nx & 2**112 > 0)
157: result += result * 0x1635f4b5797dac2535627d823b92a89f >> 133;['158']
158: if (nx & 2**111 > 0)
159: result += result * 0xb190db43813d43fe33a5299e5ecf39f >> 133;['160']
160: if (nx & 2**110 > 0)
161: result += result * 0x58c0bc5d19d8a0da437f9134474021f >> 133;['162']
162: if (nx & 2**109 > 0)
163: result += result * 0x2c5e72080a3f425179538ab863cbc1f >> 133;['164']
164: if (nx & 2**108 > 0)
165: result += result * 0x162ebdffb8ed7471c62ce395272e4df >> 133;['166']
166: if (nx & 2**107 > 0)
167: result += result * 0xb17403f73f2dad959a9630122f87df >> 133;['168']
168: if (nx & 2**106 > 0)
169: result += result * 0x58b986fb52923a130b86918d1cf67f >> 133;['170']
170: if (nx & 2**105 > 0)
171: result += result * 0x2c5ca4bdc0a8ea88afab32a52404df >> 133;['172']
172: if (nx & 2**104 > 0)
173: result += result * 0x162e4aaeeb8080c317e9495bd07f9f >> 133;['174']
174: if (nx & 2**103 > 0)
175: result += result * 0xb17236b7935c5ddb03d36fa99f59f >> 133;['176']
176: if (nx & 2**102 > 0)
177: result += result * 0x58b913abd8d949af9159802f6f93f >> 133;['178']
178: if (nx & 2**101 > 0)
179: result += result * 0x2c5c87e9f0620c1c05b07353a2dbf >> 133;['180']
180: if (nx & 2**100 > 0)
181: result += result * 0x162e4379f933b3f121d40d222ec7f >> 133;['182']
182: if (nx & 2**99 > 0)
183: result += result * 0xb17219e3cdb2ff39429b7982c51f >> 133;['184']
184: if (nx & 2**98 > 0)
185: result += result * 0x58b90c76e7e02c8d1ed758b9459f >> 133;['186']
186: if (nx & 2**97 > 0)
187: result += result * 0x2c5c861cb431ec233c78045054bf >> 133;['188']
188: if (nx & 2**96 > 0)
189: result += result * 0x162e4306aa2970dcdb2ddfa37fff >> 133;['190']
190: if (nx & 2**95 > 0)
191: result += result * 0xb1721816918d7cbbf08d8b65e1f >> 133;['192']
192: if (nx & 2**94 > 0)
193: result += result * 0x58b90c0398d73d284278e4e6f5f >> 133;['194']
194: if (nx & 2**93 > 0)
195: result += result * 0x2c5c85ffe06fbe715456433e0df >> 133;['196']
196: if (nx & 2**92 > 0)
197: result += result * 0x162e42ff7538e7354b033e89f3f >> 133;['198']
198: if (nx & 2**91 > 0)
199: result += result * 0xb17217f9bdcb59a7839db9047f >> 133;['200']
200: if (nx & 2**90 > 0)
201: result += result * 0x58b90bfc63e6b4d461b437ca1f >> 133;['202']
202: if (nx & 2**89 > 0)
203: result += result * 0x2c5c85fe13339c6a8373fff9ff >> 133;['204']
204: if (nx & 2**88 > 0)
205: result += result * 0x162e42ff01e9deb55bb48aaa9f >> 133;['206']
206: if (nx & 2**87 > 0)
207: result += result * 0xb17217f7f08f37ab5036a35bf >> 133;['208']
208: if (nx & 2**86 > 0)
209: result += result * 0x58b90bfbf097ac55c614e999f >> 133;['210']
210: if (nx & 2**85 > 0)
211: result += result * 0x2c5c85fdf65fda4aeab37b55f >> 133;['212']
212: if (nx & 2**84 > 0)
213: result += result * 0x162e42fefab4ee2d7749535ff >> 133;['214']
214: if (nx & 2**83 > 0)
215: result += result * 0xb17217f7d3bb758bc21399ff >> 133;['216']
216: if (nx & 2**82 > 0)
217: result += result * 0x58b90bfbe962bbcde2fd61bf >> 133;['218']
218: if (nx & 2**81 > 0)
219: result += result * 0x2c5c85fdf4929e28f1fbc0bf >> 133;['220']
220: if (nx & 2**80 > 0)
221: result += result * 0x162e42fefa419f24f91d299f >> 133;['222']
222: if (nx & 2**79 > 0)
223: result += result * 0xb17217f7d1ee3969c9667df >> 133;['224']
224: if (nx & 2**78 > 0)
225: result += result * 0x58b90bfbe8ef6cc564d28bf >> 133;['226']
226: if (nx & 2**77 > 0)
227: result += result * 0x2c5c85fdf475ca66d27119f >> 133;['228']
228: if (nx & 2**76 > 0)
229: result += result * 0x162e42fefa3a6a34713a81f >> 133;['230']
230: if (nx & 2**75 > 0)
231: result += result * 0xb17217f7d1d165a7a9dbff >> 133;['232']
232: if (nx & 2**74 > 0)
233: result += result * 0x58b90bfbe8e837d4dcefff >> 133;['234']
234: if (nx & 2**73 > 0)
235: result += result * 0x2c5c85fdf473fd2ab0787f >> 133;['236']
236: if (nx & 2**72 > 0)
237: result += result * 0x162e42fefa39f6e568bc5f >> 133;['238']
238: if (nx & 2**71 > 0)
239: result += result * 0xb17217f7d1cf986b87e3f >> 133;['240']
240: if (nx & 2**70 > 0)
241: result += result * 0x58b90bfbe8e7c485d471f >> 133;['242']
242: if (nx & 2**69 > 0)
243: result += result * 0x2c5c85fdf473e056ee59f >> 133;['244']
244: if (nx & 2**68 > 0)
245: result += result * 0x162e42fefa39efb07835f >> 133;['246']
246: if (nx & 2**67 > 0)
247: result += result * 0xb17217f7d1cf7b97c5df >> 133;['248']
248: if (nx & 2**66 > 0)
249: result += result * 0x58b90bfbe8e7bd50e3ff >> 133;['250']
250: if (nx & 2**65 > 0)
251: result += result * 0x2c5c85fdf473de89b23f >> 133;['252']
252: if (nx & 2**64 > 0)
253: result += result * 0x162e42fefa39ef3d293f >> 133;['254']
254: if (nx & 2**63 > 0)
255: result += result * 0xb17217f7d1cf79ca89f >> 133;['256']
256: if (nx & 2**62 > 0)
257: result += result * 0x58b90bfbe8e7bcdd95f >> 133;['258']
258: if (nx & 2**61 > 0)
259: result += result * 0x2c5c85fdf473de6cdff >> 133;['260']
260: if (nx & 2**60 > 0)
261: result += result * 0x162e42fefa39ef35f5f >> 133;['262']
262: if (nx & 2**59 > 0)
263: result += result * 0xb17217f7d1cf79adbf >> 133;['264']
264: if (nx & 2**58 > 0)
265: result += result * 0x58b90bfbe8e7bcd65f >> 133;['266']
266: if (nx & 2**57 > 0)
267: result += result * 0x2c5c85fdf473de6b1f >> 133;['268']
268: if (nx & 2**56 > 0)
269: result += result * 0x162e42fefa39ef359f >> 133;['270']
270: if (nx & 2**55 > 0)
271: result += result * 0xb17217f7d1cf79abf >> 133;['272']
272: if (nx & 2**54 > 0)
273: result += result * 0x58b90bfbe8e7bcd5f >> 133;['274']
274: if (nx & 2**53 > 0)
275: result += result * 0x2c5c85fdf473de6bf >> 133;['276']
276: if (nx & 2**52 > 0)
277: result += result * 0x162e42fefa39ef35f >> 133;['278']
278: if (nx & 2**51 > 0)
279: result += result * 0xb17217f7d1cf79bf >> 133;['280']
280: if (nx & 2**50 > 0)
281: result += result * 0x58b90bfbe8e7bcdf >> 133;['282']
282: if (nx & 2**49 > 0)
283: result += result * 0x2c5c85fdf473de7f >> 133;['284']
284: if (nx & 2**48 > 0)
285: result += result * 0x162e42fefa39ef3f >> 133;['286']
286: if (nx & 2**47 > 0)
287: result += result * 0xb17217f7d1cf79f >> 133;['288']
288: if (nx & 2**46 > 0)
289: result += result * 0x58b90bfbe8e7bdf >> 133;['290']
290: if (nx & 2**45 > 0)
291: result += result * 0x2c5c85fdf473dff >> 133;['292']
292: if (nx & 2**44 > 0)
293: result += result * 0x162e42fefa39eff >> 133;['294']
294: if (nx & 2**43 > 0)
295: result += result * 0xb17217f7d1cf7f >> 133;['296']
296: if (nx & 2**42 > 0)
297: result += result * 0x58b90bfbe8e7bf >> 133;['298']
298: if (nx & 2**41 > 0)
299: result += result * 0x2c5c85fdf473df >> 133;['300']
300: if (nx & 2**40 > 0)
301: result += result * 0x162e42fefa39ff >> 133;['302']
302: if (nx & 2**39 > 0)
303: result += result * 0xb17217f7d1cff >> 133;['304']
304: if (nx & 2**38 > 0)
305: result += result * 0x58b90bfbe8e7f >> 133;['306']
306: if (nx & 2**37 > 0)
307: result += result * 0x2c5c85fdf473f >> 133;['308']
308: if (nx & 2**36 > 0)
309: result += result * 0x162e42fefa39f >> 133;['310']
310: if (nx & 2**35 > 0)
311: result += result * 0xb17217f7d1df >> 133;['312']
312: if (nx & 2**34 > 0)
313: result += result * 0x58b90bfbe8ff >> 133;['314']
314: if (nx & 2**33 > 0)
315: result += result * 0x2c5c85fdf47f >> 133;['316']
316: if (nx & 2**32 > 0)
317: result += result * 0x162e42fefa3f >> 133;['318']
318: if (nx & 2**31 > 0)
319: result += result * 0xb17217f7d1f >> 133;['320']
320: if (nx & 2**30 > 0)
321: result += result * 0x58b90bfbe9f >> 133;['322']
322: if (nx & 2**29 > 0)
323: result += result * 0x2c5c85fdf5f >> 133;['324']
324: if (nx & 2**28 > 0)
325: result += result * 0x162e42fefbf >> 133;['326']
326: if (nx & 2**27 > 0)
327: result += result * 0xb17217f7df >> 133;['328']
328: if (nx & 2**26 > 0)
329: result += result * 0x58b90bfbff >> 133;['330']
330: if (nx & 2**25 > 0)
331: result += result * 0x2c5c85fdff >> 133;['332']
332: if (nx & 2**24 > 0)
333: result += result * 0x162e42feff >> 133;['334']
334: if (nx & 2**23 > 0)
335: result += result * 0xb17217f7f >> 133;['336']
336: if (nx & 2**22 > 0)
337: result += result * 0x58b90bfbf >> 133;['338']
338: if (nx & 2**21 > 0)
339: result += result * 0x2c5c85fdf >> 133;['340']
340: if (nx & 2**20 > 0)
341: result += result * 0x162e42fff >> 133;['342']
342: if (nx & 2**19 > 0)
343: result += result * 0xb17217ff >> 133;['344']
344: if (nx & 2**18 > 0)
345: result += result * 0x58b90bff >> 133;['346']
346: if (nx & 2**17 > 0)
347: result += result * 0x2c5c85ff >> 133;['348']
348: if (nx & 2**16 > 0)
349: result += result * 0x162e42ff >> 133;['350']
350: if (nx & 2**15 > 0)
351: result += result * 0xb17217f >> 133;['352']
352: if (nx & 2**14 > 0)
353: result += result * 0x58b90bf >> 133;['354']
354: if (nx & 2**13 > 0)
355: result += result * 0x2c5c85f >> 133;['356']
356: if (nx & 2**12 > 0)
357: result += result * 0x162e43f >> 133;['358']
358: if (nx & 2**11 > 0)
359: result += result * 0xb1721f >> 133;['360']
360: if (nx & 2**10 > 0)
361: result += result * 0x58b91f >> 133;['362']
362: if (nx & 2**9 > 0)
363: result += result * 0x2c5c9f >> 133;['364']
364: if (nx & 2**8 > 0)
365: result += result * 0x162e5f >> 133;['366']
366: if (nx & 2**7 > 0)
367: result += result * 0xb173f >> 133;['368']
368: if (nx & 2**6 > 0)
369: result += result * 0x58b9f >> 133;['370']
370: if (nx & 2**5 > 0)
371: result += result * 0x2c5df >> 133;['372']
372: if (nx & 2**4 > 0)
373: result += result * 0x162ff >> 133;['374']
374: if (nx & 2**3 > 0)
375: result += result * 0xb17f >> 133;['376']
376: if (nx & 2**2 > 0)
377: result += result * 0x58bf >> 133;['378']
378: if (nx & 2**1 > 0)
379: result += result * 0x2c5f >> 133;['380']
380: if (nx & 2**0 > 0)
381: result += result * 0x163f >> 133;['223']
223: if (amountToRedeem > 0) alchemist.redeem(amountToRedeem);['232']
232:
233: if (blocksLeft > 0) _updateStakingGraph(-position.amount.toInt256() * BLOCK_SCALING_FACTOR / transmutationTime.toInt256(), blocksLeft);['133']
133: require (nx < 0); When performing divisions in Solidity, the operation costs gas and includes a check for division by zero. However, if you are dividing by a constant or an immutable value that is guaranteed to be non-zero, this check becomes unnecessary, consuming extra gas without adding safety.
Resolution: Utilize the unchecked block for divisions involving constant or immutable values that are assuredly non-zero. This bypasses the additional safety checks, optimizing gas usage. Ensure thorough testing and code reviews are conducted to verify the non-zero condition of the denominator, preventing any potential division by zero errors and maintaining contract safety.
Num of instances: 1
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: 12
Click to show findings
['140']
140: if (msg.sender != admin && !guardians[msg.sender]) { // <= FOUND
141: revert Unauthorized();
142: }['984']
984: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) { // <= FOUND
985: debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, previousRedemption.earmarkWeight - account.lastAccruedEarmarkWeight);
986:
987: earmarkPreviousState = account.earmarked + debtToEarmark;
988: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight);
989: }['1049']
1049: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) { // <= FOUND
1050: debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, previousRedemption.earmarkWeight - account.lastAccruedEarmarkWeight);
1051:
1052: earmarkedPreviousState = account.earmarked + debtToEarmark;
1053: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight);
1054: }['50']
50: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) { // <= FOUND
51: revert ERC20CallFailed(token, success, data);
52: }['64']
64: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) { // <= FOUND
65: revert ERC20CallFailed(token, success, data);
66: }['140']
140: if (msg.sender != admin && !guardians[msg.sender]) { // <= FOUND['984']
984: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) { // <= FOUND['50']
50: if (!success || (data.length != 0 && !abi.decode(data, (bool)))) { // <= FOUND['52']
52: require(amount <= DELTA_MAX && amount >= DELTA_MIN); // <= FOUND['153']
153:
154: require(ad <= DELTA_MAX && ad >= DELTA_MIN); // <= FOUND['154']
154: require(ap <= PRODUCT_MAX && ap >= PRODUCT_MIN); // <= FOUND['64']
64: if (token.code.length == 0 || !success || (data.length != 0 && !abi.decode(data, (bool)))) { // <= FOUNDStorage 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: 2
Click to show findings
['46']
46: uint256 public timeToTransmute; // <= FOUND['29']
29: uint256 immutable updateTimeInSeconds; // <= FOUNDNum 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: 2
Click to show findings
['196']
196: emit PositionCreated(msg.sender, syntheticDepositAmount, _nonce); // <= FOUND['221']
221: emit AdminUpdated(admin); // <= FOUNDOptimizing low-level calls using assembly in Solidity can be beneficial, particularly when dealing with function return data. Typically, even if return data from a low-level call is not used, Solidity still allocates memory to store it, which incurs gas costs. By using assembly, developers can bypass the automatic memory allocation for unused return data. This manual optimization involves handling the call at the assembly level and deliberately choosing not to store the return data in memory when it's not needed.
Num of instances: 15
Click to show findings
['88']
88:
89: (bool success,) = recipient.call{value: amount}(""); // <= FOUND['62']
62: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, recipient, amount)); // <= FOUND['77']
77: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, value)); // <= FOUND['93']
93: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, owner, recipient, amount)); // <= FOUND['108']
108: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Mintable.mint.selector, recipient, amount)); // <= FOUND['122']
122: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Burnable.burn.selector, amount)); // <= FOUND['137']
137: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Burnable.burnFrom.selector, owner, amount)); // <= FOUND['88']
88:
89: (bool success,) = recipient.call{value: amount}(""); // <= FOUND['62']
62: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, recipient, amount)); // <= FOUND['77']
77: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, value)); // <= FOUND['93']
93: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, owner, recipient, amount)); // <= FOUND['108']
108: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Mintable.mint.selector, recipient, amount)); // <= FOUND['122']
122: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Burnable.burn.selector, amount)); // <= FOUND['137']
137: (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Burnable.burnFrom.selector, owner, amount)); // <= FOUND['88']
88:
89: (bool success,) = recipient.call{value: amount}(""); // <= FOUNDNum of instances: 1
Click to show findings
['146']
146: modifier onlyTransmuter() { // <= FOUND
147: if (msg.sender != transmuter) {
148: revert Unauthorized();
149: }
150: _;
151: }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
['1028']
1028: function _calculateUnrealizedDebt(uint256 tokenId) internal view returns (uint256, uint256, uint256) { // <= FOUND
1029: Account storage account = _accounts[tokenId];
1030: RedemptionInfo memory previousRedemption = _redemptions[lastRedemptionBlock];
1031:
1032: uint256 amount;
1033: uint256 earmarkWeightCopy = _earmarkWeight;
1034:
1035:
1036: if (block.number > lastEarmarkBlock) {
1037: amount = ITransmuter(transmuter).queryGraph(lastEarmarkBlock + 1, block.number);
1038: if (amount > 0) {
1039: earmarkWeightCopy += PositionDecay.WeightIncrement(amount, totalDebt - cumulativeEarmarked);
1040: }
1041: }
1042:
1043: uint256 debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, earmarkWeightCopy - account.lastAccruedEarmarkWeight);
1044: uint256 earmarkedState = account.earmarked + debtToEarmark;
1045:
1046:
1047: uint256 earmarkedPreviousState;
1048: uint256 earmarkToRedeem;
1049: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) {
1050: debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, previousRedemption.earmarkWeight - account.lastAccruedEarmarkWeight); // <= FOUND
1051:
1052: earmarkedPreviousState = account.earmarked + debtToEarmark;
1053: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight);
1054: } else {
1055: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedState, _redemptionWeight - account.lastAccruedRedemptionWeight);
1056: }
1057:
1058: uint256 collateralToRemove = PositionDecay.ScaleByWeightDelta(account.rawLocked, _collateralWeight - account.lastCollateralWeight);
1059:
1060: return (account.debt - earmarkToRedeem, earmarkedState - earmarkToRedeem, account.collateralBalance - collateralToRemove);
1061: }['974']
974: function _sync(uint256 tokenId) internal { // <= FOUND
975: Account storage account = _accounts[tokenId];
976: RedemptionInfo memory previousRedemption = _redemptions[lastRedemptionBlock];
977:
978: uint256 debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, _earmarkWeight - account.lastAccruedEarmarkWeight);
979: uint256 earmarkedState = account.earmarked + debtToEarmark;
980:
981:
982: uint256 earmarkToRedeem;
983: uint256 earmarkPreviousState;
984: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) {
985: debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, previousRedemption.earmarkWeight - account.lastAccruedEarmarkWeight); // <= FOUND
986:
987: earmarkPreviousState = account.earmarked + debtToEarmark;
988: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight);
989: } else {
990: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedState, _redemptionWeight - account.lastAccruedRedemptionWeight);
991: }
992:
993: uint256 collateralToRemove = PositionDecay.ScaleByWeightDelta(account.rawLocked, _collateralWeight - account.lastCollateralWeight);
994:
995:
996: account.earmarked = earmarkedState - earmarkToRedeem;
997: account.debt -= earmarkToRedeem;
998: account.lastAccruedRedemptionWeight = _redemptionWeight;
999: account.lastAccruedEarmarkWeight = _earmarkWeight;
1000:
1001: account.collateralBalance -= collateralToRemove;
1002: account.rawLocked -= collateralToRemove;
1003: account.lastCollateralWeight = _collateralWeight;
1004: }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: 4
Click to show findings
['2']
2: pragma solidity >=0.8.4;['1']
1: pragma solidity >=0.5.0;['9']
9: pragma solidity ^0.8.7;['1']
1: pragma solidity ^0.8.13;Rather than calling .length for an array in a for loop declaration, it is far more gas efficient to cache this length before and use that instead. This will prevent the array length from being called every loop iteration
Num of instances: 2
Click to show findings
['553']
553: for (uint256 i = 0; i < accountIds.length; i++) // <= FOUND['553']
553: for (uint256 i = 0; i < accountIds.length; i++) // <= FOUNDIf a internal function is only used once it doesn't make sense to modularise it unless the function which does call the function would be overly long and complex otherwise
Num of instances: 21
Click to show findings
['21']
21: function ethToUSD(uint256 ethAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 usdAmount) // <= FOUND['49']
49: function ethToUSD(uint256 ethAmount) external view returns (uint256 usdAmount) // <= FOUND['48']
48: function usdToETH(uint256 usdAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 ethAmount) // <= FOUND['44']
44: function usdToETH(uint256 usdAmount) external view returns (uint256 ethAmount) // <= FOUND['75']
75: function underlyingTokenToUSD(uint256 underlyingTokenAmount, uint256 underlyingTokenDecimals) internal pure returns (uint256 usdAmount) // <= FOUND['54']
54: function underlyingTokenToUSD(uint256 underlyingTokenAmount) external view returns (uint256 usdAmount) // <= FOUND['735']
735: function _forceRepay(uint256 accountId, uint256 amount) internal returns (uint256) // <= FOUND['852']
852: function _addDebt(uint256 tokenId, uint256 amount) internal // <= FOUND['892']
892: function _approveMint(uint256 ownerTokenId, address spender, uint256 amount) internal // <= FOUND['903']
903: function _decreaseMintAllowance(uint256 ownerTokenId, address spender, uint256 amount) internal // <= FOUND['1067']
1067: function _isUnderCollateralized(uint256 tokenId) internal view returns (bool) // <= FOUND['38']
38: function encodeRaw(uint256 value) internal pure returns (uint256) // <= FOUND['137']
137: function div(Number memory self, uint256 value) internal pure returns (Number memory) // <= FOUND['19']
19: function generateSVG(uint256 tokenId, string memory title) internal pure returns (string memory) // <= FOUND['53']
53: function generateJSONString(uint256 tokenId, string memory svg) internal pure returns (string memory) // <= FOUND['35']
35: function remove(AddressSet storage self, address value) internal returns (bool) // <= FOUND['36']
36: function remove(address caller) external override // <= FOUND['50']
50: function addStake(Graph storage g, int256 amount, uint256 start, uint256 duration) internal // <= FOUND['108']
108: function queryStake(Graph storage g, uint256 start, uint256 end) internal view returns (int256) // <= FOUND['107']
107: function safeMint(address token, address recipient, uint256 amount) internal // <= FOUND['136']
136: function safeBurnFrom(address token, address owner, uint256 amount) internal // <= FOUNDNum of instances: 10
Click to show findings
['43']
43: constructor(address _token, address _alchemist, address _owner) Ownable(_owner) {
44: _checkNonZeroAddress(_token);
45: _checkNonZeroAddress(_alchemist);
46: token = _token;
47: authorized[_alchemist] = true;
48: authorized[_owner] = true;
49: emit AuthorizationUpdated(_alchemist, true);
50: }['37']
37: constructor(address _priceFeed, uint256 _updateTimeInSeconds, uint256 _underlyingTokenDecimals) {
38: priceFeed = _priceFeed;
39: updateTimeInSeconds = _updateTimeInSeconds;
40: underlyingTokenDecimals = _underlyingTokenDecimals;
41: }['17']
17: constructor(address _token, address _underlyingToken) {
18: token = _token;
19: underlyingToken = _underlyingToken;
20: }['27']
27: constructor(address _weth, address _alchemist, address _owner) AbstractFeeVault(_weth, _alchemist, _owner) {}['20']
20: constructor(address _token, address _alchemist, address _owner) AbstractFeeVault(_token, _alchemist, _owner) {}['52']
52: constructor(address alchemist_) ERC721("AlchemistV3Position", "ALCV3") {
53: if (alchemist_ == address(0)) {
54: revert AlchemistZeroAddressError();
55: }
56: alchemist = alchemist_;
57: }['153']
153: constructor() initializer {}['41']
41: constructor() {}['83']
83: constructor(ITransmuter.TransmuterInitializationParams memory params) ERC721("Alchemix V3 Transmuter", "TRNSMTR") {
84: syntheticToken = params.syntheticToken;
85: timeToTransmute = params.timeToTransmute;
86: transmutationFee = params.transmutationFee;
87: exitFee = params.exitFee;
88: protocolFeeReceiver = params.feeReceiver;
89: admin = msg.sender;
90: graphSize = params.graphSize;
91: }['18']
18: constructor() Ownable(msg.sender) {}Internal functions which are never used use unnecessary gas and should be safely removed.
Num of instances: 9
Click to show findings
['1080']
1080: function _getLiquidationAmount(uint256 collateral, uint256 debt, uint256 globalRatio) internal view returns (uint256 liquidationAmount) // <= FOUND['59']
59: function rational(uint256 n, uint256 d) internal pure returns (Number memory) // <= FOUND['150']
150: function cmp(Number memory self, Number memory value) internal pure returns (int256) // <= FOUND['170']
170: function equals(Number memory self, Number memory value) internal pure returns (bool) // <= FOUND['179']
179: function truncate(Number memory self) internal pure returns (uint256) // <= FOUND['32']
32: function uint256ToUint128(uint256 y) internal pure returns (uint128 z) // <= FOUND['42']
42: function uint128ToUint256(uint128 y) internal pure returns (uint256 z) // <= FOUND['76']
76: function safeApprove(address token, address spender, uint256 value) internal // <= FOUND['76']
76: function safeApprove(address token, address spender, uint256 value) internal // <= FOUNDEmitting 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: 13
Click to show findings
['57']
57: function setAuthorization(address account, bool status) external onlyOwner { // <= FOUND
58: _checkNonZeroAddress(account);
59: authorized[account] = status;
60: emit AuthorizationUpdated(account, status); // <= FOUND
61: }['204']
204: function setPendingAdmin(address value) external onlyAdmin { // <= FOUND
205: pendingAdmin = value;
206:
207: emit PendingAdminUpdated(value); // <= FOUND
208: }['226']
226: function setDepositCap(uint256 value) external onlyAdmin { // <= FOUND
227: _checkArgument(value >= IERC20(yieldToken).balanceOf(address(this)));
228:
229: depositCap = value;
230: emit DepositCapUpdated(value); // <= FOUND
231: }['242']
242: function setProtocolFee(uint256 fee) external onlyAdmin { // <= FOUND
243: _checkArgument(fee <= BPS);
244:
245: protocolFee = fee;
246: emit ProtocolFeeUpdated(fee); // <= FOUND
247: }['250']
250: function setLiquidatorFee(uint256 fee) external onlyAdmin { // <= FOUND
251: _checkArgument(fee <= BPS);
252:
253: liquidatorFee = fee;
254: emit LiquidatorFeeUpdated(fee); // <= FOUND
255: }['285']
285: function setMinimumCollateralization(uint256 value) external onlyAdmin { // <= FOUND
286: _checkArgument(value >= FIXED_POINT_SCALAR);
287: minimumCollateralization = value;
288:
289: emit MinimumCollateralizationUpdated(value); // <= FOUND
290: }['293']
293: function setGlobalMinimumCollateralization(uint256 value) external onlyAdmin { // <= FOUND
294: _checkArgument(value >= minimumCollateralization);
295: globalMinimumCollateralization = value;
296: emit GlobalMinimumCollateralizationUpdated(value); // <= FOUND
297: }['300']
300: function setCollateralizationLowerBound(uint256 value) external onlyAdmin { // <= FOUND
301: _checkArgument(value <= minimumCollateralization);
302: _checkArgument(value >= FIXED_POINT_SCALAR);
303: collateralizationLowerBound = value;
304: emit CollateralizationLowerBoundUpdated(value); // <= FOUND
305: }['116']
116: function setAlchemist(address value) external onlyAdmin { // <= FOUND
117: alchemist = IAlchemistV3(value);
118:
119: emit AlchemistUpdated(value); // <= FOUND
120: }['123']
123: function setDepositCap(uint256 cap) external onlyAdmin { // <= FOUND
124: _checkArgument(cap <= type(int256).max.toUint256());
125:
126: depositCap = cap;
127: emit DepositCapUpdated(cap); // <= FOUND
128: }['131']
131: function setTransmutationFee(uint256 fee) external onlyAdmin { // <= FOUND
132: _checkArgument(fee <= BPS);
133:
134: transmutationFee = fee;
135: emit TransmutationFeeUpdated(fee); // <= FOUND
136: }['139']
139: function setExitFee(uint256 fee) external onlyAdmin { // <= FOUND
140: _checkArgument(fee <= BPS);
141:
142: exitFee = fee;
143: emit ExitFeeUpdated(fee); // <= FOUND
144: }['147']
147: function setTransmutationTime(uint256 time) external onlyAdmin { // <= FOUND
148: timeToTransmute = time;
149:
150: emit TransmutationTimeUpdated(time); // <= FOUND
151: }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
['222']
222: emit PendingAdminUpdated(address(0)); // <= FOUND['49']
49: emit AuthorizationUpdated(_alchemist, true); // <= FOUNDThe 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: 1
Reading a state variable inside a loop in a smart contract can unnecessarily increase gas consumption, as each read operation from the blockchain state is costly. This inefficiency becomes pronounced in loops with many iterations. To optimize gas usage, it's advisable to read the state variable once before the loop starts, store its value in a local (memory) variable, and then use this local variable within the loop. This approach minimizes the number of state read operations, thereby reducing the gas cost associated with executing the contract function, making the smart contract more efficient and cost-effective to run.
Num of instances: 1
Click to show findings
['135']
135: while (index <= treeSize) {
136:
137: uint256 packed = graph[index];
138: int256 ad;
139: int256 ap;
140:
141:
142: if ((packed&DELTA_SIGNBIT) != 0) {
143: ad = int256(packed | ~DELTA_MASK);
144: } else {
145: ad = int256(packed & DELTA_MASK);
146: }
147: ap = int256(packed)>>DELTA_BITS;
148:
149: ad+=delta;
150: ap+=deltaProd;
151:
152:
153: require(ad <= DELTA_MAX && ad >= DELTA_MIN); // <= FOUND
154: require(ap <= PRODUCT_MAX && ap >= PRODUCT_MIN);
155: graph[index] = (uint256(ad)&DELTA_MASK)|uint256(ap<<DELTA_BITS);
156:
157: assembly {
158: index := add(index, and(index, sub(0, index)))
159: }
160: }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: 5
Click to show findings
['47']
47: authorized[_alchemist] = true; // <= FOUND['48']
48: authorized[_owner] = true; // <= FOUND['949']
949:
950: exists = true; // <= FOUND['48']
48: disabled = true; // <= FOUND['952']
952:
953: exists = false; // <= FOUNDConsider saving the address(this) value within a constant using foundry's script.sol or solady's LibRlp.sol to save gas
Num of instances: 15
Click to show findings
['34']
34: return address(this).balance; // <= FOUND['58']
58:
59: IERC20(token).safeTransferFrom(msg.sender, address(this), amount); // <= FOUND['85']
85:
86: if (amount > address(this).balance) revert InsufficientBalance(); // <= FOUND['28']
28: IERC20(token).transferFrom(msg.sender, address(this), amount); // <= FOUND['50']
50: return IERC20(token).balanceOf(address(this)); // <= FOUND['86']
86:
87: ERC721(address(this)).ownerOf(tokenId); // <= FOUND['227']
227: _checkArgument(value >= IERC20(yieldToken).balanceOf(address(this))); // <= FOUND['327']
327: return IERC20(yieldToken).balanceOf(address(this)); // <= FOUND['361']
361: _checkState(IERC20(yieldToken).balanceOf(address(this)) + amount <= depositCap); // <= FOUND['376']
376:
377: TokenUtils.safeTransferFrom(yieldToken, msg.sender, address(this), amount); // <= FOUND['762']
762:
763: TokenUtils.safeTransfer(yieldToken, address(this), creditToYield); // <= FOUND['1102']
1102: uint256 yieldTokenTVL = IERC20(yieldToken).balanceOf(address(this)); // <= FOUND['162']
162:
163: ERC721(address(this)).ownerOf(id); // <= FOUND['185']
185: TokenUtils.safeTransferFrom(syntheticToken, msg.sender, address(this), syntheticDepositAmount); // <= FOUND['220']
220:
221: uint256 yieldTokenBalance = TokenUtils.safeBalanceOf(alchemist.yieldToken(), address(this)); // <= FOUNDIn Solidity, choosing between memory and storage for variables, especially when dealing with structs or arrays, is crucial for optimizing gas costs. Variables declared as storage are pointers to the blockchain data, leading to lower gas consumption when fields are accessed or modified, as they don't require reading the entire structure. In contrast, memory variables copy the entire struct or array from storage, incurring significant gas costs, especially for large or complex structures. Therefore, use storage for state variables or when working within functions to manipulate existing contract data. Reserve memory for temporary data or when data needs to be passed to external functions as copies, ensuring efficient use of gas and avoiding unnecessary costs.
Num of instances: 1
Click to show findings
['976']
976: RedemptionInfo memory previousRedemption = _redemptions[lastRedemptionBlock]; // <= FOUNDIn smart contract development, utilizing constants for known maximum or minimum values, rather than computing type(uint<n>).max or assuming 0 for .min, can significantly reduce gas costs. Constants require less runtime computation and storage, optimizing contract efficiency—a crucial strategy for developers aiming for cost-effective and performant code.
Num of instances: 5
Click to show findings
['49']
49: return Number(type(uint256).max); // <= FOUND['41']
41: require(total <= type(uint128).max); // <= FOUND['68']
68: require(value <= type(uint128).max); // <= FOUND['385']
385: require (result <= uint256 (type(uint128).max)); // <= FOUND['33']
33: if (y > type(uint128).max) { // <= FOUNDAAlthough the following contracts are 'using SafeCast for uint256', no instances of down casting or uint to int casting were found. As such, this import can be removed to save Gas.
Num of instances: 1
Click to show findings
['23']
23: contract AlchemistV3 is IAlchemistV3, Initializable {
24: using SafeCast for int256; // <= FOUND
25: using SafeCast for uint256; // <= FOUND
26: using SafeCast for int128; // <= FOUND
27: using SafeCast for uint128; // <= FOUND
28:
29: uint256 public constant BPS = 10_000;
30: uint256 public constant FIXED_POINT_SCALAR = 1e18;
31:
33: string public constant version = "3.0.0";
34:
36: address public admin;
37:
39: address public alchemistFeeVault;
40:
42: address public debtToken;
43:
45: uint256 public underlyingConversionFactor;
46:
48: uint256 public blocksPerYear;
49:
51: uint256 public cumulativeEarmarked;
52:
54: uint256 public depositCap;
55:
57: uint256 public lastEarmarkBlock;
58:
60: uint256 public lastRedemptionBlock;
61:
63: uint256 public minimumCollateralization;
64:
66: uint256 public collateralizationLowerBound;
67:
69: uint256 public globalMinimumCollateralization;
70:
72: uint256 public totalDebt;
73:
75: uint256 public totalSyntheticsIssued;
76:
78: uint256 public protocolFee;
79:
81: uint256 public liquidatorFee;
82:
84: address public alchemistPositionNFT;
85:
87: address public protocolFeeReceiver;
88:
90: address public underlyingToken;
91:
93: address public yieldToken;
94:
96: address public tokenAdapter;
97:
99: address public transmuter;
100:
102: address public pendingAdmin;
103:
105: bool public depositsPaused;
106:
108: bool public loansPaused;
109:
111: mapping(address => bool) public guardians;
112:
114: uint256 private _earmarkWeight;
115:
117: uint256 private _redemptionWeight;
118:
120: uint256 private _collateralWeight;
121:
124: uint256 private _totalLocked;
125:
127: mapping(uint256 => Account) private _accounts;
128:
130: mapping(uint256 => RedemptionInfo) private _redemptions;
131:
380: }Num of instances: 59
Click to show findings
['21']
21: function ethToUSD(uint256 ethAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 usdAmount) {
22:
23: AggregatorV3Interface priceFeed = AggregatorV3Interface(usdPriceFeed);
24: (, int256 price,, uint256 updateTime,) = priceFeed.latestRoundData();
25:
26: if (price <= 0) {
27: revert ChainlinkMalfunction(usdPriceFeed, price);
28: }
29:
30: if (updateTime == 0) {
31: revert IncompleteRound(usdPriceFeed, updateTime);
32: }
33:
34: if (updateTime < block.timestamp - expectedUpdateTime) {
35: revert IncompleteRound(usdPriceFeed, updateTime);
36: }
37:
38:
39: return (ethAmount * uint256(price)) / 1e20; // <= FOUND
40: }['48']
48: function usdToETH(uint256 usdAmount, address usdPriceFeed, uint256 expectedUpdateTime) internal view returns (uint256 ethAmount) {
49:
50: AggregatorV3Interface priceFeed = AggregatorV3Interface(usdPriceFeed);
51: (, int256 price,, uint256 updateTime,) = priceFeed.latestRoundData();
52:
53: if (price <= 0) {
54: revert ChainlinkMalfunction(usdPriceFeed, price);
55: }
56:
57: if (updateTime == 0) {
58: revert IncompleteRound(usdPriceFeed, updateTime);
59: }
60:
61: if (updateTime < block.timestamp - expectedUpdateTime) {
62: revert IncompleteRound(usdPriceFeed, updateTime);
63: }
64:
65:
66:
67: return (usdAmount * 1e20) / uint256(price); // <= FOUND
68: }['44']
44: function usdToETH(uint256 usdAmount) external view returns (uint256 ethAmount) {
45: return ETHUSDConverter.usdToETH(usdAmount, priceFeed, updateTimeInSeconds); // <= FOUND
46: }['49']
49: function ethToUSD(uint256 ethAmount) external view returns (uint256 usdAmount) {
50: return ETHUSDConverter.ethToUSD(ethAmount, priceFeed, updateTimeInSeconds); // <= FOUND
51: }['54']
54: function underlyingTokenToUSD(uint256 underlyingTokenAmount) external view returns (uint256 usdAmount) {
55: return ETHUSDConverter.underlyingTokenToUSD(underlyingTokenAmount, underlyingTokenDecimals); // <= FOUND
56: }['22']
22: function price() external view returns (uint256) {
23: return IERC4626(token).convertToAssets(10**TokenUtils.expectDecimals(token)); // <= FOUND
24: }['33']
33: function totalDeposits() public view override returns (uint256) {
34: return address(this).balance; // <= FOUND
35: }['49']
49: function totalDeposits() public view override returns (uint256) {
50: return IERC20(token).balanceOf(address(this)); // <= FOUND
51: }['84']
84: function tokenURI(uint256 tokenId) public view override returns (string memory) {
85:
86: ERC721(address(this)).ownerOf(tokenId);
87: return NFTMetadataGenerator.generateTokenURI(tokenId, "Alchemist V3 Position"); // <= FOUND
88: }['93']
93: function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721Enumerable) returns (bool) {
94: return super.supportsInterface(interfaceId); // <= FOUND
95: }['320']
320: function getCDP(uint256 tokenId) external view returns (uint256, uint256, uint256) {
321: (uint256 debt, uint256 earmarked, uint256 collateral) = _calculateUnrealizedDebt(tokenId);
322: return (collateral, debt, earmarked); // <= FOUND
323: }['326']
326: function getTotalDeposited() external view returns (uint256) {
327: return IERC20(yieldToken).balanceOf(address(this)); // <= FOUND
328: }['331']
331: function getMaxBorrowable(uint256 tokenId) external view returns (uint256) {
332: (uint256 debt,, uint256 collateral) = _calculateUnrealizedDebt(tokenId);
333: uint256 debtValueOfCollateral = convertYieldTokensToDebt(collateral);
334: return (debtValueOfCollateral * FIXED_POINT_SCALAR / minimumCollateralization) - debt; // <= FOUND
335: }['338']
338: function mintAllowance(uint256 ownerTokenId, address spender) external view returns (uint256) {
339: Account storage account = _accounts[ownerTokenId];
340: return account.mintAllowances[account.allowancesVersion][spender]; // <= FOUND
341: }['344']
344: function getTotalUnderlyingValue() external view returns (uint256) {
345: return _getTotalUnderlyingValue(); // <= FOUND
346: }['349']
349: function totalValue(uint256 tokenId) public view returns (uint256) {
350: uint256 totalUnderlying;
351: (,, uint256 collateral) = _calculateUnrealizedDebt(tokenId);
352: if (collateral > 0) totalUnderlying += convertYieldTokensToUnderlying(collateral);
353: return normalizeUnderlyingTokensToDebt(totalUnderlying); // <= FOUND
354: }['678']
678: function convertYieldTokensToDebt(uint256 amount) public view returns (uint256) {
679: return normalizeUnderlyingTokensToDebt(convertYieldTokensToUnderlying(amount)); // <= FOUND
680: }['683']
683: function convertDebtTokensToYield(uint256 amount) public view returns (uint256) {
684: return convertUnderlyingTokensToYield(normalizeDebtTokensToUnderlying(amount)); // <= FOUND
685: }['688']
688: function convertYieldTokensToUnderlying(uint256 amount) public view returns (uint256) {
689: uint8 decimals = TokenUtils.expectDecimals(yieldToken);
690: return (amount * ITokenAdapter(tokenAdapter).price()) / 10 ** decimals; // <= FOUND
691: }['694']
694: function convertUnderlyingTokensToYield(uint256 amount) public view returns (uint256) {
695: uint8 decimals = TokenUtils.expectDecimals(yieldToken);
696: return amount * 10 ** decimals / ITokenAdapter(tokenAdapter).price(); // <= FOUND
697: }['700']
700: function normalizeUnderlyingTokensToDebt(uint256 amount) public view returns (uint256) {
701: return amount * underlyingConversionFactor; // <= FOUND
702: }['705']
705: function normalizeDebtTokensToUnderlying(uint256 amount) public view returns (uint256) {
706: return amount / underlyingConversionFactor; // <= FOUND
707: }['942']
942: function _tokenExists(address nft, uint256 tokenId) internal view returns (bool exists) {
943: if (tokenId == 0) {
944:
945: return false; // <= FOUND
946: }
947: try IERC721(nft).ownerOf(tokenId) {
948:
949: exists = true;
950: } catch {
951:
952: exists = false;
953: }
954: }['1028']
1028: function _calculateUnrealizedDebt(uint256 tokenId) internal view returns (uint256, uint256, uint256) {
1029: Account storage account = _accounts[tokenId];
1030: RedemptionInfo memory previousRedemption = _redemptions[lastRedemptionBlock];
1031:
1032: uint256 amount;
1033: uint256 earmarkWeightCopy = _earmarkWeight;
1034:
1035:
1036: if (block.number > lastEarmarkBlock) {
1037: amount = ITransmuter(transmuter).queryGraph(lastEarmarkBlock + 1, block.number);
1038: if (amount > 0) {
1039: earmarkWeightCopy += PositionDecay.WeightIncrement(amount, totalDebt - cumulativeEarmarked);
1040: }
1041: }
1042:
1043: uint256 debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, earmarkWeightCopy - account.lastAccruedEarmarkWeight);
1044: uint256 earmarkedState = account.earmarked + debtToEarmark;
1045:
1046:
1047: uint256 earmarkedPreviousState;
1048: uint256 earmarkToRedeem;
1049: if (block.number > lastRedemptionBlock && _redemptionWeight != 0) {
1050: debtToEarmark = PositionDecay.ScaleByWeightDelta(account.debt - account.earmarked, previousRedemption.earmarkWeight - account.lastAccruedEarmarkWeight);
1051:
1052: earmarkedPreviousState = account.earmarked + debtToEarmark;
1053: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedPreviousState, _redemptionWeight - account.lastAccruedRedemptionWeight);
1054: } else {
1055: earmarkToRedeem = PositionDecay.ScaleByWeightDelta(earmarkedState, _redemptionWeight - account.lastAccruedRedemptionWeight);
1056: }
1057:
1058: uint256 collateralToRemove = PositionDecay.ScaleByWeightDelta(account.rawLocked, _collateralWeight - account.lastCollateralWeight);
1059:
1060: return (account.debt - earmarkToRedeem, earmarkedState - earmarkToRedeem, account.collateralBalance - collateralToRemove); // <= FOUND
1061: }['1067']
1067: function _isUnderCollateralized(uint256 tokenId) internal view returns (bool) {
1068: uint256 debt = _accounts[tokenId].debt;
1069: if (debt == 0) return false; // <= FOUND
1070:
1071: uint256 collateralization = totalValue(tokenId) * FIXED_POINT_SCALAR / debt;
1072: return collateralization < minimumCollateralization; // <= FOUND
1073: }['1080']
1080: function _getLiquidationAmount(uint256 collateral, uint256 debt, uint256 globalRatio) internal view returns (uint256 liquidationAmount) {
1081: _checkState(minimumCollateralization > FIXED_POINT_SCALAR);
1082: if (debt >= collateral) {
1083:
1084: return debt; // <= FOUND
1085: }
1086:
1087: if (globalRatio < globalMinimumCollateralization) {
1088:
1089: return debt; // <= FOUND
1090: }
1091:
1092: uint256 expectedCollateralForCurrentDebt = (debt * minimumCollateralization) / FIXED_POINT_SCALAR;
1093: uint256 collateralDiff = expectedCollateralForCurrentDebt - collateral;
1094: uint256 ratioDiff = minimumCollateralization - FIXED_POINT_SCALAR;
1095: liquidationAmount = collateralDiff * FIXED_POINT_SCALAR / ratioDiff;
1096: return liquidationAmount; // <= FOUND
1097: }['29']
29: function expectDecimals(address token) internal view returns (uint8) {
30: (bool success, bytes memory data) = token.staticcall(abi.encodeWithSelector(IERC20Metadata.decimals.selector));
31:
32: if (!success || data.length < 32) {
33: revert ERC20CallFailed(token, success, data);
34: }
35:
36: return abi.decode(data, (uint8)); // <= FOUND
37: }['65']
65: function contains(AddressSet storage self, address value) internal view returns (bool) {
66: return self.indexes[value] != 0; // <= FOUND
67: }['108']
108: function queryStake(Graph storage g, uint256 start, uint256 end) internal view returns (int256) {
109: int256 begDelta;
110: int256 begProd;
111: int256 endDelta;
112: int256 endProd;
113: unchecked {
114: require (end <= GRAPH_MAX);
115:
116: start--;
117: require (start <= GRAPH_MAX);
118:
119: (begDelta,begProd) = query(g.g, start);
120: (endDelta,endProd) = query(g.g, end);
121:
122: return ((int256(end) * endDelta) - endProd) - ((int256(start) * begDelta) - begProd); // <= FOUND
123: }
124: }['26']
26: function expectDecimals(address token) internal view returns (uint8) {
27: (bool success, bytes memory data) = token.staticcall(abi.encodeWithSelector(IERC20Metadata.decimals.selector));
28:
29: if (token.code.length == 0 || !success || data.length < 32) {
30: revert ERC20CallFailed(token, success, data);
31: }
32:
33: return abi.decode(data, (uint8)); // <= FOUND
34: }['44']
44: function safeBalanceOf(address token, address account) internal view returns (uint256) {
45: (bool success, bytes memory data) = token.staticcall(abi.encodeWithSelector(IERC20.balanceOf.selector, account));
46:
47: if (token.code.length == 0 || !success || data.length < 32) {
48: revert ERC20CallFailed(token, success, data);
49: }
50:
51: return abi.decode(data, (uint256)); // <= FOUND
52: }['160']
160: function tokenURI(uint256 id) public view override returns (string memory) {
161:
162: ERC721(address(this)).ownerOf(id);
163: return NFTMetadataGenerator.generateTokenURI(id, "Transmuter V3 Position"); // <= FOUND
164: }['167']
167: function getPosition(uint256 id) external view returns (StakingPosition memory) {
168: return _positions[id]; // <= FOUND
169: }['260']
260: function queryGraph(uint256 startBlock, uint256 endBlock) external view returns (uint256) {
261: int256 queried = _stakingGraph.queryStake(startBlock, endBlock);
262:
263: if (queried == 0) return 0; // <= FOUND
264:
265: return (queried / BLOCK_SCALING_FACTOR).toUint256() + 1; // <= FOUND
266:
267: }['21']
21: function getAddresses() external view returns (address[] memory) {
22: return addresses.values; // <= FOUND
23: }['53']
53: function isWhitelisted(address account) external view override returns (bool) {
54: return disabled || addresses.contains(account); // <= FOUND
55: }['75']
75: function underlyingTokenToUSD(uint256 underlyingTokenAmount, uint256 underlyingTokenDecimals) internal pure returns (uint256 usdAmount) {
76: uint256 usdConversionFactor = 10 ** (underlyingTokenDecimals - USDC_DECIMALS);
77: return underlyingTokenAmount / usdConversionFactor; // <= FOUND
78: }['631']
631: function calculateLiquidation(
632: uint256 collateral,
633: uint256 debt,
634: uint256 targetCollateralization,
635: uint256 alchemistCurrentCollateralization,
636: uint256 alchemistMinimumCollateralization,
637: uint256 feeBps
638: ) public pure returns (uint256 grossCollateralToSeize, uint256 debtToBurn, uint256 fee) {
639: if (debt >= collateral) {
640:
641: return (debt, debt, 0); // <= FOUND
642: }
643:
644: if (alchemistCurrentCollateralization < alchemistMinimumCollateralization) {
645:
646: return (debt, debt, 0); // <= FOUND
647: }
648:
649:
650: uint256 surplus = collateral > debt ? collateral - debt : 0;
651: fee = (surplus * feeBps) / BPS;
652:
653:
654: uint256 adjCollat = collateral - fee;
655:
656:
657: uint256 md = (targetCollateralization * debt) / FIXED_POINT_SCALAR;
658:
659:
660: if (md <= adjCollat) {
661: return (0, 0, fee); // <= FOUND
662: }
663:
664:
665: uint256 num = md - adjCollat;
666:
667:
668: uint256 denom = targetCollateralization - FIXED_POINT_SCALAR;
669:
670:
671: debtToBurn = (num * FIXED_POINT_SCALAR) / denom;
672:
673:
674: grossCollateralToSeize = debtToBurn + fee;
675: }['27']
27: function encode(uint256 value) internal pure returns (Number memory) {
28: return Number(FixedPointMath.encodeRaw(value)); // <= FOUND
29: }['38']
38: function encodeRaw(uint256 value) internal pure returns (uint256) {
39: return value * ONE; // <= FOUND
40: }['48']
48: function max() internal pure returns (Number memory) {
49: return Number(type(uint256).max); // <= FOUND
50: }['59']
59: function rational(uint256 n, uint256 d) internal pure returns (Number memory) {
60: Number memory numerator = encode(n);
61: return FixedPointMath.div(numerator, d); // <= FOUND
62: }['71']
71: function add(Number memory self, Number memory value) internal pure returns (Number memory) {
72: return Number(self.n + value.n); // <= FOUND
73: }['82']
82: function add(Number memory self, uint256 value) internal pure returns (Number memory) {
83: return add(self, FixedPointMath.encode(value)); // <= FOUND
84: }['93']
93: function sub(Number memory self, Number memory value) internal pure returns (Number memory) {
94: return Number(self.n - value.n); // <= FOUND
95: }['104']
104: function sub(Number memory self, uint256 value) internal pure returns (Number memory) {
105: return sub(self, FixedPointMath.encode(value)); // <= FOUND
106: }['115']
115: function mul(Number memory self, Number memory number) internal pure returns (Number memory) {
116: return Number((self.n * number.n) / ONE); // <= FOUND
117: }['126']
126: function mul(Number memory self, uint256 value) internal pure returns (Number memory) {
127: return Number(self.n * value); // <= FOUND
128: }['137']
137: function div(Number memory self, uint256 value) internal pure returns (Number memory) {
138: return Number(self.n / value); // <= FOUND
139: }['150']
150: function cmp(Number memory self, Number memory value) internal pure returns (int256) {
151: if (self.n < value.n) {
152: return -1; // <= FOUND
153: }
154:
155: if (self.n > value.n) {
156: return 1; // <= FOUND
157: }
158:
159: return 0; // <= FOUND
160: }['170']
170: function equals(Number memory self, Number memory value) internal pure returns (bool) {
171: return self.n == value.n; // <= FOUND
172: }['179']
179: function truncate(Number memory self) internal pure returns (uint256) {
180: return self.n / ONE; // <= FOUND
181: }[]
function generateSVG(uint256 tokenId, string memory title) internal pure returns (string memory) {
return string( // <= FOUND
abi.encodePacked(
'<svg xmlns="http:www.w3.org/2000/svg" viewBox="0 0 300 484" width="300" height="484">',
'<rect width="300" height="484" fill="',
SVG_BG_COLOR,
'" />',
'<circle cx="150" cy="150" r="120" fill="none" stroke="',
SVG_ACCENT_COLOR,
'" stroke-width="4" />',
'<text x="150" y="120" font-family="Arial" font-size="24" fill="',
SVG_TEXT_COLOR,
'" text-anchor="middle">',
title,
"</text>",
'<text x="150" y="150" font-family="Arial" font-size="28" fill="',
SVG_ACCENT_COLOR,
'" text-anchor="middle" font-weight="bold">Position</text>',
'<text x="150" y="190" font-family="monospace" font-size="24" fill="',
SVG_TEXT_COLOR,
'" text-anchor="middle">#',
tokenId.toString(),
"</text>",
"</svg>"
)
);
}['53']
53: function generateJSONString(uint256 tokenId, string memory svg) internal pure returns (string memory) {
54: string memory json = Base64.encode(
55: abi.encodePacked(
56: '{"name": "AlchemistV3 Position #',
57: tokenId.toString(),
58: '", ',
59: '"description": "Position token for Alchemist V3", ',
60: '"image": "data:image/svg+xml;base64,',
61: Base64.encode(bytes(svg)),
62: '"}'
63: )
64: );
65: return json; // <= FOUND
66: }['74']
74: function generateTokenURI(uint256 tokenId, string memory title) internal pure returns (string memory) {
75: string memory svg = generateSVG(tokenId, title);
76: string memory json = generateJSONString(tokenId, svg);
77: return string(abi.encodePacked("data:application/json;base64,", json)); // <= FOUND
78: }['38']
38: function WeightIncrement(uint256 increment, uint256 total) internal pure returns (uint256) {
39: unchecked {
40: require(increment <= total);
41: require(total <= type(uint128).max);
42:
43:
44: if (increment == 0) {
45:
46: return 0; // <= FOUND
47: }
48:
49: uint256 ratio = ((total - increment) << 128) / total;
50: if (ratio == 0) {
51:
52: return LOG2NEGFRAC_1+1; // <= FOUND
53: }
54: return Log2NegFrac(ratio); // <= FOUND
55: }
56: }['66']
66: function ScaleByWeightDelta(uint256 value, uint256 weightDelta) internal pure returns (uint256) {
67: unchecked {
68: require(value <= type(uint128).max);
69:
70: if (weightDelta == 0) {
71:
72: return 0; // <= FOUND
73: }
74:
75:
76:
77:
78:
79:
80:
81: return value - ((value * Exp2NegFrac(weightDelta)) >> 128); // <= FOUND
82: }
83: }['92']
92: function Log2NegFrac(uint256 x) private pure returns (uint256) {
93: unchecked {
94: if (x >= 2**128) return 0; // <= FOUND
95:
96: require(x > 0);
97:
98: int256 msb = 0;
99: uint256 xc = x;
100: if (xc >= 0x10000000000000000) { xc >>= 64; msb += 64; }
101: if (xc >= 0x100000000) { xc >>= 32; msb += 32; }
102: if (xc >= 0x10000) { xc >>= 16; msb += 16; }
103: if (xc >= 0x100) { xc >>= 8; msb += 8; }
104: if (xc >= 0x10) { xc >>= 4; msb += 4; }
105: if (xc >= 0x4) { xc >>= 2; msb += 2; }
106: if (xc >= 0x2) msb += 1;
107:
108: int256 result = (msb - 128) << 120;
109: uint256 ux = uint256(x) << uint256(127 - msb);
110: for (int256 bit = 0x800000000000000000000000000000; bit > 0; bit >>= 1) {
111: ux *= ux;
112: uint256 b = ux >> 255;
113: ux >>= 127 + b;
114: result += bit * int256 (b);
115: }
116:
117: return uint256(-result); // <= FOUND
118: }
119: }['127']
127: function Exp2NegFrac(uint256 x) private pure returns (uint256) {
128: unchecked {
129: if (x > LOG2NEGFRAC_1) return 0; // <= FOUND
130:
131: int256 nx = -int256(x);
132:
133: require (nx < 0);
134:
135:
136:
137:
138:
139: uint256 result = 0x80000000000000000000000000000051;
140:
141:
142: if (nx & 2**119 > 0)
143: result += result * 0xd413cccfe779921165f626cdd52afa89 >> 129;
144: if (nx & 2**118 > 0)
145: result += result * 0xc1bf828c6dc54b7a356918c17217b7bf >> 130;
146: if (nx & 2**117 > 0)
147: result += result * 0xb95c1e3ea8bd6e6fbe4628758a53c90f >> 131;
148: if (nx & 2**116 > 0)
149: result += result * 0xb5586cf9890f6298b92b71842a98364f >> 132;
150: if (nx & 2**115 > 0)
151: result += result * 0xb361a62b0ae875cf8a91d6d19482ffdf >> 133;
152: if (nx & 2**114 > 0)
153: result += result * 0x59347cef00c1dcdef95949ef4537bd3f >> 133;
154: if (nx & 2**113 > 0)
155: result += result * 0x2c7b53f6666adb094cd5c66db9bf481f >> 133;
156: if (nx & 2**112 > 0)
157: result += result * 0x1635f4b5797dac2535627d823b92a89f >> 133;
158: if (nx & 2**111 > 0)
159: result += result * 0xb190db43813d43fe33a5299e5ecf39f >> 133;
160: if (nx & 2**110 > 0)
161: result += result * 0x58c0bc5d19d8a0da437f9134474021f >> 133;
162: if (nx & 2**109 > 0)
163: result += result * 0x2c5e72080a3f425179538ab863cbc1f >> 133;
164: if (nx & 2**108 > 0)
165: result += result * 0x162ebdffb8ed7471c62ce395272e4df >> 133;
166: if (nx & 2**107 > 0)
167: result += result * 0xb17403f73f2dad959a9630122f87df >> 133;
168: if (nx & 2**106 > 0)
169: result += result * 0x58b986fb52923a130b86918d1cf67f >> 133;
170: if (nx & 2**105 > 0)
171: result += result * 0x2c5ca4bdc0a8ea88afab32a52404df >> 133;
172: if (nx & 2**104 > 0)
173: result += result * 0x162e4aaeeb8080c317e9495bd07f9f >> 133;
174: if (nx & 2**103 > 0)
175: result += result * 0xb17236b7935c5ddb03d36fa99f59f >> 133;
176: if (nx & 2**102 > 0)
177: result += result * 0x58b913abd8d949af9159802f6f93f >> 133;
178: if (nx & 2**101 > 0)
179: result += result * 0x2c5c87e9f0620c1c05b07353a2dbf >> 133;
180: if (nx & 2**100 > 0)
181: result += result * 0x162e4379f933b3f121d40d222ec7f >> 133;
182: if (nx & 2**99 > 0)
183: result += result * 0xb17219e3cdb2ff39429b7982c51f >> 133;
184: if (nx & 2**98 > 0)
185: result += result * 0x58b90c76e7e02c8d1ed758b9459f >> 133;
186: if (nx & 2**97 > 0)
187: result += result * 0x2c5c861cb431ec233c78045054bf >> 133;
188: if (nx & 2**96 > 0)
189: result += result * 0x162e4306aa2970dcdb2ddfa37fff >> 133;
190: if (nx & 2**95 > 0)
191: result += result * 0xb1721816918d7cbbf08d8b65e1f >> 133;
192: if (nx & 2**94 > 0)
193: result += result * 0x58b90c0398d73d284278e4e6f5f >> 133;
194: if (nx & 2**93 > 0)
195: result += result * 0x2c5c85ffe06fbe715456433e0df >> 133;
196: if (nx & 2**92 > 0)
197: result += result * 0x162e42ff7538e7354b033e89f3f >> 133;
198: if (nx & 2**91 > 0)
199: result += result * 0xb17217f9bdcb59a7839db9047f >> 133;
200: if (nx & 2**90 > 0)
201: result += result * 0x58b90bfc63e6b4d461b437ca1f >> 133;
202: if (nx & 2**89 > 0)
203: result += result * 0x2c5c85fe13339c6a8373fff9ff >> 133;
204: if (nx & 2**88 > 0)
205: result += result * 0x162e42ff01e9deb55bb48aaa9f >> 133;
206: if (nx & 2**87 > 0)
207: result += result * 0xb17217f7f08f37ab5036a35bf >> 133;
208: if (nx & 2**86 > 0)
209: result += result * 0x58b90bfbf097ac55c614e999f >> 133;
210: if (nx & 2**85 > 0)
211: result += result * 0x2c5c85fdf65fda4aeab37b55f >> 133;
212: if (nx & 2**84 > 0)
213: result += result * 0x162e42fefab4ee2d7749535ff >> 133;
214: if (nx & 2**83 > 0)
215: result += result * 0xb17217f7d3bb758bc21399ff >> 133;
216: if (nx & 2**82 > 0)
217: result += result * 0x58b90bfbe962bbcde2fd61bf >> 133;
218: if (nx & 2**81 > 0)
219: result += result * 0x2c5c85fdf4929e28f1fbc0bf >> 133;
220: if (nx & 2**80 > 0)
221: result += result * 0x162e42fefa419f24f91d299f >> 133;
222: if (nx & 2**79 > 0)
223: result += result * 0xb17217f7d1ee3969c9667df >> 133;
224: if (nx & 2**78 > 0)
225: result += result * 0x58b90bfbe8ef6cc564d28bf >> 133;
226: if (nx & 2**77 > 0)
227: result += result * 0x2c5c85fdf475ca66d27119f >> 133;
228: if (nx & 2**76 > 0)
229: result += result * 0x162e42fefa3a6a34713a81f >> 133;
230: if (nx & 2**75 > 0)
231: result += result * 0xb17217f7d1d165a7a9dbff >> 133;
232: if (nx & 2**74 > 0)
233: result += result * 0x58b90bfbe8e837d4dcefff >> 133;
234: if (nx & 2**73 > 0)
235: result += result * 0x2c5c85fdf473fd2ab0787f >> 133;
236: if (nx & 2**72 > 0)
237: result += result * 0x162e42fefa39f6e568bc5f >> 133;
238: if (nx & 2**71 > 0)
239: result += result * 0xb17217f7d1cf986b87e3f >> 133;
240: if (nx & 2**70 > 0)
241: result += result * 0x58b90bfbe8e7c485d471f >> 133;
242: if (nx & 2**69 > 0)
243: result += result * 0x2c5c85fdf473e056ee59f >> 133;
244: if (nx & 2**68 > 0)
245: result += result * 0x162e42fefa39efb07835f >> 133;
246: if (nx & 2**67 > 0)
247: result += result * 0xb17217f7d1cf7b97c5df >> 133;
248: if (nx & 2**66 > 0)
249: result += result * 0x58b90bfbe8e7bd50e3ff >> 133;
250: if (nx & 2**65 > 0)
251: result += result * 0x2c5c85fdf473de89b23f >> 133;
252: if (nx & 2**64 > 0)
253: result += result * 0x162e42fefa39ef3d293f >> 133;
254: if (nx & 2**63 > 0)
255: result += result * 0xb17217f7d1cf79ca89f >> 133;
256: if (nx & 2**62 > 0)
257: result += result * 0x58b90bfbe8e7bcdd95f >> 133;
258: if (nx & 2**61 > 0)
259: result += result * 0x2c5c85fdf473de6cdff >> 133;
260: if (nx & 2**60 > 0)
261: result += result * 0x162e42fefa39ef35f5f >> 133;
262: if (nx & 2**59 > 0)
263: result += result * 0xb17217f7d1cf79adbf >> 133;
264: if (nx & 2**58 > 0)
265: result += result * 0x58b90bfbe8e7bcd65f >> 133;
266: if (nx & 2**57 > 0)
267: result += result * 0x2c5c85fdf473de6b1f >> 133;
268: if (nx & 2**56 > 0)
269: result += result * 0x162e42fefa39ef359f >> 133;
270: if (nx & 2**55 > 0)
271: result += result * 0xb17217f7d1cf79abf >> 133;
272: if (nx & 2**54 > 0)
273: result += result * 0x58b90bfbe8e7bcd5f >> 133;
274: if (nx & 2**53 > 0)
275: result += result * 0x2c5c85fdf473de6bf >> 133;
276: if (nx & 2**52 > 0)
277: result += result * 0x162e42fefa39ef35f >> 133;
278: if (nx & 2**51 > 0)
279: result += result * 0xb17217f7d1cf79bf >> 133;
280: if (nx & 2**50 > 0)
281: result += result * 0x58b90bfbe8e7bcdf >> 133;
282: if (nx & 2**49 > 0)
283: result += result * 0x2c5c85fdf473de7f >> 133;
284: if (nx & 2**48 > 0)
285: result += result * 0x162e42fefa39ef3f >> 133;
286: if (nx & 2**47 > 0)
287: result += result * 0xb17217f7d1cf79f >> 133;
288: if (nx & 2**46 > 0)
289: result += result * 0x58b90bfbe8e7bdf >> 133;
290: if (nx & 2**45 > 0)
291: result += result * 0x2c5c85fdf473dff >> 133;
292: if (nx & 2**44 > 0)
293: result += result * 0x162e42fefa39eff >> 133;
294: if (nx & 2**43 > 0)
295: result += result * 0xb17217f7d1cf7f >> 133;
296: if (nx & 2**42 > 0)
297: result += result * 0x58b90bfbe8e7bf >> 133;
298: if (nx & 2**41 > 0)
299: result += result * 0x2c5c85fdf473df >> 133;
300: if (nx & 2**40 > 0)
301: result += result * 0x162e42fefa39ff >> 133;
302: if (nx & 2**39 > 0)
303: result += result * 0xb17217f7d1cff >> 133;
304: if (nx & 2**38 > 0)
305: result += result * 0x58b90bfbe8e7f >> 133;
306: if (nx & 2**37 > 0)
307: result += result * 0x2c5c85fdf473f >> 133;
308: if (nx & 2**36 > 0)
309: result += result * 0x162e42fefa39f >> 133;
310: if (nx & 2**35 > 0)
311: result += result * 0xb17217f7d1df >> 133;
312: if (nx & 2**34 > 0)
313: result += result * 0x58b90bfbe8ff >> 133;
314: if (nx & 2**33 > 0)
315: result += result * 0x2c5c85fdf47f >> 133;
316: if (nx & 2**32 > 0)
317: result += result * 0x162e42fefa3f >> 133;
318: if (nx & 2**31 > 0)
319: result += result * 0xb17217f7d1f >> 133;
320: if (nx & 2**30 > 0)
321: result += result * 0x58b90bfbe9f >> 133;
322: if (nx & 2**29 > 0)
323: result += result * 0x2c5c85fdf5f >> 133;
324: if (nx & 2**28 > 0)
325: result += result * 0x162e42fefbf >> 133;
326: if (nx & 2**27 > 0)
327: result += result * 0xb17217f7df >> 133;
328: if (nx & 2**26 > 0)
329: result += result * 0x58b90bfbff >> 133;
330: if (nx & 2**25 > 0)
331: result += result * 0x2c5c85fdff >> 133;
332: if (nx & 2**24 > 0)
333: result += result * 0x162e42feff >> 133;
334: if (nx & 2**23 > 0)
335: result += result * 0xb17217f7f >> 133;
336: if (nx & 2**22 > 0)
337: result += result * 0x58b90bfbf >> 133;
338: if (nx & 2**21 > 0)
339: result += result * 0x2c5c85fdf >> 133;
340: if (nx & 2**20 > 0)
341: result += result * 0x162e42fff >> 133;
342: if (nx & 2**19 > 0)
343: result += result * 0xb17217ff >> 133;
344: if (nx & 2**18 > 0)
345: result += result * 0x58b90bff >> 133;
346: if (nx & 2**17 > 0)
347: result += result * 0x2c5c85ff >> 133;
348: if (nx & 2**16 > 0)
349: result += result * 0x162e42ff >> 133;
350: if (nx & 2**15 > 0)
351: result += result * 0xb17217f >> 133;
352: if (nx & 2**14 > 0)
353: result += result * 0x58b90bf >> 133;
354: if (nx & 2**13 > 0)
355: result += result * 0x2c5c85f >> 133;
356: if (nx & 2**12 > 0)
357: result += result * 0x162e43f >> 133;
358: if (nx & 2**11 > 0)
359: result += result * 0xb1721f >> 133;
360: if (nx & 2**10 > 0)
361: result += result * 0x58b91f >> 133;
362: if (nx & 2**9 > 0)
363: result += result * 0x2c5c9f >> 133;
364: if (nx & 2**8 > 0)
365: result += result * 0x162e5f >> 133;
366: if (nx & 2**7 > 0)
367: result += result * 0xb173f >> 133;
368: if (nx & 2**6 > 0)
369: result += result * 0x58b9f >> 133;
370: if (nx & 2**5 > 0)
371: result += result * 0x2c5df >> 133;
372: if (nx & 2**4 > 0)
373: result += result * 0x162ff >> 133;
374: if (nx & 2**3 > 0)
375: result += result * 0xb17f >> 133;
376: if (nx & 2**2 > 0)
377: result += result * 0x58bf >> 133;
378: if (nx & 2**1 > 0)
379: result += result * 0x2c5f >> 133;
380: if (nx & 2**0 > 0)
381: result += result * 0x163f >> 133;
382:
383:
384: result >>= uint256 (int256 (-1 - (nx >> 120)));
385: require (result <= uint256 (type(uint128).max));
386:
387: return result; // <= FOUND
388: }
389: }