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: 6/75
Findings: 3
Award: $2,166.27
๐ Selected for report: 0
๐ Solo Findings: 0
https://github.com/code-423n4/2022-07-axelar/blob/9c4c44b94cddbd48b9baae30051a4e13cbe39539/contracts/auth/AxelarAuthWeighted.sol#L76 https://github.com/code-423n4/2022-07-axelar/blob/9c4c44b94cddbd48b9baae30051a4e13cbe39539/contracts/auth/AxelarAuthWeighted.sol#L36
It is possible to transfer operatorship to the same operators by simply doubling the values of the newWeights
array and newThreshold
value. This could be used by newly appointed operators to invalidate all previous operators and thus invalidate the intended functionality of AxelarAuthWeighted.OLD_KEY_RETENTION
. It is possible operators might not do this maliciously: they might be testing or reconfiguring, unaware of the impact on the older commands.
UPDATE: It has been explained on the Discord that "the operator address for a given epoch is derived from the validator key along with a nonce that is unique for each operator epoch and chain.". Given that, operators would be able to transfer operatorship to themselves indefinitely and would not need to even change weights.
https://github.com/code-423n4/2022-07-axelar/blob/9c4c44b94cddbd48b9baae30051a4e13cbe39539/contracts/auth/AxelarAuthWeighted.sol#L76 https://github.com/code-423n4/2022-07-axelar/blob/9c4c44b94cddbd48b9baae30051a4e13cbe39539/contracts/auth/AxelarAuthWeighted.sol#L36
Maintain a state variable of the oldest allowable epoch, and create a command to update it.
#0 - re1ro
2022-08-05T06:37:18Z
Duplicate of #98
#1 - GalloDaSballo
2022-09-04T19:26:57Z
Dup of #19
๐ 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.2349 USDC - $56.23
https://github.com/code-423n4/2022-07-axelar/blob/9c4c44b94cddbd48b9baae30051a4e13cbe39539/contracts/deposit-service/DepositBase.sol#L12
The comment on line 12 of DepositBase
says that the relevant variables are immutable
for gas optimisation. In fact, the immutable keyword is essential for functionality. The contract ReceiverImplementation
inherits from DepositBase
and is the target of delegatecall()
from DepositReceiver
. The functions called in these delegatecall
s use the value of the variable gateway
throughout. This variable would not be set in the storage of the calling DepositReceiver
and it is only accessible because immutable
variables are hardocded into the contract code by the constructor. That is why gateway
is accessible in a delegatecall
. If gateway
were no longer immutable, it would take on the value in the corresponding storage slot of DepositReceiver
.
https://github.com/code-423n4/2022-07-axelar/blob/9c4c44b94cddbd48b9baae30051a4e13cbe39539/contracts/deposit-service/DepositBase.sol#L10
https://github.com/code-423n4/2022-07-axelar/blob/9c4c44b94cddbd48b9baae30051a4e13cbe39539/contracts/deposit-service/ReceiverImplementation.sol#L10
These comments refer to the contracts being "owned". Neither contract has any ownable functionality. Also, as they are used as targets for a delegatecall
, the deployng account is not significant either.
https://github.com/code-423n4/2022-07-axelar/blob/9c4c44b94cddbd48b9baae30051a4e13cbe39539/contracts/auth/AxelarAuthWeighted.sol#L76 It has been explained on the Discord that "the operator address for a given epoch is derived from the validator key along with a nonce that is unique for each operator epoch and chain.". Given that, this test would not be able to detect if the operators were the same.
#0 - re1ro
2022-08-05T06:34:42Z
Good spot. immutable
is doing much more than just gas saving
Good spot. Will correct that
We still have the duplicate operators hash check in the AxelarAuthWeighted
. So the unit test just covering that functionality. Despite the fact that such duplicate addresses should never appear in practice
#1 - GalloDaSballo
2022-09-01T00:50:33Z
##ย Dangerous comment could break functionality Neat report -> L
R
Disagree as the test increases coverage (try getting anything to 100% coverage and you'll agree with me it's hard to write a pointless test)
1 L 1 R
Good unique perspective, just needs more finds
๐ 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.2406 USDC - $31.24
https://github.com/code-423n4/2022-07-axelar/blob/9c4c44b94cddbd48b9baae30051a4e13cbe39539/contracts/AxelarGateway.sol#L271-L316
Because commands
is an array of strings, the commands are being processed on line 298 into a bytes32 variable for easy comparison with constants. Functionally, however, these selectors are simply being used to switch between 6 options. It would be more gas efficient to submit the commands as uint
variables or, at the very least, to submit them processed into bytes32
form.
If necessary, it would be possible to have a function available for operators which converts the command strings to these uints. That function could be called in a gasless call when composing the transaction which calls execute()
.
https://github.com/code-423n4/2022-07-axelar/blob/9c4c44b94cddbd48b9baae30051a4e13cbe39539/contracts/deposit-service/AxelarDepositService.sol#L242
https://github.com/code-423n4/2022-07-axelar/blob/9c4c44b94cddbd48b9baae30051a4e13cbe39539/contracts/deposit-service/AxelarDepositServiceProxy.sol#L9
https://github.com/code-423n4/2022-07-axelar/blob/9c4c44b94cddbd48b9baae30051a4e13cbe39539/contracts/gas-service/AxelarGasService.sol#L181
https://github.com/code-423n4/2022-07-axelar/blob/9c4c44b94cddbd48b9baae30051a4e13cbe39539/contracts/gas-service/AxelarGasServiceProxy.sol#L10
https://github.com/code-423n4/2022-07-axelar/blob/9c4c44b94cddbd48b9baae30051a4e13cbe39539/xc20/contracts/XC20Wrapper.sol#L41
My gas tests showed that using a constant is more gas efficient than these keccak256()
calls.
contract KeccakTest { bytes32 public constant myconstant = keccak256('axelar-deposit-service'); function contractId() public pure returns (bytes32) { return keccak256('axelar-deposit-service'); } function contractId2() public pure returns (bytes32) { return myconstant; } }
#0 - re1ro
2022-08-05T04:13:06Z
Ack. We prefer this approach for simplicity of verification. This is called on only upgrades
#1 - GalloDaSballo
2022-08-23T01:04:17Z
50 gas per command
5 * 30 gas per keccak 150
200 gas saved
#2 - GalloDaSballo
2022-08-23T01:05:20Z
Would recommend always showing the result (image or copy paste of terminal)
Also note that the benchmark is setup in a way that is biased, a second function in the same contract will cause overhead due to the way the dispatch
for contract calls is done in solidity.