low level vulnerablity

Use .call instead of .transfer to send ether .transfer will relay 2300 gas and .call will relay all the gas. If the receive/fallback function from the recipient proxy contract has complex logic, using .transfer will fail, causing integration issues.
Unbounded loop
1
2
3
4
5
6
7
8
9
10
11
function claimGovFees() public {
address[] memory assets = bondNFT.getAssets();

for (uint i=0; i < assets.length; i++) {
uint balanceBefore = IERC20(assets[i]).balanceOf(address(this));
IGovNFT(govNFT).claim(assets[i]);
uint balanceAfter = IERC20(assets[i]).balanceOf(address(this));
IERC20(assets[i]).approve(address(bondNFT), type(uint256).max);
bondNFT.distribute(assets[i], balanceAfter - balanceBefore);
}
}
Use time units directly
1
uint constant private DAY = 24 * 60 * 60;
Chainlink price feed is not sufficiently validated and can return stale price
1
2
3
(uint80 roundId, int256 assetChainlinkPriceInt, , uint256 updatedAt, uint80 answeredInRound) = IPrice(_chainlinkFeed).latestRoundData();
require(answeredInRound >= roundId, "price is stale");
require(updatedAt > 0, "round is incomplete");
  • Use the safe variant and ERC721.mint
  • Add an event for critical parameter changes
  • Declare interfaces on separate files
  • Constants should be upper case
  • Replace constant private with private constant

ERC1155Enumerable Implement

When minting and burning tokens,the ERC1155Enumerable implementation does not correctly update the following states:

  • uint256[] private _allTokens;
  • mapping(uint256 => uint256) private _allTokensIndex;
  • mapping(address => uint256) internal _currentIndex;

In particular:

  • the _allTokens array length (and therefore the totalSupply()) always increases (never decreases)
  • the _allTokensIndex[id] always increases
  • the _curentIndex[from] always increases
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

contract HelperERC1155 is ERC1155Enumerable, ERC1155Holder {
constructor() ERC1155("Test") {
}
function mint(uint256 id, uint256 amount) external {
_mint(msg.sender, id, amount, bytes(""));
}
function burn(uint256 id, uint256 amount) external {
_burn(msg.sender, id, amount);
}
function currentIndex(address owner) external view returns (uint256) {
return _currentIndex[owner];
}
function allTokensIndex(uint256 id) external view returns (uint256) {
return _allTokensIndex[id];
}
function allTokens(uint256 idx) external view returns (uint256) {
return _allTokens[idx];
}
function idTotalSupply(uint256 id) external view returns (uint256) {
return _idTotalSupply[id];
}
}

Solidity gas saving tips(to be continued)

Using delete instead of setting struct 0 saves gas
1
2
-   pool.long0ProtocolFees = 0;
+ delete pool.long0ProtocolFees;
Saving on gas costs should be prioritized
1
2
3
4
5
6
7
8
9
10
+    require(block.timestamp <= deadline, "KYCRegistry: signature expired");

require(
!kycState[kycRequirementGroup][user],
"KYCRegistry: user already verified"
);
- require(block.timestamp <= deadline, "KYCRegistry: signature expired");
bytes32 structHash = keccak256(
abi.encode(_APPROVAL_TYPEHASH, kycRequirementGroup, user, deadline)
);
The way to save on gas costs with a for loop
1
2
3
4
5
6
7
8
-    for (uint256 i = 0; i < exCallData.length; ++i) {

+ for (uint256 i = 0; i < exCallData.length;) {
+ unchecked {
+ ++i;
+ }
}
}
No necessary global variable read
1
2
3
4
5
-    doTransferOut(admin, reduceAmount);
+ doTransferOut(payable(msg.sender), reduceAmount);
- emit ReservesReduced(admin, reduceAmount, totalReservesNew);
+ emit ReservesReduced(msg.sender, reduceAmount, totalReservesNew);

Cache mapping instead of reading multiple times
1
2
3
4
5
6
-    if (fTokenToUnderlyingPrice[fToken] != 0) {
- return fTokenToUnderlyingPrice[fToken];
+ uint256 fToken = fTokenToUnderlyingPrice[fToken];
+ if(fToken != 0) {
+ return fToken;
}
Immutable save more gas
1
2
-  address public owner;
+ address public immutable owner;
mapping(address⇒bool) using bool for storage incurs overhead
1
2
-  mapping(address => bool) public allowedAsset;
+ mapping(address => uint256) public allowedAsset;
Save gas with the use of the import statement Solidity code is also cleaner in another way that might not be noticeable: the struct Point. We were importing it previously with global import but not using it. The Point struct polluted the source code with an unnecessary object we were not using because we did not need it. This was breaking the rule of modularity and modular programming: only import what you need Specific imports with curly braces allow us to apply this rule better.
Recommendation:
import {contract1 , contract2} from "filename.sol";
Sort Solidity operations using short-circuit mode //f(x) is a low gas cost operation //g(y) is a high gas cost operation //Sort operations with different gas costs as follows f(x) || g(y) f(x) && g(y)
Change for loop behavior by removing add (+1) and ++x is more gas efficient
1
2
3
4
5
6
7
8
function buy(uint256 _amount) external payable {
...
- for (uint48 x = sale_.currentId + 1; x <= newId; x++) {
+ for (uint48 x = sale_.currentId; x < newId; ++x) {
nft.mint(msg.sender, x);
}
...
}
  • Multiple access to mapping/array should use local variable cache
  • Duplicated require should be modifier or function
  • Use custom error rather than revert()/require()
  • Use calldata instead of memory for read-only variable
  • require()/revert() string longer than 32 bytes cost extra gas
  • Using Openzeppelin Ownable2Step.sol is gas efficient
  • Using UniswapV3 mulDiv function is gas-optimized
  • Use nested if and, avoid multiple check combinations
  • Avoid using state variable in emit (130 gas)
  • Instead of cache a whole object ,try cache single Attributes
  • Using int32 for time
  • Don’t use _msgSender() if not supporting EIP-2771
  • Using > 0 costs more gas than != 0 when used on a uint in a require() statement(version>0.8.13)
  • Using bools for storage incurs overhead
  • .length should not be looked up in every loop of a for-loop
  • Using calldata instead of memory for read-only arguments in external functions saves gas
  • Splitting require() statements that use && saves gas
  • Use a more recent version of solidity
  • require()/revert() strings longer than 32 bytes cost extra gas
  • += costs more gas than = + for state variables
  • Using storage instead of memory for structs/arrays saves gas