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: 16/47
Findings: 2
Award: $664.41
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: immeas
Also found by: Bauchibred, DavidGiladi, Emmanuel, Jeiwan, MohammedRizwan, MrPotatoMagic, Rolezn, Sathish9098, T1MOH, Udsen, banpaleo5, hals, matrix_0wl, naman1778
474.6749 USDC - $474.67
Issue | Contexts | |
---|---|---|
LOW‑1 | Dangerous use of the burn function | 1 |
LOW‑2 | decimals() not part of ERC20 standard | 1 |
LOW‑3 | Remove unused code | 1 |
LOW‑4 | Unnecessary Low level calls should be avoided | 1 |
Total: 4 contexts over 4 issues
Issue | Contexts | |
---|---|---|
NC‑1 | Avoid the use of sensitive terms | 42 |
NC‑2 | Do not calculate constants | 22 |
NC‑3 | block.timestamp is already used when emitting events, no need to input timestamp | 1 |
NC‑4 | Generate perfect code headers for better solidity code layout and readability | 31 |
NC‑5 | override function arguments that are unused should have the variable name removed or commented out to avoid compiler warnings | 2 |
NC‑6 | Using underscore at the end of variable name | 16 |
NC‑7 | Use abi.encodeCall() instead of abi.encodeSignature() /abi.encodeSelector() | 7 |
NC‑8 | Use named function calls | 9 |
NC‑9 | Use SMTChecker | 1 |
NC‑10 | Cast to bytes or bytes32 for clearer semantic meaning | 4 |
NC‑11 | Utilizing delegatecall within a loop | 1 |
Total: 136 contexts over 11 issues
burnToken
functionThe function burnToken
is used by users to burn an option position by minting the required liquidity and unlocking the collateral. As how the function is designed right now in order to do that, the user needs to send his shares to the contract balance. This is simply too risky, as anyone can call the function and basically burn the shares deposited by the users, before they even get the chance to call the function first.
Instead of the need to send the shares to the contract balance, the function can be refactored to check the balance of shares the user posses and to burn them in the moment of execution or on top of that to input a uint value of how many shares the user wants to burn.
File: AxelarGateway.sol function burnToken(bytes calldata params, bytes32) external onlySelf { (string memory symbol, bytes32 salt) = abi.decode(params, (string, bytes32)); address tokenAddress = tokenAddresses(symbol); if (tokenAddress == address(0)) revert TokenDoesNotExist(symbol); if (_getTokenType(symbol) == TokenType.External) { address depositHandlerAddress = _getCreate2Address(salt, keccak256(abi.encodePacked(type(DepositHandler).creationCode))); if (_hasCode(depositHandlerAddress)) return; DepositHandler depositHandler = new DepositHandler{ salt: salt }(); (bool success, bytes memory returnData) = depositHandler.execute( tokenAddress, abi.encodeWithSelector(IERC20.transfer.selector, address(this), IERC20(tokenAddress).balanceOf(address(depositHandler))) ); if (!success || (returnData.length != uint256(0) && !abi.decode(returnData, (bool)))) revert BurnFailed(symbol); depositHandler.destroy(address(this)); } else { IBurnableMintableCappedERC20(tokenAddress).burn(salt); } }
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L427
decimals()
not part of ERC20 standarddecimals()
is not part of the official ERC20 standard and might fail for tokens that do not implement it. While in practice it is very unlikely, as usually most of the tokens implement it, this should still be considered as a potential issue.
File: InterchainTokenService.sol 737: decimals = token.decimals()
This code is not used in the main project contract files, remove it or add event-emit Code that is not in use, suggests that they should not be present and could potentially contain insecure functionalities.
File: InterchainGovernance.sol function _executeWithToken
Avoid making unnecessary low-level calls to the system whenever possible, as they function differently from contract-type calls. For instance:
Using address.call(abi.encodeWithSelector("fancy(bytes32)", mybytes)
does not confirm whether the target is genuinely a contract, whereas ContractInterface(address).fancy(mybytes)
does.
Moreover, when invoking functions declared as view
/pure
, the Solidity compiler executes a staticcall
, offering extra security guarantees that are absent in low-level calls. Additionally, return values must be manually decoded when making low-level calls.
Note: If a low-level call is required, consider using Contract.function.selector
instead of employing a hardcoded ABI string for encoding.
File: AxelarGateway.sol function execute(bytes calldata input) external override { (bytes memory data, bytes memory proof) = abi.decode(input, (bytes, bytes)); bytes32 messageHash = ECDSA.toEthSignedMessageHash(keccak256(data)); bool allowOperatorshipTransfer = IAxelarAuth(AUTH_MODULE).validateProof(messageHash, proof); uint256 chainId; bytes32[] memory commandIds; string[] memory commands; bytes[] memory params; (chainId, commandIds, commands, params) = abi.decode(data, (uint256, bytes32[], string[], bytes[])); if (chainId != block.chainid) revert InvalidChainId(); uint256 commandsLength = commandIds.length; if (commandsLength != commands.length || commandsLength != params.length) revert InvalidCommands(); for (uint256 i; i < commandsLength; ++i) { bytes32 commandId = commandIds[i]; if (isCommandExecuted(commandId)) continue; bytes4 commandSelector; bytes32 commandHash = keccak256(abi.encodePacked(commands[i])); if (commandHash == SELECTOR_DEPLOY_TOKEN) { commandSelector = AxelarGateway.deployToken.selector; } else if (commandHash == SELECTOR_MINT_TOKEN) { commandSelector = AxelarGateway.mintToken.selector; } else if (commandHash == SELECTOR_APPROVE_CONTRACT_CALL) { commandSelector = AxelarGateway.approveContractCall.selector; } else if (commandHash == SELECTOR_APPROVE_CONTRACT_CALL_WITH_MINT) { commandSelector = AxelarGateway.approveContractCallWithMint.selector; } else if (commandHash == SELECTOR_BURN_TOKEN) { commandSelector = AxelarGateway.burnToken.selector; } else if (commandHash == SELECTOR_TRANSFER_OPERATORSHIP) { if (!allowOperatorshipTransfer) continue; allowOperatorshipTransfer = false; commandSelector = AxelarGateway.transferOperatorship.selector; } else { continue; } _setCommandExecuted(commandId, true); (bool success, ) = address(this).call(abi.encodeWithSelector(commandSelector, params[i], commandId)); if (success) emit Executed(commandId); else _setCommandExecuted(commandId, false); } }
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L374
When reaching out to a known contract within the system, always opt for typed contract calls (interfaces/contracts) rather than low-level calls. This approach helps prevent mistakes, potentially unverified return values, and ensures security guarantees.
Use <a href="https://www.zdnet.com/article/mysql-drops-master-slave-and-blacklist-whitelist-terminology/">alternative variants</a>, e.g. allowlist/denylist instead of whitelist/blacklist
File: InterchainProposalExecutor.sol 15: * The contract maintains whitelists for proposal senders and proposal callers. Proposal senders 23: // Whitelisted proposal callers. The proposal caller is the contract that calls the `InterchainProposalSender` at the source chain. 24: mapping(string => mapping(address => bool)) public whitelistedCallers; 26: // Whitelisted proposal senders. The proposal sender is the `InterchainProposalSender` contract address at the source chain. 27: mapping(string => mapping(address => bool)) public whitelistedSenders; 34: * @dev Executes the proposal. The source address must be a whitelisted sender. 48: // Check that the source address is whitelisted 49: if (!whitelistedSenders[sourceChain][StringToAddress.toAddress(sourceAddress)]) { 50: revert NotWhitelistedSourceAddress(); 56: // Check that the caller is whitelisted 57: if (!whitelistedCallers[sourceChain][interchainProposalCaller]) { 58: revert NotWhitelistedCaller(); 87: * @dev Set the proposal caller whitelist status 90: * @param whitelisted The whitelist status 105: * @param whitelisted The whitelist status 92: function setWhitelistedProposalCaller( 95: bool whitelisted 110: bool whitelisted 97: whitelistedCallers[sourceChain][sourceCaller] = whitelisted; 98: emit WhitelistedProposalCallerSet(sourceChain, sourceCaller, whitelisted); 102: * @dev Set the proposal sender whitelist status 107: function setWhitelistedProposalSender( 110: bool whitelisted 112: whitelistedSenders[sourceChain][sourceSender] = whitelisted; 113: emit WhitelistedProposalSenderSet(sourceChain, sourceSender, whitelisted);
File: IInterchainProposalExecutor.sol 6: // An event emitted when the proposal caller is whitelisted 7: event WhitelistedProposalCallerSet(string indexed sourceChain, address indexed sourceCaller, bool whitelisted); 9: // An event emitted when the proposal sender is whitelisted 10: event WhitelistedProposalSenderSet(string indexed sourceChain, address indexed sourceSender, bool whitelisted); 17: // An error emitted when the proposal caller is not whitelisted 18: error NotWhitelistedCaller(); 20: // An error emitted when the proposal sender is not whitelisted 21: error NotWhitelistedSourceAddress(); 24: * @notice set the whitelisted status of a proposal sender which is the `InterchainProposalSender` contract address on the source chain 27: * @param whitelisted The whitelisted status 39: * @param whitelisted The whitelisted status 29: function setWhitelistedProposalSender( 32: bool whitelisted 44: bool whitelisted 36: * @notice set the whitelisted status of a proposal caller which normally set to the `Timelock` contract address on the source chain 41: function setWhitelistedProposalCaller( 44: bool whitelisted
Due to how constant
variables are implemented (replacements at compile-time), an expression assigned to a constant variable is recomputed each time that the variable is used, which wastes some gas.
File: AxelarGateway.sol 31: // bytes32 internal constant KEY_ALL_TOKENS_FROZEN = keccak256('all-tokens-frozen');
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L31
File: AxelarGateway.sol 32: // bytes32 internal constant PREFIX_TOKEN_FROZEN = keccak256('token-frozen');
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L32
File: AxelarGateway.sol 44: bytes32 internal constant PREFIX_COMMAND_EXECUTED = keccak256('command-executed');
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L44
File: AxelarGateway.sol 45: bytes32 internal constant PREFIX_TOKEN_ADDRESS = keccak256('token-address');
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L45
File: AxelarGateway.sol 46: bytes32 internal constant PREFIX_TOKEN_TYPE = keccak256('token-type');
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L46
File: AxelarGateway.sol 47: bytes32 internal constant PREFIX_CONTRACT_CALL_APPROVED = keccak256('contract-call-approved');
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L47
File: AxelarGateway.sol 48: bytes32 internal constant PREFIX_CONTRACT_CALL_APPROVED_WITH_MINT = keccak256('contract-call-approved-with-mint');
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L48
File: AxelarGateway.sol 49: bytes32 internal constant PREFIX_TOKEN_MINT_LIMIT = keccak256('token-mint-limit');
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L49
File: AxelarGateway.sol 50: bytes32 internal constant PREFIX_TOKEN_MINT_AMOUNT = keccak256('token-mint-amount');
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L50
File: FinalProxy.sol 18: bytes32 internal constant FINAL_IMPLEMENTATION_SALT = keccak256('final-implementation');
File: TimeLock.sol 13: bytes32 internal constant PREFIX_TIME_LOCK = keccak256('time-lock');
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/gmp-sdk/util/TimeLock.sol#L13
File: InterchainTokenService.sol 62: bytes32 internal constant PREFIX_CUSTOM_TOKEN_ID = keccak256('its-custom-token-id');
File: InterchainTokenService.sol 63: bytes32 internal constant PREFIX_STANDARDIZED_TOKEN_ID = keccak256('its-standardized-token-id');
File: InterchainTokenService.sol 64: bytes32 internal constant PREFIX_STANDARDIZED_TOKEN_SALT = keccak256('its-standardized-token-salt');
File: InterchainTokenService.sol 71: bytes32 private constant CONTRACT_ID = keccak256('interchain-token-service');
File: InterchainTokenServiceProxy.sol 12: bytes32 private constant CONTRACT_ID = keccak256('interchain-token-service');
File: RemoteAddressValidatorProxy.sol 12: bytes32 private constant CONTRACT_ID = keccak256('remote-address-validator');
File: StandardizedTokenProxy.sol 14: bytes32 private constant CONTRACT_ID = keccak256('standardized-token');
File: RemoteAddressValidator.sol 21: bytes32 private constant CONTRACT_ID = keccak256('remote-address-validator');
File: StandardizedToken.sol 27: bytes32 private constant CONTRACT_ID = keccak256('standardized-token');
File: FlowLimit.sol 15: uint256 internal constant PREFIX_FLOW_OUT_AMOUNT = uint256(keccak256('prefix-flow-out-amount'));
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/utils/FlowLimit.sol#L15
File: FlowLimit.sol 16: uint256 internal constant PREFIX_FLOW_IN_AMOUNT = uint256(keccak256('prefix-flow-in-amount'));
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/utils/FlowLimit.sol#L16
</details>File: InterchainGovernance.sol 78: emit ProposalExecuted(proposalHash, target, callData, nativeValue, block.timestamp);
It is recommended to use pre-made headers for Solidity code layout and readability: https://github.com/transmissions11/headers
/*////////////////////////////////////////////////////////////// TESTING 123 //////////////////////////////////////////////////////////////*/
File: AxelarGateway.sol 7: AxelarGateway.sol
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L7
File: MultisigBase.sol 5: MultisigBase.sol
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/auth/MultisigBase.sol#L5
File: AxelarServiceGovernance.sol 5: AxelarServiceGovernance.sol
File: InterchainGovernance.sol 7: InterchainGovernance.sol
File: Multisig.sol 5: Multisig.sol
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/governance/Multisig.sol#L5
File: Caller.sol 5: Caller.sol
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/util/Caller.sol#L5
File: Upgradable.sol 5: Upgradable.sol
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/util/Upgradable.sol#L5
File: FinalProxy.sol 6: FinalProxy.sol
File: InitProxy.sol 5: InitProxy.sol
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/gmp-sdk/upgradable/InitProxy.sol#L5
File: Proxy.sol 5: Proxy.sol 7: Proxy.sol
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/gmp-sdk/upgradable/Proxy.sol#L5
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/gmp-sdk/upgradable/Proxy.sol#L7
File: Upgradable.sol 5: Upgradable.sol
File: TimeLock.sol 5: TimeLock.sol
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/gmp-sdk/util/TimeLock.sol#L5
File: InterchainProposalExecutor.sol 7: InterchainProposalExecutor.sol
File: InterchainProposalSender.sol 7: InterchainProposalSender.sol
File: InterchainToken.sol 5: InterchainToken.sol
File: InterchainTokenService.sol 11: InterchainTokenService.sol
File: StandardizedTokenProxy.sol 7: StandardizedTokenProxy.sol
File: TokenManagerProxy.sol 6: TokenManagerProxy.sol
File: RemoteAddressValidator.sol 4: RemoteAddressValidator.sol
File: Pausable.sol 5: Pausable.sol
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/test/utils/Pausable.sol#L5
File: TokenManager.sol 5: TokenManager.sol
File: Distributable.sol 5: Distributable.sol
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/utils/Distributable.sol#L5
File: ExpressCallHandler.sol 5: ExpressCallHandler.sol
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/utils/ExpressCallHandler.sol#L5
File: FlowLimit.sol 5: FlowLimit.sol
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/utils/FlowLimit.sol#L5
File: Implementation.sol 5: Implementation.sol
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/utils/Implementation.sol#L5
File: Multicall.sol 5: Multicall.sol
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/utils/Multicall.sol#L5
File: Operatable.sol 5: Operatable.sol
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/utils/Operatable.sol#L5
File: Pausable.sol 5: Pausable.sol
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/utils/Pausable.sol#L5
File: StandardizedTokenDeployer.sol 7: StandardizedTokenDeployer.sol
</details>File: TokenManagerDeployer.sol 7: TokenManagerDeployer.sol
override
function arguments that are unused should have the variable name removed or commented out to avoid compiler warningsFile: AxelarGateway.sol 224: function admins : uint256
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L224
File: AxelarGateway.sol 238: function tokenFrozen : string memory
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L238
The use of underscore at the end of the variable name is uncommon and also suggests that the variable name was not completely changed. Consider refactoring variableName_
to variableName
.
File: AxelarGateway.sol 68: authModule_; 69: tokenDeployerImplementation_;
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L68-L69
File: InterchainGovernance.sol 39: governanceChain_; 40: governanceAddress_;
File: InterchainTokenService.sol 100: tokenManagerDeployer_; 101: standardizedTokenDeployer_;
File: TokenManagerProxy.sol 32: implementationType_; 33: tokenId_;
File: StandardizedToken.sol 51: distributor_; 52: tokenManager_; 56: tokenManager_; 52: tokenManager_; 56: tokenManager_;
File: TokenManager.sol 63: operator_;
</details>File: StandardizedTokenDeployer.sol 34: implementationLockUnlockAddress_; 35: implementationMintBurnAddress_;
abi.encodeCall()
instead of abi.encodeSignature()
/abi.encodeSelector()
abi.encodeCall()
has compiler <a href="https://github.com/OpenZeppelin/openzeppelin-contracts/issues/3693">type safety</a>, whereas the other two functions do not
File: AxelarGateway.sol 294: (bool success, ) = newImplementation.delegatecall(abi.encodeWithSelector(IAxelarGateway.setup.selector, setupParams));
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L294
File: AxelarGateway.sol 374: (bool success, ) = address(this).call(abi.encodeWithSelector(commandSelector, params[i], commandId));
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L374
File: AxelarGateway.sol 399: abi.encodeWithSelector(ITokenDeployer.deployToken.selector, name, symbol, decimals, cap, salt)
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L399
File: AxelarGateway.sol 443: abi.encodeWithSelector(IERC20.transfer.selector, address(this), IERC20(tokenAddress).balanceOf(address(depositHandler)))
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L443
File: Upgradable.sol 49: (bool success, ) = newImplementation.delegatecall(abi.encodeWithSelector(this.setup.selector, params));
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/util/Upgradable.sol#L49
File: InitProxy.sol 58: (bool success, ) = implementationAddress.delegatecall(abi.encodeWithSelector(IUpgradable.setup.selector, params));
</details>File: Upgradable.sol 61: (bool success, ) = newImplementation.delegatecall(abi.encodeWithSelector(this.setup.selector, params));
Code base has an extensive use of named function calls, but it somehow missed one instance where this would be appropriate.
It should use named function calls on function call, as such:
library.exampleFunction{value: _data.amount.value}({ _id: _data.id, _amount: _data.amount.value, _token: _data.token, _example: "", _metadata: _data.metadata });
File: Caller.sol 18: (bool success, ) = target.call{ value: nativeValue }(callData);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/util/Caller.sol#L18
File: InterchainProposalExecutor.sol 76: (bool success, bytes memory result) = call.target.call{ value: call.value }(call.callData);
File: InterchainProposalSender.sol 92: gasService.payNativeGasForContractCall{ value: interchainCall.gas }( address(this), interchainCall.destinationChain, interchainCall.destinationContract, payload, msg.sender );
File: InterchainToken.sol 66: tokenManager.transmitInterchainTransfer{ value: msg.value }(sender, destinationChain, recipient, amount, metadata); 104: tokenManager.transmitInterchainTransfer{ value: msg.value }(sender, destinationChain, recipient, amount, metadata);
File: InterchainTokenService.sol 715: gasService.payNativeGasForContractCall{ value: gasValue }( address(this), destinationChain, destinationAddress, payload, refundTo );
File: TokenManager.sol 92: interchainTokenService.transmitSendToken{ value: msg.value }( _getTokenId(), sender, destinationChain, destinationAddress, amount, metadata ); 145: interchainTokenService.transmitSendToken{ value: msg.value }( _getTokenId(), sender, destinationChain, destinationAddress, amount, metadata );
</details>File: TokenManager.sol 119: interchainTokenService.transmitSendToken{ value: msg.value }( _getTokenId(), sender, destinationChain, destinationAddress, amount, abi.encodePacked(version, data) );
The highest tier of smart contract behavior assurance is formal mathematical verification. All assertions that are made are guaranteed to be true across all inputs → The quality of your asserts is the quality of your verification
https://twitter.com/0xOwenThurm/status/1614359896350425088?t=dbG9gHFigBX85Rv29lOjIQ&s=19
bytes
or bytes32
for clearer semantic meaningUsing a <a href="https://ethereum.stackexchange.com/questions/30912/how-to-compare-strings-in-solidity#answer-82739">cast</a> on a single argument, rather than abi.encodePacked() makes the intended operation more clear, leading to less reviewer confusion.
File: AxelarGateway.sol 350: bytes32 commandHash = keccak256(abi.encodePacked(commands[i]));
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L350
File: AxelarGateway.sol 396: bytes32 salt = keccak256(abi.encodePacked(symbol));
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L396
File: AxelarGateway.sol 435: address depositHandlerAddress = _getCreate2Address(salt, keccak256(abi.encodePacked(type(DepositHandler).creationCode)));
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L435
File: TokenManagerDeployer.sol 39: bytes memory bytecode = abi.encodePacked(type(TokenManagerProxy).creationCode, args);
delegatecall
within a loopUsing delegatecall
in a for
loop can lead to high gas costs, as delegatecall
is an expensive operation and its costs compound when used in a loop. Additionally, it can pose security risks including reentrancy attacks, as it executes code in the calling contract's context. The function selector collisions can also lead to unpredictable behaviour. To mitigate these risks, control the loop's iterations, apply a reentrancy guard, strictly audit contracts called via delegatecall
, and consider alternatives like call
or proxy patterns if the use case allows. Always thoroughly vet contracts involved in delegatecall
operations.
File: Multicall.sol 25: (bool success, bytes memory result) = address(this).delegatecall(data[i]);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/utils/Multicall.sol#L25
#0 - c4-judge
2023-09-08T11:49:25Z
berndartmueller marked the issue as grade-a
🌟 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
Issue | Contexts | Estimated Gas Saved | |
---|---|---|---|
GAS‑1 | abi.encode() is less efficient than abi.encodepacked() | 28 | 1484 |
GAS‑2 | Use assembly in place of abi.decode to extract calldata values more efficiently | 2 | 224 |
GAS‑3 | Use assembly to emit events | 49 | 1862 |
GAS‑4 | Counting down in for statements is more gas efficient | 13 | 3341 |
GAS‑5 | Use assembly to write address storage values | 1 | 74 |
GAS‑6 | Multiple accesses of a mapping/array should use a local variable cache | 6 | 480 |
GAS‑7 | The result of a function call should be cached rather than re-calling the function | 26 | 1300 |
GAS‑8 | Superfluous event fields | 2 | 68 |
GAS‑9 | Use nested if and avoid multiple check combinations | 4 | 24 |
GAS‑10 | Using XOR (^) and AND (&) bitwise equivalents | 57 | 741 |
GAS‑11 | Using this.<fn>() wastes gas | 2 | 200 |
Total: 196 contexts over 11 issues
abi.encode()
is less efficient than abi.encodepacked()
See for more information: https://github.com/ConnorBlockchain/Solidity-Encode-Gas-Comparison
File: AxelarGateway.sol 562: return keccak256(abi.encode(PREFIX_TOKEN_MINT_AMOUNT, symbol, day));
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L562
File: AxelarGateway.sol 584: return keccak256(abi.encode(PREFIX_CONTRACT_CALL_APPROVED, commandId, sourceChain, sourceAddress, contractAddress, payloadHash));
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L584
File: AxelarGateway.sol 598: abi.encode(
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L598
File: ConstAddressDeployer.sol 25: deployedAddress_ = _deploy(bytecode, keccak256(abi.encode(msg.sender, salt)));
File: ConstAddressDeployer.sol 47: deployedAddress_ = _deploy(bytecode, keccak256(abi.encode(msg.sender, salt)));
File: ConstAddressDeployer.sol 63: bytes32 newSalt = keccak256(abi.encode(sender, salt));
File: InterchainProposalExecutor.sol 66: emit ProposalExecuted(keccak256(abi.encode(sourceChain, sourceAddress, interchainProposalCaller, payload)));
File: InterchainProposalSender.sol 89: bytes memory payload = abi.encode(msg.sender, interchainCall.calls);
File: InterchainTokenService.sol 203: tokenId = keccak256(abi.encode(PREFIX_STANDARDIZED_TOKEN_ID, chainNameHash, tokenAddress));
File: InterchainTokenService.sol 214: tokenId = keccak256(abi.encode(PREFIX_CUSTOM_TOKEN_ID, sender, salt));
File: InterchainTokenService.sol 242: params = abi.encode(operator, tokenAddress);
File: InterchainTokenService.sol 252: params = abi.encode(operator, tokenAddress);
File: InterchainTokenService.sol 267: params = abi.encode(operator, tokenAddress, liquidityPoolAddress);
File: InterchainTokenService.sol 313: _deployTokenManager(tokenId, TokenManagerType.LOCK_UNLOCK, abi.encode(address(this).toBytes(), tokenAddress));
File: InterchainTokenService.sol 401: _deployTokenManager(tokenId, tokenManagerType, abi.encode(msg.sender.toBytes(), tokenAddress));
File: InterchainTokenService.sol 512: payload = abi.encode(SELECTOR_SEND_TOKEN, tokenId, destinationAddress, amount);
File: InterchainTokenService.sol 520: payload = abi.encode(SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destinationAddress, amount, sourceAddress.toBytes(), metadata);
File: InterchainTokenService.sol 696: abi.encode(operatorBytes.length == 0 ? address(this).toBytes() : operatorBytes, tokenAddress)
File: InterchainTokenService.sol 755: bytes memory payload = abi.encode(SELECTOR_DEPLOY_TOKEN_MANAGER, tokenId, tokenManagerType, params);
File: InterchainTokenService.sol 780: bytes memory payload = abi.encode(
File: InterchainTokenService.sol 828: return keccak256(abi.encode(PREFIX_STANDARDIZED_TOKEN_SALT, tokenId));
File: ExpressCallHandler.sol 32: slot = uint256(keccak256(abi.encode(PREFIX_EXPRESS_RECEIVE_TOKEN, tokenId, destinationAddress, amount, commandId)));
File: ExpressCallHandler.sol 57: abi.encode(
File: FlowLimit.sol 47: slot = uint256(keccak256(abi.encode(PREFIX_FLOW_OUT_AMOUNT, epoch)));
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/utils/FlowLimit.sol#L47
File: FlowLimit.sol 56: slot = uint256(keccak256(abi.encode(PREFIX_FLOW_IN_AMOUNT, epoch)));
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/utils/FlowLimit.sol#L56
File: StandardizedTokenDeployer.sol 62: bytes memory params = abi.encode(tokenManager, distributor, name, symbol, decimals, mintAmount, mintTo);
File: StandardizedTokenDeployer.sol 63: bytecode = abi.encodePacked(type(StandardizedTokenProxy).creationCode, abi.encode(implementationAddress, params));
</details>File: TokenManagerDeployer.sol 38: bytes memory args = abi.encode(address(this), implementationType, tokenId, params);
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.not_optimized(); c1.optimized(); } } contract Contract0 { string a = "Code4rena"; function not_optimized() public returns(bytes32){ return keccak256(abi.encode(a)); } } contract Contract1 { string a = "Code4rena"; function optimized() public returns(bytes32){ return keccak256(abi.encodePacked(a)); } }
Contract0 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
101871 | 683 | ||||
Function Name | min | avg | median | max | # calls |
not_optimized | 2661 | 2661 | 2661 | 2661 | 1 |
Contract1 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
99465 | 671 | ||||
Function Name | min | avg | median | max | # calls |
optimized | 2608 | 2608 | 2608 | 2608 | 1 |
abi.decode
to extract calldata values more efficientlyInstead of using abi.decode
, we can use assembly to decode our desired calldata values directly. This will allow us to avoid decoding calldata values that we will not use.
For example, for a generic abi.decode
call:
(bytes32 varA, bytes32 varB, uint256 varC, uint256 varD) = abi.decode(metadata, (bytes32, bytes32, uint256, uint256));
We can use the following assembly call to extract the relevant metadata:
bytes32 varA; bytes32 varB; uint256 varC; uint256 varD; { // used to discard `data` variable and avoid extra stack manipulation bytes calldata data = metadata; assembly { varA := calldataload(add(data.offset, 0x00)) varB := calldataload(add(data.offset, 0x20)) varC := calldataload(add(data.offset, 0x40)) varD := calldataload(add(data.offset, 0x60)) } }
File: AxelarGateway.sol 422: (string memory symbol, address account, uint256 amount) = abi.decode(params, (string, address, uint256));
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L422
File: InterchainTokenService.sol 600: (, bytes32 tokenId, bytes memory destinationAddressBytes, uint256 amount) = abi.decode(payload, (uint256, bytes32, bytes, uint256));
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.
Note: In order to do this optimization safely, we will need to cache and restore the free memory pointer.
For example, for a generic emit
event for eventSentAmountExample
:
// uint256 id, uint256 value, uint256 amount emit eventSentAmountExample(id, value, amount);
We can use the following assembly emit events:
assembly { let memptr := mload(0x40) mstore(0x00, calldataload(0x44)) mstore(0x20, calldataload(0xa4)) mstore(0x40, amount) log1( 0x00, 0x60, // keccak256("eventSentAmountExample(uint256,uint256,uint256)") 0xa622cf392588fbf2cd020ff96b2f4ebd9c76d7a4bc7f3e6b2f18012312e76bc3 ) mstore(0x40, memptr) }
File: AxelarGateway.sol 104: emit TokenSent(msg.sender, destinationChain, destinationAddress, symbol, amount);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L104
File: AxelarGateway.sol 112: emit ContractCall(msg.sender, destinationChain, destinationContractAddress, keccak256(payload), payload);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L112
File: AxelarGateway.sol 123: emit ContractCallWithToken(msg.sender, destinationChain, destinationContractAddress, keccak256(payload), payload, symbol, amount);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L123
File: AxelarGateway.sol 289: emit Upgraded(newImplementation);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L289
File: AxelarGateway.sol 319: emit OperatorshipTransferred(newOperatorsData);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L319
File: AxelarGateway.sol 376: if (success) emit Executed(commandId);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L376
File: AxelarGateway.sol 418: emit TokenDeployed(symbol, tokenAddress);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L418
File: AxelarGateway.sol 466: emit ContractCallApproved(commandId, sourceChain, sourceAddress, contractAddress, payloadHash, sourceTxHash, sourceEventIndex);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L466
File: AxelarGateway.sol 498: emit OperatorshipTransferred(newOperatorsData);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L498
File: AxelarGateway.sol 630: emit TokenMintLimitUpdated(symbol, limit);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L630
File: AxelarGateway.sol 682: emit GovernanceTransferred(getAddress(KEY_GOVERNANCE), newGovernance);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L682
File: AxelarGateway.sol 688: emit MintLimiterTransferred(getAddress(KEY_MINT_LIMITER), newMintLimiter);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L688
File: MultisigBase.sol 178: emit SignersRotated(newAccounts, newThreshold);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/auth/MultisigBase.sol#L178
File: AxelarServiceGovernance.sol 61: emit MultisigExecuted(proposalHash, target, callData, nativeValue);
File: AxelarServiceGovernance.sol 89: emit ProposalScheduled(proposalHash, target, callData, nativeValue, eta); 94: emit ProposalCancelled(proposalHash, target, callData, nativeValue, eta); 99: emit MultisigApproved(proposalHash, target, callData, nativeValue); 104: emit MultisigCancelled(proposalHash, target, callData, nativeValue);
File: InterchainGovernance.sol 78: emit ProposalExecuted(proposalHash, target, callData, nativeValue, block.timestamp);
File: InterchainGovernance.sol 130: emit ProposalScheduled(proposalHash, target, callData, nativeValue, eta); 135: emit ProposalCancelled(proposalHash, target, callData, nativeValue, eta);
File: Upgradable.sol 27: emit OwnershipTransferred(newOwner);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/util/Upgradable.sol#L27
File: Upgradable.sol 54: emit Upgraded(newImplementation);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/util/Upgradable.sol#L54
File: ConstAddressDeployer.sol 90: emit Deployed(keccak256(bytecode), salt, deployedAddress_);
File: Upgradable.sol 66: emit Upgraded(newImplementation);
File: InterchainProposalExecutor.sol 66: emit ProposalExecuted(keccak256(abi.encode(sourceChain, sourceAddress, interchainProposalCaller, payload)));
File: InterchainProposalExecutor.sol 98: emit WhitelistedProposalCallerSet(sourceChain, sourceCaller, whitelisted);
File: InterchainProposalExecutor.sol 113: emit WhitelistedProposalSenderSet(sourceChain, sourceSender, whitelisted);
File: InterchainTokenService.sol 351: emit CustomTokenIdClaimed(tokenId, deployer_, salt);
File: InterchainTokenService.sol 375: emit CustomTokenIdClaimed(tokenId, deployer_, salt);
File: InterchainTokenService.sol 514: emit TokenSent(tokenId, destinationChain, destinationAddress, amount); 522: emit TokenSentWithData(tokenId, destinationChain, destinationAddress, amount, sourceAddress, metadata);
File: InterchainTokenService.sol 611: emit TokenReceived(tokenId, sourceChain, destinationAddress, amount);
File: InterchainTokenService.sol 659: emit TokenReceivedWithData(tokenId, sourceChain, destinationAddress, amount, sourceAddress, data);
File: InterchainTokenService.sol 757: emit RemoteTokenManagerDeploymentInitialized(tokenId, destinationChain, gasValue, tokenManagerType, params);
File: InterchainTokenService.sol 819: emit TokenManagerDeployed(tokenId, tokenManagerType, params);
File: InterchainTokenService.sol 869: emit StandardizedTokenDeployed(tokenId, name, symbol, decimals, mintAmount, mintTo);
File: RemoteAddressValidator.sol 88: emit TrustedAddressAdded(chain, addr);
File: RemoteAddressValidator.sol 99: emit TrustedAddressRemoved(chain);
File: RemoteAddressValidator.sol 111: emit GatewaySupportedChainAdded(chainName);
File: RemoteAddressValidator.sol 124: emit GatewaySupportedChainRemoved(chainName);
File: Pausable.sol 15: emit TestEvent();
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/test/utils/Pausable.sol#L15
File: Distributable.sol 43: emit DistributorChanged(distributor_);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/utils/Distributable.sol#L43
File: ExpressCallHandler.sol 95: emit ExpressReceive(tokenId, destinationAddress, amount, commandId, expressCaller);
File: ExpressCallHandler.sol 137: emit ExpressReceiveWithData(tokenId, sourceChain, sourceAddress, destinationAddress, amount, data, commandId, expressCaller);
File: ExpressCallHandler.sol 216: emit ExpressExecutionFulfilled(tokenId, destinationAddress, amount, commandId, expressCaller);
File: FlowLimit.sol 38: emit FlowLimitSet(flowLimit);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/utils/FlowLimit.sol#L38
File: Operatable.sol 43: emit OperatorChanged(operator_);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/utils/Operatable.sol#L43
File: Pausable.sol 46: emit PausedSet(paused);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/utils/Pausable.sol#L46
</details>for
statements is more gas efficientCounting down is more gas efficient than counting up because neither we are making zero variable to non-zero variable and also we will get gas refund in the last transaction when making non-zero to zero variable.
File: AxelarGateway.sol 270: for (uint256 i; i < length; ++i) {
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L270
File: AxelarGateway.sol 344: for (uint256 i; i < commandsLength; ++i) {
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L344
File: MultisigBase.sol 122: for (uint256 i; i < length; ++i) {
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/auth/MultisigBase.sol#L122
File: MultisigBase.sol 153: for (uint256 i; i < length; ++i) {
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/auth/MultisigBase.sol#L153
File: InterchainProposalExecutor.sol 74: for (uint256 i = 0; i < calls.length; i++) {
File: InterchainProposalSender.sol 63: for (uint256 i = 0; i < interchainCalls.length; ) {
File: InterchainProposalSender.sol 106: for (uint256 i = 0; i < interchainCalls.length; ) {
File: InterchainTokenService.sol 537: for (uint256 i; i < length; ++i) {
File: RemoteAddressValidator.sol 44: for (uint256 i; i < length; ++i) {
File: RemoteAddressValidator.sol 56: for (uint256 i; i < length; i++) {
File: RemoteAddressValidator.sol 108: for (uint256 i; i < length; ++i) {
File: RemoteAddressValidator.sol 121: for (uint256 i; i < length; ++i) {
File: Multicall.sol 24: for (uint256 i = 0; i < data.length; ++i) {
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/utils/Multicall.sol#L24
</details>contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.AddNum(); c1.AddNum(); } } contract Contract0 { uint256 num = 3; function AddNum() public { uint256 _num = num; for(uint i=0;i<=9;i++){ _num = _num +1; } num = _num; } } contract Contract1 { uint256 num = 3; function AddNum() public { uint256 _num = num; for(uint i=9;i>=0;i--){ _num = _num +1; } num = _num; } }
Contract0 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
77011 | 311 | ||||
Function Name | min | avg | median | max | # calls |
AddNum | 7040 | 7040 | 7040 | 7040 | 1 |
Contract1 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
73811 | 295 | ||||
Function Name | min | avg | median | max | # calls |
AddNum | 3819 | 3819 | 3819 | 3819 | 1 |
assembly
to write address storage valuesFile: TimeLock.sol 22: _minimumTimeLockDelay = minimumTimeDelay;
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/gmp-sdk/util/TimeLock.sol#L22
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.
File: MultisigBase.sol 172: if (signers.isSigner[account]) revert DuplicateSigner(account); 175: signers.isSigner[account] = true;
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/auth/MultisigBase.sol#L172
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/auth/MultisigBase.sol#L175
File: AxelarServiceGovernance.sol 55: if (!multisigApprovals[proposalHash]) revert NotApproved(); 57: multisigApprovals[proposalHash] = false; 102: multisigApprovals[proposalHash] = false;
File: AxelarServiceGovernance.sol 97: multisigApprovals[proposalHash] = true;
Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot.
File: MultisigBase.sol 15: mapping(address => bool) hasVoted;
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/auth/MultisigBase.sol#L15
File: MultisigBase.sol 21: mapping(address => bool) isSigner;
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/auth/MultisigBase.sol#L21
File: InterchainProposalExecutor.sol 24: mapping(string => mapping(address => bool)) public whitelistedCallers;
File: InterchainProposalExecutor.sol 27: mapping(string => mapping(address => bool)) public whitelistedSenders;
Contracts most called functions could simply save gas by function ordering via Method ID. Calling a function at runtime will be cheaper if the function is positioned earlier in the order (has a relatively lower Method ID) because 22 gas are added to the cost of a function for every position that came before it. The caller can save on gas if you prioritize most called functions.
See more <a href="https://medium.com/joyso/solidity-how-does-function-name-affect-gas-consumption-in-smart-contract-47d270d8ac92">here</a>
All in-scope contracts
Find a lower method ID name for the most called functions for example Call() vs. Call1() is cheaper by 22 gas For example, the function IDs in the Gauge.sol contract will be the most used; A lower method ID may be given.
<x> += <y>
Costs More Gas Than <x> = <x> + <y>
For State VariablesFile: InterchainProposalSender.sol 107: totalGas += interchainCalls[i].gas;
External calls are expensive. Results of external function calls should be cached rather than call them multiple times. Consider caching the following:
File: AxelarGateway.sol 287: if (AxelarGateway(newImplementation).contractId() != contractId()) revert InvalidImplementation();
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L287
File: Upgradable.sol 45: if (IUpgradable(newImplementation).contractId() != IUpgradable(this).contractId()) revert InvalidImplementation();
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/util/Upgradable.sol#L45
File: BaseProxy.sol 49: calldatacopy(0, 0, calldatasize()) 51: let result := delegatecall(gas(), implementation_, 0, calldatasize(), 0, 0) 52: returndatacopy(0, 0, returndatasize()) 56: revert(0, returndatasize()) 59: return(0, returndatasize())
File: FixedProxy.sol 41: calldatacopy(0, 0, calldatasize()) 43: let result := delegatecall(gas(), implementation_, 0, calldatasize(), 0, 0) 44: returndatacopy(0, 0, returndatasize()) 48: revert(0, returndatasize()) 51: return(0, returndatasize())
File: InitProxy.sol 49: bytes32 id = contractId(); 50: if (id != bytes32(0) && IUpgradable(implementationAddress).contractId() != id) revert InvalidImplementation();
File: Upgradable.sol 57: if (IUpgradable(newImplementation).contractId() != IUpgradable(this).contractId()) revert InvalidImplementation();
File: InterchainToken.sol 21: function getTokenManager() public view virtual returns (ITokenManager tokenManager); 32: function tokenManagerRequiresApproval() public view virtual returns (bool); 50: ITokenManager tokenManager = getTokenManager(); 92: ITokenManager tokenManager = getTokenManager(); 54: if (tokenManagerRequiresApproval()) { 93: if (tokenManagerRequiresApproval()) {
</details>File: TokenManagerProxy.sol 76: calldatacopy(0, 0, calldatasize()) 78: let result := delegatecall(gas(), implementaion_, 0, calldatasize(), 0, 0) 79: returndatacopy(0, 0, returndatasize()) 83: revert(0, returndatasize()) 86: return(0, returndatasize())
block.number
and block.timestamp
are added to the event information by default, so adding them manually will waste additional gas.
File: IInterchainGovernance.sol 20: event ProposalExecuted(bytes32 indexed proposalHash, address indexed target, bytes callData, uint256 value, uint256 indexed timestamp);
File: IMultisigBase.sol 22: event SignersRotated(address[] newAccounts, uint256 newThreshold);
if
and avoid multiple check combinationsUsing nested if
, is cheaper than using &&
multiple check combinations. There are more advantages, such as easier to read code and better coverage reports.
File: AxelarGateway.sol 446: if (!success || (returnData.length != uint256(0) && !abi.decode(returnData, (bool)))) revert BurnFailed(symbol);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L446
File: AxelarGateway.sol 635: if (limit > 0 && amount > limit) revert ExceedMintLimit(symbol);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L635
File: InitProxy.sol 50: if (id != bytes32(0) && IUpgradable(implementationAddress).contractId() != id) revert InvalidImplementation();
File: RemoteAddressValidator.sol 58: if ((b >= 65) && (b <= 70)) bytes(s)[i] = bytes1(b + uint8(32));
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.checkAge(19); c1.checkAgeOptimized(19); } } contract Contract0 { function checkAge(uint8 _age) public returns(string memory){ if(_age>18 && _age<22){ return "Eligible"; } } } contract Contract1 { function checkAgeOptimized(uint8 _age) public returns(string memory){ if(_age>18){ if(_age<22){ return "Eligible"; } } } }
Contract0 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
76923 | 416 | ||||
Function Name | min | avg | median | max | # calls |
checkAge | 651 | 651 | 651 | 651 | 1 |
Contract1 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
76323 | 413 | ||||
Function Name | min | avg | median | max | # calls |
checkAgeOptimized | 645 | 645 | 645 | 645 | 1 |
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.However, it is much cheaper to use the bitwise OR operator (|) than comparing the truthy or falsy values.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.
File: AxelarGateway.sol 255: if (newGovernance == address(0)) revert InvalidGovernance();
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L255
File: AxelarGateway.sol 261: if (newMintLimiter == address(0)) revert InvalidMintLimiter();
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L261
File: AxelarGateway.sol 342: if (commandsLength != commands.length || commandsLength != params.length) revert InvalidCommands(); 352: if (commandHash == SELECTOR_DEPLOY_TOKEN) { 354: } else if (commandHash == SELECTOR_MINT_TOKEN) { 356: } else if (commandHash == SELECTOR_APPROVE_CONTRACT_CALL) { 358: } else if (commandHash == SELECTOR_APPROVE_CONTRACT_CALL_WITH_MINT) { 360: } else if (commandHash == SELECTOR_BURN_TOKEN) { 362: } else if (commandHash == SELECTOR_TRANSFER_OPERATORSHIP) {
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L342
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L352
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L354
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L356
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L358
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L360
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L362
File: AxelarGateway.sol 394: if (tokenAddress == address(0)) { 409: if (tokenAddress.code.length == uint256(0)) revert TokenContractDoesNotExist(tokenAddress);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L394
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L409
File: AxelarGateway.sol 432: if (tokenAddress == address(0)) revert TokenDoesNotExist(symbol);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L432
File: AxelarGateway.sol 519: if (tokenAddress == address(0)) revert TokenDoesNotExist(symbol);
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L519
File: AxelarGateway.sol 537: if (tokenAddress == address(0)) revert TokenDoesNotExist(symbol); 538: if (amount == 0) revert InvalidAmount(); 542: if (tokenType == TokenType.External) { 544: } else if (tokenType == TokenType.InternalBurnableFrom) {
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L537
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L538
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L542
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/AxelarGateway.sol#L544
File: MultisigBase.sol 161: if (newThreshold == 0) revert InvalidSignerThreshold(); 173: if (account == address(0)) revert InvalidSigners();
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/auth/MultisigBase.sol#L161
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/auth/MultisigBase.sol#L173
File: AxelarServiceGovernance.sol 86: if (command == ServiceGovernanceCommand.ScheduleTimeLockProposal) { 91: } else if (command == ServiceGovernanceCommand.CancelTimeLockProposal) { 96: } else if (command == ServiceGovernanceCommand.ApproveMultisigProposal) { 101: } else if (command == ServiceGovernanceCommand.CancelMultisigApproval) {
File: InterchainGovernance.sol 92: if (keccak256(bytes(sourceChain)) != governanceChainHash || keccak256(bytes(sourceAddress)) != governanceAddressHash) 100: if (target == address(0)) revert InvalidTarget();
File: InterchainGovernance.sol 127: if (command == GovernanceCommand.ScheduleTimeLockProposal) { 132: } else if (command == GovernanceCommand.CancelTimeLockProposal) {
File: Upgradable.sol 25: if (newOwner == address(0)) revert InvalidOwner();
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/util/Upgradable.sol#L25
File: ConstAddressDeployer.sol 81: if (bytecode.length == 0) revert EmptyBytecode(); 88: if (deployedAddress_ == address(0)) revert FailedDeploy();
File: FinalProxy.sol 39: if (implementation_ == address(0)) {
File: FinalProxy.sol 63: if (implementation_.code.length == 0) implementation_ = address(0);
File: Bytes32String.sol 13: if (stringBytes.length == 0 || stringBytes.length > 31) revert InvalidStringLength();
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/gmp-sdk/util/Bytes32String.sol#L13
File: TimeLock.sol 51: if (hash == 0) revert InvalidTimeLockHash();
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/gmp-sdk/util/TimeLock.sol#L51
File: TimeLock.sol 68: if (hash == 0) revert InvalidTimeLockHash();
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/gmp-sdk/util/TimeLock.sol#L68
File: TimeLock.sol 82: if (hash == 0 || eta == 0) revert InvalidTimeLockHash();
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/gmp-sdk/util/TimeLock.sol#L82
File: InterchainTokenService.sol 399: TokenManagerType tokenManagerType = distributor == tokenManagerAddress ? TokenManagerType.MINT_BURN : TokenManagerType.LOCK_UNLOCK;
File: InterchainTokenService.sol 565: if (implementation == address(0)) revert ZeroAddress();
File: InterchainTokenService.sol 581: if (selector == SELECTOR_SEND_TOKEN) { 583: } else if (selector == SELECTOR_SEND_TOKEN_WITH_DATA) { 585: } else if (selector == SELECTOR_DEPLOY_TOKEN_MANAGER) { 587: } else if (selector == SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN) {
File: InterchainTokenService.sol 609: if (expressCaller == address(0)) {
File: InterchainTokenService.sol 692: TokenManagerType tokenManagerType = distributor == tokenManagerAddress ? TokenManagerType.MINT_BURN : TokenManagerType.LOCK_UNLOCK; 696: abi.encode(operatorBytes.length == 0 ? address(this).toBytes() : operatorBytes, tokenAddress)
File: RemoteAddressValidator.sol 72: if (sourceAddressHash == interchainTokenServiceAddressHash) { 75: return sourceAddressHash == remoteAddressHashes[sourceChain];
File: RemoteAddressValidator.sol 84: if (bytes(chain).length == 0) revert ZeroStringLength(); 85: if (bytes(addr).length == 0) revert ZeroStringLength();
File: RemoteAddressValidator.sol 96: if (bytes(chain).length == 0) revert ZeroStringLength();
File: RemoteAddressValidator.sol 135: if (bytes(remoteAddress).length == 0) {
File: TokenManager.sol 68: if (operatorBytes.length == 0) {
File: FlowLimit.sol 113: if (flowLimit == 0) return;
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/utils/FlowLimit.sol#L113
File: FlowLimit.sol 126: if (flowLimit == 0) return;
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/its/utils/FlowLimit.sol#L126
File: StandardizedTokenDeployer.sol 60: address implementationAddress = distributor == tokenManager ? implementationMintBurnAddress : implementationLockUnlockAddress; 66: if (tokenAddress.code.length == 0) revert TokenDeploymentFailed();
</details>File: TokenManagerDeployer.sol 41: if (tokenManagerAddress.code.length == 0) revert TokenManagerDeploymentFailed();
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.not_optimized(1,2); c1.optimized(1,2); } } contract Contract0 { function not_optimized(uint8 a,uint8 b) public returns(bool){ return ((a==1) || (b==1)); } } contract Contract1 { function optimized(uint8 a,uint8 b) public returns(bool){ return ((a ^ 1) & (b ^ 1)) == 0; } }
Contract0 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
46099 | 261 | ||||
Function Name | min | avg | median | max | # calls |
not_optimized | 456 | 456 | 456 | 456 | 1 |
Contract1 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
42493 | 243 | ||||
Function Name | min | avg | median | max | # calls |
optimized | 430 | 430 | 430 | 430 | 1 |
this.<fn>()
wastes gasCalling an external function internally, through the use of this
wastes the gas overhead of calling an external function (100 gas).
File: Upgradable.sol 49: (bool success, ) = newImplementation.delegatecall(abi.encodeWithSelector(this.setup.selector, params));
https://github.com/code-423n4/2023-07-axelar/tree/main/contracts/cgp/util/Upgradable.sol#L49
File: Upgradable.sol 61: (bool success, ) = newImplementation.delegatecall(abi.encodeWithSelector(this.setup.selector, params));
#0 - c4-pre-sort
2023-07-29T01:14:38Z
0xSorryNotSorry marked the issue as high quality report
#1 - c4-judge
2023-09-04T19:37:45Z
berndartmueller marked the issue as grade-a