15,000 USDC
View results
Submission Details
Severity: gas
Valid

Summary for GAS findings

Number Details Instances
[G-01] Use calldata instead of memory for function parameters 1
[G-02] Setting the constructor to payable 2
[G-03] Functions guaranteed to revert when called by normal users can be marked payable 2
[G-04] Use hardcode address instead address(this) 2
[G-05] += costs more gas than = + for state variables 3
[G-06] Public Functions To External 2
[G-07] Using unchecked blocks to save gas 2
[G-08] Use functions instead of modifiers 1
[G-09] Save loop calls 1
[G-10] Use assembly to emit events 2
[G-11] IF’s/require() statements that check input arguments should be at the top of the function 1
[G-12] ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops 2
[G-13] ++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too) 2
[G-14] A modifier used only once and not being inherited should be inlined to save gas 1
[G-15] Use assembly for loops 2
[G-16] .length should not be looked up in every loop of a for-loop 2
[G-17] >= costs less gas than > 1
[G-18] Use assembly to check for address(0) 2
[G-19] Don’t initialize variables with default value 2
[G-20] Use solidity version 0,8,19 3
[G-21] Amounts should be checked for 0 before calling a transfer 3

[G-01] Use calldata instead of memory for function parameters

If a reference type function parameter is read-only, it is cheaper in gas to use calldata instead of memory. Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory.

