Platform: Code4rena
Start Date: 29/07/2022
Pot Size: $50,000 USDC
Total HM: 6
Participants: 75
Period: 5 days
Judge: GalloDaSballo
Total Solo HM: 3
Id: 149
League: ETH
Rank: 45/75
Findings: 2
Award: $87.35
π Selected for report: 0
π Solo Findings: 0
π Selected for report: oyc_109
Also found by: 0x1f8b, 0x52, 0xNazgul, 0xSmartContract, 0xf15ers, 8olidity, Aymen0909, Bnke0x0, CertoraInc, Chom, CodingNameKiki, Deivitto, Dravee, ElKu, IllIllI, JC, Lambda, Noah3o6, NoamYakov, RedOneN, Respx, ReyAdmirado, Rohan16, Rolezn, Ruhum, Sm4rty, TomJ, Twpony, Waze, Yiko, __141345__, ajtra, apostle0x01, ashiq0x01, asutorufos, bardamu, benbaessler, berndartmueller, bharg4v, bulej93, c3phas, cccz, ch13fd357r0y3r, codexploder, cryptonue, cryptphi, defsec, djxploit, durianSausage, fatherOfBlocks, gogo, hansfriese, horsefacts, ignacio, kyteg, lucacez, mics, rbserver, robee, sashik_eth, simon135, sseefried, tofunmi, xiaoming90
56.1273 USDC - $56.13
contracts\AxelarGatewayProxy.sol: 47 48: receive() external payable { 49 revert('NO_ETHER'); contracts\deposit-service\AxelarDepositServiceProxy.sol: 12 // solhint-disable-next-line no-empty-blocks 13: receive() external payable override {} 14 } contracts\deposit-service\DepositReceiver.sol: 28 // solhint-disable-next-line no-empty-blocks 29: receive() external payable {} 30 } contracts\util\Proxy.sol: 85 86: receive() external payable virtual { 87 revert EtherNotAccepted(); xc20\contracts\Proxy.sol: 59 60: receive() external payable virtual { 61 revert EtherNotAccepted();
The use of payable.transfer() is heavily frowned upon because it can lead to the locking of funds. The transfer() call requires that the recipient has a payable callback, only provides 2300 gas for its operation. This means the following cases can cause the transfer to fail:
The contract does not have a payable callback The contract's payable callback spends more than 2300 gas (which is only enough to emit something) The contract is called through a proxy which itself uses up the 2300 gas
contracts\deposit-service\ReceiverImplementation.sol: 22 // Always refunding native otherwise it's sent on DepositReceiver self destruction 23: if (address(this).balance > 0) refundAddress.transfer(address(this).balance); 24 50 if (refund != address(0)) { 51: if (address(this).balance > 0) refundAddress.transfer(address(this).balance); 52 70 function receiveAndUnwrapNative(address payable refundAddress, address payable recipient) external { 71: if (address(this).balance > 0) refundAddress.transfer(address(this).balance); 72 85 IWETH9(wrappedTokenAddress).withdraw(amount); 86: recipient.transfer(amount); 87 } contracts\gas-service\AxelarGasService.sol: 127 uint256 amount = address(this).balance; 128: if (amount > 0) receiver.transfer(amount); 129 } else { 143 if (token == address(0)) { 144: receiver.transfer(amount); 145 } else { xc20\contracts\XC20Wrapper.sol: 62 if (!LocalAsset(xc20Token).set_metadata(newName, newSymbol, IERC20(axelarToken).decimals())) revert('CannotSetMetadata()'); 63: payable(msg.sender).transfer(address(this).balance); 64 }
Zero-address checks are a best practice for input validation of critical address parameters. While the codebase applies this to most cases, there are many places where this is missing in constructors and setters.
2022-07-axelar/contracts/AxelarGateway.sol 48β constructor(address authModule, address tokenDeployerImplementation) { 49β if (authModule.code.length == 0) revert InvalidAuthModule(); 50β if (tokenDeployerImplementation.code.length == 0) revert InvalidTokenDeployer(); 51β 52β AUTH_MODULE = authModule; 53β TOKEN_DEPLOYER_IMPLEMENTATION = tokenDeployerImplementation; 54β } 2022-07-axelar/contracts/AxelarGatewayProxy.sol 16β constructor(address gatewayImplementation, bytes memory params) { 17β _setAddress(KEY_IMPLEMENTATION, gatewayImplementation); 18β 19β if (gatewayImplementation.code.length == 0) revert InvalidImplementation(); 20β 21β (bool success, ) = gatewayImplementation.delegatecall(abi.encodeWithSelector(IAxelarGateway.setup.selector, params)); 22β 23β if (!success) revert SetupFailed(); 24β } 2022-07-axelar/contracts/BurnableMintableCappedERC20.sol 12β constructor( 13β string memory name, 14β string memory symbol, 15β uint8 decimals, 16β uint256 capacity 17β ) MintableCappedERC20(name, symbol, decimals, capacity) {} 2022-07-axelar/contracts/ERC20.sol 46β constructor( 47β string memory name_, 48β string memory symbol_, 49β uint8 decimals_ 50β ) { 51β name = name_; 52β symbol = symbol_; 53β decimals = decimals_; 54β } 2022-07-axelar/contracts/ERC20Permit.sol 28β constructor(string memory name) { 29β DOMAIN_SEPARATOR = keccak256( 30β abi.encode(DOMAIN_TYPE_SIGNATURE_HASH, keccak256(bytes(name)), keccak256(bytes('1')), block.chainid, address(this)) 31β ); 32β } 2022-07-axelar/contracts/MintableCappedERC20.sol 14β constructor( 15β string memory name, 16β string memory symbol, 17β uint8 decimals, 18β uint256 capacity 19β ) ERC20(name, symbol, decimals) ERC20Permit(name) Ownable() { 20β cap = capacity; 21β } 2022-07-axelar/contracts/Ownable.sol 10β constructor() { 11β owner = msg.sender; 12β emit OwnershipTransferred(address(0), msg.sender); 13β } 2022-07-axelar/contracts/auth/AxelarAuthWeighted.sol 16β constructor(bytes[] memory recentOperators) { 17β for (uint256 i; i < recentOperators.length; ++i) { 18β _transferOperatorship(recentOperators[i]); 19β } 20β } 2022-07-axelar/contracts/deposit-service/AxelarDepositService.sol 18β constructor(address gateway, string memory wrappedSymbol) DepositBase(gateway, wrappedSymbol) { 19β receiverImplementation = address(new ReceiverImplementation(gateway, wrappedSymbol)); 20β } 2022-07-axelar/contracts/deposit-service/DepositBase.sol 21β constructor(address gateway_, string memory wrappedSymbol_) { 22β if (gateway_ == address(0)) revert InvalidAddress(); 23β 24β gateway = gateway_; 25β 26β // Checking if token symbol exists in the gateway 27β if (IAxelarGateway(gateway_).tokenAddresses(wrappedSymbol_) == address(0)) revert InvalidSymbol(); 28β 29β // Converting a string to bytes32 for immutable storage 30β bytes memory symbolBytes = bytes(wrappedSymbol_); 2022-07-axelar/contracts/deposit-service/DepositReceiver.sol 8β constructor(bytes memory delegateData) { 9β // Reading the implementation of the AxelarDepositService 10β // and delegating the call back to it 11β // solhint-disable-next-line avoid-low-level-calls 12β (bool success, ) = IAxelarDepositService(msg.sender).receiverImplementation().delegatecall(delegateData); 13β 14β // if not success revert with the original revert data 15β if (!success) { 16β // solhint-disable-next-line no-inline-assembly 17β assembly { 2022-07-axelar/contracts/deposit-service/ReceiverImplementation.sol 12β constructor(address gateway, string memory wrappedSymbol) DepositBase(gateway, wrappedSymbol) {} 2022-07-axelar/contracts/interfaces/IAxelarExecutable.sol 12β constructor(address gateway_) { 13β gateway = IAxelarGateway(gateway_); 14β } 2022-07-axelar/contracts/interfaces/IAxelarForecallable.sol 16β constructor(address gatewayAddress) { 17β gateway = IAxelarGateway(gatewayAddress); 18β } 2022-07-axelar/contracts/util/Proxy.sol 19β constructor() { 20β // solhint-disable-next-line no-inline-assembly 21β assembly { 22β sstore(_OWNER_SLOT, caller()) 23β } 24β } 2022-07-axelar/xc20/contracts/ERC20.sol 46β constructor( 47β string memory name_, 48β string memory symbol_, 49β uint8 decimals_ 50β ) { 51β name = name_; 52β symbol = symbol_; 53β decimals = decimals_; 54β } 2022-07-axelar/xc20/contracts/ERC20Permit.sol 28β constructor(string memory name) { 29β DOMAIN_SEPARATOR = keccak256( 30β abi.encode(DOMAIN_TYPE_SIGNATURE_HASH, keccak256(bytes(name)), keccak256(bytes('1')), block.chainid, address(this)) 31β ); 32β } 2022-07-axelar/xc20/contracts/Proxy.sol 17β constructor(address implementationAddress, bytes memory params) { 18β // solhint-disable-next-line no-inline-assembly 19β assembly { 20β sstore(_IMPLEMENTATION_SLOT, implementationAddress) 21β } 22β // solhint-disable-next-line avoid-low-level-calls 23β (bool success, ) = implementationAddress.delegatecall( 24β //0x9ded06df is the setup selector. 25β abi.encodeWithSelector(0x9ded06df, params) 26β ); 2022-07-axelar/xc20/contracts/XC20Sample.sol 19β constructor(address owner_, string memory name) ERC20Permit(name) ERC20('', '', 0) { 20β owner = owner_; 21β } 2022-07-axelar/xc20/contracts/XC20Wrapper.sol 26β constructor(address gatewayAddress_) { 27β gatewayAddress = gatewayAddress_; 28β }
<array>.length
Β should not be looked up in every loop of aΒ for
-loopcontracts\AxelarGateway.sol: 206 207: for (uint256 i = 0; i < symbols.length; i++) { 208 string memory symbol = symbols[i]; contracts\auth\AxelarAuthWeighted.sol: 16 constructor(bytes[] memory recentOperators) { 17: for (uint256 i; i < recentOperators.length; ++i) { 18 _transferOperatorship(recentOperators[i]); 97 // assuming that both operators and signatures are sorted 98: for (uint256 i = 0; i < signatures.length; ++i) { 115 function _isSortedAscAndContainsNoDuplicate(address[] memory accounts) internal pure returns (bool) { 116: for (uint256 i; i < accounts.length - 1; ++i) { 117 if (accounts[i] >= accounts[i + 1]) { contracts\deposit-service\AxelarDepositService.sol: 113 ) external { 114: for (uint256 i; i < refundTokens.length; i++) { 115 address gatewayToken = IAxelarGateway(gateway).tokenAddresses(tokenSymbol); 167 168: for (uint256 i; i < refundTokens.length; i++) { 169 refundToken = refundTokens[i]; 203 ) external { 204: for (uint256 i; i < refundTokens.length; i++) { 205 address wrappedTokenAddress = wrappedToken(); contracts\gas-service\AxelarGasService.sol: 122 123: for (uint256 i; i < tokens.length; i++) { 124 address token = tokens[i];
keccak256()
, should useΒ immutable
Β rather thanΒ constant
contracts\AdminMultisigBase.sol: 14 // AUDIT: slot names should be prefixed with some standard string 15: bytes32 internal constant KEY_ADMIN_EPOCH = keccak256('admin-epoch'); 16 17: bytes32 internal constant PREFIX_ADMIN = keccak256('admin'); 18: bytes32 internal constant PREFIX_ADMIN_COUNT = keccak256('admin-count'); 19: bytes32 internal constant PREFIX_ADMIN_THRESHOLD = keccak256('admin-threshold'); 20: bytes32 internal constant PREFIX_ADMIN_VOTE_COUNTS = keccak256('admin-vote-counts'); 21: bytes32 internal constant PREFIX_ADMIN_VOTED = keccak256('admin-voted'); 22: bytes32 internal constant PREFIX_IS_ADMIN = keccak256('is-admin'); 23 contracts\AxelarGateway.sol: 27 bytes32 internal constant KEY_IMPLEMENTATION = bytes32(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc); 29 // AUDIT: slot names should be prefixed with some standard string 30: bytes32 internal constant PREFIX_COMMAND_EXECUTED = keccak256('command-executed'); 31: bytes32 internal constant PREFIX_TOKEN_ADDRESS = keccak256('token-address'); 32: bytes32 internal constant PREFIX_TOKEN_TYPE = keccak256('token-type'); 33: bytes32 internal constant PREFIX_CONTRACT_CALL_APPROVED = keccak256('contract-call-approved'); 34: bytes32 internal constant PREFIX_CONTRACT_CALL_APPROVED_WITH_MINT = keccak256('contract-call-approved-with-mint'); 35: bytes32 internal constant PREFIX_TOKEN_DAILY_MINT_LIMIT = keccak256('token-daily-mint-limit'); 36: bytes32 internal constant PREFIX_TOKEN_DAILY_MINT_AMOUNT = keccak256('token-daily-mint-amount'); 37 38: bytes32 internal constant SELECTOR_BURN_TOKEN = keccak256('burnToken'); 39: bytes32 internal constant SELECTOR_DEPLOY_TOKEN = keccak256('deployToken'); 40: bytes32 internal constant SELECTOR_MINT_TOKEN = keccak256('mintToken'); 41: bytes32 internal constant SELECTOR_APPROVE_CONTRACT_CALL = keccak256('approveContractCall'); 42: bytes32 internal constant SELECTOR_APPROVE_CONTRACT_CALL_WITH_MINT = keccak256('approveContractCallWithMint'); 43: bytes32 internal constant SELECTOR_TRANSFER_OPERATORSHIP = keccak256('transferOperatorship');
#0 - re1ro
2022-08-05T00:08:03Z
Not applicable. Dup #2
Yup. Dup #4
Applicable for few splaces. Dup #3
Yup. Dup #2
Correct. Very good spot
#1 - GalloDaSballo
2022-08-28T20:32:51Z
Check the bot bro <img width="380" alt="Screenshot 2022-08-28 at 22 30 42" src="https://user-images.githubusercontent.com/13383782/187093300-82e455a8-ac43-4e9a-b4c8-7d506477fb02.png">
L
Half of these are false positives
Nope, debunked https://twitter.com/GalloDaSballo/status/1543729080926871557
1L
#2 - GalloDaSballo
2022-08-28T21:48:34Z
Technically one of the recevies will lock ETH, however, the blanked submission has more false positives than real findings so am choosing to not give it a point
π Selected for report: IllIllI
Also found by: 0x1f8b, 0xNazgul, 0xsam, 8olidity, Aymen0909, Bnke0x0, Chom, CodingNameKiki, Deivitto, Dravee, ElKu, Fitraldys, JC, Lambda, MiloTruck, Noah3o6, NoamYakov, RedOneN, Respx, ReyAdmirado, Rohan16, Rolezn, Ruhum, Sm4rty, TomJ, Tomio, Waze, __141345__, a12jmx, ajtra, ak1, apostle0x01, asutorufos, benbaessler, bharg4v, bulej93, c3phas, defsec, djxploit, durianSausage, erictee, fatherOfBlocks, gerdusx, gogo, kyteg, lucacez, medikko, mics, owenthurm, oyc_109, rbserver, robee, sashik_eth, simon135, tofunmi
31.2158 USDC - $31.22
x = x + y
Β is cheaper thanΒ x += y
contracts\auth\AxelarAuthWeighted.sol: 69 for (uint256 i = 0; i < weightsLength; ++i) { 70: totalWeight += newWeights[i]; 71 } 104 // return if weight sum above threshold 105: weight += weights[operatorIndex]; 106 // weight needs to reach or surpass threshold
Some lines useΒ // x
Β and some useΒ //x
. The instances below point out the usages that don't follow the majority, within each file
File: contracts\util\Proxy.sol 45 // solhint-disable-next-line avoid-low-level-calls 46 (bool success, ) = implementationAddress.delegatecall( 47 //0x9ded06df is the setup selector. 48 abi.encodeWithSelector(0x9ded06df, params) );
#0 - re1ro
2022-08-05T00:03:58Z
Correct. Dup #2
Good spot
#1 - GalloDaSballo
2022-08-20T18:54:22Z
20 gas saved