Platform: Code4rena
Start Date: 12/07/2023
Pot Size: $80,000 USDC
Total HM: 11
Participants: 47
Period: 9 days
Judge: berndartmueller
Total Solo HM: 1
Id: 260
League: ETH
Rank: 19/47
Findings: 1
Award: $189.74
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Sathish9098
Also found by: 0x11singh99, 0xAnah, 0xn006e7, Arz, DavidGiladi, K42, Raihan, ReyAdmirado, Rolezn, SAQ, SM3_SS, SY_S, Walter, dharma09, flutter_developer, hunter_w3b, matrix_0wl, naman1778, petrichor, ybansal2403
189.7433 USDC - $189.74
S.N. | Issues | Instances | Gas Savings |
---|---|---|---|
G-01 | Varaibles which are assigned only once in the contract and then never changed thereafter should be declared constant | 4 | 80000 + additional gas |
G-02 | bytes32 constants are more efficient than string constants | 6 | - |
G-03 | Don't calculate constants | 3 | - |
G-04 | Using XOR (^ ) and OR (| ) bitwise equivalents can save gas | 2 | 106 |
G-05 | Use do while loops instead of for loops | 12 | 782 + additional gas |
G-06 | Avoid explicit initialization of counter variable in for loop | 4 | 32 |
G-07 | Fail as early as possible | 8 | - |
G-08 | Save a storage variable reference instead of repeatedly fetching the value in a mapping or an array | 2 | 80 |
G-09 | Avoid caching the result of a function call when that result is being used only once | 42 | 321 + additional gas |
G-10 | Use assembly to write contract type/address storage varibales | 3 | - |
G-11 | Cache calldata/memory pointers for complex types to avoid complex offset calculations | 6 | 30 |
G-12 | Use a positive conditional flow to save a NOT opcode | 1 | 3 * no. of iterations in for loop |
Constant variables are stored directly in bytecode rather than storage unlike state variables and thus are much cheaper to read from.Declaring a variable as constant will avoid a Gsset (20000 gas) and replace the first access in each transaction (Gcoldsload - 2100 gas) and each access thereafter (Gwarmacces - 100 gas) with a PUSH32 (3 gas).
Note : These are the instances missed by automated finding(the exact issue found in automated finding is "State variables which are not modified within functions should be set as constants or immutable for values set at deployment" which is a bit different from this one)
4 instances in 1 file
22-25
File: contracts/its/token-implementations/StandardizedToken.sol 22: address public tokenManager; 23: string public name; 24: string public symbol; 25: uint8 public decimals;
bytes32
constants are more efficient than string
constantsUse bytes32 to store values that can fit into 32 bytes instead of string as string is more robust in comparison to bytes32.Generally,in Solidity fixed types cost less gas than dynamic types.
6 instances in 3 files
21-22
Note : These variables are not declared constants in the original contract but as suggested by the automated finding's GAS-21
issue,they should be declared as constants.
File: contracts/cgp/governance/InterchainGovernance.sol 21: string public governanceChain; 22: string public governanceAddress;
File: contracts/interchain-governance-executor/lib/InterchainCalls.sol 14: string destinationChain; 15: string destinationContract;
23-24
Note : These variables are not declared constants in the original contract but they should be as suggested here
File: contracts/its/token-implementations/StandardizedToken.sol 23: string public name; 24: string public symbol;
Assigning constant variables based on the result of a mathematical calculation/function call/casting wastes gas as constant variables need to be revaluated each time they are accessed.
3 instances in 1 file
34-41
File: contracts/cgp/AxelarGateway.sol 34: /// @dev Storage slot with the address of the current implementation. `keccak256('eip1967.proxy.implementation') - 1`. 35: bytes32 internal constant KEY_IMPLEMENTATION = bytes32(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc); 36: 37: /// @dev Storage slot with the address of the current governance. `keccak256('governance') - 1`. 38: bytes32 internal constant KEY_GOVERNANCE = bytes32(0xabea6fd3db56a6e6d0242111b43ebb13d1c42709651c032c7894962023a1f909); 39: 40: /// @dev Storage slot with the address of the current governance. `keccak256('mint-limiter') - 1`. 41: bytes32 internal constant KEY_MINT_LIMITER = bytes32(0x627f0c11732837b3240a2de89c0b6343512886dd50978b99c76a68c6416a4d92);
Mitigation : Either use hardcoded values for these constants or chnage them into immutables as immutable variables are evaluated only once i.e during contract creation time and thus can save some gas in comparison to constants.
https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/cgp/AxelarGateway.sol#L35-L41
^
) and OR (|
) bitwise equivalents can save gasOn Remix, given only uint256 types, the following are logical equivalents, but don’t cost the same amount of gas:
(a != b || c != d || e != f) costs 571 ((a ^ b) | (c ^ d) | (e ^ f)) != 0 costs 498 // (saving 73 gas)
Logic POC
Given 4 variables a
, b
, c
and d
represented as such:
0 0 0 0 0 1 1 0 <- a 0 1 1 0 0 1 1 0 <- b 0 0 0 0 0 0 0 0 <- c 1 1 1 1 1 1 1 1 <- d
To have a == b
means that every 0
and 1
match on both variables. Meaning that a XOR (operator ^
) would evaluate to 0 ((a ^ b) == 0)
, as it excludes by definition any equalities.Now, if a != b
, this means that there’s at least somewhere a 1
and a 0
not matching between a and b, making (a ^ b) != 0
.
Both formulas are logically equivalent and using the XOR bitwise operator costs actually the same amount of gas:
function xOrEquivalence(uint a, uint b) external returns (bool) { //return a != b; //370 //return a ^ b != 0; //370 }
However, it is much cheaper to use the bitwise OR operator (|
) than comparing the truthy or falsy values:
function xOrOrEquivalence(uint a, uint b, uint c, uint d) external returns (bool) { //return (a != b || c != d); // 495 //return (a ^ b | c ^ d) != 0; // 442 }
These are logically equivalent too, as the OR
bitwise operator (|
) would result in a 1
somewhere if any value is not 0
between the XOR (^
) statements, meaning if any XOR (^
) statement verifies that its arguments are different.
Coded Proof of Concept :
This little POC also proves that the formulas are equivalent:
function test_XorEq(uint a, uint b, uint c, uint d, uint e, uint f) external { assert((a != b || c != d || e != f) == ((a ^ b) | (c ^ d) | (e ^ f)) != 0); }
Consider applying the suggested equivalence and add a comment mentioning what this is equivalent to, as this is less human-readable, but still understandable once it’s been taught.
2 instances in 2 files
File: contracts/cgp/governance/InterchainGovernance.sol 92: if (keccak256(bytes(sourceChain)) != governanceChainHash || keccak256(bytes(sourceAddress)) != governanceAddressHash)
recommended code :
92: if ( 93: ( 94: (uint256(keccak256(bytes(sourceChain))) ^ uint256(governanceChainHash)) | (uint256(keccak256(bytes(sourceAddress))) ^ uint256(governanceAddressHash)) 95: ) != 0 96: )
Note : As XOR(^
) operators work only with the integer
type values,the bytes32
value in above if
statement must be safely casted into unint256
value
File: contracts/cgp/AxelarGateway.sol 342: if (commandsLength != commands.length || commandsLength != params.length) revert InvalidCommands();
recommended code :
342: if ( 343: ( 344: (commandsLength ^ commands.length) | (commandsLength ^ params.length) 345: ) != 0 346: ) revert InvalidCommands();
https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/cgp/AxelarGateway.sol#L342
do while
loops instead of for
loopsA do while
loop costs less gas in comparison to a for
loop as the condition is not checked for the first iteration in a do while
12 instances in 7 files
Note : The gas savings calculated in this section have been calculated using protocol's provided test.There are other instances too in this section where gas savings have not been demonstrated and that is due to unavailability of the tests for the functions involved in these instances or even if there are tests available for those functions, REPORT_GAS = true npm run test
command in hardhat didn't produce gas reports for them.But as it can be analyzed from other similar instances,applying the recommended mitigation will definitely save significant amount of gas
rotateSigners
function as this modifier is being used by rotateSigners
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | rotateSigners | 75010 | 140004 | 90571 | 21 |
After | rotateSigners | 75010 | 139948 | 90558 | 21 |
File: contracts/cgp/auth/MultisigBase.sol 70: for (uint256 i; i < count; ++i) { 71: voting.hasVoted[signers.accounts[i]] = false; 72: }
recommended code :
70: uint256 i; 71: do { 72: voting.hasVoted[signers.accounts[i]] = false; 73: ++i; 74: } while (i < count);
https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/cgp/auth/MultisigBase.sol#L70-L72
File: contracts/cgp/auth/MultisigBase.sol 122: for (uint256 i; i < length; ++i) { 123: if (votingPerTopic[signerEpoch][topic].hasVoted[signers.accounts[i]]) { 124: voteCount++; 125: } 126: }
recommended code :
122: uint256 i; 123: do { 124: if (votingPerTopic[signerEpoch][topic].hasVoted[signers.accounts[i]]) { 125: voteCount++; 126: } 127: ++i; 128: } while (i < length); 129:
https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/cgp/auth/MultisigBase.sol#L122-L126
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | setTokenMintLimits | 58484 | 87280 | 80081 | 4 |
After | setTokenMintLimits | 58452 | 87219 | 80027 | 4 |
File: contracts/cgp/AxelarGateway.sol 270: for (uint256 i; i < length; ++i) { 271: string memory symbol = symbols[i]; 272: uint256 limit = limits[i]; 273: 274: if (tokenAddresses(symbol) == address(0)) revert TokenDoesNotExist(symbol); 275: 276: _setTokenMintLimit(symbol, limit); 277: }
recommended code :
270: uint256 i; 271: do { 272: string memory symbol = symbols[i]; 273: uint256 limit = limits[i]; 274: 275: if (tokenAddresses(symbol) == address(0)) revert TokenDoesNotExist(symbol); 276: 277: _setTokenMintLimit(symbol, limit); 278: ++i; 279: } while (i < length);
https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/cgp/AxelarGateway.sol#L270-L277
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | sendProposals | - | - | 115005 | 8 |
After | sendProposals | - | - | 114926 | 8 |
File: contracts/interchain-governance-executor/InterchainProposalSender.sol 63: for (uint256 i = 0; i < interchainCalls.length; ) { 64: _sendProposal(interchainCalls[i]); 65: unchecked { 66: ++i; 67: } 68: }
recommended code :
62: uint256 i = 0; 63: do { 64: _sendProposal(interchainCalls[i]); 65: unchecked { 66: ++i; 67: } 68: } while (i < interchainCalls.length);
sendProposals
function as it is a private function used by sendProposals
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | sendProposals | - | - | 115005 | 8 |
After | sendProposals | - | - | 114950 | 8 |
File: contracts/interchain-governance-executor/InterchainProposalSender.sol 106: for (uint256 i = 0; i < interchainCalls.length; ) { 107: totalGas += interchainCalls[i].gas; 108: unchecked { 109: ++i; 110: } 111: }
recommended code :
106: uint256 i = 0; 107: do { 108: totalGas += interchainCalls[i].gas; 109: unchecked { 110: ++i; 111: } 112: } while(i < interchainCalls.length);
No test has been provided for the specific function involved in this instance from protocol's side(using which actual gas saving could be calculated).But,this function has been mimiced by forceExecute
function in the required test script using which the demonstrated gas saving has been calculated
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | forceExecute | 76655 | 94306 | 82539 | 6 |
After | forceExecute | 76608 | 94259 | 82492 | 6 |
File: contracts/interchain-governance-executor/InterchainProposalExecutor.sol 74: for (uint256 i = 0; i < calls.length; i++) { 75: InterchainCalls.Call memory call = calls[i]; 76: (bool success, bytes memory result) = call.target.call{ value: call.value }(call.callData); 77: 78: if (!success) { 79: _onTargetExecutionFailed(call, result); 80: } else { 81: _onTargetExecuted(call, result); 82: } 83: }
recommended code :
74: uint256 i = 0; 75: do { 76: InterchainCalls.Call memory call = calls[i]; 77: (bool success, bytes memory result) = call.target.call{ value: call.value }(call.callData); 78: 79: if (!success) { 80: _onTargetExecutionFailed(call, result); 81: } else { 82: _onTargetExecuted(call, result); 83: } 84: i++; 85: } while (i < calls.length);
File: contracts/its/interchain-token-service/InterchainTokenService.sol 537: for (uint256 i; i < length; ++i) { 538: ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenIds[i])); 539: tokenManager.setFlowLimit(flowLimits[i]); 540: }
recommended code :
537: uint256 i; 538: do { 539: ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenIds[i])); 540: tokenManager.setFlowLimit(flowLimits[i]); 541: ++i; 542: } while (i < length);
File: contracts/its/remote-address-validator/RemoteAddressValidator.sol 44: for (uint256 i; i < length; ++i) { 45: addTrustedAddress(trustedChainNames[i], trustedAddresses[i]); 46: }
recommended code :
44: uint256 i; 45: do { 46: addTrustedAddress(trustedChainNames[i], trustedAddresses[i]); 47: ++i; 48: } while (i < length);
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | addTrustedAddress | - | - | 84742 | 2 |
After | addTrustedAddress | - | - | 84379 | 2 |
File: contracts/its/remote-address-validator/RemoteAddressValidator.sol 56: for (uint256 i; i < length; i++) { 57: uint8 b = uint8(bytes(s)[i]); 58: if ((b >= 65) && (b <= 70)) bytes(s)[i] = bytes1(b + uint8(32)); 59: }
recommended code :
56: uint256 i; 57: do { 58: uint8 b = uint8(bytes(s)[i]); 59: if ((b >= 65) && (b <= 70)) bytes(s)[i] = bytes1(b + uint8(32)); 60: i++; 61: } while (i < length);
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | addGatewaySupportedChains | - | - | 53913 | 2 |
After | addGatewaySupportedChains | - | - | 53869 | 2 |
File: contracts/its/remote-address-validator/RemoteAddressValidator.sol 108: for (uint256 i; i < length; ++i) { 109: string calldata chainName = chainNames[i]; 110: supportedByGateway[chainName] = true; 111: emit GatewaySupportedChainAdded(chainName); 112: }
recommended code :
108: uint256 i; 109: do { 110: string calldata chainName = chainNames[i]; 111: supportedByGateway[chainName] = true; 112: emit GatewaySupportedChainAdded(chainName); 113: ++i; 114: } while (i < length);
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | removeGatewaySupportedChains | - | - | 32013 | 2 |
After | removeGatewaySupportedChains | - | - | 31969 | 2 |
File: contracts/its/remote-address-validator/RemoteAddressValidator.sol 121: for (uint256 i; i < length; ++i) { 122: string calldata chainName = chainNames[i]; 123: supportedByGateway[chainName] = false; 124: emit GatewaySupportedChainRemoved(chainName); 125: }
recommended code :
121: uint256 i; 122: do { 123: string calldata chainName = chainNames[i]; 124: supportedByGateway[chainName] = false; 125: emit GatewaySupportedChainRemoved(chainName); 126: ++i; 127: } while (i < length);
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | multicall | - | - | 56868 | 2 |
After | multicall | - | - | 56785 | 2 |
File: contracts/its/utils/Multicall.sol 24: for (uint256 i = 0; i < data.length; ++i) { 25: (bool success, bytes memory result) = address(this).delegatecall(data[i]); 26: 27: if (!success) { 28: revert(string(result)); 29: } 30: 31: results[i] = result; 32: }
recommended code :
24: uint256 i = 0; 25: do { 26: (bool success, bytes memory result) = address(this).delegatecall(data[i]); 27: 28: if (!success) { 29: revert(string(result)); 30: } 31: 32: results[i] = result; 33: ++i; 34: } while (i < data.length);
https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/its/utils/Multicall.sol#L24-L32
for
loopIn solidity,concept of null
value for a variable doesn't exist i.e when variables are declared in solidity,they are initialized with the default value of their type rather than being set to null
.Thus,explicitly initializing variables(counter varaible for which generally i
is used) in for
loops wastes gas.
According to remix,around 8 gas per loop can be saved by avoiding the explicit initialization of counter varaible in a for loop.Here is a little POC experimented in remix to demonstrate the gas saving that can be obtained via the recommended mitigation.
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; contract AvoidExplicitInitializationGasTest { // @audit : call to test in CASE 1 costs 3090 gas wheras it costs 3082 gas in CASE 2 function test(uint256 a) public pure returns(uint256) { // CASE 1 // for(uint256 i = 0; i < 10;) { // a = a + i; // unchecked { // ++i; // } // } // return a; // CASE 2 for(uint256 i; i < 10;) { a = a + i; unchecked { ++i; } } return a; } }
4 instances in 3 files
File: contracts/interchain-governance-executor/InterchainProposalSender.sol 63: for (uint256 i = 0; i < interchainCalls.length; ) {
File: contracts/interchain-governance-executor/InterchainProposalSender.sol 106: for (uint256 i = 0; i < interchainCalls.length; ) {
File: contracts/interchain-governance-executor/InterchainProposalExecutor.sol 74: for (uint256 i = 0; i < calls.length; i++) {
File: contracts/its/utils/Multicall.sol 24: for (uint256 i = 0; i < data.length; ++i) {
https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/its/utils/Multicall.sol#L24-L32
Move revert statements to the top of the function as it's logic is not influenced by the code above it in any way and in the current code design,users will have to pay more gas if function reverts.Thus by moving revert statements to the top,users gas can be saved if they call the function and the function reverts as early as possible
8 instances in 4 files
159-161
to the top of the function by refactoring the code as per the expected functionalityFile: contracts/cgp/auth/MultisigBase.sol 159: if (newThreshold > length) revert InvalidSigners(); 160: 161: if (newThreshold == 0) revert InvalidSignerThreshold();
https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/cgp/auth/MultisigBase.sol#L159-L161
272
with 274
File: contracts/cgp/AxelarGateway.sol 271: string memory symbol = symbols[i]; 272: uint256 limit = limits[i]; 273: 274: if (tokenAddresses(symbol) == address(0)) revert TokenDoesNotExist(symbol);
recommended code :
271: string memory symbol = symbols[i]; 272: if (tokenAddresses(symbol) == address(0)) revert TokenDoesNotExist(symbol); 273: 274: uint256 limit = limits[i];
https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/cgp/AxelarGateway.sol#L271-L274
File: contracts/cgp/AxelarGateway.sol 289: emit Upgraded(newImplementation); 290: 291: // AUDIT: If `newImplementation.setup` performs `selfdestruct`, it will result in the loss of _this_ implementation (thereby losing the gateway) 292: // if `upgrade` is entered within the context of _this_ implementation itself. 293: if (setupParams.length != 0) { 294: (bool success, ) = newImplementation.delegatecall(abi.encodeWithSelector(IAxelarGateway.setup.selector, setupParams)); 295: 296: if (!success) revert SetupFailed(); 297: }
recommended code :
289: // AUDIT: If `newImplementation.setup` performs `selfdestruct`, it will result in the loss of _this_ implementation (thereby losing the gateway) 290: // if `upgrade` is entered within the context of _this_ implementation itself. 291: if (setupParams.length != 0) { 292: (bool success, ) = newImplementation.delegatecall(abi.encodeWithSelector(IAxelarGateway.setup.selector, setupParams)); 293: 294: if (!success) revert SetupFailed(); 295: } 296: 297: emit Upgraded(newImplementation);
https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/cgp/AxelarGateway.sol#L289-L297
File: contracts/cgp/AxelarGateway.sol 326: bytes32 messageHash = ECDSA.toEthSignedMessageHash(keccak256(data)); 327: 328: // returns true for current operators 329: bool allowOperatorshipTransfer = IAxelarAuth(AUTH_MODULE).validateProof(messageHash, proof); 330: 331: uint256 chainId; 332: bytes32[] memory commandIds; 333: string[] memory commands; 334: bytes[] memory params; 335: 336: (chainId, commandIds, commands, params) = abi.decode(data, (uint256, bytes32[], string[], bytes[])); 337: 338: if (chainId != block.chainid) revert InvalidChainId(); 339: 340: uint256 commandsLength = commandIds.length; 341: 342: if (commandsLength != commands.length || commandsLength != params.length) revert InvalidCommands();
recommended code :
326: uint256 chainId; 327: bytes32[] memory commandIds; 328: string[] memory commands; 329: bytes[] memory params; 330: 331: (chainId, commandIds, commands, params) = abi.decode(data, (uint256, bytes32[], string[], bytes[])); 332: 333: if (chainId != block.chainid) revert InvalidChainId(); 334: 335: uint256 commandsLength = commandIds.length; 336: 337: if (commandsLength != commands.length || commandsLength != params.length) revert InvalidCommands(); 338: 339: bytes32 messageHash = ECDSA.toEthSignedMessageHash(keccak256(data)); 340: 341: // returns true for current operators 342: bool allowOperatorshipTransfer = IAxelarAuth(AUTH_MODULE).validateProof(messageHash, proof);
https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/cgp/AxelarGateway.sol#L326-L342
File: contracts/its/interchain-token-service/InterchainTokenService.sol 092: if ( 093: remoteAddressValidator_ == address(0) || 094: gasService_ == address(0) || 095: tokenManagerDeployer_ == address(0) || 096: standardizedTokenDeployer_ == address(0) 097: ) revert ZeroAddress(); 098: remoteAddressValidator = IRemoteAddressValidator(remoteAddressValidator_); 099: gasService = IAxelarGasService(gasService_); 100: tokenManagerDeployer = tokenManagerDeployer_; 101: standardizedTokenDeployer = standardizedTokenDeployer_; 102: deployer = ITokenManagerDeployer(tokenManagerDeployer_).deployer(); 103: 104: if (tokenManagerImplementations.length != uint256(type(TokenManagerType).max) + 1) revert LengthMismatch();
recommended code :
092: if ( 093: remoteAddressValidator_ == address(0) || 094: gasService_ == address(0) || 095: tokenManagerDeployer_ == address(0) || 096: standardizedTokenDeployer_ == address(0) 097: ) revert ZeroAddress(); 098: 099: if (tokenManagerImplementations.length != uint256(type(TokenManagerType).max) + 1) revert LengthMismatch(); 100: 101: remoteAddressValidator = IRemoteAddressValidator(remoteAddressValidator_); 102: gasService = IAxelarGasService(gasService_); 103: tokenManagerDeployer = tokenManagerDeployer_; 104: standardizedTokenDeployer = standardizedTokenDeployer_; 105: deployer = ITokenManagerDeployer(tokenManagerDeployer_).deployer();
File: contracts/its/utils/ExpressCallHandler.sol 86: uint256 slot = _getExpressReceiveTokenSlot(tokenId, destinationAddress, amount, commandId); 87: address prevExpressCaller; 88: assembly { 89: prevExpressCaller := sload(slot) 90: } 91: if (prevExpressCaller != address(0)) revert AlreadyExpressCalled();
recommended code :
86: address prevExpressCaller; 87: assembly { 88: prevExpressCaller := sload(slot) 89: } 90: if (prevExpressCaller != address(0)) revert AlreadyExpressCalled(); 91: 92: uint256 slot = _getExpressReceiveTokenSlot(tokenId, destinationAddress, amount, commandId);
Caching the value of a particular key of a mapping or the value of a particular index of an array in a local storage variable when the value is accessed(read and written) multiple times saves ~40 gas per access due to not having to perform the same offset calculation every time.Help the Optimizer by saving a storage variable’s reference instead of repeatedly fetching it.
To help the optimizer,declare a storage type variable and use it instead of repeatedly fetching the reference in a map or an array.
As an example, instead of repeatedly calling someMap[someKey]
and SomeArray[someIndex]
, save their references like SomeType storage someVariable = someMap[someIndex]
and SomeType storage someVariable = SomeArray[someIndex]
and use them.
Note : These are the instances missed by automated finding
2 instance in 1 file
signers.isSigner[account]
should be cached in locla storageFile: contracts/cgp/auth/MultisigBase.sol 172: if (signers.isSigner[account]) revert DuplicateSigner(account); // @audit : 1st access 173: if (account == address(0)) revert InvalidSigners(); 174: 175: signers.isSigner[account] = true; // @audit : 2nd access
https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/cgp/auth/MultisigBase.sol#L172-L175
Caching the result of a function call can save gas only when that result is being accessed mutiple times inside a function .There is no point in caching such result when it is being used only once inside a function as it would increase gas costs due to involved memory operations.
42 instances in 10 files
Note : The gas savings calculated in this section have been calculated using protocol's provided test.There are other instances too in this section where gas savings have not been demonstrated and that is due to unavailability of the tests for the functions involoved in these instances or even if there are tests available for those functions, REPORT_GAS = true npm run test
command in hardhat didn't produce gas reports for them.But as it can be analyzed from other similar instances,applying the recommended mitigation will definitely save significant amount of gas
Here, messageHash
cached in line 326
has been used only once in line 329
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | execute | 80924 | 2551246 | 514858 | 101 |
After | execute | 80911 | 2551253 | 514847 | 101 |
File: contracts/cgp/AxelarGateway.sol 326: bytes32 messageHash = ECDSA.toEthSignedMessageHash(keccak256(data)); 327: 328: // returns true for current operators 329: bool allowOperatorshipTransfer = IAxelarAuth(AUTH_MODULE).validateProof(messageHash, proof);
recommended code :
326: // returns true for current operators 327: bool allowOperatorshipTransfer = IAxelarAuth(AUTH_MODULE).validateProof(ECDSA.toEthSignedMessageHash(keccak256(data)), proof);
https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/cgp/AxelarGateway.sol#L326-L329
Here, salt
cached in line 396
has been used only once in line 399
File: contracts/cgp/AxelarGateway.sol 396: bytes32 salt = keccak256(abi.encodePacked(symbol)); 397: 398: (bool success, bytes memory data) = TOKEN_DEPLOYER_IMPLEMENTATION.delegatecall( 399: abi.encodeWithSelector(ITokenDeployer.deployToken.selector, name, symbol, decimals, cap, salt) 400: );
recommended code :
396: (bool success, bytes memory data) = TOKEN_DEPLOYER_IMPLEMENTATION.delegatecall( 397: abi.encodeWithSelector(ITokenDeployer.deployToken.selector, name, symbol, decimals, cap, keccak256(abi.encodePacked(symbol))) 398: );
https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/cgp/AxelarGateway.sol#L396-L400
Here, depositHandlerAddress
cached in line 435
has been used only once in line 437
File: contracts/cgp/AxelarGateway.sol 435: address depositHandlerAddress = _getCreate2Address(salt, keccak256(abi.encodePacked(type(DepositHandler).creationCode))); 436: 437: if (_hasCode(depositHandlerAddress)) return;
recommended code :
435: if (_hasCode(_getCreate2Address(salt, keccak256(abi.encodePacked(type(DepositHandler).creationCode))))) return;
https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/cgp/AxelarGateway.sol#L435-L437
Here, newSalt
cached in line 63
has been used only once in line 71
File: contracts/gmp-sdk/deploy/ConstAddressDeployer.sol 63: bytes32 newSalt = keccak256(abi.encode(sender, salt)); 64: deployedAddress_ = address( 65: uint160( 66: uint256( 67: keccak256( 68: abi.encodePacked( 69: hex'ff', 70: address(this), 71: newSalt,
recommended code :
63: deployedAddress_ = address( 64: uint160( 65: uint256( 66: keccak256( 67: abi.encodePacked( 68: hex'ff', 69: address(this), 70: keccak256(abi.encode(sender, salt)),
Here, deploySalt
cached in line 30
has been used only once in line 31
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | deploy | 179041 | 720770 | 643370 | 14 |
After | deploy | 179028 | 720757 | 643357 | 14 |
File: contracts/gmp-sdk/deploy/Create3Deployer.sol 30: bytes32 deploySalt = keccak256(abi.encode(msg.sender, salt)); 31: deployedAddress_ = Create3.deploy(deploySalt, bytecode);
recommended code :
30: deployedAddress_ = Create3.deploy(keccak256(abi.encode(msg.sender, salt)), bytecode);
Here, deploySalt
cached in line 54
has been used only once in line 55
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | deployAndInit | - | - | 824646 | 2 |
After | deployAndInit | - | - | 824633 | 2 |
File: contracts/gmp-sdk/deploy/Create3Deployer.sol 54: bytes32 deploySalt = keccak256(abi.encode(msg.sender, salt)); 55: deployedAddress_ = Create3.deploy(deploySalt, bytecode);
recommended code :
54: deployedAddress_ = Create3.deploy(keccak256(abi.encode(msg.sender, salt)), bytecode);
Here, deploySalt
cached in line 68
has been used only once in line 69
File: contracts/gmp-sdk/deploy/Create3Deployer.sol 68: bytes32 deploySalt = keccak256(abi.encode(sender, salt)); 69: return Create3.deployedAddress(address(this), deploySalt);
recommended code :
68: return Create3.deployedAddress(address(this), keccak256(abi.encode(sender, salt)));
Here, tokenManagerAddress
cached in line 181
has been used only once in line 182
File: contracts/its/interchain-token-service/InterchainTokenService.sol 181: address tokenManagerAddress = getValidTokenManagerAddress(tokenId); 182: tokenAddress = ITokenManager(tokenManagerAddress).tokenAddress();
recommended code :
181: tokenAddress = ITokenManager(getValidTokenManagerAddress(tokenId)).tokenAddress();
Here, tokenManager
cached in line 276
has been used only once in line 277
File: contracts/its/interchain-token-service/InterchainTokenService.sol 276: ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId)); 277: flowLimit = tokenManager.getFlowLimit();
recommended code :
276: flowLimit = (ITokenManager(getValidTokenManagerAddress(tokenId))).getFlowLimit();
Here, tokenManager
cached in line 286
has been used only once in line 287
File: contracts/its/interchain-token-service/InterchainTokenService.sol 286: ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId)); 287: flowOutAmount = tokenManager.getFlowOutAmount();
recommended code :
286: flowOutAmount = (ITokenManager(getValidTokenManagerAddress(tokenId))).getFlowOutAmount();
Here, tokenManager
cached in line 296
has been used only once in line 297
File: contracts/its/interchain-token-service/InterchainTokenService.sol 296: ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId)); 297: flowInAmount = tokenManager.getFlowInAmount();
recommended code :
296: flowInAmount = (ITokenManager(getValidTokenManagerAddress(tokenId))).getFlowInAmount();
Here, tokenManagerAddress
cached in line 398
has been used only once in line 399
.And,tokenManagerType
and tokenAddress
cached in lines 399
and 400
has been used only once in line 401
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | deployAndRegisterStandardizedToken | 912763 | 912847 | 912803 | 10 |
After | deployAndRegisterStandardizedToken | 912712 | 912820 | 912762 | 10 |
File: contracts/its/interchain-token-service/InterchainTokenService.sol 398: address tokenManagerAddress = getTokenManagerAddress(tokenId); 399: TokenManagerType tokenManagerType = distributor == tokenManagerAddress ? TokenManagerType.MINT_BURN : TokenManagerType.LOCK_UNLOCK; 400: address tokenAddress = getStandardizedTokenAddress(tokenId); 401: _deployTokenManager(tokenId, tokenManagerType, abi.encode(msg.sender.toBytes(), tokenAddress));
recommended code :
398: _deployTokenManager(tokenId, 399: distributor == getTokenManagerAddress(tokenId) ? TokenManagerType.MINT_BURN : TokenManagerType.LOCK_UNLOCK, 400: abi.encode(msg.sender.toBytes(), 401: getStandardizedTokenAddress(tokenId)));
deployAndRegisterRemoteStandardizedToken
function as it is an internal function used by deployAndRegisterRemoteStandardizedToken
Here, tokenId
cached in line 427
has been used only once in line 428
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | deployAndRegisterRemoteStandardizedToken | - | - | 107304 | 4 |
After | deployAndRegisterRemoteStandardizedToken | - | - | 107270 | 4 |
File: contracts/its/interchain-token-service/InterchainTokenService.sol 427: bytes32 tokenId = getCustomTokenId(msg.sender, salt); 428: _deployRemoteStandardizedToken(tokenId, name, symbol, decimals, distributor, operator, destinationChain, gasValue);
recommended code :
427: _deployRemoteStandardizedToken(getCustomTokenId(msg.sender, salt), 428: name, symbol, decimals, distributor, operator, destinationChain, gasValue);
Here, tokenManager
and token
cached in lines 448
and 449
has been used only once in line 449
and 451
respectively
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | expressReceiveToken | 109920 | 119520 | 113177 | 9 |
After | expressReceiveToken | 109894 | 119494 | 113151 | 9 |
File: contracts/its/interchain-token-service/InterchainTokenService.sol 448: ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId)); 449: IERC20 token = IERC20(tokenManager.tokenAddress()); 450: 451: SafeTokenTransferFrom.safeTransferFrom(token, caller, destinationAddress, amount);
recommended code :
448: SafeTokenTransferFrom.safeTransferFrom(IERC20((ITokenManager(getValidTokenManagerAddress(tokenId))).tokenAddress()), 449: caller, destinationAddress, amount);
Here, tokenManager
and token
cached in lines 479
and 480
has been used only once in line 480
and 482
respectively
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | expressReceiveTokenWithData | 128645 | 148704 | 141384 | 11 |
After | expressReceiveTokenWithData | 128619 | 148678 | 141358 | 11 |
File: contracts/its/interchain-token-service/InterchainTokenService.sol 479: ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId)); 480: IERC20 token = IERC20(tokenManager.tokenAddress()); 481: 482: SafeTokenTransferFrom.safeTransferFrom(token, caller, destinationAddress, amount);
recommended code :
479: SafeTokenTransferFrom.safeTransferFrom(IERC20((ITokenManager(getValidTokenManagerAddress(tokenId))).tokenAddress()), 480: caller, destinationAddress, amount);
Here, tokenManager
cached in line 538
has been used only once in line 539
Note : Gas savings obtained in this instance will actually be very high since the caching is being performed inside a for
loop
File: contracts/its/interchain-token-service/InterchainTokenService.sol 538: ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenIds[i])); 539: tokenManager.setFlowLimit(flowLimits[i]);
recommended code :
538: (ITokenManager(getValidTokenManagerAddress(tokenIds[i]))).setFlowLimit(flowLimits[i]);
Here, tokenAddress
cached in line 688
has been used only once in line 696
File: contracts/its/interchain-token-service/InterchainTokenService.sol 688: address tokenAddress = getStandardizedTokenAddress(tokenId); 689: address tokenManagerAddress = getTokenManagerAddress(tokenId); 690: address distributor = distributorBytes.length > 0 ? distributorBytes.toAddress() : tokenManagerAddress; 691: _deployStandardizedToken(tokenId, distributor, name, symbol, decimals, 0, distributor); 692: TokenManagerType tokenManagerType = distributor == tokenManagerAddress ? TokenManagerType.MINT_BURN : TokenManagerType.LOCK_UNLOCK; 693: _deployTokenManager( 694: tokenId, 695: tokenManagerType, 696: abi.encode(operatorBytes.length == 0 ? address(this).toBytes() : operatorBytes, tokenAddress)
recommended code :
688: address tokenManagerAddress = getTokenManagerAddress(tokenId); 689: address distributor = distributorBytes.length > 0 ? distributorBytes.toAddress() : tokenManagerAddress; 690: _deployStandardizedToken(tokenId, distributor, name, symbol, decimals, 0, distributor); 691: TokenManagerType tokenManagerType = distributor == tokenManagerAddress ? TokenManagerType.MINT_BURN : TokenManagerType.LOCK_UNLOCK; 692: _deployTokenManager( 693: tokenId, 694: tokenManagerType, 695: abi.encode(operatorBytes.length == 0 ? address(this).toBytes() : operatorBytes, getStandardizedTokenAddress(tokenId))
deployRemoteCustomTokenManager
function as it is an internal function used by deployRemoteCustomTokenManager
Here, payload
cached in line 755
has been used only once in line 756
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | deployRemoteCustomTokenManager | - | - | 99132 | 8 |
After | deployRemoteCustomTokenManager | - | - | 99119 | 8 |
File: contracts/its/interchain-token-service/InterchainTokenService.sol 755: bytes memory payload = abi.encode(SELECTOR_DEPLOY_TOKEN_MANAGER, tokenId, tokenManagerType, params); 756: _callContract(destinationChain, payload, gasValue, msg.sender);
recommended code :
755: _callContract(destinationChain, 756: abi.encode(SELECTOR_DEPLOY_TOKEN_MANAGER, tokenId, tokenManagerType, params), 757: gasValue, msg.sender);
deployRemoteCanonicalToken
function as it is an internal function used by deployRemoteCanonicalToken
Here, payload
cached in line 780
has been used only once in line 789
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | deployRemoteCanonicalToken | - | - | 132106 | 4 |
After | deployRemoteCanonicalToken | - | - | 132093 | 4 |
File: contracts/its/interchain-token-service/InterchainTokenService.sol 780: bytes memory payload = abi.encode( 781: SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, 782: tokenId, 783: name, 784: symbol, 785: decimals, 786: distributor, 787: operator 788: ); 789: _callContract(destinationChain, payload, gasValue, msg.sender);
recommended code :
780: _callContract(destinationChain, 781: abi.encode( 782: SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, 783: tokenId, 784: name, 785: symbol, 786: decimals, 787: distributor, 788: operator 789: ), 790: gasValue, msg.sender);
deployAndRegisterStandardizedToken
function as it is an internal function used by deployAndRegisterStandardizedToken
Here, salt
and tokenManagerAddress
cached in lines 850
and 851
has been used only once in line 856
and 857
respectively
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | deployAndRegisterStandardizedToken | 912763 | 912859 | 912805 | 10 |
After | deployAndRegisterStandardizedToken | 912731 | 912827 | 912773 | 10 |
File: contracts/its/interchain-token-service/InterchainTokenService.sol 850: bytes32 salt = _getStandardizedTokenSalt(tokenId); 851: address tokenManagerAddress = getTokenManagerAddress(tokenId); 852: 853: (bool success, ) = standardizedTokenDeployer.delegatecall( 854: abi.encodeWithSelector( 855: IStandardizedTokenDeployer.deployStandardizedToken.selector, 856: salt, 857: tokenManagerAddress,
recommended code :
850: (bool success, ) = standardizedTokenDeployer.delegatecall( 851: abi.encodeWithSelector( 852: IStandardizedTokenDeployer.deployStandardizedToken.selector, 853: _getStandardizedTokenSalt(tokenId), 854: getTokenManagerAddress(tokenId),
Here, impl
cached in line 34
has been used only once in line 36
File: contracts/its/proxies/TokenManagerProxy.sol 34: address impl = _getImplementation(IInterchainTokenService(interchainTokenServiceAddress_), implementationType_); 35: 36: (bool success, ) = impl.delegatecall(abi.encodeWithSelector(TokenManagerProxy.setup.selector, params));
recommended code :
35: (bool success, ) = _getImplementation(IInterchainTokenService(interchainTokenServiceAddress_), implementationType_) 36: .delegatecall(abi.encodeWithSelector(TokenManagerProxy.setup.selector, params));
Here, sourceAddressLC
cached in line 70
has been used only once in line 71
File: contracts/its/remote-address-validator/RemoteAddressValidator.sol 70: string memory sourceAddressLC = _lowerCase(sourceAddress); 71: bytes32 sourceAddressHash = keccak256(bytes(sourceAddressLC));
recommended code :
70: bytes32 sourceAddressHash = keccak256(bytes(_lowerCase(sourceAddress)));
Here, token
cached in line 46
has been used only once in line 48
File: contracts/its/token-manager/implementations/TokenManagerMintBurn.sol 46: IERC20 token = IERC20(tokenAddress()); 47: 48: SafeTokenCall.safeCall(token, abi.encodeWithSelector(IERC20BurnableMintable.burn.selector, from, amount));
recommended code :
46: SafeTokenCall.safeCall(IERC20(tokenAddress()), abi.encodeWithSelector(IERC20BurnableMintable.burn.selector, from, amount));
Here, token
cached in line 60
has been used only once in line 62
File: contracts/its/token-manager/implementations/TokenManagerMintBurn.sol 60: IERC20 token = IERC20(tokenAddress()); 61: 62: SafeTokenCall.safeCall(token, abi.encodeWithSelector(IERC20BurnableMintable.mint.selector, to, amount));
recommended code :
60: SafeTokenCall.safeCall(IERC20(tokenAddress()), abi.encodeWithSelector(IERC20BurnableMintable.mint.selector, to, amount));
Here, epoch
cached in line 64
has been used only once in line 65
File: contracts/its/utils/FlowLimit.sol 64: uint256 epoch = block.timestamp / EPOCH_TIME; 65: uint256 slot = _getFlowOutSlot(epoch);
recommended code :
64: uint256 slot = _getFlowOutSlot(block.timestamp / EPOCH_TIME);
https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/its/utils/FlowLimit.sol#L64-L65
Here, epoch
cached in line 76
has been used only once in line 77
File: contracts/its/utils/FlowLimit.sol 76: uint256 epoch = block.timestamp / EPOCH_TIME; 77: uint256 slot = _getFlowInSlot(epoch);
recommended code :
76: uint256 slot = _getFlowInSlot(block.timestamp / EPOCH_TIME);
https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/its/utils/FlowLimit.sol#L76-L77
Here, slotToAdd
and slotToCompare
cached in lines 115
and 116
has been used only once in line 117
No test has been provided for the specific function involved in this instance from protocol's side(using which actual gas saving could be calculated).But,this function has been mimiced by addFlowOut
function in the required test script using which the demonstrated gas saving has been calculated
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | addFlowOut | 31471 | 48571 | 37171 | 12 |
After | addFlowOut | 31445 | 48545 | 37145 | 12 |
File: contracts/its/utils/FlowLimit.sol 115: uint256 slotToAdd = _getFlowOutSlot(epoch); 116: uint256 slotToCompare = _getFlowInSlot(epoch); 117: _addFlow(flowLimit, slotToAdd, slotToCompare, flowOutAmount);
recommended code :
115: _addFlow(flowLimit, _getFlowOutSlot(epoch), _getFlowInSlot(epoch), flowOutAmount);
https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/its/utils/FlowLimit.sol#L115-L117
Here, slotToAdd
and slotToCompare
cached in lines 128
and 129
has been used only once in line 130
No test has been provided for the specific function involved in this instance from protocol's side(using which actual gas saving could be calculated).But,this function has been mimiced by addFlowIn
function in the required test script using which the demonstrated gas saving has been calculated
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | addFlowIn | 31428 | 48528 | 37128 | 12 |
After | addFlowIn | 31402 | 48502 | 37102 | 12 |
File: contracts/its/utils/FlowLimit.sol 128: uint256 slotToAdd = _getFlowInSlot(epoch); 129: uint256 slotToCompare = _getFlowOutSlot(epoch); 130: _addFlow(flowLimit, slotToAdd, slotToCompare, flowInAmount);
recommended code :
128: _addFlow(flowLimit, _getFlowInSlot(epoch), _getFlowOutSlot(epoch), flowInAmount);
https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/its/utils/FlowLimit.sol#L128-L130
Here, implementationAddress
and params
cached in lines 60
and 62
has been used only once in line 63
.And bytecode
and tokenAddress
cached in lines 63
and 65
has been used only once in line 65
and 66
respectively
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | deployStandardizedToken | 480405 | 480407 | 480406 | 6 |
After | deployStandardizedToken | 480354 | 480364 | 480359 | 6 |
File: contracts/its/utils/StandardizedTokenDeployer.sol 59: bytes memory bytecode; 60: address implementationAddress = distributor == tokenManager ? implementationMintBurnAddress : implementationLockUnlockAddress; 61: { 62: bytes memory params = abi.encode(tokenManager, distributor, name, symbol, decimals, mintAmount, mintTo); 63: bytecode = abi.encodePacked(type(StandardizedTokenProxy).creationCode, abi.encode(implementationAddress, params)); 64: } 65: address tokenAddress = deployer.deploy(bytecode, salt); 66: if (tokenAddress.code.length == 0) revert TokenDeploymentFailed();
recommended code :
59: if ( 60: (deployer.deploy( 61: abi.encodePacked(type(StandardizedTokenProxy).creationCode, abi.encode( 62: distributor == tokenManager ? implementationMintBurnAddress : implementationLockUnlockAddress, 63: abi.encode(tokenManager, distributor, name, symbol, decimals, mintAmount, mintTo))), 64: salt)) 65: .code.length == 0 66: ) revert TokenDeploymentFailed();
Here, args
, bytecode
and tokenManagerAddress
cached in lines 38
, 39
and 40
has been used only once in lines 39
, 40
and 41
respectively
File: contracts/its/utils/TokenManagerDeployer.sol 38: bytes memory args = abi.encode(address(this), implementationType, tokenId, params); 39: bytes memory bytecode = abi.encodePacked(type(TokenManagerProxy).creationCode, args); 40: address tokenManagerAddress = deployer.deploy(bytecode, tokenId); 41: if (tokenManagerAddress.code.length == 0) revert TokenManagerDeploymentFailed();
recommended code :
38: if ( 39: (deployer.deploy( 40: abi.encodePacked(type(TokenManagerProxy).creationCode, 41: abi.encode(address(this), implementationType, tokenId, params)), 42: tokenId)) 43: .code.length == 0 44: ) revert TokenManagerDeploymentFailed();
3 instances in 2 files
File: contracts/interchain-governance-executor/InterchainProposalSender.sol 43: gateway = IAxelarGateway(_gateway); 44: gasService = IAxelarGasService(_gasService);
File: contracts/its/token-implementations/StandardizedToken.sol 56: tokenManager = tokenManager_;
The function parameters in the following instances are complex types and thus will result in more complex offset calculations to retrieve specific data from calldata. We can avoid peforming some of these offset calculations by instantiating calldata/memory pointers.
6 instance in 1 file
interchainCall.gas
, interchainCall.destinationChain
and interchainCall.destinationContract
Function | min | max | avg | calls | |
---|---|---|---|---|---|
Before | sendProposals | - | - | 115005 | 8 |
After | sendProposals | - | - | 114975 | 8 |
Note : The calculated gas saving is applicable only when the condition on line 91
in the original contract is satisfied.
File: contracts/interchain-governance-executor/InterchainProposalSender.sol 091: if (interchainCall.gas > 0) { // @audit : 1st access 092: gasService.payNativeGasForContractCall{ value: interchainCall.gas }( // @audit : 2nd access 093: address(this), 094: interchainCall.destinationChain, // @audit : 1st access 095: interchainCall.destinationContract, // @audit : 1st access 097: msg.sender 098: ); 099: } 100: 101: gateway.callContract(interchainCall.destinationChain, interchainCall.destinationContract, payload); // @audit : 2nd access
recommended code :
091: uint256 interchainCallGas = interchainCall.gas; 092: string memory interchainCallDestinationChain = interchainCall.destinationChain; 093: string memory interchainCallDestinationContract = interchainCall.destinationContract; 094: 095: if (interchainCallGas > 0) { 096: gasService.payNativeGasForContractCall{ value: interchainCallGas }( 097: address(this), 098: interchainCallDestinationChain, 099: interchainCallDestinationContract, 100: payload, 101: msg.sender 102: ); 103: } 104: 105: gateway.callContract(interchainCallDestinationChain, interchainCallDestinationContract, payload);
By switiching to a positive conditional flow in if
statement, a NOT opcode(costing 3 gas) can be saved
1 instance in 1 file
for
loop will be executed as piece of the code where the issue has been identified is being used inside a for
loopFile: contracts/interchain-governance-executor/InterchainProposalExecutor.sol 78: if (!success) { 79: _onTargetExecutionFailed(call, result); 80: } else { 81: _onTargetExecuted(call, result); 82: }
recommended code :
78: if (success) { 79: _onTargetExecuted(call, result); 80: } else { 81: _onTargetExecutionFailed(call, result); 82: }
#0 - c4-pre-sort
2023-07-29T01:13:16Z
0xSorryNotSorry marked the issue as high quality report
#1 - c4-judge
2023-09-04T19:38:07Z
berndartmueller marked the issue as grade-a