Platform: Code4rena
Start Date: 03/05/2022
Pot Size: $75,000 USDC
Total HM: 6
Participants: 55
Period: 7 days
Judge: Albert Chon
Total Solo HM: 2
Id: 116
League: COSMOS
Rank: 33/55
Findings: 2
Award: $181.53
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: IllIllI
Also found by: 0x1337, 0x1f8b, 0xDjango, 0xkatana, AmitN, CertoraInc, Dravee, Funen, GermanKuber, GimelSec, Hawkeye, JC, MaratCerby, WatchPug, Waze, broccolirob, cccz, ch13fd357r0y3r, cryptphi, danb, defsec, delfin454000, dipp, dirk_y, ellahi, gzeon, hake, hubble, ilan, jah, jayjonah8, kebabsec, kirk-baird, m9800, orion, oyc_109, robee, shenwilly, simon135, sorrynotsorry
115.3865 USDC - $115.39
cumulativePower
does not use safe addition and can overflowIn both Gravity.sol#L659 and Gravity.sol#L233, _currentPowers[i]
is summed using the +
operator and not the safe .add()
:
cumulativePower = cumulativePower + _currentPowers[i]
As a result, cumulativePower could overflow and require(cumulativePower > _powerThreshold);
would fail.
Recommendation
use .add()
for safe addition, or make sure to check every iteration that if
max_int - _currentPowers[i] < cumulativePower
then
cumulativePower = max_int
#0 - V-Staykov
2022-05-10T15:00:41Z
L01 cumulativePower does not use safe addition and can overflow - on the cosmos side the comulative total power will never pass 10B, so we don't fear overflow here.
🌟 Selected for report: GermanKuber
Also found by: 0v3rf10w, 0x1f8b, 0xDjango, 0xNazgul, 0xf15ers, 0xkatana, AlleyCat, CertoraInc, Dravee, Funen, GimelSec, IllIllI, JC, MaratCerby, WatchPug, Waze, defsec, delfin454000, ellahi, gzeon, hake, hansfriese, ilan, jonatascm, nahnah, oyc_109, peritoflores, rfa, robee, simon135, slywaters, sorrynotsorry
66.1426 USDC - $66.14
Some variables could be set immutable to save gas, in Gravity.sol
// These are set once at initialization bytes32 public state_gravityId; uint256 public state_powerThreshold; CudosAccessControls public cudosAccessControls;
should be immutable:
// These are set once at initialization bytes32 public immutable state_gravityId; uint256 public immutable state_powerThreshold; CudosAccessControls public immutable cudosAccessControls;
require
checks in checkValidatorSignatures
Gravity.sol#L231-L258
In the loop, if cumulativePower > _powerThreshold
then the loop will break and require the exact same thing. If this if
statement was not satisfied until the end of the loop, we know we haven't passed the threshold.
I suggest this:
uint256 cumulativePower; bool passedThreshold; for (uint256 i; i < _currentValidators.length; ++i) { // If v is set to 0, this signifies that it was not possible to get a signature from this validator and we skip evaluation // (In a valid signature, it is either 27 or 28) if (_v[i] != 0) { // Check that the current validator has signed off on the hash require( verifySig(_currentValidators[i], _theHash, _v[i], _r[i], _s[i]), "Validator signature does not match." ); // Sum up cumulative power cumulativePower = cumulativePower + _currentPowers[i]; // Break early to avoid wasting gas if (cumulativePower > _powerThreshold) { passedThreshold = true; break; } } } // Check that there was enough power require( passedThreshold, "Submitted validator set signatures do not have enough power." ); // Success }
To take it a step further, we can just return;
once cumulativePower > _powerThreshold
and call require(false)
after the loop. However this is less readable so I'll leave the choice to you.
Do the same for Gravity.sol#L657-L670
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 uses one number in memory.
Example:
for (uint256 i = 0; i < _users.length; i++) -> for (uint256 i = 0; i < _users.length; ++i)
Gravity.sol#L128
Use calldata instead of memory for external functions where the function argument is read-only. Reading directly from calldata using calldataload instead of going via memory saves the gas from the intermediate memory operations that carry the values.
If a variable is not initialized it is automatically set to 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.
Example:
uint256 i = 0 -> uint256 i
Gravity.sol#L128