2022-05-sturdy-findings's Issues
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()"
}
User can lose collateral do to unsafe ETH transfer
Lines of code
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
(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 toreturn
placement. withdrawAmount
still reflectsreceivedETHAmount
.- 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;
Gas Optimizations
Impact
[1] Consider using optimized for-loop and apply the following optimizations:
- cache .length into local variable to avoid looking up every for-loop iteration.
- using ++i consumes 5 less gas than i++ (same applies to --i)
- using unchecked keyword for counter i unchecked{ ++i; } consumes 49 less gas each iteration (same applies to --i)
- don't initialize uint256 i = 0; instead use the default value uint256 i;
- make sure to specify uint256 type instead of uint type for readability
Affected code:
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L106
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L218
- 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
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:
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L75
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L36
- 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:
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/CollateralAdapter.sol#L18
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L38
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L71
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L95
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L101
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L137
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L183
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L184
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L197
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L30
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L35
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L125
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L166
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L167
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L179
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L88
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L92
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L97
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L142
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L145
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L52
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L65
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L97
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L122
- 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:
Proof of Concept
Tools Used
Recommended Mitigation Steps
Gas Optimizations
Missing zero check
Description:
https://github.com/sturdyfi/code4rena-may-2022/blob/main/contracts/protocol/vault/YieldManager.sol#L124
https://github.com/sturdyfi/code4rena-may-2022/blob/main/contracts/protocol/vault/YieldManager.sol#L132
On line 124 and 312, the amount is not validated before the exchange is called. If the amount is zero no need to exchange.
Mitigation:
require(_amount != 0,"...");
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):
- Contract calls transfer from contractA 100 tokens to current contract
- Current contract thinks it received 100 tokens
- It updates internal balance sheet +100 tokens
- While actually contract received only 90 tokens
- That breaks whole math for given token
- 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.
- Check how much contract balance is increased/decreased after every transfer (costs more gas)
- 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);
}
Gas Optimizations
Table of Contents:
- Caching storage values in memory
- Not using SafeMath can save gas on arithmetics operations that can't underflow/overflow
- A modifier used only once and not being inherited should be inlined to save gas
- Use of
this
instead of marking aspublic
anexternal
function > 0
is less efficient than!= 0
for unsigned integers (with proof)- Contract is Ownable but owner capabilites are not used
- An array's length should be cached to save gas in for-loops
++i
costs less gas compared toi++
ori += 1
- No need to explicitly initialize variables with default values
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.
LidoVault : Wrong code order prevents users from receiving ether
Lines of code
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
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;
User should be able to select slippage in GeneralVault
Lines of code
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
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L73
registerAsset function: Duplicate _asset can be added to the contract. Ideally there should be check to reject an asset if it already exists
QA Report
Impact
[1] By default, function types and state variables/constants are internal, so the internal keyword can be omitted.
Affected code:
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/CollateralAdapter.sol#L24
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/CollateralAdapter.sol#L27
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/CollateralAdapter.sol#L29
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/CollateralAdapter.sol#L49
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L28
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L29
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L30
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L48
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L55
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L117
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L145
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L146
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L148
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L161
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L173
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L49
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L52
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L53
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L36
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L37
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L39
- 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:
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L123
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L48
- 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:
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:
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L28
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L29
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L37
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L93
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L94
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L108
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L43
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L64
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L93
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L94
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L106
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L106
- https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L195
- 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:
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:
- https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/GeneralVault.sol#L220-L229
- https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/YieldManager.sol#L158-L167
- 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:
- https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/GeneralVault.sol#L246
- https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/GeneralVault.sol#L255
- https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/GeneralVault.sol#L265
- 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:
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:
Proof of Concept
Tools Used
Recommended Mitigation Steps
Impact
[10] Brackets aren't necessary here, consider making this code one-liner.
Affected code:
Proof of Concept
Tools Used
Recommended Mitigation Steps
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:
- CollateralAdapter.sol#L36
- CollateralAdapter.sol#L44-L46
- ConvexCurveLPVault.sol#L37
- GeneralVault.sol#L62
- YieldManager.sol#L61
- YieldManager.sol#L73
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:
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**decimalsto
decimals.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;
}
}
Require incorrect ordering
Lines of code
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
Return is executed before require check
Lines of code
Vulnerability details
Impact
Regardless of the success or failure of the call, function will exit as if everything was succeeded.
Consider swapping require and return.
Proof of Concept
Tools Used
Recommended Mitigation Steps
Gas Optimizations
> 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)
I suggest changing > 0 with != 0 here:
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L88
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L179
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
Tools Used
Recommended Mitigation Steps
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 Report
Table of content
Gas Optimizations
State variables that could be set immutable
You can set the following state variables to immutable and save gas:
- CollateralAdapter.sol: _addressesProvider
- GeneralVault.sol: _addressesProvider
- YieldManager.sol: _addressesProvider
LidoVault: msg.value == 0 not checked when user deposits LIDO tokens
Lines of code
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
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);
}
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
- User tries to withdraw collateral to a smart contract.
- The smart contract does not has a fallback function. The transaction should revert but doesn't.
- 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;
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
++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:
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;
}
}
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.
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. AfterEIP-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.
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
Tools Used
Recommended Mitigation Steps
reentrancy in _withdrawFromYieldPool
Lines of code
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}('');
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)
require statement should be checked first
Lines of code
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
YieldManger swap highly vulnerable to sandwich attacks
Lines of code
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
`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
[Low - 01] - Slippage is incoherent in LidoVault
.
In LidoVault
, the slippage when swapping stETH to ETH is 2% whereas in GenericVault
it is 1%. This should be unified.
Code:
https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/LidoVault.sol#L130
https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/GeneralVault.sol#L125
[Non-Critical - 01]
Incorrect Comment: https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/GeneralVault.sol#L77
Gas Optimizations
For loops in YieldManager and GeneralVault
To optimize the for loop and make it consume less gas, i suggest to:
-
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.
-
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
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 extrajump
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 of100
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
Approval must be set to zero and after that increased to the amount you need.
Lines of code
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L141
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L146
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L102
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L221
Vulnerability details
Impact
Approval must be set to zero and after that increased to the amount you need.
Some of the tokens such as USDT require that.
Proof of Concept
Please read more information here: https://www.adrianhetman.com/unboxing-erc20-approve-issues/
Tools Used
Recommended Mitigation Steps
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
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
.
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
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
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:
-
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.
-
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
Gas Optimizations
For-Loop :
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L130
1 - 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
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 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
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
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++) {
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);
}
Gas Optimizations
Use != 0 instead of > 0 at the above mentioned codes. The variable is uint, so it will not be below 0 so it can just check != 0.
!= 0 costs less gas compared to > 0 for unsigned integers in require statements with the optimizer enabled
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/ConvexCurveLPVault.sol#L75
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L179
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L36
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L88
https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L131
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
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
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
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.