CertiK reviewed Unreal Finance on 13 Oct 2022: 20 findings, 0 critical; 16 marked fixed, 4 open.
Report hosted on skynet.certik.com.
Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 2 | 0 | 1 |
| Medium | 0 | 0 | 1 |
| Low | 2 | 0 | 14 |
`Core` is an upgradeable contract that does not protect its `initialize()` function. Anyone can delete the contract with: `UUPSUpgradeable.upgradeTo(address)` or `UUPSUpgradeable.upgradeToAndCall(address, bytes)`. ```solidity function initialize(address _treasuryAddress) public initializer{ ``` ```solidity function upgradeTo(address newImplementation) external virtual onlyProxy { ``` ```solidity function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual onlyProxy { ```
A state variable is shadowing another component defined in a parent contract. Variable `_owner` in `Core` shadows the variable `_owner` in `OwnableUpgradeable`. ```solidity=18 address private _owner; ``` ```solidity=22 address private _owner; ```
Variable `_decimals` can be made private to avoid two getter functions `_decimals()` and `decimals()` that return the same value.
The input `_protocol` used to check whether the given protocol is supported does not necessarily have to match the protocol that corresponds to the futures contract implemented through the input `_bytecode`. It is possible bytecode for a futures contract corresponding to one protocol is stored under the `streamKey` of a different protocol.
`_amountUnderlying` is the amount of underlying asset the `msg.sender` wishes to deposit, while `totalSupply` refers to the total supply of associated yield tokens for that underlying asset. The yield tokens are in one-to-one ratio with the asset tokens that are deposited, and they have the same decimals as the underlying asset. It is possible for the entire product `yield * _amountUnderlying / totalSupply` to exceed the value of `_amountUnderlying`, causing a revert due to underflow in `amountOT`. This may keep a user from depositing the underlying asset.
When transferring deflationary ERC20 tokens, the input amount may not be equal to the received amount due to the charged transaction fee. For example, if a user sends 100 deflationary tokens (with a 10% transaction fee), only 90 tokens actually arrived to the contract. However, a failure to discount such fees may allow the same user to withdraw 100 tokens from the contract, which causes the contract to lose 10 tokens in such a transaction. Reference: https://thoreum-finance.medium.com/what-exploit-happened-today-for-gocerberus-and-garuda-also-for-lokum-ybear-piggy-caramelswap-3943ee23a39f ```solidity=31 IERC20(underlying).safeTransferFrom(msg.sender, address(this), _amount); ``` - Transferring tokens by `_amount`. ```solidity=33 ILendingPool(getProtocolFrontend()).deposit(underlying, _amount, address(this), 0); ``` - The `_amount` appears to be used for bookkeeping purposes without compensating the potential transfer fees. - Note: `deposit` is an external function and its behavior wasn't evaluated. --- ```solidity=31 IERC20(underlying).safeTransferFrom(msg.sender, address(this), _amount); ``` - Transferring tokens by `_amount`. ```solidity=33 IPool(getProtocolFrontend()).supply(underlying, _amount, address(this), 0); ``` - The `_amount` appears to be used for bookkeeping purposes without compensating the potential transfer fees. - Note: `supply` is an external function and its behavior wasn't evaluated. --- ```solidity=30 IERC20(underlying).safeTransferFrom(msg.sender, address(this), _amount); ``` - Transferring tokens by `_amount`. ```solidity=32 cToken.mint(_amount); ``` - The `_amount` appears to be used for bookkeeping purposes without compensating the potential transfer fees. - Note: `mint` is an external function and its behavior wasn't evaluated. --- ```solidity=120 deposit(_amountInUnderlying); ``` - Transferring tokens by `_amountInUnderlying`. - This function call executes the following operation. - In `AFuture.deposit`, - `IERC20(underlying).safeTransferFrom(msg.sender, address(this), _amount);` ```solidity=120 deposit(_amountInUnderlying); ``` - This function call executes the following operation. - In `AFuture.deposit`, - `ILendingPool(getProtocolFrontend()).deposit(underlying, _amount, address(this), 0);` - Note: `deposit` is an external function and its behavior wasn't evaluated. - The `_amountInUnderlying` appears to be used for bookkeeping purposes without compensating the potential transfer fees. --- ```solidity=120 deposit(_amountInUnderlying); ``` - Transferring tokens by `_amountInUnderlying`. - This function call executes the following operation. - In `YFuture.deposit`, - `IERC20(underlying).safeTransferFrom(msg.sender, address(this), _amount);` ```solidity=120 deposit(_amountInUnderlying); ``` - This function call executes the following operation. - In `YFuture.deposit`, - `yVault.deposit(_amount, address(this));` - Note: `deposit` is an external function and its behavior wasn't evaluated. - The `_amountInUnderlying` appears to be used for bookkeeping purposes without compensating the potential transfer fees. --- ```solidity=126 deposit(_amount); ``` - Transferring tokens by `_amount`. - This function call executes the following operation. - In `AFuture.deposit`, - `IERC20(underlying).safeTransferFrom(msg.sender, address(this), _amount);` ```solidity=126 deposit(_amount); ``` - This function call executes the following operation. - In `AFuture.deposit`, - `ILendingPool(getProtocolFrontend()).deposit(underlying, _amount, address(this), 0);` - Note: `deposit` is an external function and its behavior wasn't evaluated. - The `_amount` appears to be used for bookkeeping purposes without compensating the potential transfer fees.
Functions that affect the status of sensitive variables should emit events as notifications.
- Input `_underlying` is missing a check that it is a non-zero address - Input `_durationSeconds` is missing a check that it is a non-zero value. - Input `_amountUnderlying` is missing a check that it is a non-zero value. - Input `_supply` is missing a check that it is a non-zero value. - Input `_core` and `_treasuryAddr` are missing a check that they are non-zero addresses. - State variable `yT` is missing a check that it is a non-zero address. - State variable `oT` is missing a check that it is a non-zero address. - Local variables `lendingProvider`, `compToken`, and `yearnVault` are missing a check that they are non-zero addresses.
Function `redeemYield()` can be called more than once, where all subsequent times will be for an `amountBurned` value of 0. Like function `redeemPrinciple()`, there should be a check that `amountBurned` is positive before proceeding with `claimYield()`.
In function `startEpoch()`, a check is made that the calculated `streamKey` corresponding to the input for `_protocol`, `_underlying`, and `_durationSeconds` is not `bytes32(0)`. No such check is made in `registerNewStream()`.
The construction of a futures contract depends on the correct input for `_bytecode` by the `_owner` of contract `Core`. However, the only check that the intended contract deployment was successful is that the `owner()` of the new futures contract is `address(this)`.
The return value of the transfer()/transferFrom() call is not checked. ```solidity=43 IERC20(underlying).transfer(treasury, underlyingbalance); ``` --- ```solidity=83 yT.transfer(_receiver, _amount); ``` --- ```solidity=93 oT.transfer(_receiver, _amount); ``` --- ```solidity=40 IERC20(underlying).transfer(treasury, underlyingbalance); ```
The contracts cited have unlocked compiler versions. An unlocked compiler version in the source code of the contract permits the user to compile it at or above a particular version. This, in turn, leads to differences in the generated bytecode between compilations due to differing compiler version numbers. This can lead to an ambiguity when debugging as compiler specific bugs may occur in the codebase that would be hard to identify over a span of multiple compiler versions rather than a specific one.
The return value of an external call is not stored in a local or state variable. ```solidity=66 prevEpochInstance.expire(); ``` --- ```solidity=42 ILendingPool(getProtocolFrontend()).withdraw(underlying, _amount, treasury); ``` --- ```solidity=42 IPool(getProtocolFrontend()).withdraw(underlying, _amount, treasury); ``` --- ```solidity=32 cToken.mint(_amount); ``` --- ```solidity=41 cToken.redeem(_amountCToken); ``` --- ```solidity=29 yVault.deposit(_amount, address(this)); ``` --- ```solidity=38 yVault.withdraw(_amount); ```
One or more state variables are never used in the codebase. Variable `_owner` in `Core` is never used in `Core`. ```solidity=18 address private _owner; ``` ```solidity=15 contract Core is Initializable, UUPSUpgradeable, OwnableUpgradeable { ``` --- Variable `initialized` in `Core` is never used in `Core`. ```solidity=20 bool initialized; ``` ```solidity=15 contract Core is Initializable, UUPSUpgradeable, OwnableUpgradeable { ```
The variables `provider`, `yVault`, `cToken`, and `_decimals` assigned in the constructor can declared with `Immutable`. Immutable state variables can be assigned during contract creation but will remain constant throughout the lifetime of a deployed contract. An advantage of immutable variables is that reading them is significantly cheaper than reading from regular state variables since will not be stored in storage.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (13)
1,044 projects reviewed · 1,375 reports · latest May 2025
See CertiK’s auditsSomething wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us