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: 23/175
Findings: 3
Award: $332.99
🌟 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-
11.4657 USDC - $11.47
// Current Code: ERC20(_hToken).approve(_localPortAddress, _amount - _deposit); // Optimized Code: IERC20(_hToken).approve(_localPortAddress, _amount - _deposit);
Rationale: Using the IERC20 interface instead of ERC20 can make the contract more flexible and potentially reduce bytecode size, enhancing the contract's security by adhering to standard interfaces.
Security Improvements: The use of the IERC20 standard interface simplifies the code and adheres to established best practices, reducing the attack surface.
lock()
Modifier// Current Code: modifier lock() { require(_unlocked == 1); _unlocked = 2; _; _unlocked = 1; } // Optimized Code: modifier lock() { uint256 originalState = _unlocked; _unlocked = 2; _; _unlocked = originalState; }
_unlocked
state variable is restored to its original state, making it safer in scenarios where nested calls might occur.// Current Code: depositNonce = _depositNonce + 1; IPort(localPortAddress).bridgeOut(msg.sender, _hToken, _token, _amount, _deposit); // Optimized Code: IPort(localPortAddress).bridgeOut(msg.sender, _hToken, _token, _amount, _deposit); depositNonce = _depositNonce + 1;
Rationale: By adhering to the Checks-Effects-Interactions pattern, the optimized code reduces the risk of re-entrancy attacks.
Security Improvements: The change increases the contract's security by ensuring that state changes occur after external calls, reducing the risk associated with re-entrancy attacks.
#0 - c4-pre-sort
2023-10-15T13:09:42Z
0xA5DF marked the issue as sufficient quality report
#1 - c4-judge
2023-10-21T12:44:54Z
alcueca marked the issue as grade-b
🌟 Selected for report: rvierdiiev
Also found by: 0x11singh99, 0xAnah, 0xta, Aamir, DavidGiladi, DevABDee, Eurovickk, JCK, K42, MrPotatoMagic, Pessimistic, Raihan, Rolezn, SM3_SS, SY_S, Sathish9098, Udsen, ayo_dev, blutorque, c3phas, clara, dharma09, hihen, hunter_w3b, jamshed, koxuan, lsaudit, marqymarq10, oualidpro, pfapostol, sivanesh_808, tabriz, wahedtalash77, zabihullahazadzoi, ziyou-
78.1884 USDC - $78.19
Possible Optimization =
external
contract calls by using batch calls.After Optimization:
IRootPort(_rootPortAddress).batchExecuteMintAndBurn(_recipient, _depositor, _globalToken, _globalAddress, _deposit, _amount);
CALL
opcodes.Possible Optimization 1 =
bytes4
for funcId instead of bytes1
to directly compare function selectors.After Optimization:
bytes4 funcId = bytes4(encodedData[0:4]); // Then update the if statement like so if (funcId == bytes4(keccak256("execute(bytes,uint16)"))) { // ... }
Possible Optimization 2 =
After Optimization:
_unlocked ^= 1;
Possible Optimization 3 =
After Optimization:
function execute(bytes calldata encodedData, uint16) external payable override lock requiresExecutor { bytes1 funcId = encodedData[0]; bytes memory remainingData = _decode(encodedData[1:]); if (funcId == 0x01) { _multicall(abi.decode(remainingData, (IMulticall.Call[]))); } else if (funcId == 0x02 || funcId == 0x03) { (IMulticall.Call[] memory callData, bytes memory outputData) = abi.decode(remainingData, (IMulticall.Call[], bytes)); _multicall(callData); _handleOutputData(outputData, funcId); } else { revert UnrecognizedFunctionId(); } }
abi.decode
operations.Possible Optimization 1 =
bytes4
in this contract also, for funcId on line 304 and 334, instead of bytes1
to directly compare function selectors.Here is the optimized code snippet:
bytes4 funcId = bytes4(_encodedData[0:4]);
Possible Optimization 2 =
delegatecall
for calling IBridgeAgent(bridgeAgentAddress).callOut on line 139, 174, 199, 226, 257, 287 and 437 if the function does not change the state of the contract.Here is the optimized code:
(address(bridgeAgentAddress).delegatecall(abi.encodeWithSignature("callOut(address,address,uint16,bytes,GasParams)", payable(_refundee), _refundee, _dstChainId, payload, _gParams)));
Possible Optimization 3 =
Here is the optimized code snippet:
function execute(bytes calldata encodedData, uint16) external payable override requiresExecutor { // Assuming you add previous optimization bytes4 funcId = bytes4(encodedData[0:4]); // Then define array here bytes memory remainingData = encodedData[4:]; // ... (rest of the code) }
abi.decode
operations.Possible Optimization 4 =
abi.decode
call.Here is the optimized code snippet:
(address underlyingAddress, address localAddress, string memory name, string memory symbol, uint8 decimals, address globalAddress, address newBranchBridgeAgent, address rootBridgeAgent) = abi.decode(_encodedData[1:], (address, address, string, string, uint8, address, address, address));
abi.decode
operations, saving gas.Possible Optimization =
abi.decode
to unpack all variables and then use them in the conditions.Here is the optimized code snippet:
(bytes1 functionId, bytes memory params) = abi.decode(_params, (bytes1, bytes)); if (functionId == 0x01) { // Use params here } else if (functionId == 0x02) { // Use params here }
abi.decode
operations, saving gas.Possible Optimization =
Here is the optimized code snippet:
// Modify here if (executionState[_srcChainId][nonce] & STATUS_READY == STATUS_READY) { // ... }
Possible Optimization =
++i
operation in loops is subject to overflow checks by default since Solidity 0.8.x.Here is the optimized code snippet:
// In the 'call' function for (uint256 i = 0; i < length;) { // ... existing code ... unchecked { ++i; // Remove overflow check for loop counter } } // Similarly in the 'payableCall' function for (uint256 i = 0; i < length;) { // ... existing code ... unchecked { ++i; // Remove overflow check for loop counter } }
JUMPI
opcode for overflow checks. This is safe, as the loop bounds are well-defined.Possible Optimization =
bytes4
for Function Selectors in lzReceive(() function.Here is the optimized code:
// Current: abi.encodeWithSelector(this.lzReceiveNonBlocking.selector, msg.sender, _srcAddress, _payload) // Optimized: abi.encodeWithSelector(0x12345678, msg.sender, _srcAddress, _payload) // Replace 0x12345678 with actual function selector
Possible Optimization =
[G-05]
on the bot report, which suggests caching state variables in stack variables to save gas. However, msg.sender
is not a state variable, so this is a distinct optimization.Here is the optimized code snippet:
// Current: _hToken.safeTransferFrom(msg.sender, address(this), _amount - _deposit); // Optimized: address sender = msg.sender; _hToken.safeTransferFrom(sender, address(this), _amount - _deposit);
Possible Optimization =
||
and &&
to short-circuit the require
statements in the initialize() function, reducing the number of JUMPI
, ISZERO
, and DUP
executed.Here is the optimized code snippet:
// Current: require(_coreRootRouter != address(0), "Core Root Router cannot be 0 address."); require(_coreRootBridgeAgent != address(0), "Core Root Bridge Agent cannot be 0 address."); // Optimized: require(_coreRootRouter != address(0) && _coreRootBridgeAgent != address(0), "Addresses cannot be 0.");
#0 - c4-pre-sort
2023-10-15T17:38:15Z
0xA5DF marked the issue as sufficient quality report
#1 - c4-judge
2023-10-26T13:41:31Z
alcueca marked the issue as grade-a
🌟 Selected for report: MrPotatoMagic
Also found by: 0xHelium, 0xSmartContract, 0xbrett8571, 0xsagetony, 33BYTEZZZ, Bauchibred, K42, Littlebeast, LokiThe5th, Oxsadeeq, SAAJ, Sathish9098, ZdravkoHr, albertwh1te, alexxander, catellatech, chaduke, hunter_w3b, ihtishamsudo, invitedtea, jauvany, klau5, kodyvim, lsaudit, pavankv, pfapostol, yongskiws
243.335 USDC - $243.33
Virtualized liquidity and Virtual Accounts:
VirtualAccount.sol
withdrawNative()
, withdrawERC20()
, and withdrawERC721()
functions.IRootPort(localPortAddress).isRouterApproved()
, which could be a point of failure if compromised.BranchPort.sol
, RootPort.sol
, and ArbitrumBranchPort.sol
ArbitrumBranchBridgeAgent.sol
, RootBridgeAgentExecutor.sol
, RootBridgeAgent.sol
, BranchBridgeAgent.sol
, BranchBridgeAgentExecutor.sol
, RootBridgeAgentFactory.sol
, and ArbitrumBranchBridgeAgentFactory.sol
requiresApprovedCaller
and lock
for access control and reentrancy protection.3. Error Handling:
revert
statements with custom messages for debugging.AccessControl
instead of current usage, for a more standardized approach to access control.withdraw
and manage
functions.manage
function could lead to liquidity issues.lock
modifier's effectiveness is not clear without full context.Security Concerns:
IRootPort(localPortAddress).isRouterApproved()
Technical Insights:
Security Concerns:
_unlocked
is not managed securely.ReentrancyGuard
.Security Analysis: Functions and Risks:
_setup
variable, allowing for potential overwrites.initialize
could overwrite important state variables._setupCore
variable.initialize
, multiple calls could overwrite core settings._bridgeAgent
._bridgeAgent
.coreRootRouterAddress
and coreRootBridgeAgentAddress
.IRootPort
for access control in VirtualAccount.sol, a more standardized and decentralized solution for access control is recommended. Specific recommendations tied to individual functions have been provided for mitigating identified risks. More rigorous testing, 100% coverage using formal verification and invariant testing, and continued future third-party audits are crucial for its long-term success and security.36 hours
#0 - c4-pre-sort
2023-10-15T14:32:39Z
0xA5DF marked the issue as sufficient quality report
#1 - alcueca
2023-10-20T09:11:07Z
Good function-by-function analysis. No need to say in the overview that you focused on gas optimizations. Consider fleshing out the overview with diagrams and adding a more global view of the system to have a chance at best report. Good job.
#2 - c4-judge
2023-10-20T09:11:12Z
alcueca marked the issue as grade-a