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: 51/75
Findings: 1
Award: $60.77
🌟 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
60.7712 USDC - $60.77
0
Operators are assigned a weight when transferring operatorship. Validating signatures requires reaching a certain weight threshold
of operators who signed a message to consider the message to be valid.
It is assumed that each honest operator has a weight != 0. However, the current implementation lacks such a check, thus allowing operator weights to have a value of 0 (as long as the total weights of all operators equal or surpass newThreshold
).
function _transferOperatorship(bytes memory params) internal { (address[] memory newOperators, uint256[] memory newWeights, uint256 newThreshold) = abi.decode( params, (address[], uint256[], uint256) ); uint256 operatorsLength = newOperators.length; uint256 weightsLength = newWeights.length; // operators must be sorted binary or alphabetically in lower case if (operatorsLength == 0 || !_isSortedAscAndContainsNoDuplicate(newOperators)) revert InvalidOperators(); if (weightsLength != operatorsLength) revert InvalidWeights(); uint256 totalWeight = 0; for (uint256 i = 0; i < weightsLength; ++i) { totalWeight += newWeights[i]; // @audit-info Weight of an operator can potentially be 0 } if (newThreshold == 0 || totalWeight < newThreshold) revert InvalidThreshold(); bytes32 newOperatorsHash = keccak256(params); if (epochForHash[newOperatorsHash] > 0) revert SameOperators(); uint256 epoch = currentEpoch + 1; currentEpoch = epoch; hashForEpoch[epoch] = newOperatorsHash; epochForHash[newOperatorsHash] = epoch; emit OperatorshipTransferred(newOperators, newWeights, newThreshold); }
Consider adding a check to _transferOperatorship
to ensure operator weights are != 0
(see @audit-info
annotation):
function _transferOperatorship(bytes memory params) internal { (address[] memory newOperators, uint256[] memory newWeights, uint256 newThreshold) = abi.decode( params, (address[], uint256[], uint256) ); uint256 operatorsLength = newOperators.length; uint256 weightsLength = newWeights.length; // operators must be sorted binary or alphabetically in lower case if (operatorsLength == 0 || !_isSortedAscAndContainsNoDuplicate(newOperators)) revert InvalidOperators(); if (weightsLength != operatorsLength) revert InvalidWeights(); uint256 totalWeight = 0; for (uint256 i = 0; i < weightsLength; ++i) { require(newWeights[i] != 0, 'Invalid operator weight'); // @audit-info Check if operator weight is not 0 totalWeight += newWeights[i]; } if (newThreshold == 0 || totalWeight < newThreshold) revert InvalidThreshold(); bytes32 newOperatorsHash = keccak256(params); if (epochForHash[newOperatorsHash] > 0) revert SameOperators(); uint256 epoch = currentEpoch + 1; currentEpoch = epoch; hashForEpoch[epoch] = newOperatorsHash; epochForHash[newOperatorsHash] = epoch; emit OperatorshipTransferred(newOperators, newWeights, newThreshold); }
msg.sender
contextThe ReceiverImplementation.receiveAndUnwrapNative
mentions the wrong (or not completely correct) msg.sender
of the function. Contrary to the other functions within the ReceiverImplementation
contract, this function specifies that the msg.sender
is DepositBase
, but in fact it is AxelarDepositService
.
ReceiverImplementation.sol#L69
// @dev This function is used for delegate by DepositReceiver deployed above // Context: msg.sender == DepositBase, this == DepositReceiver @audit-info Incorrect msg.sender function receiveAndUnwrapNative(address payable refundAddress, address payable recipient) external { ... }
Consider changing the comment to mention the correct msg.sender
:
// @dev This function is used for delegate by DepositReceiver deployed above // Context: msg.sender == AxelarDepositService, this == DepositReceiver function receiveAndUnwrapNative(address payable refundAddress, address payable recipient) external { ... }
DepositReceiver
to msg.sender
on selfdestruct
are lockedThe DepositReceiver
contract calls selfdestruct
immediately after the delegatecall
executed successfully. In the event that any left-over native tokens are in the deployed contract, those native tokens would be sent to msg.sender
which is the AxelarDepositService
contract.
However, the AxelarDepositService
has no way of transferring out those native tokens, hence they are locked.
deposit-service/DepositReceiver.sol#L25
constructor(bytes memory delegateData) { // Reading the implementation of the AxelarDepositService // and delegating the call back to it // solhint-disable-next-line avoid-low-level-calls (bool success, ) = IAxelarDepositService(msg.sender).receiverImplementation().delegatecall(delegateData); // if not success revert with the original revert data if (!success) { // solhint-disable-next-line no-inline-assembly assembly { let ptr := mload(0x40) let size := returndatasize() returndatacopy(ptr, 0, size) revert(ptr, size) } } selfdestruct(payable(msg.sender)); }
Consider adding an owner-callable function to the AxelarDepositService
contract to transfer native tokens out.
#0 - re1ro
2022-08-05T07:23:24Z
Good spot
Ack
Not likely to happen. Since ReceiverImplementation
takes care of all native ether and selfdestruct(payable(msg.sender))
is a last resort measure. If ether will get trapped in AxelarDepositService
IRL we will add such a method
#1 - GalloDaSballo
2022-08-28T20:48:58Z
NC
NC
Valid Low
#2 - GalloDaSballo
2022-08-28T20:49:01Z
1L 2NC