Summary
Using a do while
instead of a for
loop can reduce gas costs on deployment of the contract.
Vulnerability Details
The standard for
loop will check the condition before executing the statement. Whereas a do while
will execute the statement at least once, then check the conditions, allowing for gas savings.
Impact
Original Example:
Gas: 587157
Transaction Cost: 510571
Execution Cost: 419451
Gas optimisation Example:
Gas: 586978 (gas saving -179)
Transaction Cost: 510415 (gas saving -156)
Execution Cost: 419271 (gas saving -180)
Input used for Remix deployment: duplicated addresses for simplicity
[0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2],
[0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2],
0xd9145CCE52D386f254917e481eB44e9943F39138
Contracts Used in Remix IDE
pragma solidity ^0.8.0;
contract ExampleGasOpt {
mapping(address => address) public s_priceFeeds;
address[] public s_collateralTokens;
DecentralizedStableCoin public i_dsc;
error TokenAddressesAndPriceFeedAddressesMustBeSameLength();
constructor(
address[] memory tokenAddresses,
address[] memory priceFeedAddresses,
address dscAddress
) {
uint tokenArrLength = tokenAddresses.length;
uint pricefeedArrLength = priceFeedAddresses.length;
if (tokenArrLength != pricefeedArrLength) {
revert TokenAddressesAndPriceFeedAddressesMustBeSameLength();
}
uint256 i;
do {
s_priceFeeds[tokenAddresses[i]] = priceFeedAddresses[i];
s_collateralTokens.push(tokenAddresses[i]);
++i;
} while (i < tokenArrLength);
i_dsc = DecentralizedStableCoin(dscAddress);
}
}
contract ExampleOrg {
mapping(address => address) public s_priceFeeds;
address[] public s_collateralTokens;
DecentralizedStableCoin public i_dsc;
error TokenAddressesAndPriceFeedAddressesMustBeSameLength();
constructor(
address[] memory tokenAddresses,
address[] memory priceFeedAddresses,
address dscAddress
) {
if (tokenAddresses.length != priceFeedAddresses.length) {
revert TokenAddressesAndPriceFeedAddressesMustBeSameLength();
}
for (uint256 i = 0; i < tokenAddresses.length; i++) {
s_priceFeeds[tokenAddresses[i]] = priceFeedAddresses[i];
s_collateralTokens.push(tokenAddresses[i]);
}
i_dsc = DecentralizedStableCoin(dscAddress);
}
}
contract DecentralizedStableCoin {
}
Tools Used
Manual review, Remix
Recommendations
Implement a do while
loop for the constructor to reduce the gas costs, along with the additional gas saving opportunities identified in the "known issues" repo (provided by Cyfrin) to benefit from a gas saving upon initialisation of the contract, especially in the case there are multiple token contracts and price feeds to add to the arrays upon deployment. There is a caveat with do while
loops that its always going to execute at least once, so ensuring that the code is protected against infinite loops. The constructor has an if statement
to check the arrays are of the same length, which would imply there are valid addresses in there for the loop to execute at least once.