Platform: Code4rena
Start Date: 09/11/2021
Pot Size: $30,000 ETH
Total HM: 6
Participants: 17
Period: 3 days
Judge: pauliax
Total Solo HM: 3
Id: 50
League: ETH
Rank: 9/17
Findings: 2
Award: $573.30
🌟 Selected for report: 4
🚀 Solo Findings: 0
0.059 ETH - $278.98
0x0x0x
The amount of ethereum at funding pool is simply checked by pool.balance
. This is not recommended, because the amount of total funding can change by withdrawing funds. In case, funds are withdrawn before the end of hatch phase, it can result in getting funded more than claimed on launch. In current implementation, funds shouldn't be touched till the hatch phase finished, otherwise this behaviour can damage the reputation of this project.
Accumulate total funding with a variable rather than checking balance
I checked FSD.sol
and the whitepaper https://fairside-network.gitbook.io/fairside-network/white-paper/augmented-bonding-curve
. According to whitepaper, during hatch phase, funding pool should be funded till 500 ETH, but on code it is 2000 ETH.
Manual
#0 - YunChe404
2021-11-14T17:05:35Z
There have been some last minute changes (that I guess never made it in the whitepaper). The tokens in hatch phase will be directly vested, so the user will not be able to liquefy before the end of that phase.
#1 - pauliax
2021-11-19T12:35:24Z
As per the sponsor's comment, the funds are vested and can't be withdrawn before the phase ends, but because the warden also identified discrepancies between the code and the whitepaper, and a direct check of the pool balance (see #96), I think it also deserves a severity of low.
#2 - pauliax
2021-11-19T12:41:32Z
A duplicate of #96
🌟 Selected for report: 0x0x0x
0.0155 ETH - $73.58
0x0x0x
Gas optimization
The function first checks _reserveDelta < 0
for calculations and after that we apply the same check for return statement. Return statements can directly written inside if-else statement. Furthermore, it will produce more readable code.
Manual
Current code:
if (_reserveDelta < 0) { uint256 capitalPostWithdrawal = capitalPool.sub( _abs(_reserveDelta) ); require( capitalPostWithdrawal >= fShare, "ABC::_calculateDeltaOfFSD: Insufficient Capital to Withdraw" ); nextSupply = FairSideFormula.g(capitalPostWithdrawal, fShare); } else { nextSupply = FairSideFormula.g( capitalPool.add(uint256(_reserveDelta)), fShare ); } return _reserveDelta < 0 ? currentSupply.sub(nextSupply) : nextSupply.sub(currentSupply);
Recommended code:
if (_reserveDelta < 0) { uint256 capitalPostWithdrawal = capitalPool.sub( _abs(_reserveDelta) ); require( capitalPostWithdrawal >= fShare, "ABC::_calculateDeltaOfFSD: Insufficient Capital to Withdraw" ); nextSupply = FairSideFormula.g(capitalPostWithdrawal, fShare); return currentSupply.sub(nextSupply); } else { nextSupply = FairSideFormula.g( capitalPool.add(uint256(_reserveDelta)), fShare ); return nextSupply.sub(currentSupply); }
#0 - pauliax
2021-11-16T23:33:57Z
Valid optimization.
🌟 Selected for report: 0x0x0x
0.0155 ETH - $73.58
0x0x0x
Gas optimization
When forceOnchain
, we can ensure the same logic by using less require statements. Since when its forceOnchain
, we check whether the length is 1, we do not need the require statements before it. We can have those checks in an else part to save gas.
The code block L396-L418:
require( targets.length != 0, "FairSideDAO::propose: must provide actions" ); require( targets.length <= proposalMaxOperations(), "FairSideDAO::propose: too many actions" ); if (forceOnchain) { require( targets.length == 1, "FairSideDAO::propose: only a single action is permitted for a forced onchain vote" ); bytes32 signatureHash = keccak256(abi.encode(signatures[0])); require( (targets[0] == address(this) && signatureHash == DISABLE_OFFCHAIN_HASH) || (targets[0] == address(FSD) && signatureHash == ADJUST_THRESHOLD_HASH), "FairSideDAO::propose: only adjustment of governance threshold or revocation of offchain process can be forced onchain" ); }
Can be replaced with:
if (forceOnchain) { require( targets.length == 1, "FairSideDAO::propose: only a single action is permitted for a forced onchain vote" ); bytes32 signatureHash = keccak256(abi.encode(signatures[0])); require( (target## Recommended Mitigation Stepss[0] == address(this) && signatureHash == DISABLE_OFFCHAIN_HASH) || (targets[0] == address(FSD) && signatureHash == ADJUST_THRESHOLD_HASH), "FairSideDAO::propose: only adjustment of governance threshold or revocation of offchain process can be forced onchain" ); } else { require( targets.length != 0, "FairSideDAO::propose: must provide actions" ); require( targets.length <= proposalMaxOperations(), "FairSideDAO::propose: too many actions" ); }
Manual analysis
#0 - pauliax
2021-11-16T23:37:48Z
Valid optimization.
🌟 Selected for report: 0x0x0x
0.0155 ETH - $73.58
0x0x0x
!= 0
costs less gass compared to > 0
for unsigned integer
contracts/dao/FairSideDAO.sol:306: proposalCount >= proposalId && proposalId > 0, contracts/dependencies/ERC20ConvictionScore.sol:129: upper = center > 0 ? center - 1 : 0; contracts/dependencies/ERC20ConvictionScore.sol:176: if (locked > 0) { contracts/dependencies/ERC20ConvictionScore.sol:280: if (checkpointCount > 0) { contracts/dependencies/ERC20ConvictionScore.sol:308: if (nCheckpoints > 0 && checkpoint.fromBlock == blockNumber) { contracts/dependencies/Withdrawable.sol:58: require(reserveAmount > 0, "FSD::withdraw: Insufficient Withdrawal"); contracts/network/FSDNetwork.sol:230: costShareBenefit % 10 ether == 0 && costShareBenefit > 0, contracts/token/FSD.sol:174: require(bonded > 0, "FSD::mintHatch: Insufficient Deposit"); contracts/token/FSDVesting.sol:127: tokenClaim > 0,
Change > 0
with != 0
.
#0 - YunChe404
2021-11-14T17:03:33Z
This statement is true only in view
and pure
functions and is otherwise optimized by the compiler. Please remove all but the aforementioned exhibits listed here
#1 - pauliax
2021-11-16T23:12:14Z
As mentioned by the sponsor, this improvement is partially valid. In my opinion, the gas savings are not that significant to force the change.
🌟 Selected for report: 0x0x0x
0.0155 ETH - $73.58
0x0x0x
Gas optimization
contracts/token/FSD.sol:174: require(bonded > 0, "FSD::mintHatch: Insufficient Deposit");
On L170, we check whether bonded
is bigger than 5 ETH. After multiplying with HATCH_CURVE_RATIO
, it is still over 0. Therefore, it is a tautology and not needed.
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
Manual analysis
#0 - pauliax
2021-11-16T23:08:13Z
Valid optimization.