Axelar Network contest - IllIllI's results

Decentralized interoperability network.

General Information

Platform: Code4rena

Start Date: 07/04/2022

Pot Size: $50,000 USDC

Total HM: 5

Participants: 19

Period: 5 days

Judge: 0xean

Total Solo HM: 4

Id: 109

League: COSMOS

Axelar Network

Findings Distribution

Researcher Performance

Rank: 6/19

Findings: 2

Award: $2,136.00

🌟 Selected for report: 2

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: IllIllI

Also found by: CertoraInc, Dravee, Funen, cccz, delfin454000, dirk_y, ilan, rayn, rishabh

Labels

bug
QA (Quality Assurance)

Awards

1485.8239 USDC - $1,485.82

External Links

Low Risk Issues

Cross-chain replay attacks

Storing the block.chainid is not safe. See this issue from a prior contest for details.

  1. File: src/ERC20Permit.sol (lines 23-28)
        DOMAIN_SEPARATOR = keccak256(
            abi.encode(
                DOMAIN_TYPE_SIGNATURE_HASH,
                keccak256(bytes(name)),
                keccak256(bytes('1')),
                block.chainid,

Incorrect EIP-2612 deadline check

allow transferFrom to occur while expiry >= block.timestamp.

https://github.com/ethereum/EIPs/blob/1473025f064929bfab405eb00b8cd16dd741f269/EIPS/eip-2612.md?plain=1#L172 The current code should change to use <=

  1. File: src/ERC20Permit.sol (line 43)
        require(block.timestamp < deadline, 'EXPIRED');

Unumplemented functions should have the default behavior of revert()ing rather than allowing operations to go through

  1. File: src/interfaces/IAxelarExecutable.sol (lines 55-67)
    function _execute(
        string memory sourceChain,
        string memory sourceAddress,
        bytes calldata payload
    ) internal virtual {}

    function _executeWithToken(
        string memory sourceChain,
        string memory sourceAddress,
        bytes calldata payload,
        string memory tokenSymbol,
        uint256 amount
    ) internal virtual {}

Missing checks for address(0x0) when assigning values to address state variables

  1. File: src/AxelarGateway.sol (line 67)
        TOKEN_DEPLOYER_IMPLEMENTATION = tokenDeployerImplementation;

Open TODOs

Code architecture, incentives, and error handling/reporting questions/issues should be resolved before deployment

  1. File: src/MintableCappedERC20.sol (line 30)
    // TODO move burnFrom into a separate BurnableERC20 contract

Missing contract-existence checks before low-level calls

Low-level calls return success if there is no code present at the specified address. In addition to the zero-address checks, add a check to verify that <address>.code.length > 0

  1. File: src/AxelarGateway.sol (lines 398-415)
        if (tokenAddress == address(0)) revert TokenDoesNotExist(symbol);

        if (_getTokenType(symbol) == TokenType.External) {
            _checkTokenStatus(symbol);

            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);
  1. File: src/AxelarGatewayProxy.sol (line 19)
        (bool success, ) = gatewayImplementation.delegatecall(
  1. File: src/AxelarGatewayProxy.sol (line 34)
            let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
  1. File: src/AxelarGateway.sol (line 350)
            (bool success, bytes memory data) = TOKEN_DEPLOYER_IMPLEMENTATION.delegatecall(

Non-critical Issues

require()/revert() statements should have descriptive reason strings

  1. File: src/DepositHandler.sol (line 12)
        require(_lockedStatus == IS_NOT_LOCKED);

public functions not called by the contract should be declared external instead

Contracts are allowed to override their parents' functions and change the visibility from external to public.

  1. File: src/BurnableMintableCappedERC20.sol (line 48)
    function burn(bytes32 salt) public onlyOwner {
  1. File: src/MintableCappedERC20.sol (line 23)
    function mint(address account, uint256 amount) public onlyOwner {

constants should be defined rather than using magic numbers

  1. File: src/BurnableMintableCappedERC20.sol (line 37)
                                bytes1(0xff),
  1. File: src/ECDSA.sol (line 33)
        if (signature.length != 65) revert InvalidSignatureLength();
  1. File: src/ECDSA.sol (line 58)
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) revert InvalidS();
  1. File: src/ECDSA.sol (line 60)
        if (v != 27 && v != 28) revert InvalidV();
  1. File: src/ECDSA.sol (line 60)
        if (v != 27 && v != 28) revert InvalidV();
  1. File: src/ERC20Permit.sol (line 44)
        require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, 'INV_S');
  1. File: src/ERC20Permit.sol (line 45)
        require(v == 27 || v == 28, 'INV_V');
  1. File: src/ERC20Permit.sol (line 45)
        require(v == 27 || v == 28, 'INV_V');

Use a more recent version of solidity

Use a solidity version of at least 0.8.12 to get string.concat() to be used instead of abi.encodePacked(<str>,<str>)

  1. File: src/AxelarGateway.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/AdminMultisigBase.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/AxelarGatewayMultisig.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/BurnableMintableCappedERC20.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/ECDSA.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/ERC20Permit.sol (line 3)
pragma solidity 0.8.9;

Multiple address mappings can be combined into a single mapping of an address to a struct, where appropriate

  1. File: src/ERC20.sol (lines 34-36)
    mapping(address => uint256) public override balanceOf;

    mapping(address => mapping(address => uint256)) public override allowance;

Variable names that consist of all capital letters should be reserved for const/immutable variables

If the variable needs to be different based on which class it comes from, a view/pure function should be used instead (e.g. like this).

  1. File: src/ERC20Permit.sol (line 8)
    bytes32 public DOMAIN_SEPARATOR;

File is missing NatSpec

  1. File: src/AdminMultisigBase.sol (line 0)
// SPDX-License-Identifier: MIT
  1. File: src/DepositHandler.sol (line 0)
// SPDX-License-Identifier: MIT
  1. File: src/BurnableMintableCappedERC20.sol (line 0)
// SPDX-License-Identifier: MIT
  1. File: src/Ownable.sol (line 0)
// SPDX-License-Identifier: MIT
  1. File: src/TokenDeployer.sol (line 0)
// SPDX-License-Identifier: MIT
  1. File: src/interfaces/IAxelarGateway.sol (line 0)
// SPDX-License-Identifier: MIT
  1. File: src/interfaces/IAxelarGatewayMultisig.sol (line 0)
// SPDX-License-Identifier: MIT
  1. File: src/interfaces/IERC20BurnFrom.sol (line 0)
// SPDX-License-Identifier: MIT
  1. File: src/interfaces/IAxelarExecutable.sol (line 0)
// SPDX-License-Identifier: MIT
  1. File: src/MintableCappedERC20.sol (line 0)
// SPDX-License-Identifier: MIT
  1. File: src/ERC20Permit.sol (line 0)
// SPDX-License-Identifier: MIT

Event is missing indexed fields

Each event should use three indexed fields if there are three or more fields

  1. File: src/interfaces/IAxelarGateway.sol (lines 10-16)
    event TokenSent(
        address indexed sender,
        string destinationChain,
        string destinationAddress,
        string symbol,
        uint256 amount
    );
  1. File: src/interfaces/IAxelarGateway.sol (lines 18-24)
    event ContractCall(
        address indexed sender,
        string destinationChain,
        string destinationContractAddress,
        bytes32 indexed payloadHash,
        bytes payload
    );
  1. File: src/interfaces/IAxelarGateway.sol (lines 26-34)
    event ContractCallWithToken(
        address indexed sender,
        string destinationChain,
        string destinationContractAddress,
        bytes32 indexed payloadHash,
        bytes payload,
        string symbol,
        uint256 amount
    );
  1. File: src/interfaces/IAxelarGateway.sol (line 38)
    event TokenDeployed(string symbol, address tokenAddresses);
  1. File: src/interfaces/IAxelarGateway.sol (line 62)
    event TokenFrozen(string symbol);
  1. File: src/interfaces/IAxelarGateway.sol (line 64)
    event TokenUnfrozen(string symbol);
  1. File: src/interfaces/IAxelarGatewayMultisig.sol (line 8)
    event OwnershipTransferred(address[] preOwners, uint256 prevThreshold, address[] newOwners, uint256 newThreshold);
  1. File: src/interfaces/IAxelarGatewayMultisig.sol (lines 10-15)
    event OperatorshipTransferred(
        address[] preOperators,
        uint256 prevThreshold,
        address[] newOperators,
        uint256 newThreshold
    );
  1. File: src/interfaces/IERC20.sol (line 74)
    event Transfer(address indexed from, address indexed to, uint256 value);
  1. File: src/interfaces/IERC20.sol (line 80)
    event Approval(address indexed owner, address indexed spender, uint256 value);

Unsafe early return from a modifier

If the modifier is used with a function that has a named return, the default value will be returned which may lead to confusing behavior. Consider using a function instead of a modifier

  1. File: src/AdminMultisigBase.sol (lines 42-44)
        if (adminVoteCount < _getAdminThreshold(adminEpoch)) return;

        _;

Consider allowing infinite approval

Doing what OpenZeppelin does and considering type(uint256).max to be infinite approval may help users to create smaller transactions.

  1. File: src/ERC20.sol (line 105)
        _approve(sender, _msgSender(), allowance[sender][_msgSender()] - amount);

Consider using a two-step-transfer of ownership

The current owner would nominate a new owner, and to become the new owner, the nominated account would have to approve the change, so that the address is proven to be valid

  1. File: src/Ownable.sol (line 20)
    function transferOwnership(address newOwner) public virtual onlyOwner {

Consider adding a comment saying that EIP-2098 short signatures are not supported

  1. File: src/ECDSA.sol (line 33)
        if (signature.length != 65) revert InvalidSignatureLength();

#0 - deluca-mike

2022-04-20T09:38:18Z

Cross-chain replay attacks

Acknowledged, since just as that linked finding suggested, if Ethereum forks we will not use any minority fork, since we are dealing with (bridging) assets that cannot be split.

Incorrect EIP-2612 deadline check

Confirmed.

Unumplemented functions should have the default behavior of revert()ing rather than allowing operations to go through

Disputed. They are no unimplemented, they are virtual and meant to be overridden, similar to _beforeTokenTransfer in ERC20.

Missing checks for address(0x0) when assigning values to address state variables

Confirmed. Will correct by checking code length of the address, which will also help with a delegatcall elsewhere in the contract.

Open TODOs

Confirmed, will remove TODO and move the fucntion as noted.

Missing contract-existence checks before low-level calls

  1. File: src/AxelarGateway.sol (lines 398-415) Disputed since that is not a low-level call, despite looking similar to one.

  2. File: src/AxelarGatewayProxy.sol (line 19) Acknowledged, but given that it is a highly-used delegatecall as part of the proxy pattern, there is no point checking if the contract exists. If it does not, we have much much larger problems.

  3. File: src/AxelarGatewayProxy.sol (line 34) As above, acknowledged, but given that it is a highly-used delegatecall as part of the proxy pattern, there is no point checking if the contract exists. If it does not, we have much much larger problems.

  4. File: src/AxelarGateway.sol (line 350) Confirmed, but we will solve this but doing a if (tokenDeployerImplementation.code.length == 0) revert InvalidTokenDeployer(); in the gateway's constructor, to prevent it form ever being initialized with an invalid TokenDeployer.

require()/revert() statements should have descriptive reason strings

Confirmed, and will be fixed with adopting if-revert with custom error messages in place of all requires.

public functions not called by the contract should be declared external instead

Confirmed.

constants should be defined rather than using magic numbers

Acknowledged, but these were intentional so they could be compared to (Solidity Docs)[https://docs.soliditylang.org/en/v0.8.9/control-structures.html?highlight=bytes1(0xff)#salted-contract-creations-create2] and standard ECDSA implementations.

Use a more recent version of solidity

Acknowledged, but these were all tested on 0.8.9, and this is locked in for release. The next release will take latest Solidity available at the time testing is started.

Multiple address mappings can be combined into a single mapping of an address to a struct, where appropriate

Disputed. While true, and would save gas, this would e a deviation from the ERC20 standard, and add complexity (in terms of standards and readability) for minimal gain.

Variable names that consist of all capital letters should be reserved for const/immutable variables

Confirmed. That variable should have been, and will be, made immutable.

File is missing NatSpec

Acknowledged, but more documentation will come in subsequent releases.

Each event should use three indexed fields if there are three or more fields

Acknowledged. Indexed fields actually cost a bit more gas, and should only be reserved for data that can be known ahead of time. Further, it is not yet clear which fields should be indexed, so they will only be indexed at a later release when we gather requirements. However, one should never index fields whose possible values are not part of some reasonable set (i.e. amount).

Unsafe early return from a modifier

Confirmed, however since all functions that currently use this modifier do not return data, we will just leave a comment above the modifier noting that it should be used with care.

Consider allowing infinite approval

Confirmed.

Consider adding a comment saying that EIP-2098 short signatures are not supported

Acknowledged, but such a comment would be misplaced here, and would be better in normal documentation.

#1 - 0xean

2022-05-12T19:17:08Z

Severities proposed by warden seem correct with the exception of

Unumplemented functions should have the default behavior of revert()ing rather than allowing operations to go through which is invalid as stated by the sponsor.

#2 - liveactionllama

2022-06-13T06:19:59Z

Just a note that C4 is excluding the invalid entry (as noted by the judge above) from the official report.

Findings Information

🌟 Selected for report: IllIllI

Also found by: 0v3rf10w, 0xNazgul, 0xkatana, CertoraInc, Chom, Dravee, Funen, Hawkeye, Tomio, ilan, nahnah, rayn, rfa

Labels

bug
G (Gas Optimization)

Awards

650.1836 USDC - $650.18

External Links

Using 1 and 2 rather than 0 and 1 saves gas

See this issue from a prior contest for details

  1. File: src/DepositHandler.sol (lines 6-7)
    uint256 internal constant IS_NOT_LOCKED = uint256(0);
    uint256 internal constant IS_LOCKED = uint256(1);

Not using the named return variables when a function returns, wastes deployment gas

  1. File: src/Context.sol (line 17)
        return payable(msg.sender);

Use a more recent version of solidity

Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value

  1. File: src/AxelarGateway.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/ERC20.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/AdminMultisigBase.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/Context.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/DepositHandler.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/AxelarGatewayMultisig.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/BurnableMintableCappedERC20.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/Ownable.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/EternalStorage.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/TokenDeployer.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/ECDSA.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/interfaces/IAxelarGateway.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/interfaces/IAxelarGatewayMultisig.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/interfaces/IERC20BurnFrom.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/interfaces/IAxelarExecutable.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/interfaces/IERC20.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/AxelarGatewayProxy.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/MintableCappedERC20.sol (line 3)
pragma solidity 0.8.9;
  1. File: src/ERC20Permit.sol (line 3)
pragma solidity 0.8.9;

Using bools for storage incurs overhead

    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27

  1. File: src/EternalStorage.sol (line 14)
    mapping(bytes32 => bool) private _boolStorage;

<array>.length should not be looked up in every loop of a for-loop

Even memory arrays incur the overhead of bit tests and bit shifts to calculate the array length

  1. File: src/AxelarGatewayMultisig.sol (line 118)
        for (uint256 i; i < accounts.length; i++) {
  1. File: src/AxelarGatewayMultisig.sol (line 271)
        for (uint256 i; i < accounts.length; i++) {

Using calldata instead of memory for read-only arguments in external functions saves gas

  1. File: src/AxelarGateway.sol (line 81)
        string memory destinationChain,
  1. File: src/AxelarGateway.sol (line 82)
        string memory destinationAddress,
  1. File: src/AxelarGateway.sol (line 83)
        string memory symbol,
  1. File: src/AxelarGateway.sol (line 91)
        string memory destinationChain,
  1. File: src/AxelarGateway.sol (line 92)
        string memory destinationContractAddress,
  1. File: src/AxelarGateway.sol (line 93)
        bytes memory payload
  1. File: src/AxelarGateway.sol (line 99)
        string memory destinationChain,
  1. File: src/AxelarGateway.sol (line 100)
        string memory destinationContractAddress,
  1. File: src/AxelarGateway.sol (line 101)
        bytes memory payload,
  1. File: src/AxelarGateway.sol (line 102)
        string memory symbol,
  1. File: src/AxelarGateway.sol (line 119)
        string memory sourceChain,
  1. File: src/AxelarGateway.sol (line 120)
        string memory sourceAddress,
  1. File: src/AxelarGateway.sol (line 130)
        string memory sourceChain,
  1. File: src/AxelarGateway.sol (line 131)
        string memory sourceAddress,
  1. File: src/AxelarGateway.sol (line 134)
        string memory symbol,
  1. File: src/AxelarGateway.sol (line 153)
        string memory sourceChain,
  1. File: src/AxelarGateway.sol (line 154)
        string memory sourceAddress,
  1. File: src/AxelarGateway.sol (line 164)
        string memory sourceChain,
  1. File: src/AxelarGateway.sol (line 165)
        string memory sourceAddress,
  1. File: src/AxelarGateway.sol (line 167)
        string memory symbol,
  1. File: src/AxelarGateway.sol (line 234)
    function freezeToken(string memory symbol) external override onlyAdmin {
  1. File: src/AxelarGateway.sol (line 240)
    function unfreezeToken(string memory symbol) external override onlyAdmin {
  1. File: src/TokenDeployer.sol (line 9)
        string memory name,
  1. File: src/TokenDeployer.sol (line 10)
        string memory symbol,
  1. File: src/interfaces/IAxelarGateway.sol (line 81)
        string memory destinationChain,
  1. File: src/interfaces/IAxelarGateway.sol (line 82)
        string memory destinationAddress,
  1. File: src/interfaces/IAxelarGateway.sol (line 83)
        string memory symbol,
  1. File: src/interfaces/IAxelarGateway.sol (line 88)
        string memory destinationChain,
  1. File: src/interfaces/IAxelarGateway.sol (line 89)
        string memory contractAddress,
  1. File: src/interfaces/IAxelarGateway.sol (line 90)
        bytes memory payload
  1. File: src/interfaces/IAxelarGateway.sol (line 94)
        string memory destinationChain,
  1. File: src/interfaces/IAxelarGateway.sol (line 95)
        string memory contractAddress,
  1. File: src/interfaces/IAxelarGateway.sol (line 96)
        bytes memory payload,
  1. File: src/interfaces/IAxelarGateway.sol (line 97)
        string memory symbol,
  1. File: src/interfaces/IAxelarGateway.sol (line 103)
        string memory sourceChain,
  1. File: src/interfaces/IAxelarGateway.sol (line 104)
        string memory sourceAddress,
  1. File: src/interfaces/IAxelarGateway.sol (line 111)
        string memory sourceChain,
  1. File: src/interfaces/IAxelarGateway.sol (line 112)
        string memory sourceAddress,
  1. File: src/interfaces/IAxelarGateway.sol (line 115)
        string memory symbol,
  1. File: src/interfaces/IAxelarGateway.sol (line 121)
        string memory sourceChain,
  1. File: src/interfaces/IAxelarGateway.sol (line 122)
        string memory sourceAddress,
  1. File: src/interfaces/IAxelarGateway.sol (line 128)
        string memory sourceChain,
  1. File: src/interfaces/IAxelarGateway.sol (line 129)
        string memory sourceAddress,
  1. File: src/interfaces/IAxelarGateway.sol (line 131)
        string memory symbol,
  1. File: src/interfaces/IAxelarGateway.sol (line 143)
    function tokenAddresses(string memory symbol) external view returns (address);
  1. File: src/interfaces/IAxelarGateway.sol (line 145)
    function tokenFrozen(string memory symbol) external view returns (bool);
  1. File: src/interfaces/IAxelarGateway.sol (line 159)
    function freezeToken(string memory symbol) external;
  1. File: src/interfaces/IAxelarGateway.sol (line 161)
    function unfreezeToken(string memory symbol) external;
  1. File: src/interfaces/IAxelarExecutable.sol (line 18)
        string memory sourceChain,
  1. File: src/interfaces/IAxelarExecutable.sol (line 19)
        string memory sourceAddress,
  1. File: src/interfaces/IAxelarExecutable.sol (line 30)
        string memory sourceChain,
  1. File: src/interfaces/IAxelarExecutable.sol (line 31)
        string memory sourceAddress,
  1. File: src/interfaces/IAxelarExecutable.sol (line 33)
        string memory tokenSymbol,

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

  1. File: src/AxelarGateway.sol (line 225)
        for (uint256 i; i < adminCount; i++) {
  1. File: src/AdminMultisigBase.sol (line 51)
        for (uint256 i; i < adminCount; i++) {
  1. File: src/AdminMultisigBase.sol (line 158)
        for (uint256 i; i < adminLength; i++) {
  1. File: src/AxelarGatewayMultisig.sol (line 42)
        for (uint256 i; i < accounts.length - 1; ++i) {
  1. File: src/AxelarGatewayMultisig.sol (line 118)
        for (uint256 i; i < accounts.length; i++) {
  1. File: src/AxelarGatewayMultisig.sol (line 140)
        for (uint256 i; i < ownerCount; i++) {
  1. File: src/AxelarGatewayMultisig.sol (line 181)
        for (uint256 i; i < accountLength; i++) {
  1. File: src/AxelarGatewayMultisig.sol (line 271)
        for (uint256 i; i < accounts.length; i++) {
  1. File: src/AxelarGatewayMultisig.sol (line 293)
        for (uint256 i; i < operatorCount; i++) {
  1. File: src/AxelarGatewayMultisig.sol (line 332)
        for (uint256 i; i < accountLength; i++) {
  1. File: src/AxelarGatewayMultisig.sol (line 495)
        for (uint256 i; i < signatureCount; i++) {
  1. File: src/AxelarGatewayMultisig.sol (line 526)
        for (uint256 i; i < commandsLength; i++) {

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

  1. File: src/AxelarGateway.sol (line 225)
        for (uint256 i; i < adminCount; i++) {
  1. File: src/AdminMultisigBase.sol (line 51)
        for (uint256 i; i < adminCount; i++) {
  1. File: src/AdminMultisigBase.sol (line 158)
        for (uint256 i; i < adminLength; i++) {
  1. File: src/AxelarGatewayMultisig.sol (line 118)
        for (uint256 i; i < accounts.length; i++) {
  1. File: src/AxelarGatewayMultisig.sol (line 140)
        for (uint256 i; i < ownerCount; i++) {
  1. File: src/AxelarGatewayMultisig.sol (line 181)
        for (uint256 i; i < accountLength; i++) {
  1. File: src/AxelarGatewayMultisig.sol (line 271)
        for (uint256 i; i < accounts.length; i++) {
  1. File: src/AxelarGatewayMultisig.sol (line 293)
        for (uint256 i; i < operatorCount; i++) {
  1. File: src/AxelarGatewayMultisig.sol (line 332)
        for (uint256 i; i < accountLength; i++) {
  1. File: src/AxelarGatewayMultisig.sol (line 495)
        for (uint256 i; i < signatureCount; i++) {
  1. File: src/AxelarGatewayMultisig.sol (line 526)
        for (uint256 i; i < commandsLength; i++) {

Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead

When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.

https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Use a larger size then downcast where needed

  1. File: src/AxelarGateway.sol (line 62)
    uint8 internal constant OLD_KEY_RETENTION = 16;
  1. File: src/AxelarGateway.sol (line 339)
        uint8 decimals,
  1. File: src/ERC20.sol (line 43)
    uint8 public immutable decimals;
  1. File: src/ERC20.sol (line 54)
        uint8 decimals_
  1. File: src/AxelarGatewayMultisig.sol (line 363)
        (string memory name, string memory symbol, uint8 decimals, uint256 cap, address tokenAddr) = abi.decode(
  1. File: src/BurnableMintableCappedERC20.sol (line 24)
        uint8 decimals,
  1. File: src/TokenDeployer.sol (line 11)
        uint8 decimals,
  1. File: src/ECDSA.sol (line 38)
        uint8 v;
  1. File: src/MintableCappedERC20.sol (line 17)
        uint8 decimals,
  1. File: src/ERC20Permit.sol (line 39)
        uint8 v,

Expressions for constant values such as a call to keccak256(), should use immutable rather than constant

See this issue for a detail description of the issue

  1. File: src/AxelarGateway.sol (line 45)
    bytes32 internal constant KEY_ALL_TOKENS_FROZEN = keccak256('all-tokens-frozen');
  1. File: src/AxelarGateway.sol (line 47)
    bytes32 internal constant PREFIX_COMMAND_EXECUTED = keccak256('command-executed');
  1. File: src/AxelarGateway.sol (line 48)
    bytes32 internal constant PREFIX_TOKEN_ADDRESS = keccak256('token-address');
  1. File: src/AxelarGateway.sol (line 49)
    bytes32 internal constant PREFIX_TOKEN_TYPE = keccak256('token-type');
  1. File: src/AxelarGateway.sol (line 50)
    bytes32 internal constant PREFIX_TOKEN_FROZEN = keccak256('token-frozen');
  1. File: src/AxelarGateway.sol (line 51)
    bytes32 internal constant PREFIX_CONTRACT_CALL_APPROVED = keccak256('contract-call-approved');
  1. File: src/AxelarGateway.sol (line 52)
    bytes32 internal constant PREFIX_CONTRACT_CALL_APPROVED_WITH_MINT = keccak256('contract-call-approved-with-mint');
  1. File: src/AxelarGateway.sol (line 54)
    bytes32 internal constant SELECTOR_BURN_TOKEN = keccak256('burnToken');
  1. File: src/AxelarGateway.sol (line 55)
    bytes32 internal constant SELECTOR_DEPLOY_TOKEN = keccak256('deployToken');
  1. File: src/AxelarGateway.sol (line 56)
    bytes32 internal constant SELECTOR_MINT_TOKEN = keccak256('mintToken');
  1. File: src/AxelarGateway.sol (line 57)
    bytes32 internal constant SELECTOR_APPROVE_CONTRACT_CALL = keccak256('approveContractCall');
  1. File: src/AxelarGateway.sol (line 58)
    bytes32 internal constant SELECTOR_APPROVE_CONTRACT_CALL_WITH_MINT = keccak256('approveContractCallWithMint');
  1. File: src/AxelarGateway.sol (line 59)
    bytes32 internal constant SELECTOR_TRANSFER_OPERATORSHIP = keccak256('transferOperatorship');
  1. File: src/AxelarGateway.sol (line 60)
    bytes32 internal constant SELECTOR_TRANSFER_OWNERSHIP = keccak256('transferOwnership');
  1. File: src/AdminMultisigBase.sol (line 16)
    bytes32 internal constant KEY_ADMIN_EPOCH = keccak256('admin-epoch');
  1. File: src/AdminMultisigBase.sol (line 18)
    bytes32 internal constant PREFIX_ADMIN = keccak256('admin');
  1. File: src/AdminMultisigBase.sol (line 19)
    bytes32 internal constant PREFIX_ADMIN_COUNT = keccak256('admin-count');
  1. File: src/AdminMultisigBase.sol (line 20)
    bytes32 internal constant PREFIX_ADMIN_THRESHOLD = keccak256('admin-threshold');
  1. File: src/AdminMultisigBase.sol (line 21)
    bytes32 internal constant PREFIX_ADMIN_VOTE_COUNTS = keccak256('admin-vote-counts');
  1. File: src/AdminMultisigBase.sol (line 22)
    bytes32 internal constant PREFIX_ADMIN_VOTED = keccak256('admin-voted');
  1. File: src/AdminMultisigBase.sol (line 23)
    bytes32 internal constant PREFIX_IS_ADMIN = keccak256('is-admin');
  1. File: src/AxelarGatewayMultisig.sol (line 25)
    bytes32 internal constant KEY_OWNER_EPOCH = keccak256('owner-epoch');
  1. File: src/AxelarGatewayMultisig.sol (line 27)
    bytes32 internal constant PREFIX_OWNER = keccak256('owner');
  1. File: src/AxelarGatewayMultisig.sol (line 28)
    bytes32 internal constant PREFIX_OWNER_COUNT = keccak256('owner-count');
  1. File: src/AxelarGatewayMultisig.sol (line 29)
    bytes32 internal constant PREFIX_OWNER_THRESHOLD = keccak256('owner-threshold');
  1. File: src/AxelarGatewayMultisig.sol (line 30)
    bytes32 internal constant PREFIX_IS_OWNER = keccak256('is-owner');
  1. File: src/AxelarGatewayMultisig.sol (line 32)
    bytes32 internal constant KEY_OPERATOR_EPOCH = keccak256('operator-epoch');
  1. File: src/AxelarGatewayMultisig.sol (line 34)
    bytes32 internal constant PREFIX_OPERATOR = keccak256('operator');
  1. File: src/AxelarGatewayMultisig.sol (line 35)
    bytes32 internal constant PREFIX_OPERATOR_COUNT = keccak256('operator-count');
  1. File: src/AxelarGatewayMultisig.sol (line 36)
    bytes32 internal constant PREFIX_OPERATOR_THRESHOLD = keccak256('operator-threshold');
  1. File: src/AxelarGatewayMultisig.sol (line 37)
    bytes32 internal constant PREFIX_IS_OPERATOR = keccak256('is-operator');

Duplicated require()/revert() checks should be refactored to a modifier or function

  1. File: src/ERC20.sol (line 205)
        require(account != address(0), 'ZERO_ADDR');

State variables only set in the constructor should be declared immutable

  1. File: src/ERC20.sol (line 40)
    string public name;
  1. File: src/ERC20.sol (line 41)
    string public symbol;
  1. File: src/MintableCappedERC20.sol (line 12)
    uint256 public cap;

Use custom errors rather than revert()/require() strings to save deployment gas

  1. File: src/ERC20.sol (Various lines throughout the file)
  2. File: src/DepositHandler.sol (Various lines throughout the file)
  3. File: src/BurnableMintableCappedERC20.sol (Various lines throughout the file)
  4. File: src/Ownable.sol (Various lines throughout the file)
  5. File: src/AxelarGatewayProxy.sol (Various lines throughout the file)
  6. File: src/MintableCappedERC20.sol (Various lines throughout the file)
  7. File: src/ERC20Permit.sol (Various lines throughout the file)

Functions guaranteed to revert when called by normal users can be marked payable

If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided.

  1. File: src/AxelarGateway.sol (line 234)
    function freezeToken(string memory symbol) external override onlyAdmin {
  1. File: src/AxelarGateway.sol (line 240)
    function unfreezeToken(string memory symbol) external override onlyAdmin {
  1. File: src/AxelarGateway.sol (line 246)
    function freezeAllTokens() external override onlyAdmin {
  1. File: src/AxelarGateway.sol (line 252)
    function unfreezeAllTokens() external override onlyAdmin {
  1. File: src/AxelarGateway.sol (lines 258-262)
    function upgrade(
        address newImplementation,
        bytes32 newImplementationCodeHash,
        bytes calldata setupParams
    ) external override onlyAdmin {
  1. File: src/AxelarGatewayMultisig.sol (line 362)
    function deployToken(bytes calldata params, bytes32) external onlySelf {
  1. File: src/AxelarGatewayMultisig.sol (line 371)
    function mintToken(bytes calldata params, bytes32) external onlySelf {
  1. File: src/AxelarGatewayMultisig.sol (line 377)
    function burnToken(bytes calldata params, bytes32) external onlySelf {
  1. File: src/AxelarGatewayMultisig.sol (line 383)
    function approveContractCall(bytes calldata params, bytes32 commandId) external onlySelf {
  1. File: src/AxelarGatewayMultisig.sol (line 404)
    function approveContractCallWithMint(bytes calldata params, bytes32 commandId) external onlySelf {
  1. File: src/AxelarGatewayMultisig.sol (line 429)
    function transferOwnership(bytes calldata params, bytes32) external onlySelf {
  1. File: src/AxelarGatewayMultisig.sol (line 440)
    function transferOperatorship(bytes calldata params, bytes32) external onlySelf {
  1. File: src/BurnableMintableCappedERC20.sol (line 48)
    function burn(bytes32 salt) public onlyOwner {
  1. File: src/Ownable.sol (line 20)
    function transferOwnership(address newOwner) public virtual onlyOwner {
  1. File: src/MintableCappedERC20.sol (line 23)
    function mint(address account, uint256 amount) public onlyOwner {
  1. File: src/MintableCappedERC20.sol (line 31)
    function burnFrom(address account, uint256 amount) external onlyOwner {

#0 - deluca-mike

2022-04-20T09:50:56Z

Using 1 and 2 rather than 0 and 1 saves gas

Confirmed.

Not using the named return variables when a function returns, wastes deployment gas

Disputed, since in most cases it does not actually cost more gas, however, we are removing the Context contract anyway.

Use a more recent version of solidity

Acknowledged, but these were all tested on 0.8.9, and this is locked in for release. The next release will take latest Solidity available at the time testing is started.

Using bools for storage incurs overhead

Acknowledged, but it's better to use bools for readability in many cases.

<array>.length should not be looked up in every loop of a for-loop

Disputed, since recent versions of solidity optimize for this if the array has a fixed length.

Using calldata instead of memory for read-only arguments in external functions saves gas

Confirmed.

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

Confirmed for using ++i instead of i++, but acknowledged for the unchecked suggestion, which we will forgo for readability, for now.

Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead

Acknowledged, but will not change since the overhead is at worst minimal, and much of this functionality/code is shared in the space. Further, it helps with readability.

Expressions for constant values such as a call to keccak256(), should use immutable rather than constant

Disputed, since the issue being referred to is 2 years old, and since then, Solidity computes the literals at compile-time.

Duplicated require()/revert() checks should be refactored to a modifier or function

Acknowledged, but will not change as this is a design preference and the compiler can optimize out duplicate code.

State variables only set in the constructor should be declared immutable

While true, and thus confirmed, for cap, this is not true, and thus disputed for strings, since non-value types cannot be immutable in Solidity (yet).

Use custom errors rather than revert()/require() strings to save deployment gas

Confirmed.

Functions guaranteed to revert when called by normal users can be marked payable

Acknowledged, but will not do since it is gas savings at the expense of confusion, readability issues, and the possibilities for ETH to be received.

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