Axelar Network - dharma09's results

Decentralized interoperability network.

General Information

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

Axelar Network

Findings Distribution

Researcher Performance

Rank: 39/47

Findings: 1

Award: $19.28

Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

19.2767 USDC - $19.28

Labels

bug
G (Gas Optimization)
grade-b
edited-by-warden
G-19

External Links

Gas Optimizations

NumberIssueInstancesEstimated Gas Saved
G-01Using storage instead of memory for structs/arrays saves gas24200
G-02Use calldata instead of memory for function parameters3300
G-03call environment variables directly instead of caching them721
G-04Cache state variables outside of loop to avoid reading/writing storage on every iteration1100
G-05Multiple accesses of a mapping/array should use a local variable cache1100
G-06Can Make The Variable Outside The Loop To Save Gas4400
G-07IF's/require() statements that check input arguments should be at the top of the function24200
G-08<array>.length should not be looked up in every loop of a for-loop1100

Total gas saved across all listed functions: 9421

[G-1] Using storage instead of memory for structs/arrays saves gas

When fetching data from a storage location, assigning the data to a memory variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD rather than a cheap stack read. Instead of declearing the variable with the memory keyword, declaring the variable with the storage keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory, or if the array/struct is being read from another memory array/struct

File: /contracts/interchain-governance-executor/InterchainProposalExecutor.sol

75: InterchainCalls.Call memory call = calls[i];
File: /contracts/cgp/AxelarGateway.sol

271: string memory symbol = symbols[i];

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

When you specify a data location as memory, that value will be copied into memory. When you specify the location as calldata, the value will stay static within calldata. If the value is a large, complex type, using memory may result in extra memory expansion costs.

File: /contracts/cgp/auth
/MultisigBase.sol

142: function rotateSigners(address[] memory newAccounts, uint256 newThreshold) external virtual onlySigners {
143:        _rotateSigners(newAccounts, newThreshold);
144:    }
contracts/gmp-sdk/deploy/Create3Deployer.sol

