Platform: Code4rena
Start Date: 22/09/2023
Pot Size: $100,000 USDC
Total HM: 15
Participants: 175
Period: 14 days
Judge: alcueca
Total Solo HM: 4
Id: 287
League: ETH
Rank: 115/175
Findings: 1
Award: $25.68
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: MrPotatoMagic
Also found by: 0xAadi, 0xDING99YA, 0xDemon, 0xRstStn, 0xSmartContract, 0xStriker, 0xWaitress, 0xbrett8571, 0xfuje, 0xsagetony, 0xsurena, 33BYTEZZZ, 3docSec, 7ashraf, ABA, ABAIKUNANBAEV, Aamir, Audinarey, Bauchibred, Black_Box_DD, Daniel526, DanielArmstrong, DanielTan_MetaTrust, Dinesh11G, Eurovickk, Franklin, Inspecktor, John, Jorgect, Joshuajee, K42, Kek, Koolex, LokiThe5th, MIQUINHO, Myd, NoTechBG, QiuhaoLi, SanketKogekar, Sathish9098, Sentry, Soul22, SovaSlava, Stormreckson, Tendency, Topmark, Udsen, V1235816, Viktor_Cortess, Viraz, Yanchuan, ZdravkoHr, Zims, albahaca, albertwh1te, alexweb3, alexxander, ast3ros, audityourcontracts, bareli, bin2chen, bronze_pickaxe, c0pp3rscr3w3r, cartlex_, castle_chain, chaduke, debo, ether_sky, gumgumzum, imare, its_basu, jaraxxus, jasonxiale, josephdara, kodyvim, ladboy233, lanrebayode77, lsaudit, mert_eren, minhtrng, n1punp, nadin, niroh, nmirchev8, orion, peakbolt, perseverancesuccess, pfapostol, ptsanev, rvierdiiev, saneryee, shaflow2, te_aut, terrancrypt, twcctop, unsafesol, ustas, versiyonbir, windhustler, yongskiws, zhaojie, ziyou-
25.6785 USDC - $25.68
_deposit
is 0 before performing transfer in ArbitrumBranchPort.sol#depositToPort()
requiresApprovedCaller()
modifier in VirtualAccount.sol#payableCall()
PARAMS_ADDRESS_SIZE
rather than 20
in BranchBridgeAgent.sol#_requiresEndpoint()
_nonce
is not used in RootBridgeAgent.sol#lzReceive()
and BranchBridgeAgent.sol#lzReceive()
RootBridgeAgent.sol#lzReceive()
should be overrideBranchBridgeAgent.sol#getFeeEstimate()
and RootBridgeAgent.sol#getFeeEstimate()
are not usedgetBranchBridgeAgentPath[_dstChainId]
should be packed into 40 bytes before executing endpoint.send()
_deposit
is 0 before performing transfer in ArbitrumBranchPort.sol#depositToPort()
If the _deposit
is 0, the code below woud revert : here
File: ArbitrumBranchPort.sol 50: function depositToPort(address _depositor, address _recipient, address _underlyingAddress, uint256 _deposit) ---SNIP--- 66: _underlyingAddress.safeTransferFrom(_depositor, address(this), _deposit);
The protocol should check if _deposit
is 0 before performing transfer.
requiresApprovedCaller()
modifier in VirtualAccount.sol#payableCall()
VirtualAccount.sol
there is a requireApprovedCaller()
modifier to verify whether the sender of the message is approved to use the virtual account or not. Either the owner or an approved router.requiresApprovedCaller()
modifier is used in most functions in the VirtualAccount.sol
contract: withdrawNative()
, withdrawERC20()
, withdrawERC721()
, call()
. But is missing in payableCall()
.Consider add requiresApprovedCaller()
modifier to payableCall()
.
PARAMS_ADDRESS_SIZE
rather than 20
in BranchBridgeAgent.sol#_requiresEndpoint()
File: BranchBridgeAgent.sol 936: function _requiresEndpoint(address _endpoint, bytes calldata _srcAddress) internal view virtual { ---SNIP--- 943: if (rootBridgeAgentAddress != address(uint160(bytes20(_srcAddress[20:])))) revert LayerZeroUnauthorizedCaller();
File: BridgeAgentConstants.sol 37: uint256 internal constant PARAMS_ADDRESS_SIZE = 20;
Consider change to
if (rootBridgeAgentAddress != address(uint160(bytes20(_srcAddress[PARAMS_ADDRESS_SIZE:])))) revert LayerZeroUnauthorizedCaller();
_nonce
is not used in RootBridgeAgent.sol#lzReceive()
and BranchBridgeAgent.sol#lzReceive()
ILayerZeroReceiver.sol
, the third argument is uint64 _nonce
, however it is not used in
RootBridgeAgent.sol#lzReceive()and
BranchBridgeAgent.sol#lzReceive()` : herefunction lzReceive(uint16 _srcChainId, bytes calldata _srcAddress, uint64 _nonce, bytes calldata _payload) external;
BranchBridgeAgent.sol#lzReceive()
: herefunction lzReceive(uint16, bytes calldata _srcAddress, uint64, bytes calldata _payload) public override { address(this).excessivelySafeCall( gasleft(), 150, abi.encodeWithSelector(this.lzReceiveNonBlocking.selector, msg.sender, _srcAddress, _payload) ); }
RootBridgeAgent.sol#lzReceive()
: herefunction lzReceive(uint16 _srcChainId, bytes calldata _srcAddress, uint64, bytes calldata _payload) public { (bool success,) = address(this).excessivelySafeCall( gasleft(), 150, abi.encodeWithSelector(this.lzReceiveNonBlocking.selector, msg.sender, _srcChainId, _srcAddress, _payload) ); if (!success) if (msg.sender == getBranchBridgeAgent[localChainId]) revert ExecutionFailure(); }
RootBridgeAgent.sol#lzReceive()
should be overrideRootBridgeAgent.sol#lzReceive()
: herefunction lzReceive(uint16 _srcChainId, bytes calldata _srcAddress, uint64, bytes calldata _payload) public { (bool success,) = address(this).excessivelySafeCall( gasleft(), 150, abi.encodeWithSelector(this.lzReceiveNonBlocking.selector, msg.sender, _srcChainId, _srcAddress, _payload) ); if (!success) if (msg.sender == getBranchBridgeAgent[localChainId]) revert ExecutionFailure(); }
Add override to RootBridgeAgent.sol#lzReceive()
BranchBridgeAgent.sol#getFeeEstimate()
and RootBridgeAgent.sol#getFeeEstimate()
are not usedhttps://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L140-L153
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchBridgeAgent.sol#L161C14-L173
BranchBridgeAgent.sol#getFeeEstimate()
and RootBridgeAgent.sol#getFeeEstimate()
are implemented but not used so consider removing unused functions
getBranchBridgeAgentPath[_dstChainId]
should be packed into 40 bytes before executing endpoint.send()
There are 03 instances of this issue: _performCall() , _performRetrySettlementCall() , _performFallbackCall()
File: RootBridgeAgent.sol 64: /// @notice Message Path for each connected Branch Bridge Agent as bytes for Layzer Zero interaction = localAddress + destinationAddress abi.encodePacked() 65: mapping(uint256 chainId => bytes branchBridgeAgentPath) public getBranchBridgeAgentPath; --- 808: function _performCall( ---SNIP--- 823: ILayerZeroEndpoint(lzEndpointAddress).send{value: msg.value}( 824: _dstChainId, 825: getBranchBridgeAgentPath[_dstChainId], --- 856: function _performRetrySettlementCall( ---SNIP--- 915: ILayerZeroEndpoint(lzEndpointAddress).send{value: _value}( 916: _dstChainId, 917: getBranchBridgeAgentPath[_dstChainId], --- 938: function _performFallbackCall(address payable _refundee, uint32 _depositNonce, uint16 _dstChainId) internal { 939 //Sends message to LayerZero messaging layer 940: ILayerZeroEndpoint(lzEndpointAddress).send{value: address(this).balance}( 941: _dstChainId, 942: getBranchBridgeAgentPath[_dstChainId],
According to docs : remote address concated with local address packed into 40 bytes
then it is passed into the endpoint.send
function.
getBranchBridgeAgentPath[_dstChainId]
should be packed into 40 bytes before executing endpoint.send()
Eg:
getBranchBridgeAgentPath[_dstChainId] = abi.encodePacked(getBranchBridgeAgent[_dstChainId], address(this));
_performCall()
and _performRetrySettlementCall()
:address callee = getBranchBridgeAgent[_dstChainId];
#0 - 0xA5DF
2023-10-15T12:34:48Z
L2 is dupe of #888
#1 - c4-pre-sort
2023-10-15T12:34:53Z
0xA5DF marked the issue as sufficient quality report
#2 - c4-judge
2023-10-20T13:50:47Z
alcueca marked the issue as grade-a