file: main/src/DSCEngine.sol
405 function getCollateralTokens() external view returns (address[] memory) {

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DSCEngine.sol#L405

[G-02] Setting the constructor to payable

Saves ~13 gas per instance.

file:
44 constructor() ERC20("DecentralizedStableCoin", "DSC") {}

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DecentralizedStableCoin.sol#L44

file:
112 constructor(address[] memory tokenAddresses, address[] memory priceFeedAddresses, address dscAddress) {

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DSCEngine.sol#L112

[G-03] Functions guaranteed to revert when called by normal users can be marked payable

If a function modifier or require such as onlyOwner/onlyX is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2), DUP1(3), ISZERO(3), PUSH2(3), JUMPI(10), PUSH1(3), DUP1(3), REVERT(0), JUMPDEST(1), POP(2) which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost.

file: main/src/DecentralizedStableCoin.sol
46 function burn(uint256 _amount) public override onlyOwner {
57 function mint(address _to, uint256 _amount) external onlyOwner returns (bool) {

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DecentralizedStableCoin.sol#L46

[G-04] Use hardcode address instead address(this)

Instead of using address(this), it is more gas-efficient to pre-calculate and use the hardcoded address. Foundry’s script.sol and solmate’s LibRlp.sol contracts can help achieve this.

References:

https://book.getfoundry.sh/reference/forge-std/compute-create-address
https://twitter.com/transmissions11/status/1518507047943245824

file: main/src/DSCEngine.sol
157 bool success = IERC20(tokenCollateralAddress).transferFrom(msg.sender, address(this), amountCollateral);
274 bool success = i_dsc.transferFrom(dscFrom, address(this), amountDscToBurn);

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DSCEngine.sol#L157

[G-05] += costs more gas than = + for state variables

file: main/src/DSCEngine.sol
155 s_collateralDeposited[msg.sender][tokenCollateralAddress] += amountCollateral;
198 s_DSCMinted[msg.sender] += amountDscToMint;
356 totalCollateralValueInUsd += getUsdValue(token, amount);

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DSCEngine.sol#L155

[G-06] Public Functions To External

The following functions could be set external to save gas and improve code quality.
External call cost is less expensive than of public functions.

file: main/src/libraries/OracleLib.sol
21 function staleCheckLatestRoundData(AggregatorV3Interface priceFeed)
public
view
returns (uint80, int256, uint256, uint256, uint80)
{
35 function getTimeout(AggregatorV3Interface /* chainlinkFeed */ ) public pure returns (uint256) {

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/libraries/OracleLib.sol#L21-L25

[G-07] Using unchecked blocks to save gas

Solidity version 0.8+ comes with implicit overflow and underflow checks on unsigned integers. When an overflow or an underflow isn’t possible (as an example, when a comparison is made before the arithmetic operation), some gas can be saved by using an unchecked block.

file: main/src/DSCEngine.sol
252 uint256 totalCollateralToRedeem = tokenAmountFromDebtCovered + bonusCollateral;

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DSCEngine.sol#L252

file: main/src/libraries/OracleLib.sol
29 uint256 secondsSince = block.timestamp - updatedAt;

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/libraries/OracleLib.sol#L29

[G-08] Use functions instead of modifiers

Modifiers:
Modifiers are used to add pre or post-conditions to functions. They are typically applied using the modifier keyword and can be used to enforce access control, check conditions, or perform other checks before or after a function's execution. Modifiers are often used to reduce code duplication and enforce certain rules consistently across multiple functions.

However, using modifiers can lead to increased gas costs, especially when multiple function are applied to a single modifier. Each modifier adds overhead in terms of gas consumption because its code is effectively inserted into the function at the point of the modifier keyword. This results in additional gas usage for each modifier, which can accumulate if multiple modifiers are involved.

Functions:
Functions, on the other hand, are more efficient in terms of gas consumption. When you define a function, its code is written only once and can be called from multiple places within the contract. This reduces gas costs compared to using modifiers since the function's code is not duplicated every time it's called.

By using functions instead of modifiers, you can avoid the gas overhead associated with multiple modifiers and create more gas-efficient smart contracts. If you find that certain logic needs to be applied to multiple functions, you can write a separate function to encapsulate that logic and call it from the functions that require it. This way, you achieve code reusability without incurring unnecessary gas costs.

Let's illustrate the difference between using modifiers and functions with a simple example.

Using Modifiers:

pragma solidity ^0.8.0;
contract BankAccountUsingModifiers {
address public owner;
uint public balance;
modifier onlyOwner() {
require(msg.sender == owner, "Only the owner can perform this action.");
_;
}
constructor() {
owner = msg.sender;
}
function withdraw(uint amount) public onlyOwner {
require(amount <= balance, "Insufficient balance.");
balance -= amount;
// Perform withdrawal logic
}
function transfer(address recipient, uint amount) public onlyOwner {
require(amount <= balance, "Insufficient balance.");
balance -= amount;
// Perform transfer logic
}
}

Using Functions:

pragma solidity ^0.8.0;
contract BankAccountUsingFunctions {
address public owner;
uint public balance;
constructor() {
owner = msg.sender;
}
function onlyOwner() private view returns (bool) {
return msg.sender == owner;
}
function withdraw(uint amount) public {
require(onlyOwner(), "Only the owner can perform this action.");
require(amount <= balance, "Insufficient balance.");
balance -= amount;
// Perform withdrawal logic
}
function transfer(address recipient, uint amount) public {
require(onlyOwner(), "Only the owner can perform this action.");
require(amount <= balance, "Insufficient balance.");
balance -= amount;
// Perform transfer logic
}
}

In the first contract (BankAccountUsingModifiers), we used a modifier called onlyOwner to check if the caller is the owner of the bank account. The onlyOwner modifier is applied to both the withdraw and transfer functions to ensure that only the owner can perform these actions. However, using the modifier will result in additional gas costs since the modifier's code is duplicated in both functions.

In the second contract (BankAccountUsingFunctions), we replaced the modifier with a private function called onlyOwner, which checks if the caller is the owner. We then call this function at the beginning of both the withdraw and transfer functions. By doing this, we avoid the gas overhead associated with using

file: main/src/DSCEngine.sol
95 modifier moreThanZero(uint256 amount) {

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DSCEngine.sol#L95

[G-09] Save loop calls

Instead of calling tokenAddresses[i] 2 times in each loop for fetching data, it can be saved as a variable.

file: main/src/DSCEngine.sol
118 for (uint256 i = 0; i < tokenAddresses.length; i++) {
s_priceFeeds[tokenAddresses[i]] = priceFeedAddresses[i];
s_collateralTokens.push(tokenAddresses[i]);
}

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DSCEngine.sol#L118-L121

Recommended Mitigation Steps

for (uint256 i = 0; i < tokenAddresses.length; i++) {
+ uint256 _tokenAddresses = tokenAddresses[i];
- s_priceFeeds[tokenAddresses[i]] = priceFeedAddresses[i];
+ s_priceFeeds[_tokenAddresses] = priceFeedAddresses[i];
- s_collateralTokens.push(tokenAddresses[i]);
+ s_collateralTokens.push(_tokenAddresses);
}

[G-10] Use assembly to emit events

We can use assembly to emit events efficiently by utilizing scratch space and the free memory pointer. This will allow us to potentially avoid memory expansion costs.

file: main/src/DSCEngine.sol
156 emit CollateralDeposited(msg.sender, tokenCollateralAddress, amountCollateral);
286 emit CollateralRedeemed(from, to, tokenCollateralAddress, amountCollateral);

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DSCEngine.sol#L156

[G-11] IF’s/require() statements that check input arguments should be at the top of the function

Checks that involve constants should come before checks that involve state variables, function calls, and calculations. By doing these checks first, the function is able to revert before wasting a Gcoldsload (2100 gas) in a function that may ultimately revert in the unhappy case.

Cheaper to check the function parameter before assgining value to they balacne varible

file: main/src/DecentralizedStableCoin.sol
46 function burn(uint256 _amount) public override onlyOwner {
uint256 balance = balanceOf(msg.sender);
if (_amount <= 0) {
revert DecentralizedStableCoin__MustBeMoreThanZero();
}
if (balance < _amount) {
revert DecentralizedStableCoin__BurnAmountExceedsBalance();
}
super.burn(_amount);
}

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DecentralizedStableCoin.sol#L46-L55

Recommended Mitigation Steps write like this

function burn(uint256 _amount) public override onlyOwner {
if (_amount <= 0) {
revert DecentralizedStableCoin__MustBeMoreThanZero();
}
uint256 balance = balanceOf(msg.sender);
if (balance < _amount) {
revert DecentralizedStableCoin__BurnAmountExceedsBalance();
}
super.burn(_amount);
}

[G-12] ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops

file: main/src/DSCEngine.sol
118 for (uint256 i = 0; i < tokenAddresses.length; i++) {
353 for (uint256 i = 0; i < s_collateralTokens.length; i++) {

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DSCEngine.sol#L118

[G-13] ++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)

file: main/src/DSCEngine.sol
118 for (uint256 i = 0; i < tokenAddresses.length; i++) {
353 for (uint256 i = 0; i < s_collateralTokens.length; i++) {

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DSCEngine.sol#L118

[G-14] A modifier used only once and not being inherited should be inlined to save gas

file: main/src/DSCEngine.sol
102 modifier isAllowedToken(address token) {

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DSCEngine.sol#L102

[G-15] Use assembly for loops

In the following instances, assembly is used for more gas efficient loops. The only memory slots that are manually used in the loops are scratch space (0x00-0x20), the free memory pointer (0x40), and the zero slot (0x60). This allows us to avoid using the free memory pointer to allocate new memory, which may result in memory expansion costs.

file: main/src/DSCEngine.sol
118 for (uint256 i = 0; i < tokenAddresses.length; i++) {
s_priceFeeds[tokenAddresses[i]] = priceFeedAddresses[i];
s_collateralTokens.push(tokenAddresses[i]);
}
353 for (uint256 i = 0; i < s_collateralTokens.length; i++) {
address token = s_collateralTokens[i];
uint256 amount = s_collateralDeposited[user][token];
totalCollateralValueInUsd += getUsdValue(token, amount);
}

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DSCEngine.sol#L118-L121

[G‑16] .length should not be looked up in every loop of a for-loop

file:
118 for (uint256 i = 0; i < tokenAddresses.length; i++) {
353 for (uint256 i = 0; i < s_collateralTokens.length; i++) {

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DSCEngine.sol#L118

[G-17] >= costs less gas than >

file: main/src/libraries/OracleLib.sol
30 if (secondsSince > TIMEOUT) revert OracleLib__StalePrice();

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/libraries/OracleLib.sol#L30

[G-18] Use assembly to check for address(0)

Saves 6 gas per instance

file: main/src/DecentralizedStableCoin.sol
58 if (_to == address(0))

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DecentralizedStableCoin.sol#L58

file: main/src/DSCEngine.sol
103 if (s_priceFeeds[token] == address(0))

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DSCEngine.sol#L103

[G-19] Don’t initialize variables with default value

file: main/src/DSCEngine.sol
118 for (uint256 i = 0; i < tokenAddresses.length; i++) {
353 for (uint256 i = 0; i < s_collateralTokens.length; i++) {

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DSCEngine.sol#L118

[G‑20] Use solidity version 0,8,19

file: main/src/libraries/OracleLib.sol
3 pragma solidity ^0.8.18;

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/libraries/OracleLib.sol#L3

file: main/src/DecentralizedStableCoin.sol
24 pragma solidity ^0.8.18;

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DecentralizedStableCoin.sol#L24

file: main/src/DSCEngine.sol
24 pragma solidity ^0.8.18;

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DSCEngine.sol#L24

[G-21] Amounts should be checked for 0 before calling a transfer

file: blob/main/src/DSCEngine.sol
157 bool success = IERC20(tokenCollateralAddress).transferFrom(msg.sender, address(this), amountCollateral);
274 bool success = i_dsc.transferFrom(dscFrom, address(this), amountDscToBurn);
287 bool success = IERC20(tokenCollateralAddress).transfer(to, amountCollateral);

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DSCEngine.sol#L157

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.