Functions allowing users to specify the receiving address should check whether the referenced address is not address(this). This would prevent tokens to remain lock inside the contract. An example of the potential for loss by leaving this open is the EOS token smart contract where more than 90,000 tokens are stuck at the contract address.
Proof Of Concept
File: Beedle.sol
22: function _mint(address to, uint256 amount)
internal
override(ERC20, ERC20Votes)
{
super._mint(to, amount);
}
The use of fixed decimal values such as 1e18 or 1e8 in Solidity contracts can lead to inaccuracies, bugs, and vulnerabilities, particularly when interacting with tokens having different decimal configurations. Not all ERC20 tokens follow the standard 18 decimal places, and assumptions about decimal places can lead to miscalculations.
Resolution: Always retrieve and use the decimals() function from the token contract itself when performing calculations involving token amounts. This ensures that your contract correctly handles tokens with any number of decimal places, mitigating the risk of numerical errors or under/overflows that could jeopardize contract integrity and user funds.
Contracts are importing draft dependencies. These imported contracts are still a draft and are not considered ready for mainnet use and have not received adequate security auditing or are liable to change with future development.
Proof Of Concept
File: Beedle.sol
6: import {ERC20Permit} from "openzeppelin-contracts/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
Consider limiting the number of iterations in for-loops that make external calls
Proof Of Concept
File: Lender.sol
293: for (uint256 i = 0; i < loanIds.length; i++) {
359: for (uint256 i = 0; i < loanIds.length; i++) {
438: for (uint256 i = 0; i < loanIds.length; i++) {
549: for (uint256 i = 0; i < loanIds.length; i++) {
Division by large numbers may result in the result being zero, due to solidity not supporting fractions. Consider requiring a minimum amount for the numerator to ensure that it is always larger than the denominator
The core function mint is used by users to mint an option position by providing token1 as collateral and borrowing the max amount of liquidity. Address(0) check is missing in both this function and the internal function _mint, which is triggered to mint the tokens to the to address. Consider applying a check in the function to ensure tokens aren't minted to the zero address.
Proof Of Concept
File: Beedle.sol
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount);
}
}
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.
Lack of zero-address validation on address parameters may lead to transaction reverts, waste gas, require resubmission of transactions and may even force contract redeployments in certain cases within the protocol.
Proof Of Concept
File: Lender.sol
100: function setFeeReceiver: address _feeReceiver
The non-upgradeable standard version of OpenZeppelin’s library are inherited / used by the contracts.
It would be safer to use the upgradeable versions of the library contracts to avoid unexpected behaviour.
Proof of Concept
File: Beedle.sol
5: import {ERC20} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
6: import {ERC20Permit} from "openzeppelin-contracts/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
7: import {ERC20Votes} from "openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Votes.sol";
The compiler for Solidity 0.8.20 switches the default target EVM version to Shanghai, which includes the new PUSH0 op code. This op code may not yet be implemented on all L2s, so deployment on these chains will fail. To work around this issue, use an earlier EVMversion. While the project itself may or may not compile with 0.8.20, other projects with which it integrates, or which extend this project may, and those projects will have problems deploying these contracts/libraries.
Recommend considering implementing a two step process where the owner or admin nominates an account and the nominated account needs to call an acceptOwnership() function for the transfer of ownership to fully succeed. This ensures the nominated EOA account is a valid and active account.
Proof Of Concept
File: Ownable.sol
19: function transferOwnership: function transferOwnership(address _owner) public virtual onlyOwner {
New items are pushed into the following arrays but there is no option to pop them out. Currently, the array can grow indefinitely. E.g. there's no maximum limit and there's no functionality to remove array values.
If the array grows too large, calling relevant functions might run out of gas and revert. Calling these functions could result in a DOS condition.
Ownable2Step and Ownable2StepUpgradeable prevent the contract ownership from mistakenly being transferred to an address that cannot handle it (e.g. due to a typo in the address), by requiring that the recipient of the owner permissions actively accept via a contract call of its own.
Low Risk Report
Severity
Low Risk
QA Summary
Low Risk Issues
address(this)
is disabledfor-
loop may result in a DOSERC20
tokensPUSH0
Ownable2Step
rather thanOwnable
safeTransferOwnership
instead oftransferOwnership
functionTotal: 75 contexts over 17 issues
Low Risk Issues
[LOW‑1] Consider checking that transfer to
address(this)
is disabledFunctions allowing users to specify the receiving address should check whether the referenced address is not
address(this)
. This would prevent tokens to remain lock inside the contract. An example of the potential for loss by leaving this open is the EOS token smart contract where more than 90,000 tokens are stuck at the contract address.Proof Of Concept
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Beedle.sol#L22
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Beedle.sol#L29
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Beedle.sol#L36
[LOW‑2] Constant decimal values
The use of fixed decimal values such as 1e18 or 1e8 in Solidity contracts can lead to inaccuracies, bugs, and vulnerabilities, particularly when interacting with tokens having different decimal configurations. Not all ERC20 tokens follow the standard 18 decimal places, and assumptions about decimal places can lead to miscalculations.
Resolution: Always retrieve and use the
decimals()
function from the token contract itself when performing calculations involving token amounts. This ensures that your contract correctly handles tokens with any number of decimal places, mitigating the risk of numerical errors or under/overflows that could jeopardize contract integrity and user funds.Proof Of Concept
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L68
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L88
[LOW‑3] Draft Import Dependencies
Contracts are importing draft dependencies. These imported contracts are still a draft and are not considered ready for mainnet use and have not received adequate security auditing or are liable to change with future development.
Proof Of Concept
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Beedle.sol#L6
Recommended Mitigation Steps
Stop using draft imported contracts and use their release version if it exists.
[LOW‑4] External calls in an un-bounded
for-
loop may result in a DOSConsider limiting the number of iterations in
for-
loops that make external callsProof Of Concept
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L293
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L359
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L438
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L549
[LOW‑5] Lack of checks-effects-interactions
It's recommended to execute external calls after state changes, to prevent reentrancy bugs.
Consider moving the external calls after the state changes on the following instances.
Proof Of Concept
[LOW‑6] Large transfers may not work with some
ERC20
tokensSome
IERC20
implementations (e.gUNI
,COMP
) may fail if the valued transferred is larger thanuint96
. SourceProof Of Concept
[LOW‑7] Possible rounding issue
Division by large numbers may result in the result being zero, due to solidity not supporting fractions. Consider requiring a minimum amount for the numerator to ensure that it is always larger than the denominator
Proof Of Concept
[LOW‑8] Minting tokens to the zero address should be avoided
The core function
mint
is used by users to mint an option position by providing token1 as collateral and borrowing the max amount of liquidity.Address(0)
check is missing in both this function and the internal function_mint
, which is triggered to mint the tokens to the to address. Consider applying a check in the function to ensure tokens aren't minted to the zero address.Proof Of Concept
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Beedle.sol#L36
[LOW‑9] Constructor is missing zero address check
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.
Proof Of Concept
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Fees.sol#L19
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/utils/Ownable.sol#L14
[LOW‑10] Missing Checks for Address(0x0)
Lack of zero-address validation on address parameters may lead to transaction reverts, waste gas, require resubmission of transactions and may even force contract redeployments in certain cases within the protocol.
Proof Of Concept
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L100
Recommended Mitigation Steps
Consider adding explicit zero-address validation on input parameters of address type.
[LOW‑11] Contracts are not using their OZ Upgradeable counterparts
The non-upgradeable standard version of OpenZeppelin’s library are inherited / used by the contracts. It would be safer to use the upgradeable versions of the library contracts to avoid unexpected behaviour.
Proof of Concept
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Beedle.sol#L5-L7
Recommended Mitigation Steps
Where applicable, use the contracts from
@openzeppelin/contracts-upgradeable
instead of@openzeppelin/contracts
. See https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/tree/master/contracts for list of available upgradeable contracts[LOW‑12] Solidity version 0.8.20 may not work on other chains due to
PUSH0
The compiler for Solidity 0.8.20 switches the default target EVM version to Shanghai, which includes the new
PUSH0
op code. This op code may not yet be implemented on all L2s, so deployment on these chains will fail. To work around this issue, use an earlier EVM version. While the project itself may or may not compile with 0.8.20, other projects with which it integrates, or which extend this project may, and those projects will have problems deploying these contracts/libraries.Proof Of Concept
[LOW‑13] TransferOwnership Should Be Two Step
Recommend considering implementing a two step process where the owner or admin nominates an account and the nominated account needs to call an acceptOwnership() function for the transfer of ownership to fully succeed. This ensures the nominated EOA account is a valid and active account.
Proof Of Concept
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/utils/Ownable.sol#L19
Recommended Mitigation Steps
Lack of two-step procedure for critical operations leaves them error-prone. Consider adding two step procedure on the critical functions.
[LOW‑14] Unbounded loop
New items are pushed into the following arrays but there is no option to
pop
them out. Currently, the array can grow indefinitely. E.g. there's no maximum limit and there's no functionality to remove array values. If the array grows too large, calling relevant functions might run out of gas and revert. Calling these functions could result in a DOS condition.Proof Of Concept
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L276
Recommended Mitigation Steps
Add a functionality to delete array values or add a maximum size limit for arrays.
[LOW‑15] Use
Ownable2Step
rather thanOwnable
Ownable2Step
andOwnable2StepUpgradeable
prevent the contract ownership from mistakenly being transferred to an address that cannot handle it (e.g. due to a typo in the address), by requiring that the recipient of the owner permissions actively accept via a contract call of its own.Proof Of Concept
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Beedle.sol#L4
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Beedle.sol#L9
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Beedle.sol#L11
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L8
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L10
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L73
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L5
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L11
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L31
[LOW‑17] A year is not always 365 days
On leap years, the number of days is 366, so calculations during those years will return the wrong value
Proof Of Concept
https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L724