Skip to content

Commit

Permalink
__141345__ data for issue #257
Browse files Browse the repository at this point in the history
  • Loading branch information
code423n4 committed Sep 19, 2022
1 parent 3c1821d commit 14ff7a0
Showing 1 changed file with 363 additions and 0 deletions.
363 changes: 363 additions & 0 deletions data/__141345__-G.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,363 @@

#### FOR-LOOP LENGTH COULD BE CACHED

The for loop length can be cached to memory before the loop, even for memory variables.
The demo of the loop gas comparison can be seen [here](https://github.com/Flame57/gas_demo/blob/main/loop.sol).


```solidity
src/Vault.sol
443: for (uint256 i = 0; i < epochsLength(); i++) {
```

Suggestion:
Cache the length before the loop.
```
uint length = names.length;
```


#### FOR-LOOP `unchecked{++i}` COSTS LESS GAS COMPARED TO `i++` OR `i += 1`

Use `++i` instead of `i++` to increment the value of an uint variable, and for guaranteed non-overflow/underflow, it can be unchecked.
And the optimizer need to be turned on.

The demo of the loop gas comparison can be seen [here](https://github.com/Flame57/gas_demo/blob/main/loop.sol).


```solidity
src/Vault.sol
443: for (uint256 i = 0; i < epochsLength(); i++) {
```

Suggestion:
For readability, uncheck the whole for loop is the same.

```solidity
unchecked{
for (uint256 i; i < length; ++i) {
}
}
```

#### 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.


```solidity
src/Vault.sol
443: for (uint256 i = 0; i < epochsLength(); i++) {
src/VaultFactory.sol
159: marketIndex = 0;
src/rewards/StakingRewards.sol
36: uint256 public periodFinish = 0;
```

The demo of the loop gas comparison can be seen [here](https://github.com/Flame57/gas_demo/blob/main/loop.sol).


#### `X = X + Y / X = X - Y` IS CHEAPER THAN `X += Y / X -= Y`

The demo of the gas comparison can be seen [here](https://github.com/Flame57/gas_demo/blob/main/gas_compare.sol).

Consider use `X = X + Y / X = X - Y` to save gas.


```solidity
src/VaultFactory.sol
195: marketIndex += 1;
```


#### `!= 0` uses less gas than `> 0`

`!= 0` costs less gas compared to `> 0` for unsigned integers with the optimizer enabled (6 gas).
Here is the code as a [demo](https://github.com/Flame57/gas_demo/blob/main/ltgt0.sol).
[Opcode discussion](https://twitter.com/gzeon/status/1485428085885640706).

```solidity
src/Vault.sol
187: require(msg.value > 0, "ZeroValue");
src/oracles/PegOracle.sol
98: require(price1 > 0, "Chainlink price <= 0");
121: require(price2 > 0, "Chainlink price <= 0");
src/rewards/StakingRewards.sol
119: require(amount > 0, "Cannot withdraw 0");
134: if (reward > 0) {
```

Suggestion:
- Changing `> 0` with `!= 0`.
- Enable the Optimizer.

#### REDUCE THE SIZE OF ERROR MESSAGES


Shortening revert strings to fit in 32 bytes will decrease deployment time gas and will decrease runtime gas when the revert condition is met.
Revert strings that are longer than 32 bytes require at least one additional mstore, along with additional overhead for computing memory offset, etc.


```solidity
src/SemiFungibleVault.sol
116-119:
require(
msg.sender == owner || isApprovedForAll(owner, receiver),
"Only owner can withdraw, or owner has approved receiver for all"
);
src/oracles/PegOracle.sol
23-24:
require(_oracle1 != address(0), "oracle1 cannot be the zero address");
require(_oracle2 != address(0), "oracle2 cannot be the zero address");
src/rewards/StakingRewards.sol
217-220:
require(
tokenAddress != address(stakingToken),
"Cannot withdraw the staking token"
);
226-229:
require(
block.timestamp > periodFinish,
"Previous rewards period must be complete before changing the duration for the new period"
);
```


Suggestion:
Shortening the revert strings to fit in 32 bytes, or using custom errors as described next.



#### USE CUSTOM ERRORS INSTEAD OF REVERT STRINGS

Custom errors from Solidity 0.8.4 are more gas efficient than revert strings (lower deployment cost and runtime cost when the revert condition is met) ([reference](https://blog.soliditylang.org/2021/04/21/custom-errors/))

> Starting from Solidity v0.8.4, there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors. Until now, you could already use strings to give more information about failures (e.g., revert("Insufficient funds.");), but they are rather expensive, especially when it comes to deploy cost, and it is difficult to use dynamic information in them.
> Custom errors are defined using the error statement, which can be used inside and outside of contracts (including interfaces and libraries).
The demo of the gas comparison can be seen [here](https://github.com/Flame57/gas_demo/blob/main/errrevert.sol).


The following files have multiple `require()` statements can use custom errors:

```solidity
src/SemiFungibleVault.sol
91: require((shares = previewDeposit(id, assets)) != 0, "ZERO_SHARES");
116-119:
require(
msg.sender == owner || isApprovedForAll(owner, receiver),
"Only owner can withdraw, or owner has approved receiver for all"
);
src/Vault.sol
165: require((shares = previewDeposit(id, assets)) != 0, "ZeroValue");
187: require(msg.value > 0, "ZeroValue");
src/oracles/PegOracle.sol
23-30:
require(_oracle1 != address(0), "oracle1 cannot be the zero address");
require(_oracle2 != address(0), "oracle2 cannot be the zero address");
require(_oracle1 != _oracle2, "Cannot be same Oracle");
require(
(priceFeed1.decimals() == priceFeed2.decimals()),
"Decimals must be the same"
);
98-103:
require(price1 > 0, "Chainlink price <= 0");
require(
answeredInRound1 >= roundID1,
"RoundID from Oracle is outdated!"
);
require(timeStamp1 != 0, "Timestamp == 0 !");
121-126:
require(price2 > 0, "Chainlink price <= 0");
require(
answeredInRound2 >= roundID2,
"RoundID from Oracle is outdated!"
);
require(timeStamp2 != 0, "Timestamp == 0 !");
src/rewards/StakingRewards.sol
96: require(amount != 0, "Cannot stake 0");
119: require(amount > 0, "Cannot withdraw 0");
220-205:
require(
rewardRate <= balance.div(rewardsDuration),
"Provided reward too high"
);
217-220:
require(
tokenAddress != address(stakingToken),
"Cannot withdraw the staking token"
);
226-229:
require(
block.timestamp > periodFinish,
"Previous rewards period must be complete before changing the duration for the new period"
);
```

#### Some frequently called numbers can be save into a constant

The expression is re-calculated each time the constant is referenced.

```solidity
src/Vault.sol
388-389:
keccak256(abi.encodePacked(symbol)) ==
keccak256(abi.encodePacked("rY2K"))
```

The consequence is: each usage costs ~100gas more on each access (it is still a little better than storing the result in storage, but not much..)


Suggestion:
Change these expressions from constant to immutable and implement the calculation in the constructor or hardcode these values in the constants and add a comment to say how the value was calculated.

reference:
https://github.com/ethereum/solidity/issues/9232



#### MAKING SOME CONSTANTS/IMMUTABLE AS NON-PUBLIC

Changing the visibility from public to private or internal can save gas when a constant isn’t used outside of its contract.

Savings are due to the compiler not having to create non-payable getter functions for deployment calldata, and not adding another entry to the method ID table.

If needed, the value can be read from the verified contract source code.

A cheap way to store constants in a single location is to create an internal constant in a library. If the variable is a local cache of another contract’s value, consider making the cache variable internal or private, which will require external users to query the contract with the source of truth, so that callers don’t get out of sync.


The followings can be changed from public to internal or private:

```solidity
src/Controller.sol
uint256 public constant VAULTS_LENGTH = 2;
```

#### Use bytes32 instead of string

Use bytes32 instead of string to save gas whenever possible.
String is a dynamic data structure and therefore is more gas consuming then bytes32.

Bytes32 can be used instead of string in the following:

```solidity
src/VaultFactory.sol
199-217:
Vault hedge = new Vault(
WETH,
string(abi.encodePacked(_name,"HEDGE")),
"hY2K",
treasury,
_token,
_strikePrice,
controller
);
Vault risk = new Vault(
WETH,
string(abi.encodePacked(_name,"RISK")),
"rY2K",
treasury,
_token,
_strikePrice,
controller
);
```

#### BOOLEAN COMPARISON

Comparing to a constant (true or false) is a bit more expensive than directly checking the returned boolean value.

The demo of the gas comparison can be seen [here](https://github.com/Flame57/gas_demo/blob/main/boolean.sol).


Suggestion:
Use
`if (value) / if (!value)`
instead of
`if (value == true) / if (value == false)`

in the following:
```solidity
src/Vault.sol
80: if(idExists[id] != true)
96: if((block.timestamp < id) && idDepegged[id] == false)
215-217:
if(
msg.sender != owner &&
isApprovedForAll(owner, receiver) == false)
314: if(idExists[epochEnd] == true)
src/Controller.sol
93: if(vault.idExists(epochEnd) == false)
211: if(insrVault.idExists(epochEnd) == false || riskVault.idExists(epochEnd) == false)
src/rewards/RewardsFactory.sol
96: if(Vault(_insrToken).idExists(_epochEnd) == false || Vault(_riskToken).idExists(_epochEnd) == false)
```

#### MULTIPLE ADDRESS MAPPINGS CAN BE COMBINED INTO A SINGLE MAPPING OF AN ADDRESS TO A STRUCT WHERE APPROPRIATE

Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot.

```solidity
src/Vault.sol
47-55:
mapping(uint256 => uint256) public idFinalTVL;
mapping(uint256 => uint256) public idClaimTVL;
mapping(uint256 => uint256) public idEpochBegin;
mapping(uint256 => bool) public idDepegged;
mapping(uint256 => bool) public idExists;
mapping(uint256 => uint256) public epochFee;
src/VaultFactory.sol
119-120:
mapping(uint256 => address[]) public indexVaults; //[0] hedge and [1] risk vault
mapping(uint256 => uint256[]) public indexEpochs; //all epochs in the market
src/rewards/StakingRewards.sol
43-47:
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewards;
mapping(address => uint256) private _balances;
```

























0 comments on commit 14ff7a0

Please sign in to comment.