Platform: Code4rena
Start Date: 12/04/2023
Pot Size: $60,500 USDC
Total HM: 21
Participants: 199
Period: 7 days
Judge: hansfriese
Total Solo HM: 5
Id: 231
League: ETH
Rank: 87/199
Findings: 2
Award: $43.63
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: juancito
Also found by: 0xAgro, 0xNorman, 0xSmartContract, 0xStalin, 0xTheC0der, 0xWaitress, 0xhacksmithh, 0xnev, 3dgeville, 8olidity, Arz, Aymen0909, BGSecurity, BRONZEDISC, Bauchibred, Bauer, BenRai, ChainHunters, ChrisTina, CodeFoxInc, DedOhWale, DishWasher, EloiManuel, IceBear, Inspex, Jorgect, Kaysoft, LeoGold, LewisBroadhurst, Madalad, MiloTruck, MohammedRizwan, Nyx, Polaris_tow, RaymondFam, SaharDevep, SanketKogekar, Sathish9098, SolidityATL, Udsen, W0RR1O, aria, ayden, berlin-101, bin2chen, catellatech, codeslide, crc32, decade, descharre, evmboi32, eyexploit, fatherOfBlocks, georgits, giovannidisiena, joestakey, karanctf, kodyvim, ltyu, lukris02, m9800, matrix_0wl, mov, mrpathfindr, nadin, niser93, p0wd3r, parlayan_yildizlar_takimi, pavankv, pontifex, qpzm, ravikiranweb3, rbserver, santipu_, shealtielanz, slvDev, tnevler, wonjun, xmxanuel, yixxas
22.6007 USDC - $22.60
error NotQualified();
NotQualified() is not used.
function _power3(uint256 _x) internal pure returns(uint256) { return _mulD18(_mulD18(_x, _x), _x); }
Consider to rename _power3 to _power3D18, because it doesn't return power3 of _x, it returns
((_x)**3)/(ONE_DEC18**2)
uint256 powX3 = _mulD18(_mulD18(x, x), x);
Use defined function _power3:
uint256 powX3 = _mulD18(_mulD18(x, x), x);
becames
uint256 powX3 = _power3(x);
function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public { require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); unchecked { // unchecked to save a little gas with the nonce increment... address recoveredAddress = ecrecover( keccak256( abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR(), keccak256( abi.encode( // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), bytes32(0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9), owner, spender, value, nonces[owner]++, deadline ) ) ) ), v, r, s ); require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); _approve(recoveredAddress, spender, value); } }
Use different require check in order to avoid recoveredAddress computation when onwer==address(0)
recoveredAddress != address(0) && recoveredAddress == owner
is same of
owner != address(0) && recoveredAddress == owner
because it's an AND of two sentences, so if it's true, recoveredAddress == owner and so we can write "owner" instead of "recoveredAddress" in first sentence
As discussed in CodingNameKiki/AutomatedFindings.md - [GAS‑19] Splitting require() statements that use && saves gas, it's gas safe splitting require statement.
So we obtain:
require(owner != address(0)); require(recoveredAddress == owner);
I don't know if there is real possibility that owner == address(0).
If it can happen, it's better if you check owner address before than compute recoveredAddress.
function permit() becames:
function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public { require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); require(owner != address(0), "INVALID_SIGNER"); unchecked { // unchecked to save a little gas with the nonce increment... address recoveredAddress = ecrecover( keccak256( abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR(), keccak256( abi.encode( // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), bytes32(0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9), owner, spender, value, nonces[owner]++, deadline ) ) ) ), v, r, s ); require(recoveredAddress == owner, "INVALID_SIGNER"); _approve(recoveredAddress, spender, value); } }
function approve(address spender, uint256 value) external override returns (bool) { _approve(msg.sender, spender, value); return true; }
Function approve(address spender, uint256 value) returns only true.
approve(address spender, uint256 value) calls only _approve(address owner, address spender, uint256 value)
/** * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens. * * This is internal function is equivalent to `approve`, and can be used to * e.g. set automatic allowances for certain subsystems, etc. * * Emits an `Approval` event. * * Requirements: * * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. */ function _approve(address owner, address spender, uint256 value) internal { _allowances[owner][spender] = value; emit Approval(owner, spender, value); }
There are requirements, but there aren't checks of those requirements inside _approve(). When you call _approve() in other contracts (like in ERC20PermitLight.sol#L57), you check those requirements outside, but it could be better if you put require() inside _approve().
Anyway, as you wrote, approve() returns always true and you could think to remove return value.
function approve(address spender, uint256 value) external override returns (bool) { _approve(msg.sender, spender, value); return true; }
becames
function approve(address spender, uint256 value) external{ _approve(msg.sender, spender, value); return true; }
or, better, in order to follow ERC20 standard,
function approve(address spender, uint256 value) external override returns (bool) { if(msg.sender == address(0) || spender == address(0)){ return false; } _approve(msg.sender, spender, value); return true; }
#0 - 0xA5DF
2023-04-27T09:24:34Z
ALL NCs
#1 - c4-judge
2023-05-16T16:41:20Z
hansfriese marked the issue as grade-b
🌟 Selected for report: c3phas
Also found by: 0xDACA, 0xRB, 0xSmartContract, 0xhacksmithh, 0xnev, Aymen0909, BenRai, Breeje, DishWasher, Erko, EvanW, JCN, MohammedRizwan, NoamYakov, Polaris_tow, Proxy, Rageur, Raihan, RaymondFam, ReyAdmirado, SAAJ, Sathish9098, Satyam_Sharma, Udsen, __141345__, aria, codeslide, decade, fatherOfBlocks, hunter_w3b, karanctf, matrix_0wl, nadin, naman1778, niser93, pavankv, petrichor, pfapostol, sebghatullah, slvDev, trysam2003, xmxanuel
21.0255 USDC - $21.03
return (challenge.bid * 1005) / 1000;
Another way to increase a value X by 5‰ is:
X * 1.005 is equal to X * 1005 / 1000 is equal to X * (1000/1000) + X * (5/1000) is equal to X + X/200
However, working with uint, we must be care about rounding.
Let call F, float result. It is in this form: a.b, where
We can have 3 cases:
So, due to the fact that in both formulas we apply only one division operation, we can show equality of formulas using 3 example for this 3 cases:
Case 1 (0 < b < 0.5):
challenge.bid = 1037 (challenge.bid * 1005) / 1000 = 1.042,185 return 1.042 challenge.bid + challenge.bid/200 = 1037 + 5,185 = 1037 + 5 return 1.042
Case 2 (b == 0.5):
challenge.bid = 1100 (challenge.bid * 1005) / 1000 = 1.105,5 return 1.105 challenge.bid + challenge.bid/200 = 1100 + 5,5 = 1100 + 5 return 1.105
Case 3 (b > 0.5):
challenge.bid = 1111 (challenge.bid * 1005) / 1000 = 1.116,555 return 1.116 challenge.bid + challenge.bid/200 = 1111 + 5,555 = 1111 + 5 return 1.116
Using Remix IDE with optimization at 10k runs.
contract attempt{ function tryGas(uint256 ch) public pure returns(uint256){ return (ch * 1005) / 1000; } } Execution cost: 409
contract attempt{ function tryGas(uint256 ch) public pure returns(uint256){ return (ch + (ch/200)); } } Execution cost: 387
if(_coll < minimumCollateral) revert InsufficientCollateral();
if (price > _price) revert InsufficientCollateral();
if (block.timestamp >= start) revert TooLate();
if (minted + amount > limit) revert LimitExceeded();
if (amount > minted) revert RepaidTooMuch(amount - minted);
In this case, RepaidTooMuch is used:
error RepaidTooMuch(uint256 excess);
However, uint256 excess is not used.
if (collateralReserve * atPrice < minted * ONE_DEC18) revert InsufficientCollateral();
if (size < minimumCollateral && size < collateralBalance()) revert ChallengeTooSmall();
error Expired(); modifier alive() { if (block.timestamp > expiration) revert Expired(); _; } error Hot(); modifier noCooldown() { if (block.timestamp <= cooldown) revert Hot(); _; } error Challenged(); modifier noChallenge() { if (challengedAmount > 0) revert Challenged(); _; } error NotHub(); modifier onlyHub() { if (msg.sender != address(hub)) revert NotHub(); _; }
In which error are defined after their usage.
if (block.timestamp >= challenge.end) revert TooLate();
if (expectedSize != challenge.size) revert UnexpectedSize();
if (_bidAmountZCHF < minBid(challenge)) revert BidTooLow(_bidAmountZCHF, minBid(challenge));
In this case, BidTooLow is used:
error BidTooLow(uint256 bid, uint256 min);
However, uint256 bid and uin256 min are not used (and bid has same name of a function).
In which "error" are defined after their usage.
if (_votes * 10000 < QUORUM * totalVotes()) revert NotQualified();
In which "error" are defined after their usage.
if (_applicationPeriod < MIN_APPLICATION_PERIOD && totalSupply() > 0) revert PeriodTooShort();
if (_applicationFee < MIN_FEE && totalSupply() > 0) revert FeeTooLow();
if (minters[_minter] != 0) revert AlreadyRegistered();
if (!isMinter(msg.sender)) revert NotMinter();
if (block.timestamp > minters[_minter]) revert TooLate();
if (!isMinter(msg.sender) && !isMinter(positions[msg.sender])) revert NotMinter();
Using
require(!condition)
saves gas with respect using
if(condition) revert error();
Take care that
require(!condition, "error description")
cost more gas. So you could save gas only in case you don't need error description.
Using Remix IDE with optimization at 10k runs.
contract attempt{ error InsufficientCollateral(); function tryGas(uint256 _coll, uint256 minimumCollateral) public pure { if(_coll < minimumCollateral) revert InsufficientCollateral(); } } Execution cost: 283
contract attempt{ error InsufficientCollateral(); function tryGas(uint256 _coll, uint256 minimumCollateral) public pure { require(_coll >= minimumCollateral); } } Execution cost: 244
function adjustRecipientVoteAnchor(address to, uint256 amount) internal returns (uint256){ if (to != address(0x0)) { uint256 recipientVotes = votes(to); // for example 21 if 7 shares were held for 3 blocks uint256 newbalance = balanceOf(to) + amount; // for example 11 if 4 shares are added voteAnchor[to] = uint64(anchorTime() - recipientVotes / newbalance); // new example anchor is only 21 / 11 = 1 block in the past return recipientVotes % newbalance; // we have lost 21 % 11 = 10 votes } else { // optimization for burn, vote anchor of null address does not matter return 0; } }
Using == instead of != saves gas.
if (to != address(0x0)) { uint256 recipientVotes = votes(to); // for example 21 if 7 shares were held for 3 blocks uint256 newbalance = balanceOf(to) + amount; // for example 11 if 4 shares are added voteAnchor[to] = uint64(anchorTime() - recipientVotes / newbalance); // new example anchor is only 21 / 11 = 1 block in the past return recipientVotes % newbalance; // we have lost 21 % 11 = 10 votes } else { // optimization for burn, vote anchor of null address does not matter return 0; }
becames
if (to == address(0x0)) { // optimization for burn, vote anchor of null address does not matter return 0; } else { uint256 recipientVotes = votes(to); // for example 21 if 7 shares were held for 3 blocks uint256 newbalance = balanceOf(to) + amount; // for example 11 if 4 shares are added voteAnchor[to] = uint64(anchorTime() - recipientVotes / newbalance); // new example anchor is only 21 / 11 = 1 block in the past return recipientVotes % newbalance; // we have lost 21 % 11 = 10 votes }
You could also avoid return 0.
I sent other gas optimization report for that.
Using Remix IDE with optimization at 10k runs.
contract attempt{ function tryGas(address to) public pure returns(uint256){ if (to != address(0x0)) { return 1; } else { return 0; } } } Execution cost: 319 (with "address to" non-zero address) Execution cost: 320 (with "address to" == 0x0)
contract attempt{ function tryGas(address to) public pure returns(uint256){ if (to == address(0x0)) { return 0; } else { return 1; } } } Execution cost: 317 (with "address to" non-zero address) Execution cost: 316 (with "address to" == 0x0)
function allowanceInternal(address owner, address spender) internal view override returns (uint256) { uint256 explicit = super.allowanceInternal(owner, spender); if (explicit > 0){ return explicit; // don't waste gas checking minter } else if (isMinter(spender) || isMinter(isPosition(spender))){ return INFINITY; } else { return 0; } }
function equity() public view returns (uint256) { uint256 balance = balanceOf(address(reserve)); uint256 minReserve = minterReserve(); if (balance <= minReserve){ return 0; } else { return balance - minReserve; } }
function adjustRecipientVoteAnchor(address to, uint256 amount) internal returns (uint256){ if (to != address(0x0)) { uint256 recipientVotes = votes(to); // for example 21 if 7 shares were held for 3 blocks uint256 newbalance = balanceOf(to) + amount; // for example 11 if 4 shares are added voteAnchor[to] = uint64(anchorTime() - recipientVotes / newbalance); // new example anchor is only 21 / 11 = 1 block in the past return recipientVotes % newbalance; // we have lost 21 % 11 = 10 votes } else { // optimization for burn, vote anchor of null address does not matter return 0; } }
"returns (uint256)" will return 0, if no other return is triggered.
function allowanceInternal(address owner, address spender) internal view override returns (uint256) { uint256 explicit = super.allowanceInternal(owner, spender); if (explicit > 0){ return explicit; // don't waste gas checking minter } else if (isMinter(spender) || isMinter(isPosition(spender))){ return INFINITY; } else { return 0; } }
becames
function allowanceInternal(address owner, address spender) internal view override returns (uint256) { uint256 explicit = super.allowanceInternal(owner, spender); if (explicit > 0){ return explicit; // don't waste gas checking minter } else if (isMinter(spender) || isMinter(isPosition(spender))){ return INFINITY; } }
function equity() public view returns (uint256) { uint256 balance = balanceOf(address(reserve)); uint256 minReserve = minterReserve(); if (balance <= minReserve){ return 0; } else { return balance - minReserve; } }
becames
function equity() public view returns (uint256) { uint256 balance = balanceOf(address(reserve)); uint256 minReserve = minterReserve(); if(balance >= minReserve) return balance - minReserve; }
function adjustRecipientVoteAnchor(address to, uint256 amount) internal returns (uint256){ if (to != address(0x0)) { uint256 recipientVotes = votes(to); // for example 21 if 7 shares were held for 3 blocks uint256 newbalance = balanceOf(to) + amount; // for example 11 if 4 shares are added voteAnchor[to] = uint64(anchorTime() - recipientVotes / newbalance); // new example anchor is only 21 / 11 = 1 block in the past return recipientVotes % newbalance; // we have lost 21 % 11 = 10 votes } else { // optimization for burn, vote anchor of null address does not matter return 0; } }
becames
function adjustRecipientVoteAnchor(address to, uint256 amount) internal returns (uint256){ if (to != address(0x0)) { uint256 recipientVotes = votes(to); // for example 21 if 7 shares were held for 3 blocks uint256 newbalance = balanceOf(to) + amount; // for example 11 if 4 shares are added voteAnchor[to] = uint64(anchorTime() - recipientVotes / newbalance); // new example anchor is only 21 / 11 = 1 block in the past return recipientVotes % newbalance; // we have lost 21 % 11 = 10 votes } }
Using Remix IDE with optimization at 10k runs.
contract attempt{ function tryGas(address to) public pure returns(uint256){ if (to != address(0x0)) { return 1; } else { return 0; } } } Execution cost: 319 (with "address to" non-zero address) Execution cost: 320 (with "address to" == 0x0)
contract attempt{ function tryGas(address to) public pure returns(uint256){ if (to != address(0x0)) { return 1; } } } Execution cost: 319 (with "address to" non-zero address) Execution cost: 315 (with "address to" == 0x0)
function setOwner(address newOwner) internal { address oldOwner = owner; owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); }
Modify OwnershipTransferred event in OwnershipTransferredTo event, which only indicated newOwner. Then, avoid to create placeholder variable oldOwner in order to save gas
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); [...] function setOwner(address newOwner) internal { address oldOwner = owner; owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); }
becames
event OwnershipTransferredTo(address indexed newOwner); [...] function setOwner(address newOwner) internal { emit OwnershipTransferredTo(owner = newOwner); }
Using Remix IDE with optimization at 10k runs.
pragma solidity ^0.8.0; contract Attempt{ error NotOwner(); event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); address public owner; constructor(address _owner){ owner = _owner; } function transferOwnership(address newOwner) public onlyOwner { setOwner(newOwner); } function setOwner(address newOwner) internal { address oldOwner = owner; owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); } function requireOwner(address sender) internal view { if (owner != sender) revert NotOwner(); } modifier onlyOwner() { requireOwner(msg.sender); _; } } Execution cost of transferOwnership(): 4226 (if oldOwner==newOwner) Execution cost of transferOwnership(): 7026 (if oldOwner!=newOwner) Execution cost of transferOwnership(): 2445 (when it reverts)
pragma solidity ^0.8.0; contract Attempt{ error NotOwner(); event OwnershipTransferredTo(address indexed newOwner); address public owner; constructor(address _owner){ owner = _owner; } function transferOwnership(address newOwner) public onlyOwner { setOwner(newOwner); } function setOwner(address newOwner) internal { emit OwnershipTransferredTo(owner = newOwner); } function requireOwner(address sender) internal view { if (owner != sender) revert NotOwner(); } modifier onlyOwner() { requireOwner(msg.sender); _; } } Execution cost of transferOwnership(): 3828 (if oldOwner==newOwner) Execution cost of transferOwnership(): 6628 (if oldOwner!=newOwner) Execution cost of transferOwnership(): 2445 (when it reverts)
function _cubicRoot(uint256 _v) internal pure returns (uint256) { uint256 x = ONE_DEC18; uint256 xOld; bool cond; do { xOld = x; uint256 powX3 = _mulD18(_mulD18(x, x), x); x = _mulD18(x, _divD18( (powX3 + 2 * _v) , (2 * powX3 + _v))); cond = xOld > x ? xOld - x > THRESH_DEC18 : x - xOld > THRESH_DEC18; } while ( cond ); return x; }
Using right shift saves gas with respect multiplication by 2.
function _cubicRoot(uint256 _v) internal pure returns (uint256) { uint256 x = ONE_DEC18; uint256 xOld; bool cond; do { xOld = x; uint256 powX3 = _mulD18(_mulD18(x, x), x); x = _mulD18(x, _divD18( (powX3 + 2 * _v) , (2 * powX3 + _v))); cond = xOld > x ? xOld - x > THRESH_DEC18 : x - xOld > THRESH_DEC18; } while ( cond ); return x; }
becames
function _cubicRoot(uint256 _v) internal pure returns (uint256) { uint256 x = ONE_DEC18; uint256 xOld; bool cond; do { xOld = x; uint256 powX3 = _mulD18(_mulD18(x, x), x); x = _mulD18(x, _divD18( (powX3 + (_v << 1)) , ((powX3 << 1) + _v))); cond = xOld > x ? xOld - x > THRESH_DEC18 : x - xOld > THRESH_DEC18; } while ( cond ); return x; }
Using Remix IDE with optimization at 10k runs.
pragma solidity ^0.8.0; contract Attempt{ uint256 internal constant ONE_DEC18 = 10**18; uint256 internal constant THRESH_DEC18 = 10000000000000000;//0.01 function cubicRoot(uint256 _v) public returns (uint256){ return _cubicRoot(_v); } function _cubicRoot(uint256 _v) internal pure returns (uint256) { uint256 x = ONE_DEC18; uint256 xOld; bool cond; do { xOld = x; uint256 powX3 = _mulD18(_mulD18(x, x), x); x = _mulD18(x, _divD18( (powX3 + 2 * _v) , (2 * powX3 + _v))); cond = xOld > x ? xOld - x > THRESH_DEC18 : x - xOld > THRESH_DEC18; } while ( cond ); return x; } function _mulD18(uint256 _a, uint256 _b) internal pure returns(uint256) { return _a * _b / ONE_DEC18; } function _divD18(uint256 _a, uint256 _b) internal pure returns(uint256) { return (_a * ONE_DEC18) / _b ; } function _power3(uint256 _x) internal pure returns(uint256) { return _mulD18(_mulD18(_x, _x), _x); } } Execution cost of cubicRoot(3): 9196 (it return 7812500000010531) Execution cost of cubicRoot(27*10**18): 5429 (it return 2999999999997076112)
pragma solidity ^0.8.0; contract Attempt{ uint256 internal constant ONE_DEC18 = 10**18; uint256 internal constant THRESH_DEC18 = 10000000000000000;//0.01 function cubicRoot(uint256 _v) public returns (uint256){ return _cubicRoot(_v); } function _cubicRoot(uint256 _v) internal pure returns (uint256) { uint256 x = ONE_DEC18; uint256 xOld; bool cond; do { xOld = x; uint256 powX3 = _mulD18(_mulD18(x, x), x); x = _mulD18(x, _divD18( (powX3 + (_v << 1)) , ((powX3 << 1) + _v))); cond = xOld > x ? xOld - x > THRESH_DEC18 : x - xOld > THRESH_DEC18; } while ( cond ); return x; } function _mulD18(uint256 _a, uint256 _b) internal pure returns(uint256) { return _a * _b / ONE_DEC18; } function _divD18(uint256 _a, uint256 _b) internal pure returns(uint256) { return (_a * ONE_DEC18) / _b ; } function _power3(uint256 _x) internal pure returns(uint256) { return _mulD18(_mulD18(_x, _x), _x); } } Execution cost of cubicRoot(3): 8027 (it return 7812500000010531) Execution cost of cubicRoot(27*10**18):4761 (it return 2999999999997076112)
function _power3(uint256 _x) internal pure returns(uint256) { return _mulD18(_mulD18(_x, _x), _x); }
Consider to directly compute _power3 instead of call _mulD18 twice
function _power3(uint256 _x) internal pure returns(uint256) { return _mulD18(_mulD18(_x, _x), _x); }
becames
function _power3(uint256 _x) internal pure returns(uint256) { return _mulD18(_mulD18(_x, _x), _x); return _x ** 3 / ONE_DEC18 ** 2; }
_mulD18(_a, _b) = _a * _b / ONE_DEC18 _mulD18(_x, _x) = _x * _x / ONE_DEC18 = _x**2 / ONE_DEC18 _mulD18(_mulD18(_x, _x), _x) = (_x**2 / ONE_DEC18) * _x / ONE_DEC18 = _x**3 / ONE_DEC18**2 _power3(_x) = _x**3 / ONE_DEC18**2 or, better (multiplication cost less than power) _power3(_x) = _x*_x*_x / ONE_DEC18*ONE_DEC18
Using Remix IDE with optimization at 10k runs.
pragma solidity ^0.8.0; contract Attempt{ uint256 internal constant ONE_DEC18 = 10**18; uint256 internal constant THRESH_DEC18 = 10000000000000000;//0.01 function power3(uint256 _v) public returns (uint256){ return _power3(_v); } function _cubicRoot(uint256 _v) internal pure returns (uint256) { uint256 x = ONE_DEC18; uint256 xOld; bool cond; do { xOld = x; uint256 powX3 = _mulD18(_mulD18(x, x), x); x = _mulD18(x, _divD18( (powX3 + 2 * _v) , (2 * powX3 + _v))); cond = xOld > x ? xOld - x > THRESH_DEC18 : x - xOld > THRESH_DEC18; } while ( cond ); return x; } function _mulD18(uint256 _a, uint256 _b) internal pure returns(uint256) { return _a * _b / ONE_DEC18; } function _divD18(uint256 _a, uint256 _b) internal pure returns(uint256) { return (_a * ONE_DEC18) / _b ; } function _power3(uint256 _x) internal pure returns(uint256) { return _mulD18(_mulD18(_x, _x), _x); } } Execution cost of power3(3 * 10**18): 681 (it return 27 * 10**18)
pragma solidity ^0.8.0; contract Attempt{ uint256 internal constant ONE_DEC18 = 10**18; uint256 internal constant THRESH_DEC18 = 10000000000000000;//0.01 function power3(uint256 _v) public returns (uint256){ return _power3(_v); } function _cubicRoot(uint256 _v) internal pure returns (uint256) { uint256 x = ONE_DEC18; uint256 xOld; bool cond; do { xOld = x; uint256 powX3 = _mulD18(_mulD18(x, x), x); x = _mulD18(x, _divD18( (powX3 + 2 * _v) , (2 * powX3 + _v))); cond = xOld > x ? xOld - x > THRESH_DEC18 : x - xOld > THRESH_DEC18; } while ( cond ); return x; } function _mulD18(uint256 _a, uint256 _b) internal pure returns(uint256) { return _a * _b / ONE_DEC18; } function _divD18(uint256 _a, uint256 _b) internal pure returns(uint256) { return (_a * ONE_DEC18) / _b ; } function _power3(uint256 _x) internal pure returns(uint256) { return (_x*_x*_x)/(ONE_DEC18*ONE_DEC18); } } Execution cost of power3(3 * 10**18): 633 (it return 27 * 10**18)
function _cubicRoot(uint256 _v) internal pure returns (uint256) { uint256 x = ONE_DEC18; uint256 xOld; bool cond; do { xOld = x; uint256 powX3 = _mulD18(_mulD18(x, x), x); x = _mulD18(x, _divD18( (powX3 + 2 * _v) , (2 * powX3 + _v))); cond = xOld > x ? xOld - x > THRESH_DEC18 : x - xOld > THRESH_DEC18; } while ( cond ); return x; }
uint256 internal constant ONE_DEC18 = 10**18; [...] function _cubicRoot(uint256 _v) internal pure returns (uint256) { uint256 x = ONE_DEC18; uint256 xOld; bool cond; do { xOld = x; uint256 powX3 = _mulD18(_mulD18(x, x), x); x = _mulD18(x, _divD18( (powX3 + 2 * _v) , (2 * powX3 + _v))); cond = xOld > x ? xOld - x > THRESH_DEC18 : x - xOld > THRESH_DEC18; } while ( cond ); return x; }
becames
uint256 internal constant ONE_DEC18 = 10**18; uint256 internal constant ONE_DEC36 = 10**36; [...] function _cubicRoot(uint256 _v) internal pure returns (uint256) { uint256 x = ONE_DEC18; uint256 xOld; bool cond; do { xOld = x; x = x*(x*x*x + 2*ONE_DEC36*_v)/(2*x*x*x+ONE_DEC36*_v); cond = xOld > x ? xOld - x > THRESH_DEC18 : x - xOld > THRESH_DEC18; } while ( cond ); return x; }
_mulD18(_a, _b) = _a * _b / ONE_DEC18 _divD18(_a, _b) = _a * ONE_DEC18 / _b _power3(_x) = _x*_x*_x / ONE_DEC18*ONE_DEC18 = _x**3 / ONE_DEC36 (powX3 + 2 * _v) => (_x**3 / ONE_DEC36 + 2 * _v) (2 * powX3 + _v) => (2*_x**3 / ONE_DEC36 + _v) _divD18( (powX3 + 2 * _v) , (2 * powX3 + _v)) = = ONE_DEC18*(_x**3 / ONE_DEC36 + 2 * _v)/(2*_x**3 / ONE_DEC36 + _v) _mulD18(x, _divD18( (powX3 + 2 * _v) , (2 * powX3 + _v))) = = x*(ONE_DEC18*(_x**3 / ONE_DEC36 + 2 * _v)/(2*_x**3 / ONE_DEC36 + _v))/ONE_DEC18 = x*(_x**3 / ONE_DEC36 + 2 * _v)/(2*_x**3 / ONE_DEC36 + _v)) = = ... = = x*(x**3 + 2*ONE_DEC36*_v)/(2*x**3+ONE_DEC36*_v) (multiplication is gas safer than power) = x*(x*x*x + 2*ONE_DEC36*_v)/(2*x*x*x+ONE_DEC36*_v)
Due to rounding, result will be bit different (I don't know if you would it).
Example:
_v = 27**10*18 Original result: 2999999999997076112 (execution cost 5429) New result: 2999999999997076114 (execution cost 5077)
_v = 64**10*18 Original result: 3999999999999999999 (execution cost 6708) New result: 3999999999999999999 (execution cost 6268)
_v = 1345**10*18 Original result: 11038433054931015055 (execution cost 7987) New result: 11038433054931015063 (execution cost 7459)
It's important to check that _v is less than
Original: 10**58 New: about 27537**10*18
in order to avoid uint256 overflow
Execution cost computed by Remix IDE with optimization at 10k runs.
Original
pragma solidity ^0.8.0; contract Attempt{ uint256 internal constant ONE_DEC18 = 10**18; uint256 internal constant THRESH_DEC18 = 10000000000000000;//0.01 function cubicRoot(uint256 _v) public returns (uint256){ return _cubicRoot(_v); } function _cubicRoot(uint256 _v) internal pure returns (uint256) { uint256 x = ONE_DEC18; uint256 xOld; bool cond; do { xOld = x; uint256 powX3 = _mulD18(_mulD18(x, x), x); x = _mulD18(x, _divD18( (powX3 + 2 * _v) , (2 * powX3 + _v))); cond = xOld > x ? xOld - x > THRESH_DEC18 : x - xOld > THRESH_DEC18; } while ( cond ); return x; } function _mulD18(uint256 _a, uint256 _b) internal pure returns(uint256) { return _a * _b / ONE_DEC18; } function _divD18(uint256 _a, uint256 _b) internal pure returns(uint256) { return (_a * ONE_DEC18) / _b ; } function _power3(uint256 _x) internal pure returns(uint256) { return _mulD18(_mulD18(_x, _x), _x); } }
New
pragma solidity ^0.8.0; contract Attempt{ uint256 internal constant ONE_DEC18 = 10**18; uint256 internal constant ONE_DEC36 = 10**36; uint256 internal constant THRESH_DEC18 = 10000000000000000;//0.01 function cubicRoot(uint256 _v) public returns (uint256){ return _cubicRoot(_v); } function _cubicRoot(uint256 _v) internal pure returns (uint256) { uint256 x = ONE_DEC18; uint256 xOld; bool cond; do { xOld = x; x = x*(x*x*x + 2*ONE_DEC36*_v)/(2*x*x*x+ONE_DEC36*_v); cond = xOld > x ? xOld - x > THRESH_DEC18 : x - xOld > THRESH_DEC18; } while ( cond ); return x; } function _mulD18(uint256 _a, uint256 _b) internal pure returns(uint256) { return _a * _b / ONE_DEC18; } function _divD18(uint256 _a, uint256 _b) internal pure returns(uint256) { return (_a * ONE_DEC18) / _b ; } function _power3(uint256 _x) internal pure returns(uint256) { return _mulD18(_mulD18(_x, _x), _x); } }
function onTokenTransfer(address from, uint256 amount, bytes calldata) external returns (bool){ if (msg.sender == address(chf)){ mintInternal(from, amount); } else if (msg.sender == address(zchf)){ burnInternal(address(this), from, amount); } else { require(false, "unsupported token"); } return true; }
if/else if/else path could be different.
function onTokenTransfer(address from, uint256 amount, bytes calldata) external returns (bool){ if (msg.sender == address(chf)){ mintInternal(from, amount); } else if (msg.sender == address(zchf)){ burnInternal(address(this), from, amount); } else { require(false, "unsupported token"); } return true; }
becames
function onTokenTransfer(address from, uint256 amount) external returns (bool){ require(msg.sender == address(chf) || msg.sender == address(zchf), "unsupported token"); if (msg.sender == address(chf)){ mintInternal(from, amount); } else{ burnInternal(address(this), from, amount); } return true; }
if (condition1){action1;} else if (condition2){action2;} else {action3;}
is same of
if(!condition1 && !condition2){ action3; } else{ //condition1 = true || condition2 = true if (condition1){action1;} else{ //condition1 = false => condition2 = true action2; } }
is same of
if(!(condition1 || condition2)){ action3; } else{ if (condition1){action1;} else{action2;} }
is same of
require(condition1 || condition2, action3); if (condition1){action1;} else{action2;}
Used Remix IDE with optimization at 10k runs.
Original
pragma solidity ^0.8.0; contract Attempt{ function mintInternal(address target, uint256 amount) internal{ } function burnInternal(address zchfHolder, address target, uint256 amount) internal{ } function onTokenTransfer(address from, uint256 amount) external returns (bool){ if (msg.sender == address(chf)){ mintInternal(from, amount); } else if (msg.sender == address(zchf)){ burnInternal(address(this), from, amount); } else { require(false, "unsupported token"); } return true; } }
New
pragma solidity ^0.8.0; contract Attempt{ address chf = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; address zchf = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2; function mintInternal(address target, uint256 amount) internal{ } function burnInternal(address zchfHolder, address target, uint256 amount) internal{ } function onTokenTransfer(address from, uint256 amount) external returns (bool){ require(msg.sender == address(chf) || msg.sender == address(zchf), "unsupported token"); if (msg.sender == address(chf)){ mintInternal(from, amount); } else{ burnInternal(address(this), from, amount); } return true; } }
Result:
msg.sender == address(chf)
Original, execution cost: 2463 New, execution cost: 2468
msg.sender == address(zchf)
Original, execution cost: 4594 New, execution cost: 4584
msg.sender != address(chf) && msg.sender != address(zchf)
Original, execution cost: 4605 New, execution cost: 4607
saving an average of 3 gas.
// ERC-677 functionality, can be useful for swapping and wrapping tokens function transferAndCall(address recipient, uint256 amount, bytes calldata data) external override returns (bool) { bool success = transfer(recipient, amount); if (success){ success = IERC677Receiver(recipient).onTokenTransfer(msg.sender, amount, data); } return success; }
caused by
/** * @dev See `IERC20.transfer`. * * Requirements: * * - `recipient` cannot be the zero address. * - the caller must have a balance of at least `amount`. */ function transfer(address recipient, uint256 amount) public virtual override returns (bool) { _transfer(msg.sender, recipient, amount); return true; }
transfer(address recipient, uint256 amount) returns always true, or transaction will revert (due to checks inside _transfer). So, it's useless to check transfer return value.
// ERC-677 functionality, can be useful for swapping and wrapping tokens function transferAndCall(address recipient, uint256 amount, bytes calldata data) external override returns (bool) { bool success = transfer(recipient, amount); if (success){ success = IERC677Receiver(recipient).onTokenTransfer(msg.sender, amount, data); } return success; }
becames
// ERC-677 functionality, can be useful for swapping and wrapping tokens function transferAndCall(address recipient, uint256 amount, bytes calldata data) external override returns (bool) { transfer(recipient, amount); return IERC677Receiver(recipient).onTokenTransfer(msg.sender, amount, data); }
Used Remix IDE with optimization at 10k runs.
Original
pragma solidity ^0.8.0; contract Attempt{ function transfer(address recipient, uint256 amount) public returns(bool){ require(recipient != address(0)); return true; } function IERC677Receiver_onTokenTransfer(address recipient, uint256 amount) public returns(bool){ require(recipient != address(0)); return true; } function transferAndCall(address recipient, uint256 amount) external returns (bool) { bool success = transfer(recipient, amount); if (success){ success = IERC677Receiver_onTokenTransfer(recipient, amount); } return success; } } Execution cost of transferAndCall(): 495
New
pragma solidity ^0.8.0; contract Attempt{ function transfer(address recipient, uint256 amount) public returns(bool){ require(recipient != address(0)); return true; } function IERC677Receiver_onTokenTransfer(address recipient, uint256 amount) public returns(bool){ require(recipient != address(0)); return true; } function transferAndCall(address recipient, uint256 amount) external returns (bool) { transfer(recipient, amount); return IERC677Receiver_onTokenTransfer(recipient, amount); } } Execution cost of transferAndCall(): 464
function allowanceInternal(address owner, address spender) internal view override returns (uint256) { uint256 explicit = super.allowanceInternal(owner, spender); if (explicit > 0){ return explicit; // don't waste gas checking minter } else if (isMinter(spender) || isMinter(isPosition(spender))){ return INFINITY; } else { return 0; } }
You could rewrite if/elseif/else condition remembering that explicit is uint256 (unsigned), and so it is >=0.
function allowanceInternal(address owner, address spender) internal view override returns (uint256) { uint256 explicit = super.allowanceInternal(owner, spender); if (explicit > 0){ return explicit; // don't waste gas checking minter } else if (isMinter(spender) || isMinter(isPosition(spender))){ return INFINITY; } else { return 0; } }
becames
function allowanceInternal(address owner, address spender) internal view override returns (uint256) { uint256 explicit = super.allowanceInternal(owner, spender); if (explicit==0){ if (isMinter(spender)) return INFINITY; if (isMinter(isPosition(spender))) return INFINITY; } return explicit; }
We have 4 cases:
explicit == 0 && isMinter(spender) || isMinter(isPosition(spender)) In this case Original function return INFINITY In this case New function return INFINITY
explicit == 0 && !(isMinter(spender) || isMinter(isPosition(spender))) In this case Original function return 0 In this case New function return explicit (which is 0)
explicit > 0 && isMinter(spender) || isMinter(isPosition(spender)) In this case Original function return explicit In this case New function return explicit
explicit > 0 && !(isMinter(spender) || isMinter(isPosition(spender))) In this case Original function return explicit In this case New function return explicit
Used Remix IDE with optimization at 10k runs.
Original
pragma solidity ^0.8.0; contract Attempt{ uint256 public INFINITY = 100; function tryGas(uint256 explicit, bool isMinter_spender, bool isMinter_isPosition_spender) public returns(uint256){ if (explicit > 0){ return explicit; // don't waste gas checking minter } else if (isMinter_spender || isMinter_isPosition_spender){ return INFINITY; } } }
New
pragma solidity ^0.8.0; contract Attempt{ uint256 public INFINITY = 100; function tryGas(uint256 explicit, bool isMinter_spender, bool isMinter_isPosition_spender) public returns(uint256){ if (explicit == 0){ if(isMinter_spender) return INFINITY; if(isMinter_isPosition_spender) return INFINITY; } return explicit; } }
Original execution cost: 2610 New execution cost: 2606
Original execution cost: 510 New execution cost: 516
Original execution cost: 484 New execution cost: 477
Original execution cost: 484 New execution cost: 477
#0 - 0xA5DF
2023-04-27T13:41:59Z
G1,G2 aren't practical G6 in automated findings
#1 - c4-judge
2023-05-16T15:27:47Z
hansfriese marked the issue as grade-b