Platform: Code4rena
Start Date: 23/06/2023
Pot Size: $60,500 USDC
Total HM: 31
Participants: 132
Period: 10 days
Judge: 0xean
Total Solo HM: 10
Id: 254
League: ETH
Rank: 75/132
Findings: 1
Award: $80.43
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: JCN
Also found by: 0xAnah, DavidGiladi, MohammedRizwan, Rageur, Raihan, ReyAdmirado, Rolezn, SAAJ, SAQ, SM3_SS, Sathish9098, ayo_dev, dharma09, fatherOfBlocks, hunter_w3b, mgf15, mrudenko, naman1778, shamsulhaq123, souilos, turvy_fuzz
80.434 USDC - $80.43
The following files were imported but were not used. These files would costs gas during deployment and is a bad coding practice .
Short-circuiting is a solidity contract development model that uses OR/AND logic to sequence different cost operations. It puts low gas cost operations in the front and high gas cost operations in the back, so that if the front is low If the cost operation is feasible, you can skip (short-circuit) the subsequent high-cost Ethereum virtual machine operation.
//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)
The following instances could be restructured to use short-circuit mode
Checks that involve constants should come before checks that involve state variables, function calls, and calculations. In scenarios where this cheap checks would fail the function is able to revert before wasting alot of gas in a function that may ultimately revert in the unhappy case.
consider the following scenario:
function mint(address onBehalfOf, uint256 amount) external virtual { require(onBehalfOf != address(0), "TZA"); require(amount > 0, "ZA"); _mintPeUSD(msg.sender, onBehalfOf, amount, getAssetPrice()); }
The require(amount > 0) statement cost lesser gas than the require(onBehalfOf != address(0)) so in scenarios where the require(amount > 0) would fail the function would consume more a lot more gas than if it were re-arranged. so the above code snippet could be re-written as:
function mint(address onBehalfOf, uint256 amount) external virtual { require(amount > 0, "ZA"); require(onBehalfOf != address(0), "TZA"); _mintPeUSD(msg.sender, onBehalfOf, amount, getAssetPrice()); }
(7 Instances)
In the scenarios below storage variables are read in a conditional statement. Depending on if the conditional statement results to a true or false the state variable could be re-read from storage thereby causing a Gwarmaccess which cost 100 gas. You should consider caching the storage variable before the conditionals this replaces each Gwarmaccess (100 gas) with a much cheaper stack read.
if the condition results to true the storage value time2fullRedemption[msg.sender] would be read twice.
if (time2fullRedemption[msg.sender] > block.timestamp) { total += unstakeRatio[msg.sender] * (time2fullRedemption[msg.sender] - block.timestamp); }
This can be done instead
uint time2fullR = time2fullRedemption[msg.sender]; if (time2fullR > block.timestamp) { total += unstakeRatio[msg.sender] * (time2fullR - block.timestamp); }
storage variable constant whose values do not change should be constant
Using function calls to access a global variables is more expensive than using the actual variable use msg.sender instead of calling _msgSender().
consider the code below:
contract myERC20 is ERC20{ mapping(address => uint256) private _balances; mapping(address => mapping(address => uint256)) private _allowances; uint256 private _totalSupply = 1000; string private _name; string private _symbol; constructor() ERC20("MyERC20", "MyErc"){ _mint(msg.sender, 200); } function transfer2(address to, uint256 amount) public virtual returns (bool) { address owner = msg.sender; // msg.sender _transfer(owner, to, amount); return true; } function warmStorage() public{ transfer(0xfa458421F9E92B677D1724e2A0F0bF378940333e, 2); } }
the test script:
const {ethers} = require('hardhat'); const assert = require('assert'); describe("Gas Tests", async function() { it("Comparing Transfer functions", async function(){ const myErcFactory = await ethers.getContractFactory("myERC20"); const myErc20 = await myErcFactory.deploy(); await myErc20.warmStorage(); await myErc20.transfer2("0xfa458421F9E92B677D1724e2A0F0bF378940333e", 10); await myErc20.transfer("0xfa458421F9E92B677D1724e2A0F0bF378940333e", 10); //inherited transfer function uses _msgSender() }) })
the test result:
·············|···············|··············|·············|·············|···············|·············· | Contract · Method · Min · Max · Avg · # calls · usd (avg) │ ·············|···············|··············|·············|·············|···············|·············· | myERC20 · transfer · - · - · 35017 · 1 · - │ ·············|···············|··············|·············|·············|···············|·············· | myERC20 · transfer2 · - · - · 34983 · 1 · - │ ·············|···············|··············|·············|·············|···············|·············· | myERC20 · warmStorage · - · - · 51027 · 1 · - │ ·············|···············|··············|·············|·············|···············|·············· | Deployments · · % of limit · │ ·····························|··············|·············|·············|···············|·············· | myERC20 · - · - · 1225730 · 4.1 % · - │ ·----------------------------|--------------|-------------|-------------|---------------|-------------·
transfer() = 35017; transfer2() = 34983 difference = 34 gas units
total gas saved = 34 * 16 = 544 gas units
#0 - c4-pre-sort
2023-07-27T21:21:28Z
JeffCX marked the issue as high quality report
#1 - c4-judge
2023-07-27T23:42:15Z
0xean marked the issue as grade-a
#2 - c4-sponsor
2023-07-29T09:18:16Z
LybraFinance marked the issue as sponsor acknowledged