GithubHelp home page GithubHelp logo

2022-05-sturdy-findings's People

Contributors

c4-staff avatar cloudellie avatar code423n4 avatar kartoonjoy avatar liveactionllama avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

2022-05-sturdy-findings's Issues

Gas Optimizations

Gas Optimizations

[G-01] Use simple comparison in if statement

The comparison operators >= and <= use more gas than >, <, or ==. Replacing the >= and โ‰ค operators with a comparison operator that has an opcode in the EVM saves gas

The existing code is
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/GeneralVault.sol#L196-L198

if (stAssetBalance >= aTokenBalance) return stAssetBalance.sub(aTokenBalance);

return 0;

A simple comparison can be used for gas savings by reversing the logic

if (stAssetBalance < aTokenBalance) return 0;

return stAssetBalance.sub(aTokenBalance);

Recommended Mitigation Steps

Replace the comparison operator and reverse the logic to save gas using the suggestions above

[G-02] Use != 0 instead of > 0

Using > 0 uses slightly more gas than using != 0. Use != 0 when comparing uint variables to zero, which cannot hold values below zero

Locations where this was found include
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/LidoVault.sol#L36
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/LidoVault.sol#L88
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/YieldManager.sol#L131
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/ConvexCurveLPVault.sol#L75
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/GeneralVault.sol#L179

Recommended Mitigation Steps

Replace > 0 with != 0 to save gas

[G-03] Redundant zero initialization

Solidity does not recognize null as a value, so uint variables are initialized to zero. Setting a uint variable to zero is redundant and can waste gas.

There are many places where an int is initialized to zero
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/YieldManager.sol#L120
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/YieldManager.sol#L130
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/YieldManager.sol#L156
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/ConvexCurveLPVault.sol#L106
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/GeneralVault.sol#L218

Recommended Mitigation Steps

Remove the redundant zero initialization
uint256 i; instead of uint256 i = 0;

[G-04] Use prefix not postfix in loops

Using a prefix increment (++i) instead of a postfix increment (i++) saves gas for each loop cycle and so can have a big gas impact when the loop executes on a large number of elements.

There are many examples of this in for loops
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/YieldManager.sol#L120
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/YieldManager.sol#L130
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/YieldManager.sol#L156
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/ConvexCurveLPVault.sol#L106
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/GeneralVault.sol#L218

Recommended Mitigation Steps

Use prefix not postfix to increment in a loop

[G-05] Non-public variables save gas

Many constant variables are public, but changing the visibility of this variable to private or internal can save gas.

https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/YieldManager.sol#L41
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/YieldManager.sol#L48
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/YieldManager.sol#L49
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/GeneralVault.sol#L47
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/GeneralVault.sol#L55
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/CollateralAdapter.sol#L22

Recommended Mitigation Steps

Declare some public variables as private or internal to save gas

[G-06] Use newer solidity version

A solidity version before 0.8.X is used in Sturdy. The latest release of solidity includes changes that can provide gas savings. The improvements include:
The advantages of versions 0.8.* over <0.8.0 are:

  • Safemath by default from 0.8.0 (can be more gas efficient than some library based safemath).
  • Low level inliner from 0.8.2, leads to cheaper runtime gas. Especially relevant when the contract has small functions. For example, OpenZeppelin libraries typically have a lot of small helper functions and if they are not inlined, they cost an additional 20 to 40 gas because of 2 extra jump instructions and additional stack operations needed for function calls.
  • Optimizer improvements in packed structs: Before 0.8.3, storing packed structs, in some cases used an additional storage read operation. After EIP-2929, if the slot was already cold, this means unnecessary stack operations and extra deploy time costs. However, if the slot was already warm, this means additional cost of 100 gas alongside the same unnecessary stack operations and extra deploy time costs.
  • Custom errors from 0.8.4, leads to cheaper deploy time cost and run time cost. Note: the run time cost is only relevant when the revert condition is met. In short, replace revert strings by custom errors.
  • Solidity version 0.8.13 can save more gas with Yul IR pipeline

Source https://gist.github.com/hrkrshnn/ee8fabd532058307229d65dcd5836ddc#upgrade-to-at-least-084

Recommended Mitigation Steps

Use solidity release 0.8.13 with Yul IR pipeline and other improvements for gas savings

Gas Optimizations

do not cache variable used only once

description

for variables only used once, changing it to inline saves gas

findings

/2022-05-sturdy/smart-contracts/ConvexCurveLPVault.sol
62: IConvexBooster.PoolInfo memory poolInfo = IConvexBooster(convexBooster).poolInfo(convexPoolId);
76: uint256 treasuryAmount = _processTreasury(_asset, yieldAmount);
81: address yieldManager = _addressesProvider.getAddress('YIELD_MANAGER');
107:       address _extraReward = IConvexBaseRewardPool(baseRewardPool).extraRewards(i);
108:       address _rewardToken = IRewards(_extraReward).rewardToken();
/2022-05-sturdy/smart-contracts/GeneralVault.sol
119: uint256 withdrawAmount = _withdrawFromYieldPool(_asset, _amountToWithdraw, _to);
122: uint256 decimal = IERC20Detailed(_asset).decimals();
/2022-05-sturdy/smart-contracts/LidoVault.sol
37: uint256 treasuryStETH = _processTreasury(yieldStETH);

cache in variables instead of loading

description

The code can be optimized by minimising the number of SLOADs. SLOADs are expensive (100 gas) compared to MLOADs/MSTOREs (3 gas).

findings