50: function deployAndInit(
        bytes memory bytecode,
        bytes32 salt,
        bytes calldata init
    ) external returns (address deployedAddress_) {
File: contracts/gmp-sdk/deploy/ConstAddressDeployer.sol

42: function deployAndInit(
        bytes memory bytecode,
        bytes32 salt,
        bytes calldata init
    ) external returns (address deployedAddress_) {

[G-3] call environment variables directly instead of caching them

In the instance below, instead of caching msg.sender and incurring unnecessary stack manipulation, we can call msg.sender directly. msg.sender costs 2 Gas while the extra stack manipulation will cost 3 Gas per DUP.

File: contracts/its/token-manager/TokenManager.sol

89: address sender = msg.sender;
115: address sender = msg.sender;
File: contracts/its/interchain-token/InterchainToken.sol

49: address sender = msg.sender;
File: contracts/its/interchain-token-service/InterchainTokenService.sol

348: address deployer_ = msg.sender;
372: address deployer_ = msg.sender;
447: address caller = msg.sender;
478: address caller = msg.sender;

[G-4] Cache state variables outside of loop to avoid reading/writing storage on every iteration

Reading from storage should always try to be avoided within loops. In the following instances, we are able to cache state variables outside of the loop to save a Gwarmaccess (100 gas) per loop iteration.

Total Instances: 1

Estimated Gas Saved: 100 (This will be more if there are > 1 loop iterations for each instance)

File: contracts/cgp/auth/MultisigBase.sol

119: function getSignerVotesCount(bytes32 topic) external view override returns (uint256) {
120:        uint256 length = signers.accounts.length;
121:        uint256 voteCount;
122;        for (uint256 i; i < length; ++i) {
123:            if (votingPerTopic[signerEpoch][topic].hasVoted[signers.accounts[i]]) { //@audit cache state variables outside of loop
124:                voteCount++;
125:            }
126:        }

128:        return voteCount;
129:    }
File: contracts/cgp/auth/MultisigBase.sol

119: function getSignerVotesCount(bytes32 topic) external view override returns (uint256) {
120:        uint256 length = signers.accounts.length;
121:        uint256 voteCount;
+122:      Voting storage voting = votingPerTopic[signerEpoch][topic];
123:        for (uint256 i; i < length; ++i) {
-124:            if (votingPerTopic[signerEpoch][topic].hasVoted[signers.accounts[i]]) {
+124:            if (voting.hasVoted[signers.accounts[i]]) {
125:               voteCount++;
126:            }
127:        }

129:        return voteCount;
130:    }

[G-5] Multiple accesses of a mapping/array should use a local variable cache

Caching a mapping's value in a local storage or calldata variable when the value is accessed multiple times saves ~42 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[someIndex], save its reference like this: SomeStruct storage someStruct = someMap[someIndex] and use it.

AxelarServiceGovernance.sol.executeMultisigProposal() :multisigApprovals[proposalHash] should cached

File: contracts/cgp/governance/AxelarServiceGovernance.sol

48: function executeMultisigProposal(
        address target,
        bytes calldata callData,
        uint256 nativeValue
    ) external payable onlySigners {
        bytes32 proposalHash = keccak256(abi.encodePacked(target, callData, nativeValue));

        if (!multisigApprovals[proposalHash]) revert NotApproved(); //@audit initial call

        multisigApprovals[proposalHash] = false;     //@audit second call

        _call(target, callData, nativeValue);

        emit MultisigExecuted(proposalHash, target, callData, nativeValue);
    }

[G-6] Can Make The Variable Outside The Loop To Save Gas

When you declare a variable inside a loop, Solidity creates a new instance of the variable for each iteration of the loop. This can lead to unnecessary gas costs, especially if the loop is executed frequently or iterates over a large number of elements.

By declaring the variable outside the loop, you can avoid the creation of multiple instances of the variable and reduce the gas cost of your contract. Here's an example:

contract MyContract {
    function sum(uint256[] memory values) public pure returns (uint256) {
        uint256 total = 0;
        
        for (uint256 i = 0; i < values.length; i++) {
            total += values[i];
        }
        
        return total;
    }
}
File: contracts/cgp/auth/MultisigBase.sol

169: address account = newAccounts[i];
File: contracts/cgp/AxelarGateway.sol

272: uint256 limit = limits[i];
File: contracts/cgp/AxelarGateway.sol

345: bytes32 commandId = commandIds[i];
File: /contracts/its/remote-address-validator/RemoteAddressValidator.sol

57: uint8 b = uint8(bytes(s)[i]);

[G-7] 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.

reorder the if statement to have cheaper one before the gas consuming one

File: contracts/cgp/AxelarGateway.sol

530: function _burnTokenFrom(
531:        address sender,
532:       string memory symbol,
533:        uint256 amount
534:    ) internal {
535:        address tokenAddress = tokenAddresses(symbol);
536:
537:        if (tokenAddress == address(0)) revert TokenDoesNotExist(symbol);
538:        if (amount == 0) revert InvalidAmount();
539:
540:        TokenType tokenType = _getTokenType(symbol);
541:
542:        if (tokenType == TokenType.External) {
543:            IERC20(tokenAddress).safeTransferFrom(sender, address(this), amount);
544:        } else if (tokenType == TokenType.InternalBurnableFrom) {
545:            IERC20(tokenAddress).safeCall(abi.encodeWithSelector(IBurnableMintableCappedERC20.burnFrom.selector, sender, amount));
546:        } else {
547:            IERC20(tokenAddress).safeTransferFrom(sender, IBurnableMintableCappedERC20(tokenAddress).depositAddress(bytes32(0)), amount);
548:            IBurnableMintableCappedERC20(tokenAddress).burn(bytes32(0));
549:   }
550:    }
File: contracts/cgp/AxelarGateway.sol

530: function _burnTokenFrom(
531:        address sender,
532:       string memory symbol,
533:        uint256 amount
534:    ) internal {
+           if (amount == 0) revert InvalidAmount();
535:        address tokenAddress = tokenAddresses(symbol);
536:
537:        if (tokenAddress == address(0)) revert TokenDoesNotExist(symbol);
-538:        if (amount == 0) revert InvalidAmount();
539:
540:        TokenType tokenType = _getTokenType(symbol);
541:
542:        if (tokenType == TokenType.External) {
543:            IERC20(tokenAddress).safeTransferFrom(sender, address(this), amount);
544:        } else if (tokenType == TokenType.InternalBurnableFrom) {
545:            IERC20(tokenAddress).safeCall(abi.encodeWithSelector(IBurnableMintableCappedERC20.burnFrom.selector, sender, amount));
546:        } else {
547:            IERC20(tokenAddress).safeTransferFrom(sender, IBurnableMintableCappedERC20(tokenAddress).depositAddress(bytes32(0)), amount);
548:            IBurnableMintableCappedERC20(tokenAddress).burn(bytes32(0));
549:   }
550:    }
File: /contracts/cgp/auth/MultisigBase.sol

149: function _rotateSigners(address[] memory newAccounts, uint256 newThreshold) internal {
150:        uint256 length = signers.accounts.length;

159: if (newThreshold > length) revert InvalidSigners();
160:
161:        if (newThreshold == 0) revert InvalidSignerThreshold(); //@audit
162:
163:        ++signerEpoch;

178: emit SignersRotated(newAccounts, newThreshold);
179:    }
File: /contracts/cgp/auth/MultisigBase.sol

149: function _rotateSigners(address[] memory newAccounts, uint256 newThreshold) internal {
+            if (newThreshold == 0) revert InvalidSignerThreshold();
150:        uint256 length = signers.accounts.length;

159: if (newThreshold > length) revert InvalidSigners();
160:
-161:        if (newThreshold == 0) revert InvalidSignerThreshold(); 
162:
163:        ++signerEpoch;

178: emit SignersRotated(newAccounts, newThreshold);
179:    }

[G-8]**<array>.length should not be looked up in every loop of a for-loop**

The overheads outlined below are PER LOOP, excluding the first loop

storage arrays incur a Gwarmaccess (100 gas) memory arrays use MLOAD (3 gas) calldata arrays use CALLDATALOAD (3 gas)

Caching the length changes each of these to a DUP (3 gas), and gets rid of the extra DUP needed to store the stack offset

gas save 100

File: contracts/interchain-governance-executor/InterchainProposalSender.sol

106: for (uint256 i = 0; i < interchainCalls.length; ) {

#0 - c4-judge

2023-09-04T11:13:20Z

berndartmueller marked the issue as grade-b

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter