Platform: Code4rena
Start Date: 30/11/2021
Pot Size: $30,000 USDC
Total HM: 0
Participants: 21
Period: 3 days
Judge: pauliax
Id: 63
League: ETH
Rank: 7/21
Findings: 2
Award: $1,079.27
🌟 Selected for report: 2
🚀 Solo Findings: 0
🌟 Selected for report: gzeon
Also found by: Meta0xNull, defsec
883.4673 USDC - $883.47
defsec
During the code review, It has been observed that, minProtocolEquity is not updated on the ngmi function. That can cause to out of date data for the minProtocolEquity variable.
"https://github.com/code-423n4/2021-11-fei/blob/main/contracts/TRIBERagequit.sol#L59"
"https://github.com/code-423n4/2021-11-fei/blob/main/contracts/TRIBERagequit.sol#L110"
None
Consider to call requery
function on the ngmi
function.
function ngmi( uint256 multiplier, uint256 key, bytes32[] memory merkleProof ) public { requery(); .... }
#0 - elee1766
2021-12-06T04:26:23Z
intended
#1 - pauliax
2021-12-07T18:51:59Z
intended
Confirmed or disputed?
#2 - pauliax
2021-12-11T13:25:34Z
Similar to #126
🌟 Selected for report: defsec
73.5785 USDC - $73.58
defsec
In some cases, having function arguments in calldata instead of memory is more optimal.
Consider the following generic example:
contract C { function add(uint[] memory arr) external returns (uint sum) { uint length = arr.length; for (uint i = 0; i < arr.length; i++) { sum += arr[i]; } } }
In the above example, the dynamic array arr has the storage location memory. When the function gets called externally, the array values are kept in calldata and copied to memory during ABI decoding (using the opcode calldataload and mstore). And during the for loop, arr[i] accesses the value in memory using a mload. However, for the above example this is inefficient. Consider the following snippet instead:
contract C { function add(uint[] calldata arr) external returns (uint sum) { uint length = arr.length; for (uint i = 0; i < arr.length; i++) { sum += arr[i]; } } }
In the above snippet, instead of going via memory, the value is directly read from calldata using calldataload. That is, there are no intermediate memory operations that carries this value.
Gas savings: In the former example, the ABI decoding begins with copying value from calldata to memory in a for loop. Each iteration would cost at least 60 gas. In the latter example, this can be completely avoided. This will also reduce the number of instructions and therefore reduces the deploy time cost of the contract.
In short, use calldata instead of memory if the function argument is only read.
Note that in older Solidity versions, changing some function arguments from memory to calldata may cause "unimplemented feature error". This can be avoided by using a newer (0.8.*) Solidity compiler.
Examples Note: The following pattern is prevalent in the codebase:
function f(bytes memory data) external { (...) = abi.decode(data, (..., types, ...)); }
Here, changing to bytes calldata will decrease the gas. The total savings for this change across all such uses would be quite significant.
Examples:
https://github.com/code-423n4/2021-11-fei/blob/main/contracts/TRIBERagequit.sol#L141
https://github.com/code-423n4/2021-11-fei/blob/main/contracts/TRIBERagequit.sol#L165
None
Change memory definition with calldata.
#0 - pauliax
2021-12-10T18:54:36Z
Valid suggestion to consider.
19.8662 USDC - $19.87
defsec
For the arithmetic operations that will never over/underflow, using the unchecked directive (Solidity v0.8 has default overflow/underflow checks) can save some gas from the unnecessary internal over/underflow checks.
https://github.com/code-423n4/2021-11-fei/blob/main/contracts/TRIBERagequit.sol#L229
None
Consider applying unchecked arithmetic where overflow/underflow is not possible.
#0 - elee1766
2021-12-06T04:27:56Z
#124
#1 - pauliax
2021-12-10T19:23:16Z
A duplicate of #124
13.4097 USDC - $13.41
defsec
During the code review, The require(msg.sender != address(this), "????");
check looks like redundant. The condition can be removed for the gas optimisation.
https://github.com/code-423n4/2021-11-fei/blob/main/contracts/PegExchanger.sol#L39
None
Consider to delete redundant check.
#0 - elee1766
2021-12-06T04:20:24Z
#120
#1 - pauliax
2021-12-10T16:12:12Z
A duplicate of #71
defsec
This does not directly impact the smart contract in anyway besides cost. This is a gas optimization to reduce cost of smart contract. Calling each function, we can see that the public function uses 496 gas, while the external function uses only 261.
According to Slither Analyzer documentation (https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external), there are functions in the contract that are never called. These functions should be declared as external in order to save gas.
Slither Detector:
external-function:
https://github.com/code-423n4/2021-11-fei/blob/main/contracts/PegExchanger.sol#L36
https://github.com/code-423n4/2021-11-fei/blob/main/contracts/PegExchanger.sol#L80
https://github.com/code-423n4/2021-11-fei/blob/main/contracts/PegExchanger.sol#L89
https://github.com/code-423n4/2021-11-fei/blob/main/contracts/PegExchanger.sol#L101
https://github.com/code-423n4/2021-11-fei/blob/main/contracts/TRIBERagequit.sol#L59
https://github.com/code-423n4/2021-11-fei/blob/main/contracts/TRIBERagequit.sol#L87
https://github.com/code-423n4/2021-11-fei/blob/main/contracts/TRIBERagequit.sol#L110
https://github.com/code-423n4/2021-11-fei/blob/main/contracts/TRIBERagequit.sol#L204
https://github.com/code-423n4/2021-11-fei/blob/main/contracts/TRIBERagequit.sol#L216
Slither
#0 - elee1766
2021-12-06T03:05:29Z
duplicate #27
#1 - pauliax
2021-12-11T10:20:15Z
A duplicate of #27
33.1103 USDC - $33.11
defsec
++i is more gas efficient than i++ in loops forwarding.
"https://github.com/code-423n4/2021-11-fei/blob/main/contracts/TRIBERagequit.sol#L165"
Code Review
It is recommend to use unchecked{++i} and change i declaration to uint256.
#0 - elee1766
2021-12-06T04:27:31Z
#98
#1 - pauliax
2021-12-11T10:37:22Z
A duplicate of #98
19.8662 USDC - $19.87
defsec
On the following contract, msg.sender is set to internal variable. However, this code is redundant.
"https://github.com/code-423n4/2021-11-fei/blob/main/contracts/TRIBERagequit.sol#L67"
pragma solidity >=0.7.0 <0.9.0; /** * @title Ballot * @dev Implements voting process along with vote delegation */ contract Ballot { function ngmi() public view { require(msg.sender == address(this),"GG"); } }
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; /** * @title Ballot * @dev Implements voting process along with vote delegation */ contract Ballot { function ngmi() public view { address thisSender = msg.sender; require(thisSender == address(this),"GG"); } }
None
Consider to use msg.sender directly.
#0 - pauliax
2021-12-11T12:00:15Z
A duplicate of #99
33.1103 USDC - $33.11
defsec
Various projects (e.g. Uniswap - https://github.com/Uniswap/interface/blob/main/src/hooks/useApproveCallback.ts#L88 , see here 1 using the constant MaxUint256 from ethers.js) set the default value of the user's allowance to 2^256 - 1. Now the value 2^256 - 1 can also be represented in hex as 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff. From Ethereum's yellow paper we know that zeros are cheaper than non-zero values in the hex representation. Considering this fact, an alternative choice could be now 0x8000000000000000000000000000000000000000000000000000000000000000 or 2^255 to represent "infinity". If you do the calculations with Remix, you will see that the former costs 47'872 gas, while the latter costs 45'888 gas. If you accept that infinity can also be represented via 2^255 (instead of 2^256-1) - and I think most projects can live with that - you can already save 1'984 gas (or 4.1%) leveraging this optimisation trick.
"https://github.com/code-423n4/2021-11-fei/blob/main/contracts/TRIBERagequit.sol#L25"
Code Review
Change 2^256-1 With 2^255.
https://ethereum.github.io/yellowpaper/paper.pdf
https://forum.openzeppelin.com/t/a-collection-of-gas-optimisation-tricks/19966
#0 - elee1766
2021-12-06T03:37:01Z
ack
unless i'm misunderstood, this saving is minuscule and only on deployment
#1 - pauliax
2021-12-11T10:50:25Z
Valid suggestion, even though the improvement is minor and it may make the code less understandable.