/2022-05-sturdy/smart-contracts/YieldManager.sol
130: for (uint256 i = 0; i < assetYields.length; i++) {

using prefix increments save gas

description

Prefix increments are cheaper than postfix increments,
eg ++i rather than i++

findings

/2022-05-sturdy/smart-contracts/ConvexCurveLPVault.sol
106: for (uint256 i = 0; i < extraRewardsLength; i++) {
/2022-05-sturdy/smart-contracts/GeneralVault.sol
218: for (uint256 i = 0; i < length; i++) {
/2022-05-sturdy/smart-contracts/YieldManager.sol
120: for (uint256 i = 0; i < _count; i++) {
130: for (uint256 i = 0; i < assetYields.length; i++) {
156: for (uint256 i = 0; i < length; i++) {

Don't Initialize Variables with Default Value

description

Uninitialized variables are assigned with the types default value.

Explicitly initializing a variable with it's default value costs unnecesary gas.

findings

/2022-05-sturdy/smart-contracts/ConvexCurveLPVault.sol
106: for (uint256 i = 0; i < extraRewardsLength; i++) {
/2022-05-sturdy/smart-contracts/GeneralVault.sol
218: for (uint256 i = 0; i < length; i++) {
/2022-05-sturdy/smart-contracts/YieldManager.sol
120: for (uint256 i = 0; i < _count; i++) {
130: for (uint256 i = 0; i < assetYields.length; i++) {
156: for (uint256 i = 0; i < length; i++) {

SafeApprove is must be used instead of approve

Lines of code

https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L221

Vulnerability details

Impact

ERC20 standard allows approve function of some contracts to return bool or return nothing. Using safeApprove of SafeERC20.sol is recommended instead.

Proof of Concept

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/4a9cc8b4918ef3736229a5cc5a310bdc17bf759f/contracts/token/ERC20/utils/SafeERC20.sol

Tools Used

Recommended Mitigation Steps


Payble function allows for Eth transfer even when ERC20 tokens are being used

Lines of code

https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L75
https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/ConvexCurveLPVault.sol#L131-L149

Vulnerability details

Impact

The function depositCollateral() in GeneralVault.sol is payable. This needs to be payable for the case in LidoVault where _depositToYeild() expects ETH to be transferred.

However for the ConvexCurveLPVault.sol and the case when LidoVault is not using Eth as an asset it is still possible for msg.value to be greater than 0. If this is the case any ETH value attached to the transaction will be stuck in the contract.

Proof of Concept

The function depositCollateral() is payable

  function depositCollateral(address _asset, uint256 _amount) external payable virtual {
    // Deposit asset to vault and receive stAsset
    // Ex: if user deposit 100ETH, this will deposit 100ETH to Lido and receive 100stETH TODO No Lido
    (address _stAsset, uint256 _stAssetAmount) = _depositToYieldPool(_asset, _amount);

Recommended Mitigation Steps

Consider adding checks to _depositToYieldPool() in ConvexCurveLPVault.sol to require msg.value == 0.

Also add checks to _depositToYieldPool() in LidoVault.sol for the case when asset != address(0) to require msg.value == 0.

Gas Optimizations

++i costs less gas compared to i++ or i += 1

++i costs less gas compared to i++ or i += 1 for unsigned integer, as pre-increment is cheaper (about 5 gas per iteration). This statement is true even with the optimizer enabled.

i++ increments i and returns the initial value of i. Which means:

uint i = 1;
i++; // == 1 but i == 2

But ++i returns the actual incremented value:

uint i = 1;
++i; // == 2 and i == 2 too, so no need for a temporary variable

In the first case, the compiler has to create a temporary variable (when used) for returning 1 instead of 2
I suggest using ++i instead of i++ to increment the value of an uint variable. Same thing for --i and i--

Source code:

YieldManger swap highly vulnerable to sandwich attacks

Lines of code

https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/YieldManager.sol#L178-L212

Vulnerability details

Impact

Swap slippage of 5% allows malicious parties sandwich attack distributeYield swaps

Proof of Concept

Swap slippage of 5% allows significant extraction of funds from swaps via sandwich attack. The worst offender of this is the stable-stable swaps which realistically should never have slippage higher than 0.5%.

Tools Used

Recommended Mitigation Steps

Slippage should be specified when calling the distributeYield function according to the asset being swapped and the current market conditions. Keep current 5% slippage as a max to prevent malicious behavior, but in a majority of cases 5% is way overkill and will lead to sandwich vulnerability

Gas Optimizations

Impact

[1] Consider using optimized for-loop and apply the following optimizations:

  1. cache .length into local variable to avoid looking up every for-loop iteration.
  2. using ++i consumes 5 less gas than i++ (same applies to --i)
  3. using unchecked keyword for counter i unchecked{ ++i; } consumes 49 less gas each iteration (same applies to --i)
  4. don't initialize uint256 i = 0; instead use the default value uint256 i;
  5. make sure to specify uint256 type instead of uint type for readability

Affected code:

  1. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L106
  2. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L218
  3. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L120
  4. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L130
  5. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L156

Proof of Concept

Tools Used

Recommended Mitigation Steps


Impact

[2] Using x != 0 uses 6 less gas than x > 0.
Consider changing all "greater than zero" comparisons to "not equal to zero".

Affected code:

  1. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L75
  2. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L36
  3. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L131

Proof of Concept

Tools Used

Recommended Mitigation Steps


Impact

[3] You can upgrade to modern 0.8.4+ solidity version in order to save gas.
Custom errors are reducing 38 gas if condition is met and 22 gas otherwise.
Also reduces contract size and deployment costs.

Affected code:

  1. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/CollateralAdapter.sol#L18
  2. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L38
  3. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L71
  4. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L95
  5. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L101
  6. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L137
  7. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L183
  8. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L184
  9. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L197
  10. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L30
  11. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L35
  12. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L125
  13. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L166
  14. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L167
  15. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L179
  16. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L88
  17. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L92
  18. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L97
  19. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L142
  20. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L145
  21. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L52
  22. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L65
  23. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L97
  24. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L122
  25. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L203

Proof of Concept

Tools Used

Recommended Mitigation Steps


Impact

[4] Consider using if (stAssetBalance > aTokenBalance) return stAssetBalance.sub(aTokenBalance); because in if these values are equal return value will be zero.
This will save some gas.

Affected code:

  1. https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/GeneralVault.sol#L196

Proof of Concept

Tools Used

Recommended Mitigation Steps


QA Report

QA Report

Table of content

QA Findings

Initialize function frontrunning

Some contracts use an init function instead of a constructor. If the init function is not protected such that only the contract owner can call it then an attacker can call frontrun the function and the deployer lose the gas cost.Another critical thing is that since there is no explicit emission that allows monitoring it is not obvious that the deployer stop it at time.

open todos

Open TODOs can point to architecture or programming issues that still need to be resolved.

QA Report

Missing Equivalence Checks in Setters

Severity: Low
Context: GeneralVault.sol#L165-L172, YieldManager.sol#L64-L67, YieldManager.sol#L73-L76, YieldManager.sol#L92-L99

Description:
Setter functions are missing checks to validate if the new value being set is the same as the current value already set in the contract. Such checks will showcase mismatches between on-chain and off-chain states.

Recommendation:
Add in the additional checks to validate if the new value being set is the same as the current value already set in the contract.

Missing Time locks

Severity: Low
Context: YieldManager.sol#L64-L67, YieldManager.sol#L73-L76, YieldManager.sol#L92-L99

Description:
Some onlyOwner functions that change critical protocol addresses/parameters appear to have a time lock for a time-delayed change to alert: (1) users and give them a chance to engage/exit protocol if they are not agreeable to the changes (2) team in case of compromised owner(s) and given them a chance to perform incident response.

Recommendation:
Add a time lock to these functions for a time-delayed change to alert users and protect against possible malicious changes by compromised owners(s).

Missing Zero-address Validation

Severity: Low
Context: GeneralVault.sol#L61-L63, ConvexCurveLPVault.sol#L37-L49, CollateralAdapter.sol#L43-L50, YieldManager.sol#L73-L76, YieldManager.sol#L92-L99 (For L93 & L94)

Description:
Lack of zero-address validation on address parameters may lead to reverts and force contract redeployments.

Recommendation:
Add explicit zero-address validation on input parameters of address type.

Lack of Event Emission For Critical Functions

Severity: Low
Context: ConvexCurveLPVault.sol#L37-L49, CollateralAdapter.sol#L43-L50, YieldManager.sol#L64-L67, YieldManager.sol#L73-L76, YieldManager.sol#L92-L99, YieldManager.sol#L118-L137,

Description:
Several functions update critical parameters that are missing event emission. These should be performed to ensure tracking of changes of such critical parameters.

Recommendation:
Add events to functions that change critical parameters.

Missing or Incomplete NatSpec

Severity: Informational
Context: All Contracts

Description:
Some functions are missing @notice/@dev NatSpec comments for the function, @param for all/some of their parameters and @return for return values. Given that NatSpec is an important part of code documentation, this affects code comprehension, auditability and usability.

Recommendation:
Add in full NatSpec comments for all functions to have complete code documentation for future use.

Older Version Pragma

Severity: Informational
Context: All Contracts

Description:
Using very old versions of Solidity prevents benefits of bug fixes and newer security checks. Using the latest versions might make contracts susceptible to undiscovered compiler bugs.

Recommendation:
Consider using a more recent version such as 0.8.4.

Gas Optimizations

Gas optimizations

File ConvexCurveLPVault.sol

Variable convexBooster should be a constant

--- a/contracts/protocol/vault/ethereum/ConvexVault/ConvexCurveLPVault.sol
+++ b/contracts/protocol/vault/ethereum/ConvexVault/ConvexCurveLPVault.sol
@@ -24,7 +24,7 @@ interface IRewards {
 contract ConvexCurveLPVault is GeneralVault {
   using SafeERC20 for IERC20;
 
-  address public convexBooster;
+  address public constant convexBooster = 0xF403C135812408BFbE8713b5A23a04b3D48AAE31;
   address internal curveLPToken;
   address internal internalAssetToken;
   uint256 internal convexPoolId;
@@ -37,7 +37,6 @@ contract ConvexCurveLPVault is GeneralVault {
   function setConfiguration(address _lpToken, uint256 _poolId) external onlyAdmin {
     require(internalAssetToken == address(0), Errors.VT_INVALID_CONFIGURATION);
 
-    convexBooster = 0xF403C135812408BFbE8713b5A23a04b3D48AAE31;
     curveLPToken = _lpToken;
     convexPoolId = _poolId;
     SturdyInternalAsset _interalToken = new SturdyInternalAsset(

File GeneralVault.sol

i++ is generally more expensive because it must increment a value and "return" the old value, so it may require holding two numbers in memory. ++i only ever uses one number in memory.

--- a/contracts/protocol/vault/GeneralVault.sol
+++ b/contracts/protocol/vault/GeneralVault.sol
@@ -215,7 +215,7 @@ contract GeneralVault is VersionedInitializable {
     AssetYield[] memory assetYields = new AssetYield[](length);
     uint256 extraWETHAmount = _WETHAmount;
 
-    for (uint256 i = 0; i < length; i++) {
+    for (uint256 i = 0; i < length; ++i) {
       assetYields[i].asset = assets[i];
       if (i != length - 1) {
         // Distribute wethAmount based on percent of asset volume

File YieldManaget.sol

i++ is generally more expensive because it must increment a value and "return" the old value, so it may require holding two numbers in memory. ++i only ever uses one number in memory.

--- a/contracts/protocol/vault/YieldManager.sol
+++ b/contracts/protocol/vault/YieldManager.sol
@@ -117,7 +117,7 @@ contract YieldManager is VersionedInitializable, Ownable {
    **/
   function distributeYield(uint256 _offset, uint256 _count) external onlyAdmin {
     // 1. convert from asset to exchange token via uniswap
-    for (uint256 i = 0; i < _count; i++) {
+    for (uint256 i = 0; i < _count; ++i) {
       address asset = _assetsList[_offset + i];
       require(asset != address(0), Errors.UL_INVALID_INDEX);
       uint256 _amount = IERC20Detailed(asset).balanceOf(address(this));
@@ -127,7 +127,7 @@ contract YieldManager is VersionedInitializable, Ownable {
 
     // 2. convert from exchange token to other stable assets via curve swap
     AssetYield[] memory assetYields = _getAssetYields(exchangedAmount);
-    for (uint256 i = 0; i < assetYields.length; i++) {
+    for (uint256 i = 0; i < assetYields.length; ++i) {
       if (assetYields[i].amount > 0) {
         uint256 _amount = _convertToStableCoin(assetYields[i].asset, assetYields[i].amount);
         // 3. deposit Yield to pool for suppliers
@@ -153,7 +153,7 @@ contract YieldManager is VersionedInitializable, Ownable {
     AssetYield[] memory assetYields = new AssetYield[](length);
     uint256 extraYieldAmount = _totalYieldAmount;
 
-    for (uint256 i = 0; i < length; i++) {
+    for (uint256 i = 0; i < length; ++i) {
       assetYields[i].asset = assets[i];
       if (i != length - 1) {
         // Distribute yieldAmount based on percent of asset volume

Require incorrect ordering

Lines of code

https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/LidoVault.sol#L141-L142

Vulnerability details

Impact

ETH transfer fails but transaction doesn't revert trapping user ETH

Proof of Concept

L142 requires that send is true (i.e. successful ETH transfer) but it is placed after the return statement in L141. This means that sent is never checked because the function will always return beforehand. If transfer fails then transaction will not revert as intended and user funds will be permanently locked in contract

Tools Used

Recommended Mitigation Steps

Reverse order of L141 and L142 so sent is checked correctly

QA Report

Some tokens (like USDT L199) do not work when changing the allowance from an existing non-zero allowance value.
They must first be approved by zero and then the actual allowance must be approved.

Example

IERC20(token).safeApprove(address(operator), 0);
IERC20(token).safeApprove(address(operator), amount);

Recomendattion

--- a/contracts/protocol/vault/ethereum/ConvexVault/ConvexCurveLPVault.sol
+++ b/contracts/protocol/vault/ethereum/ConvexVault/ConvexCurveLPVault.sol
@@ -138,12 +138,15 @@ contract ConvexCurveLPVault is GeneralVault {
     TransferHelper.safeTransferFrom(curveLPToken, msg.sender, address(this), _amount);
 
     // deposit Curve LP Token to Convex
+    IERC20(curveLPToken).safeApprove(convexBooster, 0);
     IERC20(curveLPToken).safeApprove(convexBooster, _amount);
     IConvexBooster(convexBooster).deposit(convexPoolId, _amount, true);
 
     // mint
     SturdyInternalAsset(internalAssetToken).mint(address(this), _amount);
-    IERC20(internalAssetToken).safeApprove(address(_addressesProvider.getLendingPool()), _amount);
+    address _lendingPool = address(_addressesProvider.getLendingPool());
+    IERC20(internalAssetToken).safeApprove(_lendingPool, 0);
+    IERC20(internalAssetToken).safeApprove(_lendingPool, _amount);
 
     return (internalAssetToken, _amount);
   }

Gas Optimization

Gas Optimizations Report

Table of contents

Gas Findings

[Gas-1] Unnecessary index init

In for loops you initialize the index to start from 0, but it already initialized to 0 in default and this assignment cost gas. It is more clear and gas efficient to declare without assigning 0 and will have the same meaning:

    ConvexCurveLPVault.solL#106
    GeneralVault.solL#218
    YieldManager.solL#120
    YieldManager.solL#130
    YieldManager.solL#156

Gas Optimization

Gas Report

[Gas-01] Caching array length can save gas

Caching the array length is more gas efficient. This is because access to a local variable in solidity is more efficient than query storage / calldata / memory. We recommend to cache the array length as a local variable and use it instead of array.length.

[Gas-02] Prefix increments are cheaper than postfix increments

Prefix increments are cheaper than postfix increments. Further more, using unchecked {++x} is even more gas efficient, and the gas saving accumulates every iteration and can make a real change

QA Report

QA

File CollateralAdapter.sol

Missing event emission

Missing event emission, critical function addCollateralAsset doesnt emmit any event
L43

Missing address(0) check

Function addCollateralAsset doesnt validate vars _acceptVault and _internalAsset

L43

Recommentation add validation;

  function addCollateralAsset(
    address _externalAsset,
    address _internalAsset,
    address _acceptVault
  ) external onlyAdmin {
    require(_externalAsset != address(0), "External asset address is not valid");
    require(_internalAsset != address(0), "Internal asset address is not valid");
    _assetToVaults[_externalAsset] = _acceptVault;
    _collateralAssets[_externalAsset] = _internalAsset;
  }

Unused variable

Variable _acceptVault its not used in function addCollateralAsset
L43

File ConvexCurveLPVault.sol

Missing event emmision

Critical function withdrawOnLiquidation(address _asset, uint256 _amount) doesnt emit any event
L178

File GeneralVault.sol

Unused constant variable

Consider remove the next line thats never used;
L47

Simplify if

Consider change >= to > in L196

--- a/contracts/protocol/vault/GeneralVault.sol
+++ b/contracts/protocol/vault/GeneralVault.sol
@@ -193,7 +193,7 @@ contract GeneralVault is VersionedInitializable {
     // when deposit for collateral, stAssetBalance = aTokenBalance
     // But stAssetBalance should increase overtime, so vault can grab yield from lendingPool.
     // yield = stAssetBalance - aTokenBalance
-    if (stAssetBalance >= aTokenBalance) return stAssetBalance.sub(aTokenBalance);
+    if (stAssetBalance > aTokenBalance) return stAssetBalance.sub(aTokenBalance);
 
     return 0;
   }

File YieldManager.sol

Add address(0) validation

Consider add a validario on _asset variable

L73

function registerAsset(address _asset) external onlyAdmin {
    require(_asset != address(0), "Asset address cannot be 0");
    _assetsList[_assetsCount] = _asset;
    _assetsCount = _assetsCount + 1;
  }

Missing event emission on critical functions

Critical Functions setExchangeToken, registerAsset, setCurvePool and distributeYield dont emmit events,

setExchangeToken
L64

registerAsset
L73

setCurvePool
L92

distributeYield
L118

Gas Optimizations

For loops in YieldManager and GeneralVault

To optimize the for loop and make it consume less gas, i suggest to:

  1. If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for addressโ€ฆ). Explicitly initializing it with its default value is an anti-pattern and wastes gas.

  2. Use ++i instead of i++, which is a cheaper operation (in this case there is no difference between i++ and ++i because we dont use the return value of this expression, which is the only difference between these two expression).

As an example:

for (uint256 i = 0; i < numIterations; ++i) { 

should be replaced with

for (uint256 i; i < numIterations; ++i) {

Proof Of Concept

https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L120
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L130
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L156
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L218

QA Report

There are many requirement violations within the contracts revolving around _addressesProvider.

Function Examples:

setConfiguration

{
  "address": "0x0901d12ebe1b195e5aa8748e62bd7734ae19b51f",
  "gasLimit": "0x7346d",
  "gasPrice": "0x523e2d717",
  "input": "0xb8d2927600000000000000000000000000000000000000000000007e95c10843a98e42f8000000000000000000000000aaaaaa93aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  "origin": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0",
  "value": "0x0",
  "blockCoinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0",
  "blockDifficulty": "0x0",
  "blockGasLimit": "0xff0000",
  "blockNumber": "0x61e0",
  "blockTime": "0x10a41",
  "decodedInput": "setConfiguration(0x00000000000000000000007e95c10843a98e42f8, 974334417060774172954205228819906042173286165156)",
  "name": "setConfiguration(address,uint256)",
  "hasDecodedInput": "setConfiguration(0x00000000000000000000007e95c10843a98e42f8, 974334417060774172954205228819906042173286165156)",
  "hasName": true,
  "failedToParse": false,
  "humanReadableInstruction": "setConfiguration(0x00000000000000000000007e95c10843a98e42f8, 974334417060774172954205228819906042173286165156)"
}

processYield

{
  "address": "0x0901d12ebe1b195e5aa8748e62bd7734ae19b51f",
  "gasLimit": "0xff000",
  "gasPrice": "0x664d21dd5",
  "input": "0xff42f49d0000000000000000000000000000de000000000000000000000000000000000000d7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  "origin": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2",
  "value": "0x0",
  "blockCoinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0",
  "blockDifficulty": "0x17",
  "blockGasLimit": "0xff0000",
  "blockNumber": "0x2dcb7",
  "blockTime": "0x345e2d",
  "decodedInput": "processYield()",
  "name": "processYield()",
  "hasDecodedInput": "processYield()",
  "hasName": true,
  "failedToParse": false,
  "humanReadableInstruction": "processYield()"
}

withdrawOnLiquidation

{
  "address": "0x0901d12ebe1b195e5aa8748e62bd7734ae19b51f",
  "gasLimit": "0xed544",
  "gasPrice": "0x2f92ac80",
  "input": "0x8954ff3f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c11d000000000000000000000000000000000000000000fc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000fc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000490000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  "origin": "0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe",
  "value": "0x0",
  "blockCoinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0",
  "blockDifficulty": "0x0",
  "blockGasLimit": "0xff0000",
  "blockNumber": "0x26506",
  "blockTime": "0x1",
  "decodedInput": "withdrawOnLiquidation(0x0000000000000000000000000000000000000000, 0)",
  "name": "withdrawOnLiquidation(address,uint256)",
  "hasDecodedInput": "withdrawOnLiquidation(0x0000000000000000000000000000000000000000, 0)",
  "hasName": true,
  "failedToParse": false,
  "humanReadableInstruction": "withdrawOnLiquidation(0x0000000000000000000000000000000000000000, 0)"
}

setTreasuryInfo

{
  "address": "0x0901d12ebe1b195e5aa8748e62bd7734ae19b51f",
  "gasLimit": "0xff000",
  "gasPrice": "0x2f92ac80",
  "input": "0x2a2234f900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000000",
  "origin": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0",
  "value": "0x0",
  "blockCoinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0",
  "blockDifficulty": "0x18",
  "blockGasLimit": "0xff0000",
  "blockNumber": "0x2657a",
  "blockTime": "0x1",
  "decodedInput": "setTreasuryInfo(0x0000000000000000000000000000000000000000, 5)",
  "name": "setTreasuryInfo(address,uint256)",
  "hasDecodedInput": "setTreasuryInfo(0x0000000000000000000000000000000000000000, 5)",
  "hasName": true,
  "failedToParse": false,
  "humanReadableInstruction": "setTreasuryInfo(0x0000000000000000000000000000000000000000, 5)"
}

getYieldAmount

{
  "address": "0x0901d12ebe1b195e5aa8748e62bd7734ae19b51f",
  "gasLimit": "0xead95",
  "gasPrice": "0xd52faa22",
  "input": "0x121a23c100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  "origin": "0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe",
  "value": "0x0",
  "blockCoinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0",
  "blockDifficulty": "0x8",
  "blockGasLimit": "0xff0000",
  "blockNumber": "0x61e2",
  "blockTime": "0x1",
  "decodedInput": "getYieldAmount()",
  "name": "getYieldAmount()",
  "hasDecodedInput": "getYieldAmount()",
  "hasName": true,
  "failedToParse": false,
  "humanReadableInstruction": "getYieldAmount()"
}

QA Report

Low Risk Findings

[QA-1] Use timelock modifier for setter functions

Timelock modifier is commonly used for storage variable setters that effects the contract logic. Consider adding timelocks on such setters.

    ConvexCurveLPVault.solL#37
    GeneralVault.solL#165
    YieldManager.solL#64
    YieldManager.solL#92

[QA-2] you have open TODOs in the codebase

Open TODOs can hint at programming or architectural errors that still need to be fixed.

    GeneralVault.solL#76

[QA-3] Use safe math for solidity version <8

You should use safe math for solidity version <8 since there is no default over/under flow check it those versions.

    CollateralAdapter.sol
    ConvexCurveLPVault.sol
    LidoVault.sol

Integer Overflow in ConvexCurveLPVault.sol

Lines of code

https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L125

Vulnerability details

Impact

Integer overflow can lead to unexpected coincidences or loss of user funds.

Proof of Concept

https://swcregistry.io/docs/SWC-101

Contract Creation:

{
  "address": "",
  "blockCoinbase": "0xcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcb",
  "blockDifficulty": "0xa7d7343662e26",
  "blockGasLimit": "0x7d0000",
  "blockNumber": "0x66e393",
  "blockTime": "0x5bfa4639",
  "calldata": "",
  "gasLimit": "0x7d000",
  "gasPrice": "0x773594000",
  "input": "0x60806040526000805534801561001457600080fd5b50613b50806100246000396000f3fe608060405260043610620000b65760003560e01c80638954ff3f116200006d5780638954ff3f146200018e57806399530b0614620001b3578063a5d5db0c14620001cb578063b8d2927614620001e2578063c4d66de81462000207578063ff42f49d146200022c57620000b6565b8063121a23c114620000bb5780631f1088a014620000eb5780632a2234f914620001125780632cdacb5014620001375780634451691e146200015e57806375d002f41462000176575b600080fd5b348015620000c857600080fd5b50620000d362000244565b604051620000e2919062002827565b60405180910390f35b348015620000f857600080fd5b50620001106200010a36600462002249565b62000263565b005b3480156200011f57600080fd5b5062000110620001313660046200221b565b62000566565b3480156200014457600080fd5b506200014f62000712565b604051620000e2919062002507565b3480156200016b57600080fd5b506200014f62000721565b3480156200018357600080fd5b50620000d362000730565b3480156200019b57600080fd5b50620000d3620001ad3660046200221b565b62000735565b348015620001c057600080fd5b50620000d362000873565b62000110620001dc3660046200221b565b6200090c565b348015620001ef57600080fd5b5062000110620002013660046200221b565b62000a64565b3480156200021457600080fd5b506200011062000226366004620022ae565b62000dbc565b3480156200023957600080fd5b506200011062000e60565b6039546000906200025e906001600160a01b0316620013fa565b905090565b60008062000272858562001537565b915091506000603460009054906101000a90046001600160a01b03166001600160a01b0316630261bf8b6040518163ffffffff1660e01b815260040160206040518083038186803b158015620002c757600080fd5b505afa158015620002dc573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620003029190620021fc565b6001600160a01b03166312ade5ad848433306040518563ffffffff1660e01b815260040162000335949392919062002572565b602060405180830381600087803b1580156200035057600080fd5b505af115801562000365573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200038b9190620023ff565b905060006200039c87838762001549565b9050600019861415620004b9576000876001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b158015620003e557600080fd5b505afa158015620003fa573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200042091906200243c565b60ff169050620004b581600a0a620004ae306001600160a01b03166399530b066040518163ffffffff1660e01b815260040160206040518083038186803b1580156200046b57600080fd5b505afa15801562000480573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620004a69190620023ff565b8690620015ab565b90620015eb565b9650505b620004c7866126ac6200162f565b811015604051806040016040528060028152602001611c9b60f11b815250906200050f5760405162461bcd60e51b8152600401620005069190620025ca565b60405180910390fd5b50846001600160a01b0316876001600160a01b03167f1607da8e9144035d8537941425741e9e3569c81d34a7f8e0c5c44635dc7169218860405162000555919062002827565b60405180910390a350505050505050565b603454604080516315d9b46f60e31b8152905133926001600160a01b03169163aecda378916004808301926020929190829003018186803b158015620005ab57600080fd5b505afa158015620005c0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620005e69190620021fc565b6001600160a01b03161460405180604001604052806002815260200161333360f01b815250906200062c5760405162461bcd60e51b8152600401620005069190620025ca565b50604080518082019091526002815261070760f31b60208201526001600160a01b038316620006705760405162461bcd60e51b8152600401620005069190620025ca565b50604080518082019091526002815261039360f41b6020820152610bb8821115620006b05760405162461bcd60e51b8152600401620005069190620025ca565b50603680546001600160a01b0319166001600160a01b03841690811790915560358290556040517f36a32a9fd3f860ff83c8cdbf88e1444ef598769478e1fc0c673ebe3cae3e0247906200070690849062002827565b60405180910390a25050565b6037546001600160a01b031681565b6039546001600160a01b031690565b600181565b603854604080518082019091526002815261323360f01b60208201526000916001600160a01b03858116911614620007825760405162461bcd60e51b8152600401620005069190620025ca565b50603460009054906101000a90046001600160a01b03166001600160a01b0316630261bf8b6040518163ffffffff1660e01b815260040160206040518083038186803b158015620007d257600080fd5b505afa158015620007e7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200080d9190620021fc565b6001600160a01b0316336001600160a01b03161460405180604001604052806002815260200161323360f01b815250906200085d5760405162461bcd60e51b8152600401620005069190620025ca565b506200086a8233620016a8565b90505b92915050565b600080603960009054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b158015620008c557600080fd5b505afa158015620008da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200090091906200243c565b60ff16600a0a91505090565b6000806200091b8484620017a7565b91509150603460009054906101000a90046001600160a01b03166001600160a01b0316630261bf8b6040518163ffffffff1660e01b815260040160206040518083038186803b1580156200096e57600080fd5b505afa15801562000983573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620009a99190620021fc565b6001600160a01b031663e8eda9df83833360006040518563ffffffff1660e01b8152600401620009dd94939291906200259d565b600060405180830381600087803b158015620009f857600080fd5b505af115801562000a0d573d6000803e3d6000fd5b50505050336001600160a01b0316846001600160a01b03167fef12f18e2b6578b91b3c852c423ca8ee530f65f20f770e62a7ce8aa08e1ab7778560405162000a56919062002827565b60405180910390a350505050565b603454604080516315d9b46f60e31b8152905133926001600160a01b03169163aecda378916004808301926020929190829003018186803b15801562000aa957600080fd5b505afa15801562000abe573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000ae49190620021fc565b6001600160a01b03161460405180604001604052806002815260200161333360f01b8152509062000b2a5760405162461bcd60e51b8152600401620005069190620025ca565b506039546040805180820190915260028152610e4d60f21b6020820152906001600160a01b03161562000b725760405162461bcd60e51b8152600401620005069190620025ca565b50603780546001600160a01b031990811673f403c135812408bfbe8713b5a23a04b3d48aae3117909155603880546001600160a01b038516921682179055603a829055604080516395d89b4160e01b81529051600092916395d89b419160048083019286929190829003018186803b15801562000bee57600080fd5b505afa15801562000c03573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405262000c2d9190810190620022cd565b60405160200162000c3f9190620024d6565b604051602081830303815290604052836001600160a01b03166395d89b416040518163ffffffff1660e01b815260040160006040518083038186803b15801562000c8857600080fd5b505afa15801562000c9d573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405262000cc79190810190620022cd565b60405160200162000cd99190620024ab565b604051602081830303815290604052846001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b15801562000d2257600080fd5b505afa15801562000d37573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000d5d91906200243c565b60405162000d6b90620021b9565b62000d7993929190620025df565b604051809103906000f08015801562000d96573d6000803e3d6000fd5b50603980546001600160a01b0319166001600160a01b0392909216919091179055505050565b600062000dc8620019f6565b60015490915060ff168062000de2575062000de2620019fb565b8062000def575060005481115b62000e0e5760405162461bcd60e51b81526004016200050690620026c7565b60015460ff1615801562000e2e576001805460ff19168117905560008290555b603480546001600160a01b0319166001600160a01b038516179055801562000e5b576001805460ff191690555b505050565b603454604080516315d9b46f60e31b8152905133926001600160a01b03169163aecda378916004808301926020929190829003018186803b15801562000ea557600080fd5b505afa15801562000eba573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000ee09190620021fc565b6001600160a01b03161460405180604001604052806002815260200161333360f01b8152509062000f265760405162461bcd60e51b8152600401620005069190620025ca565b50600062000f3362001a01565b9050806001600160a01b0316633d18b9126040518163ffffffff1660e01b8152600401600060405180830381600087803b15801562000f7157600080fd5b505af115801562000f86573d6000803e3d6000fd5b50506034546040516321f8a72160e01b8152600093506001600160a01b0390911691506321f8a7219062000fbd9060040162002732565b60206040518083038186803b15801562000fd657600080fd5b505afa15801562000feb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620010119190620021fc565b90506000826001600160a01b031663f7c618c16040518163ffffffff1660e01b815260040160206040518083038186803b1580156200104f57600080fd5b505afa15801562001064573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200108a9190620021fc565b9050806001600160a01b0316826001600160a01b031614604051806040016040528060028152602001610e4d60f21b81525090620010dd5760405162461bcd60e51b8152600401620005069190620025ca565b50620010e98262001a9e565b6034546040516321f8a72160e01b81526001600160a01b03909116906321f8a72190620011199060040162002741565b60206040518083038186803b1580156200113257600080fd5b505afa15801562001147573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200116d9190620021fc565b9150603760009054906101000a90046001600160a01b03166001600160a01b031663075461726040518163ffffffff1660e01b815260040160206040518083038186803b158015620011be57600080fd5b505afa158015620011d3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620011f99190620021fc565b9050806001600160a01b0316826001600160a01b031614604051806040016040528060028152602001610e4d60f21b815250906200124c5760405162461bcd60e51b8152600401620005069190620025ca565b50620012588262001a9e565b6000836001600160a01b031663d55a23f46040518163ffffffff1660e01b815260040160206040518083038186803b1580156200129457600080fd5b505afa158015620012a9573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620012cf9190620023ff565b905060005b81811015620013f357604051632061aa2360e11b81526000906001600160a01b038716906340c35446906200130e90859060040162002827565b60206040518083038186803b1580156200132757600080fd5b505afa1580156200133c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620013629190620021fc565b90506000816001600160a01b031663f7c618c16040518163ffffffff1660e01b815260040160206040518083038186803b158015620013a057600080fd5b505afa158015620013b5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620013db9190620021fc565b9050620013e88162001a9e565b5050600101620012d4565b5050505050565b6000806000603460009054906101000a90046001600160a01b03166001600160a01b0316630261bf8b6040518163ffffffff1660e01b815260040160206040518083038186803b1580156200144e57600080fd5b505afa15801562001463573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620014899190620021fc565b6001600160a01b031663aaf2aa24856040518263ffffffff1660e01b8152600401620014b6919062002507565b604080518083038186803b158015620014ce57600080fd5b505afa158015620014e3573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062001509919062002418565b915091508082106200152b5762001521828262001c6f565b9250505062001532565b6000925050505b919050565b6039546001600160a01b031692909150565b6038546040805180820190915260028152610e0d60f21b60208201526000916001600160a01b03868116911614620015965760405162461bcd60e51b8152600401620005069190620025ca565b50620015a38383620016a8565b949350505050565b600082620015bc575060006200086d565b82820282848281620015ca57fe5b04146200086a5760405162461bcd60e51b8152600401620005069062002686565b60006200086a83836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f00000000000081525062001cb3565b60008215806200163d575081155b156200164c575060006200086d565b8161138819816200165957fe5b0483111560405180604001604052806002815260200161068760f31b81525090620016995760405162461bcd60e51b8152600401620005069190620025ca565b50506127109102611388010490565b600080620016b562001a01565b604051636197390160e11b81529091506001600160a01b0382169063c32e720290620016e990879060019060040162002830565b600060405180830381600087803b1580156200170457600080fd5b505af115801562001719573d6000803e3d6000fd5b50506038546200173792506001600160a01b03169050848662001cee565b603954604051632770a7eb60e21b81526001600160a01b0390911690639dc29fac906200176b903090889060040162002559565b600060405180830381600087803b1580156200178657600080fd5b505af11580156200179b573d6000803e3d6000fd5b50959695505050505050565b6038546040805180820190915260028152611c1960f11b60208201526000918291906001600160a01b03868116911614620017f75760405162461bcd60e51b8152600401620005069190620025ca565b5060385462001812906001600160a01b031633308662001dea565b60375460385462001831916001600160a01b0391821691168562001ef0565b603754603a546040516321d0683360e11b81526001600160a01b03909216916343a0d066916200186991879060019060040162002840565b602060405180830381600087803b1580156200188457600080fd5b505af115801562001899573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620018bf91906200228f565b506039546040516340c10f1960e01b81526001600160a01b03909116906340c10f1990620018f4903090879060040162002559565b602060405180830381600087803b1580156200190f57600080fd5b505af115801562001924573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200194a91906200228f565b5060345460408051630261bf8b60e01b81529051620019e2926001600160a01b031691630261bf8b916004808301926020929190829003018186803b1580156200199357600080fd5b505afa158015620019a8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620019ce9190620021fc565b6039546001600160a01b0316908562001ef0565b50506039546001600160a01b031692909150565b600190565b303b1590565b600062001a0d620021c7565b603754603a54604051631526fe2760e01b81526001600160a01b0390921691631526fe279162001a409160040162002827565b60c06040518083038186803b15801562001a5957600080fd5b505afa15801562001a6e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062001a94919062002362565b6060015191505090565b604080518082019091526002815261383760f01b60208201526001600160a01b03821662001ae15760405162461bcd60e51b8152600401620005069190620025ca565b506040516370a0823160e01b81526000906001600160a01b038316906370a082319062001b1390309060040162002507565b60206040518083038186803b15801562001b2c57600080fd5b505afa15801562001b41573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062001b679190620023ff565b6035549091501562001b9357600062001b81838362001ff8565b905062001b8f828262001c6f565b9150505b6034546040516321f8a72160e01b81526000916001600160a01b0316906321f8a7219062001bc4906004016200261c565b60206040518083038186803b15801562001bdd57600080fd5b505afa15801562001bf2573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062001c189190620021fc565b905062001c2783828462001cee565b826001600160a01b03167f12978afa755d72e090b03555cdabccdaf98d608c8271ec2c06a04c29253156738360405162001c62919062002827565b60405180910390a2505050565b60006200086a83836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f77000081525062002031565b6000818362001cd75760405162461bcd60e51b8152600401620005069190620025ca565b50600083858162001ce457fe5b0495945050505050565b60006060846001600160a01b031663a9059cbb60e01b858560405160240162001d1992919062002559565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b031990941693909317909252905162001d5991906200248d565b6000604051808303816000865af19150503d806000811462001d98576040519150601f19603f3d011682016040523d82523d6000602084013e62001d9d565b606091505b509150915081801562001dcb57508051158062001dcb57508080602001905181019062001dcb91906200228f565b620013f35760405162461bcd60e51b815260040162000506906200266a565b60006060856001600160a01b03166323b872dd60e01b86868660405160240162001e179392919062002535565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b031990941693909317909252905162001e5791906200248d565b6000604051808303816000865af19150503d806000811462001e96576040519150601f19603f3d011682016040523d82523d6000602084013e62001e9b565b606091505b509150915081801562001ec957508051158062001ec957508080602001905181019062001ec991906200228f565b62001ee85760405162461bcd60e51b8152600401620005069062002715565b505050505050565b80158062001f7f5750604051636eb1769f60e11b81526001600160a01b0384169063dd62ed3e9062001f2990309086906004016200251b565b60206040518083038186803b15801562001f4257600080fd5b505afa15801562001f57573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062001f7d9190620023ff565b155b62001f9e5760405162461bcd60e51b815260040162000506906200279a565b62000e5b8363095ea7b360e01b848460405160240162001fc092919062002559565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b03199093169290921790915262002060565b60008062002012603554846200162f90919063ffffffff16565b6036549091506200086a906001600160a01b038681169116836200215d565b60008184841115620020585760405162461bcd60e51b8152600401620005069190620025ca565b505050900390565b62002074826001600160a01b03166200217f565b620020935760405162461bcd60e51b81526004016200050690620027f0565b60006060836001600160a01b031683604051620020b191906200248d565b6000604051808303816000865af19150503d8060008114620020f0576040519150601f19603f3d011682016040523d82523d6000602084013e620020f5565b606091505b5091509150816200211a5760405162461bcd60e51b8152600401620005069062002635565b8051156200215757808060200190518101906200213891906200228f565b620021575760405162461bcd60e51b8152600401620005069062002750565b50505050565b62000e5b8363a9059cbb60e01b848460405160240162001fc092919062002559565b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470818114801590620015a3575050151592915050565b61124380620028d883390190565b6040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a081019190915290565b6000602082840312156200220e578081fd5b81516200086a81620028af565b600080604083850312156200222e578081fd5b82356200223b81620028af565b946020939093013593505050565b6000806000606084860312156200225e578081fd5b83356200226b81620028af565b92506020840135915060408401356200228481620028af565b809150509250925092565b600060208284031215620022a1578081fd5b81516200086a81620028c8565b600060208284031215620022c0578081fd5b81356200086a81620028af565b600060208284031215620022df578081fd5b815167ffffffffffffffff80821115620022f7578283fd5b818401915084601f8301126200230b578283fd5b8151818111156200231a578384fd5b6200232f601f8201601f191660200162002858565b915080825285602082850101111562002346578384fd5b6200235981602084016020860162002880565b50949350505050565b600060c0828403121562002374578081fd5b6200238060c062002858565b82516200238d81620028af565b815260208301516200239f81620028af565b60208201526040830151620023b481620028af565b60408201526060830151620023c981620028af565b60608201526080830151620023de81620028af565b608082015260a0830151620023f381620028c8565b60a08201529392505050565b60006020828403121562002411578081fd5b5051919050565b600080604083850312156200242b578182fd5b505080516020909101519092909150565b6000602082840312156200244e578081fd5b815160ff811681146200086a578182fd5b600081518084526200247981602086016020860162002880565b601f01601f19169290920160200192915050565b60008251620024a181846020870162002880565b9190910192915050565b6000606360f81b82528251620024c981600185016020870162002880565b9190910160010192915050565b600066029ba3ab9323c960cd1b82528251620024fa81600785016020870162002880565b9190910160070192915050565b6001600160a01b0391909116815260200190565b6001600160a01b0392831681529116602082015260400190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b03929092168252602082015260400190565b6001600160a01b03948516815260208101939093529083166040830152909116606082015260800190565b6001600160a01b03948516815260208101939093529216604082015261ffff909116606082015260800190565b6000602082526200086a60208301846200245f565b600060608252620025f460608301866200245f565b82810360208401526200260881866200245f565b91505060ff83166040830152949350505050565b6c2ca4a2a6222fa6a0a720a3a2a960991b815260200190565b6020808252818101527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564604082015260600190565b60208082526002908201526114d560f21b604082015260600190565b60208082526021908201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6040820152607760f81b606082015260800190565b6020808252602e908201527f436f6e747261637420696e7374616e63652068617320616c726561647920626560408201526d195b881a5b9a5d1a585b1a5e995960921b606082015260800190565b60208082526003908201526229aa2360e91b604082015260600190565b6221a92b60e91b815260200190565b62086acb60eb1b815260200190565b6020808252602a908201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6040820152691bdd081cdd58d8d9595960b21b606082015260800190565b60208082526036908201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60408201527520746f206e6f6e2d7a65726f20616c6c6f77616e636560501b606082015260800190565b6020808252601f908201527f5361666545524332303a2063616c6c20746f206e6f6e2d636f6e747261637400604082015260600190565b90815260200190565b9182521515602082015260400190565b92835260208301919091521515604082015260600190565b60405181810167ffffffffffffffff811182821017156200287857600080fd5b604052919050565b60005b838110156200289d57818101518382015260200162002883565b83811115620021575750506000910152565b6001600160a01b0381168114620028c557600080fd5b50565b8015158114620028c557600080fdfe60806040523480156200001157600080fd5b506040516200124338038062001243833981016040819052620000349162000246565b8251839083906200004d90600390602085019062000103565b5080516200006390600490602084019062000103565b50506005805460ff191660121790555060006200007f620000e9565b60058054610100600160a81b0319166101006001600160a01b03841690810291909117909155604051919250906000907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908290a350620000e081620000ed565b505050620002c7565b3390565b6005805460ff191660ff92909216919091179055565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200014657805160ff191683800117855562000176565b8280016001018555821562000176579182015b828111156200017657825182559160200191906001019062000159565b506200018492915062000188565b5090565b5b8082111562000184576000815560010162000189565b600082601f830112620001b0578081fd5b81516001600160401b0380821115620001c7578283fd5b6040516020601f8401601f1916820181018381118382101715620001e9578586fd5b806040525081945083825286818588010111156200020657600080fd5b600092505b838310156200022a57858301810151828401820152918201916200020b565b838311156200023c5760008185840101525b5050505092915050565b6000806000606084860312156200025b578283fd5b83516001600160401b038082111562000272578485fd5b62000280878388016200019f565b9450602086015191508082111562000296578384fd5b50620002a5868287016200019f565b925050604084015160ff81168114620002bc578182fd5b809150509250925092565b610f6c80620002d76000396000f3fe608060405234801561001057600080fd5b50600436106101005760003560e01c8063715018a611610097578063a457c2d711610066578063a457c2d7146101f3578063a9059cbb14610206578063dd62ed3e14610219578063f2fde38b1461022c57610100565b8063715018a6146101b95780638da5cb5b146101c357806395d89b41146101d85780639dc29fac146101e057610100565b8063313ce567116100d3578063313ce5671461016b578063395093511461018057806340c10f191461019357806370a08231146101a657610100565b806306fdde0314610105578063095ea7b31461012357806318160ddd1461014357806323b872dd14610158575b600080fd5b61010d61023f565b60405161011a9190610be7565b60405180910390f35b610136610131366004610b9e565b6102d5565b60405161011a9190610bdc565b61014b6102f3565b60405161011a9190610e72565b610136610166366004610b5e565b6102f9565b610173610380565b60405161011a9190610e7b565b61013661018e366004610b9e565b610389565b6101366101a1366004610b9e565b6103d7565b61014b6101b4366004610b0f565b610426565b6101c1610441565b005b6101cb6104cb565b60405161011a9190610bc8565b61010d6104df565b6101c16101ee366004610b9e565b610540565b610136610201366004610b9e565b610588565b610136610214366004610b9e565b6105f0565b61014b610227366004610b2a565b610604565b6101c161023a366004610b0f565b61062f565b60038054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156102cb5780601f106102a0576101008083540402835291602001916102cb565b820191906000526020600020905b8154815290600101906020018083116102ae57829003601f168201915b5050505050905090565b60006102e96102e26106f6565b84846106fa565b5060015b92915050565b60025490565b60006103068484846107ae565b610376846103126106f6565b61037185604051806060016040528060288152602001610eea602891396001600160a01b038a166000908152600160205260408120906103506106f6565b6001600160a01b0316815260208101919091526040016000205491906108c3565b6106fa565b5060019392505050565b60055460ff1690565b60006102e96103966106f6565b8461037185600160006103a76106f6565b6001600160a01b03908116825260208083019390935260409182016000908120918c1681529252902054906108ef565b60006103e16106f6565b60055461010090046001600160a01b0390811691161461041c5760405162461bcd60e51b815260040161041390610d3c565b60405180910390fd5b6102e9838361091b565b6001600160a01b031660009081526020819052604090205490565b6104496106f6565b60055461010090046001600160a01b0390811691161461047b5760405162461bcd60e51b815260040161041390610d3c565b60055460405160009161010090046001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a360058054610100600160a81b0319169055565b60055461010090046001600160a01b031690565b60048054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156102cb5780601f106102a0576101008083540402835291602001916102cb565b6105486106f6565b60055461010090046001600160a01b0390811691161461057a5760405162461bcd60e51b815260040161041390610d3c565b61058482826109db565b5050565b60006102e96105956106f6565b8461037185604051806060016040528060258152602001610f1260259139600160006105bf6106f6565b6001600160a01b03908116825260208083019390935260409182016000908120918d168152925290205491906108c3565b60006102e96105fd6106f6565b84846107ae565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6106376106f6565b60055461010090046001600160a01b039081169116146106695760405162461bcd60e51b815260040161041390610d3c565b6001600160a01b03811661068f5760405162461bcd60e51b815260040161041390610c7d565b6005546040516001600160a01b0380841692610100900416907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3600580546001600160a01b0390921661010002610100600160a81b0319909216919091179055565b3390565b6001600160a01b0383166107205760405162461bcd60e51b815260040161041390610df7565b6001600160a01b0382166107465760405162461bcd60e51b815260040161041390610cc3565b6001600160a01b0380841660008181526001602090815260408083209487168084529490915290819020849055517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906107a1908590610e72565b60405180910390a3505050565b6001600160a01b0383166107d45760405162461bcd60e51b815260040161041390610db2565b6001600160a01b0382166107fa5760405162461bcd60e51b815260040161041390610c3a565b610805838383610ab1565b61084281604051806060016040528060268152602001610ec4602691396001600160a01b03861660009081526020819052604090205491906108c3565b6001600160a01b03808516600090815260208190526040808220939093559084168152205461087190826108ef565b6001600160a01b0380841660008181526020819052604090819020939093559151908516907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906107a1908590610e72565b600081848411156108e75760405162461bcd60e51b81526004016104139190610be7565b505050900390565b6000828201838110156109145760405162461bcd60e51b815260040161041390610d05565b9392505050565b6001600160a01b0382166109415760405162461bcd60e51b815260040161041390610e3b565b61094d60008383610ab1565b60025461095a90826108ef565b6002556001600160a01b03821660009081526020819052604090205461098090826108ef565b6001600160a01b0383166000818152602081905260408082209390935591519091907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906109cf908590610e72565b60405180910390a35050565b6001600160a01b038216610a015760405162461bcd60e51b815260040161041390610d71565b610a0d82600083610ab1565b610a4a81604051806060016040528060228152602001610ea2602291396001600160a01b03851660009081526020819052604090205491906108c3565b6001600160a01b038316600090815260208190526040902055600254610a709082610ab6565b6002556040516000906001600160a01b038416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906109cf908590610e72565b505050565b600061091483836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f7700008152506108c3565b80356001600160a01b03811681146102ed57600080fd5b600060208284031215610b20578081fd5b6109148383610af8565b60008060408385031215610b3c578081fd5b610b468484610af8565b9150610b558460208501610af8565b90509250929050565b600080600060608486031215610b72578081fd5b8335610b7d81610e89565b92506020840135610b8d81610e89565b929592945050506040919091013590565b60008060408385031215610bb0578182fd5b610bba8484610af8565b946020939093013593505050565b6001600160a01b0391909116815260200190565b901515815260200190565b6000602080835283518082850152825b81811015610c1357858101830151858201604001528201610bf7565b81811115610c245783604083870101525b50601f01601f1916929092016040019392505050565b60208082526023908201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260408201526265737360e81b606082015260800190565b60208082526026908201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160408201526564647265737360d01b606082015260800190565b60208082526022908201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604082015261737360f01b606082015260800190565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60208082526021908201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736040820152607360f81b606082015260800190565b60208082526025908201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604082015264647265737360d81b606082015260800190565b60208082526024908201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646040820152637265737360e01b606082015260800190565b6020808252601f908201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604082015260600190565b90815260200190565b60ff91909116815260200190565b6001600160a01b0381168114610e9e57600080fd5b5056fe45524332303a206275726e20616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa26469706673582212200a1331bad14a4769760a4d87c11ddf315062455652c2640d7bcad1bd8d57fbbc64736f6c634300060c0033a26469706673582212208f3204cf892ff20dba2caf116701263fc1b12b663833b06fc666a63df26cbf5564736f6c634300060c0033",
  "name": "unknown",
  "origin": "0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe",
  "value": "0x0",
  "hasName": false,
  "failedToParse": true,
  "humanReadableInstruction": "Unable to decode"
}

Call Transaction

{
  "address": "0x901d12ebe1b195e5aa8748e62bd7734ae19b51f",
  "blockCoinbase": "0xcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcb",
  "blockDifficulty": "0xa7d7343662e26",
  "blockGasLimit": "0x7d0000",
  "blockNumber": "0x66e393",
  "blockTime": "0x5bfa4639",
  "calldata": "0x99530b06",
  "gasLimit": "0x7d000",
  "gasPrice": "0x773594000",
  "input": "0x99530b06",
  "name": "pricePerShare()",
  "origin": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "value": "0x0",
  "decodedInput": "pricePerShare()",
  "hasDecodedInput": "pricePerShare()",
  "hasName": true,
  "failedToParse": false,
  "humanReadableInstruction": "pricePerShare()"
}

Tools Used

Manual Review + MythX for Static & Dynamic Analysis, Fuzz Testing, and Symbolic Execution

Recommended Mitigation Steps

Upgrade your contracts to 0.8.x to take advantage of native overflow/underflow protection or use a known safe math library and change return 10**decimalstodecimals.pow(10)`

function pow(uint n, uint e) public pure returns (uint) {
    if (e == 0) {
        return 1;
    } else if (e == 1) {
        return n;
    } else {
        uint p = pow(n, e.div(2));
        p = p.mul(p);
        if (e.mod(2) == 1) {
            p = p.mul(n);
        }
        return p;
    }
}

QA Report

Impact

[1] By default, function types and state variables/constants are internal, so the internal keyword can be omitted.

Affected code:

  1. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/CollateralAdapter.sol#L24
  2. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/CollateralAdapter.sol#L27
  3. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/CollateralAdapter.sol#L29
  4. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/CollateralAdapter.sol#L49
  5. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L28
  6. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L29
  7. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L30
  8. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L48
  9. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L55
  10. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L117
  11. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L145
  12. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L146
  13. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L148
  14. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L161
  15. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L173
  16. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L49
  17. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L52
  18. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L53
  19. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L36
  20. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L37
  21. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L39
  22. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L46

Proof of Concept

Tools Used

Recommended Mitigation Steps


Impact

[2] Magic number, consider using named constant instead.

Affected code:

  1. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L123
  2. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L48
  3. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L136

Proof of Concept

Tools Used

Recommended Mitigation Steps


Impact

[3] Consider using "_" separate digit capacity i.e "100000" could be replaced to "100_000".
This increases code readability.

Affected code:

  1. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L48

Proof of Concept

Tools Used

Recommended Mitigation Steps


Impact

[4] Consider using IERC20 type instead of address.
Or IERC20[] type instead of address[].

Affected code:

  1. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L28
  2. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L29
  3. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L37
  4. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L93
  5. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L94
  6. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L108
  7. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L43
  8. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L64
  9. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L93
  10. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L94
  11. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L106
  12. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L106
  13. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L195
  14. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L202

Proof of Concept

Tools Used

Recommended Mitigation Steps


Impact

[5] Typo: variable name supposed to be 'decimals'.

Affected code:

  1. https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/GeneralVault.sol#L122

Proof of Concept

Tools Used

Recommended Mitigation Steps


Impact

[6] Consider reducing if nesting by having early continue/return and else contents clause can be placed right after.
This increases readability of the code.

Affected code:

  1. https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/GeneralVault.sol#L220-L229
  2. https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/YieldManager.sol#L158-L167
  3. https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/LidoVault.sol#L128-L147

Proof of Concept

Tools Used

Recommended Mitigation Steps


Impact

[7] Usually when you leave function empty it is a good practice to place a comment inside brackets { /* reason why here is no code */ }
Consider adding explanation in comments.

Affected code:

  1. https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/GeneralVault.sol#L246
  2. https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/GeneralVault.sol#L255
  3. https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/GeneralVault.sol#L265
  4. https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/LidoVault.sol#L24

Proof of Concept

Tools Used

Recommended Mitigation Steps


Impact

[8] Consider adding here require(msg.value == 0); since it is non-ETH token.

Affected code:

  1. https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/LidoVault.sol#L96

Proof of Concept

Tools Used

Recommended Mitigation Steps


Impact

[9] Concern: Isn't it better to break the for-loop instead of reverting whole transaction?

Affected code:

  1. https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/YieldManager.sol#L122

Proof of Concept

Tools Used

Recommended Mitigation Steps


Impact

[10] Brackets aren't necessary here, consider making this code one-liner.

Affected code:

  1. https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/YieldManager.sol#L199-L201

Proof of Concept

Tools Used

Recommended Mitigation Steps


Gas Optimizations

For loops in YieldManager and GeneralVault

To optimize the for loop and make it consume less gas, i suggest to:

  1. If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for addressโ€ฆ). Explicitly initializing it with its default value is an anti-pattern and wastes gas.

  2. Use ++i instead of i++, which is a cheaper operation (in this case there is no difference between i++ and ++i because we dont use the return value of this expression, which is the only difference between these two expression).

As an example:

for (uint256 i = 0; i < numIterations; ++i) { 

should be replaced with

for (uint256 i; i < numIterations; ++i) {

Proof Of Concept

https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L120
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L130
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L156
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L218

require statement should be checked first

Lines of code

https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/LidoVault.sol#L140-L142

Vulnerability details

description

the require statement in the _withdrawFromYieldPool function is never checked since it is after the return statement

/2022-05-sturdy/smart-contracts/LidoVault.sol
140:       (bool sent, bytes memory data) = address(_to).call{value: receivedETHAmount}('');
141:       return receivedETHAmount;
142:       require(sent, Errors.VT_COLLATERAL_WITHDRAW_INVALID);

since the _to address is an user controlled parameter, it is possible that if the user inputs a bad address the transfer will fail silently and cause the user to lose funds

QA Report

[L-01] safeApprove is deprecated

The _setupRole function is deprecated according to the Open Zeppelin comment
Deprecated. This function has issues similar to the ones found in {IERC20-approve}, and its usage is discouraged.

There are three safeApprove instances in code
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/LidoVault.sol#L102
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/ConvexCurveLPVault.sol#L141
https://github.com/code-423n4/2022-05-sturdy/tree/main/smart-contracts/ConvexCurveLPVault.sol#L146

This Open Zeppelin comment indicates it is deprecated
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/utils/SafeERC20.sol#L39-L43

Recommended Mitigation Steps

Replace safeApprove with {safeIncreaseAllowance} or {safeDecreaseAllowance} instead

User can lose collateral do to unsafe ETH transfer

Lines of code

GeneralVault.sol#L100

Vulnerability details

Impact

User can have his collateral burned (LendingPool.sol#L324) and not receive funds from the Vault when calling withdrawCollateral.

Proof of Concept

LidoVault.sol#L140

(bool sent, bytes memory data) = address(_to).call{value: receivedETHAmount}('');
return receivedETHAmount;
require(sent, Errors.VT_COLLATERAL_WITHDRAW_INVALID);

The call boolean check is unreachable because it only occurs after the function has already ceased execution with return above.

Scenario:

  • User calls withdrawCollateral.
  • Collateral is burned.
  • The call in _withdrawFromYieldPool fails but doesn't revert due to return placement.
  • withdrawAmount still reflects receivedETHAmount.
  • All checks pass without user receiving.

Recommended Mitigation Steps

Please change check position so it is reachable:

(bool sent, bytes memory data) = address(_to).call{value: receivedETHAmount}('');
require(sent, Errors.VT_COLLATERAL_WITHDRAW_INVALID);
return receivedETHAmount;

`UNISWAP_FEE` is hardcoded which will lead to significant losses compared to optimal routing

Lines of code

https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/YieldManager.sol#L48
https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/YieldManager.sol#L184

Vulnerability details

Impact

In YieldManager, UNISWAP_FEE is hardcoded, which reduce significantly the possibilities and will lead to non optimal routes. In particular, all swaps using ETH path will use the wrong pool as it will use the ETH / USDC 1% one due to this line.โ€จ

Proof of Concept

For example for CRV / USDC, the optimal route is currently CRV -> ETH and ETH -> USDC, and the pool ETH / USDC with 1% fees is tiny compared to the ones with 0.3 or 0.1%. Therefore using the current implementation would create a significant loss of revenue.

Recommended Mitigation Steps

Basic mitigation would be to hardcode in advance the best Uniswap paths in a mapping like itโ€™s done for Curve pools, then pass this path already computed to the swapping library. This would allow for complex route and save gas costs as you would avoid computing them in swapExactTokensForTokens.

Then, speaking from experience, as distributeYield is onlyAdmin, you may want to add the possibility to do the swaps through an efficient aggregator like 1Inch or Paraswap, it will be way more optimal.

QA Report

missing checks for zero address

description

the function addCollateralAsset does not check if invalid addresses are added to a vault, eg zero address checks

Checking addresses against zero-address during initialization or during setting is a security best-practice.

findings

/2022-05-sturdy/smart-contracts/CollateralAdapter.sol
43:   function addCollateralAsset(
44:     address _externalAsset,
45:     address _internalAsset,
46:     address _acceptVault
47:   ) external onlyAdmin {
48:     _assetToVaults[_externalAsset] = _acceptVault;
49:     _collateralAssets[_externalAsset] = _internalAsset;
50:   }
51: 
/2022-05-sturdy/smart-contracts/YieldManager.sol
60:  function initialize(ILendingPoolAddressesProvider _provider) public initializer {
61:     _addressesProvider = _provider;
62:   }

hard coded addresses

description

Use of hard-coded addresses may cause errors: Each contract needs contract addresses in order to be integrated into other protocols and systems. These addresses are currently hard-coded, which may cause errors and result in the codebaseโ€™s deployment with an incorrect asset. Using hard-coded values instead of deployer-provided values makes these contracts incredibly difficult to test.

Recommendation: set addresses when contracts are created rather than using hard-coded values. This practice will facilitate testing and reused across networks

findings

/2022-05-sturdy/smart-contracts/ConvexCurveLPVault.sol
40: convexBooster = 0xF403C135812408BFbE8713b5A23a04b3D48AAE31;

unnecessary parameter

description

The function _depositToYieldPool() takes in an address parameter _asset.

/2022-05-sturdy/smart-contracts/ConvexCurveLPVault.sol
131: function _depositToYieldPool(address _asset, uint256 _amount)

This parameter is not used within the function except in the require statement

/2022-05-sturdy/smart-contracts/ConvexCurveLPVault.sol
137: require(_asset == curveLPToken, Errors.VT_COLLATERAL_DEPOSIT_INVALID);

therefore this parameter can be removed from the function

this also applies to

/2022-05-sturdy/smart-contracts/ConvexCurveLPVault.sol
154: function _getWithdrawalAmount(address _asset, uint256 _amount)
178: function withdrawOnLiquidation(address _asset, uint256 _amount)
192:   function _withdrawFromYieldPool(
193:     address _asset,

missing payable modifier

description

the function _depositToYieldPool may deposit ETH however it does not have the payable modifier

/2022-05-sturdy/smart-contracts/LidoVault.sol
79:   function _depositToYieldPool(address _asset, uint256 _amount)
80:     internal
81:     override
82:     returns (address, uint256)

Reentrancy in LidoVault

Lines of code

https://github.dev/code-423n4/2022-05-sturdy/blob/6cc44472f6321d0be6844d6fe7fbd7b78d7602a9/smart-contracts/LidoVault.sol#L140
https://github.dev/code-423n4/2022-05-sturdy/blob/6cc44472f6321d0be6844d6fe7fbd7b78d7602a9/smart-contracts/LidoVault.sol#L91

Vulnerability details

Impact

The _withdrawFromYieldPool method is vulnerable to a reentry problem, it depends on the caller implementation and CurveswapAdapter.swapExactTokensForTokens in order to exploit it.

Proof of Concept

Use call instead of transfer to send ether. Because the receiver is a contract, it can make a call to another/same contract via receive, this is a re-entrancy pattern.

History supports us: https://twitter.com/Hacxyk/status/1520370432037117954?t=VgVxU0LnA8E5XHQioo1HGQ&s=09

Source reference:

Recommended Mitigation Steps

Use transfer to send ether.

Gas Optimizations

Use ++index instead of index++ to increment a loop counter

Context: GeneralVault.sol#L204-L233, ConvexCurveLPVault.sol#L87-L111, YieldManager.sol#L118-L137 (For both), YieldManager.sol#L142-L171

Description:
Due to reduced stack operations, using ++index saves 5 gas per iteration.

Recommendation:
Use ++index to increment a loop counter.

Catching The Array Length Prior To Loop

Context: YieldManager.sol#L118-L137 (For L130)

Description:
One can save gas by caching the array length (in stack) and using that set variable in the loop. Replace state variable reads and writes within loops with local variable reads and writes. This is done by assigning state variable values to new local variables, reading and/or writing the local variables in a loop, then after the loop assigning any changed local variables to their equivalent state variables.

Recommendation:
Simply do something like so before the for loop: uint length = variable.length. Then add length in place of variable.length in the for loop.

In require(), Use != 0 Instead of > 0 With Uint Values

Context: GeneralVault.sol#L177-L183, LidoVault.sol#L79-L104 (For L88)

Description:
In a require, when checking a uint, using != 0 instead of > 0 saves 6 gas. This will jump over or avoid an extra ISZERO opcode.

Recommendation:
Use != 0 instead of > 0 with uint values but only in require() statements.

State Variables That Can Be Set To Immutable

Context: ConvexCurveLPVault.sol#L27

Description:
Solidity 0.6.5 introduced immutable as a major feature. It allows setting contract-level variables at construction time which gets stored in code rather than storage. Each call to it reads from storage, using a sload costing 2100 gas cold or 100 gas warm. Setting it to immutable will have each storage read of the state variable to be replaced by the instruction push32 value, where value is set during contract construction time and this costs only 3 gas.

Recommendation:
Set the state variable to immutable

Function Ordering via Method ID

Context: All Contracts

Description:
Contracts most called functions could simply save gas by function ordering via Method ID. Calling a function at runtime will be cheaper if the function is positioned earlier in the order (has a relatively lower Method ID) because 22 gas are added to the cost of a function for every position that came before it. The caller can save on gas if you prioritize most called functions. One could use This tool to help find alternative function names with lower Method IDs while keeping the original name intact.

Recommendation:
Find a lower method ID name for the most called functions for example mostCalled() vs. mostCalled_41q() is cheaper by 44 gas.

Upgrade To At Least 0.8.4

Context: All Contracts

Description:
Using newer compiler versions and the optimizer gives gas
optimizations and additional safety checks for free!

The advantages of versions =0.8.*= over =<0.8.0= are:

  • Safemath by default from =0.8.0= (can be more gas efficient than /some/
    library based safemath).
  • Low level inliner from =0.8.2=, leads to cheaper runtime gas.
    Especially relevant when the contract has small functions. For
    example, OpenZeppelin libraries typically have a lot of small
    helper functions and if they are not inlined, they cost an
    additional 20 to 40 gas because of 2 extra =jump= instructions and
    additional stack operations needed for function calls.
  • Optimizer improvements in packed structs: Before =0.8.3=, storing
    packed structs, in some cases used an additional storage read
    operation. After EIP-2929, if the slot was already cold, this
    means unnecessary stack operations and extra deploy time costs.
    However, if the slot was already warm, this means additional cost
    of =100= gas alongside the same unnecessary stack operations and
    extra deploy time costs.
  • Custom errors from =0.8.4=, leads to cheaper deploy time cost and
    run time cost. Note: the run time cost is only relevant when the
    revert condition is met. In short, replace revert strings by
    custom errors.

Recommendation:
Upgrade to at least 0.8.4 for the additional benefits.

QA Report

LidoVault: msg.value == 0 not checked when user deposits LIDO tokens

Lines of code

https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/LidoVault.sol#L95-L99

Vulnerability details

Impact

In the depositCollateral function of the LidoVault contract, the user can choose to deposit Ether or LIDO. When the user chooses to deposit LIDO, the _depositToYieldPool function does not check msg.value == 0. When the user accidentally carries Ether in the transaction, the user will lost his Ether.

Proof of Concept

https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/LidoVault.sol#L95-L99

Tools Used

None

Recommended Mitigation Steps

When user deposits LIDO, check msg.value == 0.

    } else {
      // Case of stETH deposit from user, receive stETH from user
      require(_asset == LIDO && msg.value == 0, Errors.VT_COLLATERAL_DEPOSIT_INVALID);
      IERC20(LIDO).safeTransferFrom(msg.sender, address(this), _amount);
    }

QA Report

File LidoVault.sol

source:
OpenZeppelin/openzeppelin-contracts#2219

--- a/contracts/protocol/vault/ethereum/LidoVault.sol
+++ b/contracts/protocol/vault/ethereum/LidoVault.sol
@@ -99,6 +99,8 @@ contract LidoVault is GeneralVault {
}

 // Make lendingPool to transfer required amount
  • address _lendingPool = address(_addressesProvider.getLendingPool());
  • IERC20(LIDO).safeApprove(address(_addressesProvider.getLendingPool()), 0);
    IERC20(LIDO).safeApprove(address(_addressesProvider.getLendingPool()), assetAmount);
    return (LIDO, assetAmount);
    }

Approval must be set to zero and after that increased to the amount you need.

Non-existing recipient will return true on call

Lines of code

https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L91
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L140

Vulnerability details

Impact

Consider checking the recipient address for existence before making the call.
If the address does not exist, call will return true and the user will not get the tokens to his wallet.

Proof of Concept

https://docs.soliditylang.org/en/develop/control-structures.html#:~:text=Warning-,The%20low%2Dlevel%20functions,-call%2C%20delegatecall

Tools Used

Recommended Mitigation Steps


User should be able to select slippage in GeneralVault

Lines of code

https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/GeneralVault.sol#L100

Vulnerability details

Impact

When withdrawing collateral, user may occur a loss because of the swapping of the staked asset back to the asset. There is a build-in slippage protection here but the user has no control over it. This could easily lead to loss of user funds if they are not aware of this functionality or would have like to use a lower slippage.

Proof of Concept

We've recently witness some panic movements of stETH and its depeg on Curve, which would have certainly lead to losses for Sturdy users if the vault was live without slippage protection.

Recommended Mitigation Steps

Pass the slippage as a parameter or add an other function to do so.

QA Report

Code Quality Report

[Low-01] Add a timelock

It is good to have a timelock for functions that set key/critical variables.

[Low-02] Init function exposed to a front-run attack

This is a small frontrun issue but still worth mentioning. One can frontrun your init function and then stuck the system since you will not be able to use the function again:

[Low-03] open TODOs

You have open TODOs:

Gas Optimizations

Consider remove _addressesProvider.getAddress('WETH') and _addressesProvider.getAddress('LIDO') calls for constants to save gas

--- a/contracts/protocol/vault/ethereum/LidoVault.sol
+++ b/contracts/protocol/vault/ethereum/LidoVault.sol
@@ -18,6 +18,9 @@ import {CurveswapAdapter} from '../../libraries/swap/CurveswapAdapter.sol';
 contract LidoVault is GeneralVault {
   using SafeERC20 for IERC20;
 
+  address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
+  address constant LIDO = 0x5a98fcbea516cf06857215779fd812ca3bef1b32;
+
   /**
    * @dev Receive Ether
    */
@@ -29,7 +32,6 @@ contract LidoVault is GeneralVault {
    */
   function processYield() external override onlyAdmin {
     // Get yield from lendingPool
-    address LIDO = _addressesProvider.getAddress('LIDO');
     uint256 yieldStETH = _getYield(LIDO);
 
     // move yield to treasury
@@ -49,14 +51,13 @@ contract LidoVault is GeneralVault {
     );
 
     // ETH -> WETH
-    address weth = _addressesProvider.getAddress('WETH');
-    IWETH(weth).deposit{value: receivedETHAmount}();
+    IWETH(WETH).deposit{value: receivedETHAmount}();
 
     // transfer WETH to yieldManager
     address yieldManager = _addressesProvider.getAddress('YIELD_MANAGER');
-    TransferHelper.safeTransfer(weth, yieldManager, receivedETHAmount);
+    TransferHelper.safeTransfer(WETH, yieldManager, receivedETHAmount);
 
-    emit ProcessYield(_addressesProvider.getAddress('WETH'), receivedETHAmount);
+    emit ProcessYield(WETH, receivedETHAmount);
   }
 
   /**
@@ -81,7 +82,6 @@ contract LidoVault is GeneralVault {
     override
     returns (address, uint256)
   {
-    address LIDO = _addressesProvider.getAddress('LIDO');
     uint256 assetAmount = _amount;
     if (_asset == address(0)) {
       // Case of ETH deposit from user, user has to send ETH
@@ -113,7 +113,7 @@ contract LidoVault is GeneralVault {
     returns (address, uint256)
   {
     // In this vault, return same amount of asset.
-    return (_addressesProvider.getAddress('LIDO'), _amount);
+    return (LIDO, _amount);
   }
 
   /**
@@ -124,7 +124,6 @@ contract LidoVault is GeneralVault {
     uint256 _amount,
     address _to
   ) internal override returns (uint256) {
-    address LIDO = _addressesProvider.getAddress('LIDO');
     if (_asset == address(0)) {
       // Case of ETH withdraw request from user, so exchange stETH -> ETH via curve
       uint256 receivedETHAmount = CurveswapAdapter.swapExactTokensForTokens(
@@ -153,7 +152,7 @@ contract LidoVault is GeneralVault {
    */
   function _processTreasury(uint256 _yieldAmount) internal returns (uint256) {
     uint256 treasuryAmount = _yieldAmount.percentMul(_vaultFee);
-    IERC20(_addressesProvider.getAddress('LIDO')).safeTransfer(_treasuryAddress, treasuryAmount);
+    IERC20(LIDO).safeTransfer(_treasuryAddress, treasuryAmount);
     return treasuryAmount;
   }
 }

pump st-eth virtual_price through re-entrancy attacks

Lines of code

https://github.com/sturdyfi/code4rena-may-2022/blob/main/contracts/misc/STECRVOracle.sol#L26
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol

Vulnerability details

pump st-eth virtual_price through re-entrancy attacks

Impact

This may not be publicly recognized yet but crv st-eth pool is not safe to use.

I first noticed the issue from the makerDao emergency proposal.
maker 14th-april-emergency-executive

Also, the mim crvstETH pool was (quitely) deprecated at the same time.
abracadabra.money

What's the issue then?

There's re-entrancy vulnerabliliy in the Lido contract.
Lido: Curve Liquidity Farming Pool Contract

@external
@nonreentrant('lock')
def remove_liquidity(
    _amount: uint256,
    _min_amounts: uint256[N_COINS],
) -> uint256[N_COINS]:
    """
    @notice Withdraw coins from the pool
    @dev Withdrawal amounts are based on current deposit ratios
    @param _amount Quantity of LP tokens to burn in the withdrawal
    @param _min_amounts Minimum amounts of underlying coins to receive
    @return List of amounts of coins that were withdrawn
    """
    amounts: uint256[N_COINS] = self._balances()
    lp_token: address = self.lp_token
    total_supply: uint256 = ERC20(lp_token).totalSupply()
    CurveToken(lp_token).burnFrom(msg.sender, _amount)  # dev: insufficient funds

    for i in range(N_COINS):
        value: uint256 = amounts[i] * _amount / total_supply
        assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected"

        amounts[i] = value
        if i == 0:
            raw_call(msg.sender, b"", value=value)
        else:
            assert ERC20(self.coins[1]).transfer(msg.sender, value)

    log RemoveLiquidity(msg.sender, amounts, empty(uint256[N_COINS]), total_supply - _amount)
    return amounts

coins[0] is eth and coins[1] is stEth.
Attacker can add huge liquidity and remove_liquidity. Attacker will get the control flow before the pool transfer stEth.
At this point virtual_price would have a wrong value.

get_virtual_price of st-eth pool is not safe.

Proof of Concept

STECRVOracle.sol
STECRVOracle uses get_virtual_price.

I've done a simple POC and pumped the virtual_price to 1.6x

    function attack() internal {
        uint256[2] memory amounts;
        amounts[0] = a;
        amounts[1] = b;
        stCrv.remove_liquidity_imbalance(amounts, lp);
    }

    fallback() external payable {
        if (msg.sender == address(WETH)) {
            return;
        }
        console.log("virtual price:", stCrv.get_virtual_price());
                (, uint256 price) = oracle.get("");
        console.log("oracle:", price);
        borrowAndSell();
    }

Tools Used

Recommended Mitigation Steps

The easiest way is to use steth-weth pool instead
https://twitter.com/iearnfinance/status/1524913700334731264?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Etweet
I believe this is the path yearn team / lido team / mim team are heading. (Just a guess.)

If the team has strong reason to stick with the current steth pool, there's a hack to do it.

We can take maker dao's oracle as a reference.
CurveLPOracle

Always trigger remove_liquidity when querying get_virtual_price. This would revert the tx if it's being reentrancy attack.

        if (nonreentrant) {
            uint256[2] calldata amounts;
            CurvePoolLike(pool).remove_liquidity(0, amounts);
        }

Gas Optimizations

Table of Contents:

Caching storage values in memory

The code can be optimized by minimising the number of SLOADs.

SLOADs are expensive (100 gas after the 1st one) compared to MLOADs/MSTOREs (3 gas each). Storage values read multiple times should instead be cached in memory the first time (costing 1 SLOAD) and then read from this cache to avoid multiple SLOADs.

See the @audit tags for details about the multiple SLOADs where a cached value should be used instead of SLOAD 2 and above:

smart-contracts/ConvexCurveLPVault.sol:
   93:     address _token = _addressesProvider.getAddress('CRV'); //@audit gas: SLOAD 1 (_addressesProvider)
   99:     _token = _addressesProvider.getAddress('CVX'); //@audit gas: SLOAD 2 (_addressesProvider)
  137:     require(_asset == curveLPToken, Errors.VT_COLLATERAL_DEPOSIT_INVALID); //@audit gas: SLOAD 1 (curveLPToken)
  138:     TransferHelper.safeTransferFrom(curveLPToken, msg.sender, address(this), _amount); //@audit gas: SLOAD 2 (curveLPToken)
  141:     IERC20(curveLPToken).safeApprove(convexBooster, _amount); //@audit gas: SLOAD 3 (curveLPToken) + SLOAD 1 (convexBooster)
  142:     IConvexBooster(convexBooster).deposit(convexPoolId, _amount, true); //@audit gas: SLOAD 2 (convexBooster)
  145:     SturdyInternalAsset(internalAssetToken).mint(address(this), _amount); //@audit gas: SLOAD 1 (internalAssetToken)
  146:     IERC20(internalAssetToken).safeApprove(address(_addressesProvider.getLendingPool()), _amount); //@audit gas: SLOAD 2 (internalAssetToken)
  148:     return (internalAssetToken, _amount); //@audit gas: SLOAD 3 (internalAssetToken)

smart-contracts/LidoVault.sol:
   32:     address LIDO = _addressesProvider.getAddress('LIDO'); //@audit gas: SLOAD 1 (_addressesProvider)
   43:       _addressesProvider, //@audit gas: SLOAD 2 (_addressesProvider)
   44:       _addressesProvider.getAddress('STETH_ETH_POOL'), //@audit gas: SLOAD 3 (_addressesProvider)
   52:     address weth = _addressesProvider.getAddress('WETH'); //@audit gas: SLOAD 4 (_addressesProvider)
   56:     address yieldManager = _addressesProvider.getAddress('YIELD_MANAGER'); //@audit gas: SLOAD 5 (_addressesProvider)
   59:     emit ProcessYield(_addressesProvider.getAddress('WETH'), receivedETHAmount); //@audit gas: SLOAD 6 (_addressesProvider)
   84:     address LIDO = _addressesProvider.getAddress('LIDO'); //@audit gas: SLOAD 1 (_addressesProvider)
  102:     IERC20(LIDO).safeApprove(address(_addressesProvider.getLendingPool()), assetAmount); //@audit gas: SLOAD 2 (_addressesProvider)
  127:     address LIDO = _addressesProvider.getAddress('LIDO'); //@audit gas: SLOAD 1 (_addressesProvider)
  131:         _addressesProvider, //@audit gas: SLOAD 2 (_addressesProvider)
  132:         _addressesProvider.getAddress('STETH_ETH_POOL'), //@audit gas: SLOAD 3 (_addressesProvider)

smart-contracts/YieldManager.sol:
   74:     _assetsList[_assetsCount] = _asset; //@audit gas: SLOAD 1 (_assetsCount)
   75:     _assetsCount = _assetsCount + 1; //@audit gas: SLOAD 2 (_assetsCount)
  199:     if (_tokenOut == _exchangeToken) { //@audit gas: SLOAD 1 (_exchangeToken)
  202:     address _pool = _curvePools[_exchangeToken][_tokenOut]; //@audit gas: SLOAD 2 (_exchangeToken)
  207:       _exchangeToken, //@audit gas: SLOAD 3 (_exchangeToken)

Not using SafeMath can save gas on arithmetics operations that can't underflow/overflow

When an overflow or an underflow isn't possible (as an example, when a comparison is made before the arithmetic operation), SafeMath isn't necessary:

File: GeneralVault.sol
196:     if (stAssetBalance >= aTokenBalance) return stAssetBalance.sub(aTokenBalance); //@audit gas: no need to use SafeMath here as it can't underflow. Replace "sub" with " - "

A modifier used only once and not being inherited should be inlined to save gas

Affected code:

smart-contracts/CollateralAdapter.sol:
  17:   modifier onlyAdmin() {
  47:   ) external onlyAdmin {

Use of this instead of marking as public an external function

Using this. is like making an expensive external call.

Consider marking pricePerShare() as public:

File: GeneralVault.sol
158:   function pricePerShare() external view virtual returns (uint256) {}
...
123:       _amount = _amountToWithdraw.mul(this.pricePerShare()).div(10**decimal); //@audit gas: "this" makes an external call. Consider marking "pricePerShare()" as "public" instead of "external" 

> 0 is less efficient than != 0 for unsigned integers (with proof)

!= 0 costs less gas compared to > 0 for unsigned integers in require statements with the optimizer enabled (6 gas)

Proof: While it may seem that > 0 is cheaper than !=, this is only true without the optimizer enabled and outside a require statement. If you enable the optimizer at 10k AND you're in a require statement, this will save gas. You can see this tweet for more proofs: https://twitter.com/gzeon/status/1485428085885640706

I suggest changing > 0 with != 0 here:

GeneralVault.sol:179:    require(yieldStAsset > 0, Errors.VT_PROCESS_YIELD_INVALID);
LidoVault.sol:88:      require(msg.value > 0, Errors.VT_COLLATERAL_DEPOSIT_REQUIRE_ETH);

Also, please enable the Optimizer.

Contract is Ownable but owner capabilites are not used

Reduce contract size by removing Ownable given that its functionalities are not used.

YieldManager.sol:13:import {Ownable} from '../../dependencies/openzeppelin/contracts/Ownable.sol';
YieldManager.sol:26:contract YieldManager is VersionedInitializable, Ownable {

An array's length should be cached to save gas in for-loops

Reading array length at each iteration of the loop takes 6 gas (3 for mload and 3 to place memory_offset) in the stack.

Caching the array length in the stack saves around 3 gas per iteration.

Here, I suggest storing the array's length in a variable before the for-loop, and use it instead:

YieldManager.sol:130:    for (uint256 i = 0; i < assetYields.length; i++) {

++i costs less gas compared to i++ or i += 1

++i costs less gas compared to i++ or i += 1 for unsigned integer, as pre-increment is cheaper (about 5 gas per iteration). This statement is true even with the optimizer enabled.

The same is also true for i--.

i++ increments i and returns the initial value of i. Which means:

uint i = 1;  
i++; // == 1 but i == 2  

But ++i returns the actual incremented value:

uint i = 1;  
++i; // == 2 and i == 2 too, so no need for a temporary variable  

In the first case, the compiler has to create a temporary variable (when used) for returning 1 instead of 2

Instances include:

ConvexCurveLPVault.sol:106:    for (uint256 i = 0; i < extraRewardsLength; i++) {
GeneralVault.sol:218:    for (uint256 i = 0; i < length; i++) {
YieldManager.sol:120:    for (uint256 i = 0; i < _count; i++) {
YieldManager.sol:130:    for (uint256 i = 0; i < assetYields.length; i++) {
YieldManager.sol:156:    for (uint256 i = 0; i < length; i++) {

I suggest using ++i instead of i++ to increment the value of an uint variable.

No need to explicitly initialize variables with default values

If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for address...). Explicitly initializing it with its default value is an anti-pattern and wastes gas.

As an example: for (uint256 i = 0; i < numIterations; ++i) { should be replaced with for (uint256 i; i < numIterations; ++i) {

Instances include:

ConvexCurveLPVault.sol:106:    for (uint256 i = 0; i < extraRewardsLength; i++) {
GeneralVault.sol:218:    for (uint256 i = 0; i < length; i++) {
YieldManager.sol:120:    for (uint256 i = 0; i < _count; i++) {
YieldManager.sol:130:    for (uint256 i = 0; i < assetYields.length; i++) {
YieldManager.sol:156:    for (uint256 i = 0; i < length; i++) {

I suggest removing explicit initializations for default values.

ETH sent to receiver as collateral is not guaranteed in Lido Vault

Lines of code

https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L142

Vulnerability details

Impact

When the function '_withdrawFromYieldPool()' is called it sends Eth as collateral to the receiver. However, if the receiver is a contract that does not implement a fallback it will result in a failure but the contract continues without reverting because "receivedETHAmount" is returned before checking if the transaction was successful. The vault will assume the user has received the collateral even if it is not.

Proof of Concept

  1. User tries to withdraw collateral to a smart contract.
  2. The smart contract does not has a fallback function. The transaction should revert but doesn't.
  3. The contract assumes the collateral for that user has been withdrawn creating a discrepancy.

Recommended Mitigation Steps

https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L142
Require statement should be moved before the return statement like below.

(bool sent, bytes memory data) = address(_to).call{value: receivedETHAmount}('');
require(sent, Errors.VT_COLLATERAL_WITHDRAW_INVALID);
return receivedETHAmount;

LidoVault : Wrong code order prevents users from receiving ether

Lines of code

https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/LidoVault.sol#L140-L142

Vulnerability details

Impact

In the _withdrawFromYieldPool function of the LidoVault contract, the require statement is placed after the return statement, which makes the function not check the return value of to.call, which may result in the user not receiving ether.

  function _withdrawFromYieldPool(
    address _asset,
    uint256 _amount,
    address _to
  ) internal override returns (uint256) {
    address LIDO = _addressesProvider.getAddress('LIDO');
    if (_asset == address(0)) {
      // Case of ETH withdraw request from user, so exchange stETH -> ETH via curve
      uint256 receivedETHAmount = CurveswapAdapter.swapExactTokensForTokens(
        _addressesProvider,
        _addressesProvider.getAddress('STETH_ETH_POOL'),
        LIDO,
        ETH,
        _amount,
        200
      );

      // send ETH to user
      (bool sent, bytes memory data) = address(_to).call{value: receivedETHAmount}('');
      return receivedETHAmount;
      require(sent, Errors.VT_COLLATERAL_WITHDRAW_INVALID);

Proof of Concept

https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/LidoVault.sol#L140-L142

The example contract below shows that the require statement does not take effect when the require statement comes after the return statement

pragma solidity 0.6.12;

contract A{
  uint256 public a;
  function convertA() external{
      a = _test();
  }
  function _test() internal returns (uint256) {
    return 1;
    require(false);
  }
}

Tools Used

None

Recommended Mitigation Steps

Put the require statement before the return statement

  function _withdrawFromYieldPool(
    address _asset,
    uint256 _amount,
    address _to
  ) internal override returns (uint256) {
    address LIDO = _addressesProvider.getAddress('LIDO');
    if (_asset == address(0)) {
      // Case of ETH withdraw request from user, so exchange stETH -> ETH via curve
      uint256 receivedETHAmount = CurveswapAdapter.swapExactTokensForTokens(
        _addressesProvider,
        _addressesProvider.getAddress('STETH_ETH_POOL'),
        LIDO,
        ETH,
        _amount,
        200
      );

      // send ETH to user
      (bool sent, bytes memory data) = address(_to).call{value: receivedETHAmount}('');
      require(sent, Errors.VT_COLLATERAL_WITHDRAW_INVALID);
      return receivedETHAmount;

QA Report

Lack of empty address checks

The following methods have a lack checks if the received argument is an address, it's good practice in order to reduce human error to check that the address specified in the constructor or initialize is different than address(0).

Source code:

Change important values without emitting an event

When features are modified that affect the project's economics or ecology, it's critical to send out an event so that users and dapps can react appropriately.

Source code:

Wrong type used decimals uint8

The ERC20 standard establishes that the decimals method is of type uint8, however a variable of type uint256 is used to handle it.

Source code:

As stated in the OpenZeppelin source comments, the OpenZeppeling ERC20 safeApprove() function has been deprecated.

Using this deprecated function could result in accidental reverts and possibly fund locking. The deprecation of this function is discussed in greater depth in the OZ issue #2219.

As suggested by the OpenZeppelin comment, replace safeApprove() with safeIncreaseAllowance() or safeDecreaseAllowance() instead.

Source code:

The variable _assetsCount in the YieldManager contract could be out of sync with the total assets.

The number could be different if the owner sent the same _asset twice because the assets were not checked to see if they were already in _assetsList.

Source code:

unsafe approve

It was found some approve without checking the boolean result, ERC20 standard specify that the token can return false if this call was not made, so it's mandatory to check the result of these methods.

Source code:

reentrancy in _withdrawFromYieldPool

Lines of code

https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/LidoVault.sol#L91

Vulnerability details

description

in the _withdrawFromYieldPool function there is an unsafe external call to a user controlled _to address which could cause reentrancy

recommend adding reentrancy guards to the withdraw functions

/2022-05-sturdy/smart-contracts/LidoVault.sol
140:       (bool sent, bytes memory data) = address(_to).call{value: receivedETHAmount}('');

Fee-on-transfer tokens support must be forbidden or allowed with amount update

Lines of code

https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L82
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L138
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L170
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L207
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L57
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L98
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L146
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L156

Vulnerability details

Impact

Every time transferFrom or transfer function in ERC20 standard is called there is a possibility that underlying smart contract did not transfer the exact amount entered.
It is required to find out contract balance increase/decrease after the transfer to allow fee-on-transfer tokens or forbid non-standard tokens.
This pattern also prevents from re-entrancy attack vector.

Proof of Concept

POC (re-entrancy for fee-on-transfer tokens):

  1. Contract calls transfer from contractA 100 tokens to current contract
  2. Current contract thinks it received 100 tokens
  3. It updates internal balance sheet +100 tokens
  4. While actually contract received only 90 tokens
  5. That breaks whole math for given token
  6. Now imagine some fake token which does not send anything
    Attacker could run re-entrancy and boosting deposits in the storage while transferring nothing.
    At the end drain tokenOut (if it is DEX) or withdraw something else based on large deposit.

Tools Used

Recommended Mitigation Steps

There are several possible scenarios to prevent that.

  1. Check how much contract balance is increased/decreased after every transfer (costs more gas)
  2. Make a separate contract that checks if the token has fee-on-transfer and store bool value depending on the result.
    If there is fee-on-transfer you can throw a require not allowing to use such token in the system while still saving lots of gas checking it only once.
    Or if you still want to allow fee-on-transfer tokens, amount variable must be updated to the balance difference after and before transfer.

Recommended example code:

mapping(IERC20 => bool) doesThisContractHaveFeeOnTransfer;
enum FeeOnTransferStatuses{ NOT_INITIALIZED, HAS_FEE_ON_TRANSFER, DOES_NOT_HAVE_FEE_ON_TRANSFER }
error FeeOnTransferTokensAreForbidden();
...
function deposit(IERC20 token, address from, uint256 amount) public {

    // reverting for fee-on-transfer tokens
    if (doesThisContractHaveFeeOnTransfer[token] == FeeOnTransferStatuses.HAS_FEE_ON_TRANSFER) {
        revert FeeOnTransferTokensAreForbidden();
    }

    // NOT_INITIALIZED is the default value == 0
    if (doesThisContractHaveFeeOnTransfer[token] == FeeOnTransferStatuses.NOT_INITIALIZED) {
        uint256 balanceBefore = token.balanceOf(address(this)); // remembering asset balance before the transfer
        token.safeTransferFrom(from, address(this), amount);
        uint256 balanceAfter = token.balanceOf(address(this));

        // making sure exactly amount was transferred
        if (balanceAfter != balanceBefore + amount) {            
            revert FeeOnTransferTokensAreForbidden();
        }

        // IMPORTANT! if you allow fee-on-transfer tokens make sure to update amount with the actual balance increase/decrease
        // or make sure balanceAfter - balanceBefore == amount using require
        // amount = balanceAfter - balanceBefore; // commented because we skip fee-on-transfer tokens above

        doesThisContractHaveFeeOnTransfer[token] = FeeOnTransferStatuses.DOES_NOT_HAVE_FEE_ON_TRANSFER; // making sure to not enter this if clause anymore for given token
    } else {
        // token does not have fee-on-transfer here
        token.safeTransferFrom(from, address(this), amount);
    }

    // no re-entrancy vector attack below here 
    // amount is set to exactly how much contract balance was increased

    registerDeposit(from, amount); 
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.