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: 85/175
Findings: 2
Award: $25.79
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xTheC0der
Also found by: 0x180db, 0xDING99YA, 0xRstStn, 0xTiwa, 0xWaitress, 0xblackskull, 0xfuje, 3docSec, Aamir, Black_Box_DD, HChang26, Hama, Inspecktor, John_Femi, Jorgect, Kek, KingNFT, Kow, Limbooo, MIQUINHO, MrPotatoMagic, NoTechBG, Noro, Pessimistic, QiuhaoLi, SovaSlava, SpicyMeatball, T1MOH, TangYuanShen, Vagner, Viktor_Cortess, Yanchuan, _eperezok, alexweb3, alexxander, ast3ros, ayden, bin2chen, blutorque, btk, ciphermarco, ether_sky, gumgumzum, gztttt, hals, imare, its_basu, joaovwfreire, josephdara, klau5, kodyvim, ladboy233, marqymarq10, mert_eren, minhtrng, n1punp, nobody2018, oada, orion, peakbolt, peritoflores, perseverancesuccess, pfapostol, rvierdiiev, stuxy, tank, unsafesol, ustas, windhustler, zambody, zzzitron
0.1127 USDC - $0.11
Function payableCall has the same role as a call() function - it allows to make calls to contracts but with the ability to send some msg.value with this call. But the call() function has requiresApprovedCaller modifier that protects the contract from other people's access and payableCall doesn't have it.
function payableCall(PayableCall[] calldata calls) public payable returns (bytes[] memory returnData) { //@Mid nobody calls modifier uint256 valAccumulator; uint256 length = calls.length; returnData = new bytes[](length); PayableCall calldata _call; for (uint256 i = 0; i < length;) { _call = calls[i]; uint256 val = _call.value; // Humanity will be a Type V Kardashev Civilization before this overflows - andreas // ~ 10^25 Wei in existence << ~ 10^76 size uint fits in a uint256 unchecked { valAccumulator += val; } bool success; if (isContract(_call.target)) (success, returnData[i]) = _call.target.call{value: val}(_call.callData); if (!success) revert CallFailed(); unchecked { ++i; } }
vs
Consider adding requiresApprovedCaller modifier.
Context
#0 - c4-pre-sort
2023-10-08T14:29:40Z
0xA5DF marked the issue as duplicate of #888
#1 - c4-pre-sort
2023-10-08T14:57:13Z
0xA5DF marked the issue as sufficient quality report
#2 - c4-judge
2023-10-26T11:31:06Z
alcueca marked the issue as satisfactory
🌟 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
function depositToPort(address underlyingAddress, uint256 amount) external payable lock { IArbPort(localPortAddress).depositToPort(msg.sender, msg.sender, underlyingAddress, amount); //@LOW payable } function withdrawFromPort(address localAddress, uint256 amount) external payable lock { IArbPort(localPortAddress).withdrawFromPort(msg.sender, msg.sender, localAddress, amount); }
If user A accidentally sends native tokens with a call to these functions, the tokens will get stuck in the BranchBridgeAgent contract and may be used in subsequent operations by another user (user B), resulting in a loss of funds for user A.
BranchBridgeAgent.sol: function _execute(uint256 _settlementNonce, bytes memory _calldata) private { //Update tx state as executed executionState[_settlementNonce] = STATUS_DONE; //Try to execute the remote request (bool success,) = bridgeAgentExecutorAddress.call{value: address(this).balance}(_calldata); // No fallback is requested revert allowing for settlement retry. if (!success) revert ExecutionFailure(); }
Consider removing the payable parameter from these functions to prevent the unintended handling of native tokens.
514: function _bridgeOut( address _depositor, address _localAddress, address _underlyingAddress, uint256 _amount, uint256 _deposit ) internal virtual { // Cache hToken amount out uint256 _hTokenAmount = _amount - _deposit;//@LOW underflow // Check if hTokens are being bridged out if (_hTokenAmount > 0) { _localAddress.safeTransferFrom(_depositor, address(this), _hTokenAmount); ERC20hTokenBranch(_localAddress).burn(_hTokenAmount); } // Check if underlying tokens are being bridged out if (_deposit > 0) { _underlyingAddress.safeTransferFrom(_depositor, address(this), _deposit); } }
The _amount parameter represents the total sum of tokens, while _deposit represents the amount of underlying tokens. It should hold that _hTokenAmount + _deposit = _amount. It is advisable to include a require statement to ensure that _amount - _deposit is greater than or equal to zero for such calculations.
Add a require statement to check if _amount - _deposit is greater than or equal to zero for correctness.
There is a huge list of functions that have _dstChainId as an input parameter, so to avoid spamming with copypasta I'll show an example where _dstchain is encoded as uint256 and decoded as uint16:
CoreBranchRouter.sol 43: function addGlobalToken(address _globalAddress, uint256 _dstChainId, GasParams[3] calldata _gParams) external payable { // Encode Call Data bytes memory params = abi.encode(msg.sender, _globalAddress, _dstChainId, [_gParams[1], _gParams[2]]); // Pack FuncId bytes memory payload = abi.encodePacked(bytes1(0x01), params); // Send Cross-Chain request (System Response/Request) IBridgeAgent(localBridgeAgentAddress).callOut{value: msg.value}(payable(msg.sender), payload, _gParams[0]); } CoreRootRouter.sol 332: function execute(bytes calldata _encodedData, uint16) external payable override requiresExecutor { // Parse funcId bytes1 funcId = _encodedData[0]; /// FUNC ID: 1 (_addGlobalToken) if (funcId == 0x01) { (address refundee, address globalAddress, uint16 dstChainId, GasParams[2] memory gasParams) = abi.decode(_encodedData[1:], (address, address, uint16, GasParams[2])); _addGlobalToken(refundee, globalAddress, dstChainId, gasParams); /// Unrecognized Function Selector } else { revert UnrecognizedFunctionId(); } }
If the value of _dstChainid is more than the maximum value of uint16 this will call a revert. I'm not sure that there will be chainIDs with such a big value, so it's just a theoretical issue.
Verify and correct the data type consistency of _dstChainId across all functions and mappings to match the input parameter types accurately.
function payableCall(PayableCall[] calldata calls) public payable returns (bytes[] memory returnData) { uint256 valAccumulator; uint256 length = calls.length; returnData = new bytes[](length); PayableCall calldata _call; for (uint256 i = 0; i < length;) { _call = calls[i]; uint256 val = _call.value; // Humanity will be a Type V Kardashev Civilization before this overflows - andreas // ~ 10^25 Wei in existence << ~ 10^76 size uint fits in a uint256 unchecked { valAccumulator += val; } bool success; if (isContract(_call.target)) (success, returnData[i]) = _call.target.call{value: val}(_call.callData); if (!success) revert CallFailed(); unchecked { ++i; } } // Finally, make sure the msg.value = SUM(call[0...i].value) if (msg.value != valAccumulator) revert CallFailed(); }
If a virtual account functions as a wallet where users can hold ETH, they cannot directly use their ETH balance for a payable call. Instead, they would need to withdraw ETH from the virtual account first and then make the payable call to ensure that msg.value matches valAccumulator.
The _dailyManagementLimit variable stores the amount of a Strategy Token a given Port Strategy can manage. For added safety, it is advisable to set minimum and maximum values for such variables.
Implement both minimum and maximum limits for the _dailyManagementLimit variable to enhance safety and control.
BranchPort.sol: 403: function updatePortStrategy(address _portStrategy, address _token, uint256 _dailyManagementLimit) external override requiresCoreRouter { strategyDailyLimitAmount[_portStrategy][_token] = _dailyManagementLimit; //@LOW minimum emit PortStrategyUpdated(_portStrategy, _token, _dailyManagementLimit); }
#0 - c4-pre-sort
2023-10-15T12:20:42Z
0xA5DF marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-10-15T13:56:41Z
0xA5DF marked the issue as high quality report
#2 - c4-judge
2023-10-20T13:17:49Z
alcueca marked the issue as grade-a