019c0ea9-07a4-7162-9dd3-67d9c56c3671
Open this run to answer one question: is this real attacker-relevant behavior with real impact, or just normal contract plumbing?
| ID | Type | Status | Attempts | Heartbeat |
|---|---|---|---|---|
| 41641 | artifact.fetch | complete | 1 / 3 | 2 months ago |
| 41642 | proxy.resolve | complete | 1 / 3 | 2 months ago |
| 41643 | tool.cast_disasm | complete | 1 / 3 | 2 months ago |
| 41644 | analysis.bundle | complete | 1 / 3 | 2 months ago |
| 41645 | capability.graph | complete | 1 / 3 | 2 months ago |
| 41646 | detector.run | complete | 1 / 3 | 2 months ago |
| 41647 | validation.fork | complete | 1 / 3 | 2 months ago |
| 42242 | report.consolidate | complete | 1 / 6 | 2 months ago |
| 42243 | tool.slither | failed | 2 / 2 | 2 months ago |
{
"method": "etherscan+onchain",
"status": "confirmed",
"etherscan_hint": {
"source": "etherscan",
"etherscan": {
"proxy_flag": true,
"implementation": "0x3203e813930bd710043c1d899fe38dd359307352"
}
},
"implementation": "0x3203e813930bd710043c1d899fe38dd359307352",
"onchain_method": "eip1967"
}
Traceback (most recent call last):
File "/var/www/tripwire/worker/worker.py", line 198, in main
payload = dispatch_job(job_type, job_id, job_meta, run_data, config, laravel_client)
File "/var/www/tripwire/worker/worker.py", line 114, in dispatch_job
return slither_run.run(job_id, job_meta, run_data, config, log, laravel_client)
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/var/www/tripwire/worker/jobs/slither_run.py", line 1084, in run
stdout_text, stderr_text, result_json = _run_slither(
~~~~~~~~~~~~^
workspace,
^^^^^^^^^^
...<4 lines>...
target_path=target_path,
^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/var/www/tripwire/worker/jobs/slither_run.py", line 958, in _run_slither
raise RuntimeError(f"slither failed (exit {result.returncode}): {stderr_text or stdout_text}")
RuntimeError: slither failed (exit 1): 'forge clean' running (wd: /tmp/slither-i08qivd9)
'forge config --json' running
'forge build --build-info --skip ./test/** ./script/** --force' running (wd: /tmp/slither-i08qivd9)
Traceback (most recent call last):
File "/var/www/tripwire/.venv/bin/slither", line 10, in <module>
sys.exit(main())
~~~~^^
File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 776, in main
main_impl(all_detector_classes=detectors, all_printer_classes=printers)
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 882, in main_impl
) = process_all(filename, args, detector_classes, printer_classes)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 96, in process_all
compilations = compile_all(target, **vars(args))
File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 722, in compile_all
compilations.append(CryticCompile(target, **kwargs))
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 211, in __init__
self._compile(**kwargs)
~~~~~~~~~~~~~^^^^^^^^^^
File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 633, in _compile
self._platform.compile(self, **kwargs)
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/platform/foundry.py", line 102, in compile
hardhat_like_parsing(
~~~~~~~~~~~~~~~~~~~~^
crytic_compile, str(self._target), build_directory, str(self._project_root)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/platform/hardhat.py", line 52, in hardhat_like_parsing
raise InvalidCompilation(txt)
crytic_compile.platform.exceptions.InvalidCompilation: Compilation failed. Can you run build command?
/tmp/slither-i08qivd9/out/build-info is not a directory.
{"no_findings":false,"summary":"Two issues found: the proxy initializer is unrestricted (allowing first-caller takeover if not initialized atomically) and refund recipient aliasing is not enforced when a contract address is provided, which can strand refunds on L2.","findings":[{"severity":"high","category":"upgradeability","title":"Unrestricted initialize allows proxy takeover if not initialized atomically","exploit_scenario":"If the proxy is deployed without atomically calling `initialize`, any account can call it first and set `l2Bridge`, `l2TokenBeacon`, and `l2TokenProxyBytecodeHash` to attacker-controlled values. The attacker can then deploy or point `l2Bridge` to a malicious L2 contract that emits withdrawal messages to drain L1 token balances or permanently disrupt deposits/withdrawals.","recommendation":"Require an access control check on `initialize` (e.g., onlyProxyAdmin/onlyOwner) or ensure the proxy is deployed with initialization calldata in the same transaction so no external caller can front-run the first initialization.","evidence":"`initialize` is `external payable` and only guarded by `reentrancyGuardInitializer`, with no caller restriction or admin check.","locations":["contracts/zksync/l1-contracts/bridge/L1ERC20Bridge.sol:82"]},{"severity":"low","category":"other","title":"Refund recipient aliasing not enforced for explicit L1 contract addresses","exploit_scenario":"A user (or UI) may pass an L1 contract address as `_refundRecipient` expecting automatic aliasing. The code only aliases when `_refundRecipient == address(0)`, so the refund will be sent to the same address on L2, which the L1 contract does not control. If the L2 transaction fails, the refund can be lost or claimed by whoever controls that L2 address.","recommendation":"If `_refundRecipient` is a contract on L1, automatically apply `AddressAliasHelper.applyL1ToL2Alias`, or explicitly require callers to pass an already-aliased L2 address and update the documentation accordingly.","evidence":"Aliasing is only applied when `_refundRecipient == address(0)`; there is no contract-detection branch for explicit `_refundRecipient` values despite the comment implying it.","locations":["contracts/zksync/l1-contracts/bridge/L1ERC20Bridge.sol:264"]}]}
OpenAI Codex v0.77.0 (research preview)
--------
workdir: /tmp/codex-noz_u1fv
model: gpt-5.2-codex
provider: openai
approval: never
sandbox: read-only
reasoning effort: high
reasoning summaries: auto
session id: 019c0ee2-3807-7eb2-8027-24c037ed242d
--------
user
Context: The user requested the proxy at 0xad16edcf7deb7e90096a259c81269d811544b6b6.
Audit the implementation at 0x3203e813930bd710043c1d899fe38dd359307352.
Findings should refer to the implementation unless explicitly about proxy mechanics.
You are a security auditor. Audit this Solidity codebase for exploitable vulnerabilities.
Focus on: reentrancy, access control, upgradeability/proxy issues, signature validation, oracle manipulation,
accounting/invariant breaks, external call patterns, unchecked return values, and dangerous delegatecall.
Scope rules:
- You MUST fully read `AUDIT_BUNDLE.txt` before concluding. It contains the complete contents of all first-party Solidity sources in this workspace.
- Treat `lib/` and `@openzeppelin/` as third-party dependencies; skip them unless there is a concrete misuse angle in first-party code.
- If there are only dependency files and no first-party sources, set no_findings=true and explain in summary.
Output MUST be valid JSON matching the provided schema.
- If you find nothing meaningful: set no_findings=true and findings=[].
- Otherwise: set no_findings=false and include findings with severity/category/title and file/line locations when possible.
Output constraints (IMPORTANT):
- severity MUST be exactly one of: info, low, medium, high, critical (lowercase).
- category MUST be exactly one of: access_control, reentrancy, math, oracle, upgradeability, auth, other.
Keep summary short and concrete.
mcp startup: no servers
exec
/bin/bash -lc 'cat AUDIT_BUNDLE.txt' in /tmp/codex-noz_u1fv succeeded in 6ms:
This file concatenates the full contents of first-party Solidity sources for auditing.
Do not treat dependency code under lib/ or @openzeppelin/ as first-party.
================================================================================
FILE: contracts/zksync/l1-contracts/bridge/L1ERC20Bridge.sol
================================================================================
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IL1BridgeLegacy} from "./interfaces/IL1BridgeLegacy.sol";
import {IL1Bridge} from "./interfaces/IL1Bridge.sol";
import {IL2Bridge} from "./interfaces/IL2Bridge.sol";
import {IL2ERC20Bridge} from "./interfaces/IL2ERC20Bridge.sol";
import {BridgeInitializationHelper} from "./libraries/BridgeInitializationHelper.sol";
exec
/bin/bash -lc "sed -n '1,240p' contracts/zksync/l1-contracts/bridge/L1ERC20Bridge.sol" in /tmp/codex-noz_u1fv succeeded in 6ms:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IL1BridgeLegacy} from "./interfaces/IL1BridgeLegacy.sol";
import {IL1Bridge} from "./interfaces/IL1Bridge.sol";
import {IL2Bridge} from "./interfaces/IL2Bridge.sol";
import {IL2ERC20Bridge} from "./interfaces/IL2ERC20Bridge.sol";
import {BridgeInitializationHelper} from "./libraries/BridgeInitializationHelper.sol";
import {IMailbox, TxStatus} from "../zksync/interfaces/IMailbox.sol";
import {L2Message} from "../zksync/Storage.sol";
import {UnsafeBytes} from "../common/libraries/UnsafeBytes.sol";
import {L2ContractHelper} from "../common/libraries/L2ContractHelper.sol";
import {ReentrancyGuard} from "../common/ReentrancyGuard.sol";
exec
/bin/bash -lc "sed -n '240,520p' contracts/zksync/l1-contracts/bridge/L1ERC20Bridge.sol" in /tmp/codex-noz_u1fv succeeded in 5ms:
l2TxHash = _deposit(
_l2Receiver,
_l1Token,
_amount,
_l2TxGasLimit,
_l2TxGasPerPubdataByte,
_refundRecipient,
true
);
}
function _deposit(
address _l2Receiver,
address _l1Token,
uint256 _amount,
uint256 _l2TxGasLimit,
uint256 _l2TxGasPerPubdataByte,
address _refundRecipient,
bool _toMerge
) internal returns (bytes32 l2TxHash) {
exec
/bin/bash -lc "rg --files -g '*.sol'" in /tmp/codex-noz_u1fv succeeded in 34ms:
@openzeppelin/contracts/token/ERC20/IERC20.sol
@openzeppelin/contracts/utils/Address.sol
@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol
@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol
contracts/zksync/l1-contracts/zksync/interfaces/IMailbox.sol
contracts/zksync/l1-contracts/zksync/Storage.sol
@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
contracts/zksync/l1-contracts/bridge/L1ERC20Bridge.sol
contracts/zksync/l1-contracts/bridge/libraries/BridgeInitializationHelper.sol
contracts/zksync/l1-contracts/bridge/interfaces/IL1Bridge.sol
contracts/zksync/l1-contracts/bridge/interfaces/IL2ERC20Bridge.sol
contracts/zksync/l1-contracts/bridge/interfaces/IL2Bridge.sol
contracts/zksync/l1-contracts/bridge/interfaces/IL1BridgeLegacy.sol
contracts/zksync/l1-contracts/common/ReentrancyGuard.sol
contracts/zksync/l1-contracts/vendor/AddressAliasHelper.sol
contracts/zksync/l1-contracts/common/L2ContractAddresses.sol
contracts/zksync/l1-contracts/common/interfaces/IL2ContractDeployer.sol
contracts/zksync/l1-contracts/common/libraries/L2ContractHelper.sol
contracts/zksync/l1-contracts/common/libraries/UnsafeBytes.sol
exec
/bin/bash -lc "sed -n '1,240p' contracts/zksync/l1-contracts/bridge/libraries/BridgeInitializationHelper.sol" in /tmp/codex-noz_u1fv succeeded in 8ms:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../../zksync/interfaces/IMailbox.sol";
import "../../vendor/AddressAliasHelper.sol";
import "../../common/libraries/L2ContractHelper.sol";
import {L2_DEPLOYER_SYSTEM_CONTRACT_ADDR} from "../../common/L2ContractAddresses.sol";
import "../../common/interfaces/IL2ContractDeployer.sol";
/// @author Matter Labs
/// @custom:security-contact security@matterlabs.dev
/// @dev A helper library for initializing L2 bridges in zkSync L2 network.
library BridgeInitializationHelper {
/// @dev The L2 gas limit for requesting L1 -> L2 transaction of deploying L2 bridge instance.
/// @dev It is big enough to deploy any contract, so we can use the same value for all bridges.
/// NOTE: this constant will be accurately calculated in the future.
uint256 constant DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT = 10000000;
/// @dev The default l2GasPricePerPubdata to be used in bridges.
exec
/bin/bash -lc "sed -n '1,200p' contracts/zksync/l1-contracts/common/ReentrancyGuard.sol" in /tmp/codex-noz_u1fv succeeded in 6ms:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @custom:security-contact security@matterlabs.dev
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
exec
/bin/bash -lc "sed -n '1,240p' contracts/zksync/l1-contracts/common/libraries/L2ContractHelper.sol" in /tmp/codex-noz_u1fv succeeded in 6ms:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @author Matter Labs
* @custom:security-contact security@matterlabs.dev
* @notice Helper library for working with L2 contracts on L1.
*/
library L2ContractHelper {
/// @dev The prefix used to create CREATE2 addresses.
bytes32 private constant CREATE2_PREFIX = keccak256("zksyncCreate2");
/// @notice Validate the bytecode format and calculate its hash.
/// @param _bytecodeLength The bytecode length.
/// @param _bytecodeHash The bytecode hash.
/// @return hashedBytecode The 32-byte hash of the bytecode.
/// Note: The function reverts the execution if the bytecode has non expected format:
/// - Bytecode bytes length is not a multiple of 32
/// - Bytecode bytes length is not less than 2^21 bytes (2^16 words)
exec
/bin/bash -lc "sed -n '1,160p' contracts/zksync/l1-contracts/common/L2ContractAddresses.sol" in /tmp/codex-noz_u1fv succeeded in 7ms:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @dev The address of the L2 deployer system contract.
address constant L2_DEPLOYER_SYSTEM_CONTRACT_ADDR = address(0x8006);
/// @dev The special reserved L2 address. It is located in the system contracts space but doesn't have deployed
/// bytecode.
/// @dev The L2 deployer system contract allows changing bytecodes on any address if the `msg.sender` is this address.
/// @dev So, whenever the governor wants to redeploy system contracts, it just initiates the L1 upgrade call deployer
/// system contract
/// via the L1 -> L2 transaction with `sender == L2_FORCE_DEPLOYER_ADDR`. For more details see the
/// `diamond-initializers` contracts.
address constant L2_FORCE_DEPLOYER_ADDR = address(0x8007);
/// @dev The address of the special smart contract that can send arbitrary length message as an L2 log
address constant L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR = address(0x8008);
/// @dev The formal address of the initial program of the system: the bootloader
exec
/bin/bash -lc "sed -n '1,200p' contracts/zksync/l1-contracts/bridge/interfaces/IL1Bridge.sol" in /tmp/codex-noz_u1fv succeeded in 11ms:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title L1 Bridge contract interface
/// @author Matter Labs
/// @custom:security-contact security@matterlabs.dev
interface IL1Bridge {
event DepositInitiated(
bytes32 indexed l2DepositTxHash,
address indexed from,
address indexed to,
address l1Token,
uint256 amount
);
event DepositToMergeInitiated(
bytes32 indexed l2DepositTxHash,
address indexed from,
address indexed to,
exec
/bin/bash -lc "sed -n '1,200p' contracts/zksync/l1-contracts/bridge/interfaces/IL1BridgeLegacy.sol" in /tmp/codex-noz_u1fv succeeded in 6ms:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title L1 Bridge contract legacy interface
/// @author Matter Labs
/// @custom:security-contact security@matterlabs.dev
interface IL1BridgeLegacy {
function deposit(
address _l2Receiver,
address _l1Token,
uint256 _amount,
uint256 _l2TxGasLimit,
uint256 _l2TxGasPerPubdataByte
) external payable returns (bytes32 txHash);
}
exec
/bin/bash -lc "nl -ba contracts/zksync/l1-contracts/bridge/L1ERC20Bridge.sol | sed -n '50,160p'" in /tmp/codex-noz_u1fv succeeded in 10ms:
50 /// @dev The bytecode hash of the L2 token contract
51 bytes32 public l2TokenProxyBytecodeHash;
52
53 mapping(address => uint256) private __DEPRECATED_lastWithdrawalLimitReset;
54
55 /// @dev A mapping L1 token address => the accumulated withdrawn amount during the withdrawal limit window
56 mapping(address => uint256) private __DEPRECATED_withdrawnAmountInWindow;
57
58 /// @dev The accumulated deposited amount per user.
59 /// @dev A mapping L1 token address => user address => the total deposited amount by the user
60 mapping(address => mapping(address => uint256)) private __DEPRECATED_totalDepositedAmountPerUser;
61
62 /// @dev Contract is expected to be used as proxy implementation.
63 /// @dev Initialize the implementation to prevent Parity hack.
64 constructor(IMailbox _zkSync) reentrancyGuardInitializer {
65 zkSync = _zkSync;
66 }
67
68 /// @dev Initializes a contract bridge for later use. Expected to be used in the proxy
69 /// @dev During initialization deploys L2 bridge counterpart as well as provides some factory deps for it
exec
/bin/bash -lc "nl -ba contracts/zksync/l1-contracts/bridge/L1ERC20Bridge.sol | sed -n '200,320p'" in /tmp/codex-noz_u1fv succeeded in 6ms:
200 _amount,
201 _l2TxGasLimit,
202 _l2TxGasPerPubdataByte,
203 _refundRecipient,
204 false
205 );
206 }
207
208 /// @notice Initiates a deposit by locking funds on the contract and sending the request
209 /// of processing an L2 transaction where merge tokens would be minted
210 /// @dev If the token is bridged for the first time, the L2 token and merge token contract will be deployed. Note however, that the
211 /// newly-deployed token does not support any custom logic, i.e. rebase tokens' functionality is not supported.
212 /// @param _l2Receiver The account address that should receive funds on L2
213 /// @param _l1Token The L1 token address which is deposited
214 /// @param _amount The total amount of tokens to be bridged
215 /// @param _l2TxGasLimit The L2 gas limit to be used in the corresponding L2 transaction
216 /// @param _l2TxGasPerPubdataByte The gasPerPubdataByteLimit to be used in the corresponding L2 transaction
217 /// @param _refundRecipient The address on L2 that will receive the refund for the transaction.
218 /// @dev If the L2 deposit finalization transaction fails, the `_refundRecipient` will receive the `_l2Value`.
219 /// Please note, the contract may change the refund recipient's address to eliminate sending funds to addresses
codex
{"no_findings":false,"summary":"Two issues found: the proxy initializer is unrestricted (allowing first-caller takeover if not initialized atomically) and refund recipient aliasing is not enforced when a contract address is provided, which can strand refunds on L2.","findings":[{"severity":"high","category":"upgradeability","title":"Unrestricted initialize allows proxy takeover if not initialized atomically","exploit_scenario":"If the proxy is deployed without atomically calling `initialize`, any account can call it first and set `l2Bridge`, `l2TokenBeacon`, and `l2TokenProxyBytecodeHash` to attacker-controlled values. The attacker can then deploy or point `l2Bridge` to a malicious L2 contract that emits withdrawal messages to drain L1 token balances or permanently disrupt deposits/withdrawals.","recommendation":"Require an access control check on `initialize` (e.g., onlyProxyAdmin/onlyOwner) or ensure the proxy is deployed with initialization calldata in the same transaction so no external caller can front-run the first initialization.","evidence":"`initialize` is `external payable` and only guarded by `reentrancyGuardInitializer`, with no caller restriction or admin check.","locations":["contracts/zksync/l1-contracts/bridge/L1ERC20Bridge.sol:82"]},{"severity":"low","category":"other","title":"Refund recipient aliasing not enforced for explicit L1 contract addresses","exploit_scenario":"A user (or UI) may pass an L1 contract address as `_refundRecipient` expecting automatic aliasing. The code only aliases when `_refundRecipient == address(0)`, so the refund will be sent to the same address on L2, which the L1 contract does not control. If the L2 transaction fails, the refund can be lost or claimed by whoever controls that L2 address.","recommendation":"If `_refundRecipient` is a contract on L1, automatically apply `AddressAliasHelper.applyL1ToL2Alias`, or explicitly require callers to pass an already-aliased L2 address and update the documentation accordingly.","evidence":"Aliasing is only applied when `_refundRecipient == address(0)`; there is no contract-detection branch for explicit `_refundRecipient` values despite the comment implying it.","locations":["contracts/zksync/l1-contracts/bridge/L1ERC20Bridge.sol:264"]}]}
tokens used
62,757
{
"codex": {
"ran_at": "2026-01-30T12:32:46.632757+00:00",
"returncode": 0
},
"project": {
"mode": "source",
"file_count": 19,
"import_count": 23,
"first_party_contracts_count": 14
},
"summary": "Two issues found: the proxy initializer is unrestricted (allowing first-caller takeover if not initialized atomically) and refund recipient aliasing is not enforced when a contract address is provided, which can strand refunds on L2.",
"raw_final": {
"summary": "Two issues found: the proxy initializer is unrestricted (allowing first-caller takeover if not initialized atomically) and refund recipient aliasing is not enforced when a contract address is provided, which can strand refunds on L2.",
"findings": [
{
"title": "Unrestricted initialize allows proxy takeover if not initialized atomically",
"category": "upgradeability",
"evidence": "`initialize` is `external payable` and only guarded by `reentrancyGuardInitializer`, with no caller restriction or admin check.",
"severity": "high",
"locations": [
"contracts/zksync/l1-contracts/bridge/L1ERC20Bridge.sol:82"
],
"recommendation": "Require an access control check on `initialize` (e.g., onlyProxyAdmin/onlyOwner) or ensure the proxy is deployed with initialization calldata in the same transaction so no external caller can front-run the first initialization.",
"exploit_scenario": "If the proxy is deployed without atomically calling `initialize`, any account can call it first and set `l2Bridge`, `l2TokenBeacon`, and `l2TokenProxyBytecodeHash` to attacker-controlled values. The attacker can then deploy or point `l2Bridge` to a malicious L2 contract that emits withdrawal messages to drain L1 token balances or permanently disrupt deposits/withdrawals."
},
{
"title": "Refund recipient aliasing not enforced for explicit L1 contract addresses",
"category": "other",
"evidence": "Aliasing is only applied when `_refundRecipient == address(0)`; there is no contract-detection branch for explicit `_refundRecipient` values despite the comment implying it.",
"severity": "low",
"locations": [
"contracts/zksync/l1-contracts/bridge/L1ERC20Bridge.sol:264"
],
"recommendation": "If `_refundRecipient` is a contract on L1, automatically apply `AddressAliasHelper.applyL1ToL2Alias`, or explicitly require callers to pass an already-aliased L2 address and update the documentation accordingly.",
"exploit_scenario": "A user (or UI) may pass an L1 contract address as `_refundRecipient` expecting automatic aliasing. The code only aliases when `_refundRecipient == address(0)`, so the refund will be sent to the same address on L2, which the L1 contract does not control. If the L2 transaction fails, the refund can be lost or claimed by whoever controls that L2 address."
}
],
"no_findings": false
},
"no_findings": false,
"proxy_context": {
"proxy_run_id": "019c0ea9-07a4-7162-9dd3-67d9c56c3671",
"proxy_address": "0xad16edcf7deb7e90096a259c81269d811544b6b6",
"implementation_run_id": "019c0ea9-c986-7300-a119-1cc3848d4c55",
"implementation_address": "0x3203e813930bd710043c1d899fe38dd359307352"
},
"schema_version": 1
}
00000000: PUSH1 0x80 00000002: PUSH1 0x40 00000004: MSTORE 00000005: PUSH2 0x000c 00000008: PUSH2 0x000e 0000000b: JUMP 0000000c: JUMPDEST 0000000d: STOP 0000000e: JUMPDEST 0000000f: PUSH32 0x000000000000000000000000315255c1ba35a1ddac48cf054bc4e3a0929160b2 00000030: PUSH1 0x01 00000032: PUSH1 0x01 00000034: PUSH1 0xa0 00000036: SHL 00000037: SUB 00000038: AND 00000039: CALLER 0000003a: SUB 0000003b: PUSH2 0x007b 0000003e: JUMPI 0000003f: PUSH1 0x00 00000041: CALLDATALOAD 00000042: PUSH1 0x01 00000044: PUSH1 0x01 00000046: PUSH1 0xe0 00000048: SHL 00000049: SUB 0000004a: NOT 0000004b: AND 0000004c: PUSH4 0x278f7943 00000051: PUSH1 0xe1 00000053: SHL 00000054: EQ 00000055: PUSH2 0x0071 00000058: JUMPI 00000059: PUSH1 0x40 0000005b: MLOAD 0000005c: PUSH4 0x34ad5dbb 00000061: PUSH1 0xe2 00000063: SHL 00000064: DUP2 00000065: MSTORE 00000066: PUSH1 0x04 00000068: ADD 00000069: PUSH1 0x40 0000006b: MLOAD 0000006c: DUP1 0000006d: SWAP2 0000006e: SUB 0000006f: SWAP1 00000070: REVERT 00000071: JUMPDEST 00000072: PUSH2 0x0079 00000075: PUSH2 0x0083 00000078: JUMP 00000079: JUMPDEST 0000007a: JUMP 0000007b: JUMPDEST 0000007c: PUSH2 0x0079 0000007f: PUSH2 0x00b2 00000082: JUMP 00000083: JUMPDEST 00000084: PUSH1 0x00 00000086: DUP1 00000087: PUSH2 0x0093 0000008a: CALLDATASIZE 0000008b: PUSH1 0x04 0000008d: DUP2 0000008e: DUP5 0000008f: PUSH2 0x0312 00000092: JUMP 00000093: JUMPDEST 00000094: DUP2 00000095: ADD 00000096: SWAP1 00000097: PUSH2 0x00a0 0000009a: SWAP2 0000009b: SWAP1 0000009c: PUSH2 0x0352 0000009f: JUMP 000000a0: JUMPDEST 000000a1: SWAP2 000000a2: POP 000000a3: SWAP2 000000a4: POP 000000a5: PUSH2 0x00ae 000000a8: DUP3 000000a9: DUP3 000000aa: PUSH2 0x00c2 000000ad: JUMP 000000ae: JUMPDEST 000000af: POP 000000b0: POP 000000b1: JUMP 000000b2: JUMPDEST 000000b3: PUSH2 0x0079 000000b6: PUSH2 0x00bd 000000b9: PUSH2 0x011d 000000bc: JUMP 000000bd: JUMPDEST 000000be: PUSH2 0x0155 000000c1: JUMP 000000c2: JUMPDEST 000000c3: PUSH2 0x00cb 000000c6: DUP3 000000c7: PUSH2 0x0179 000000ca: JUMP 000000cb: JUMPDEST 000000cc: PUSH1 0x40 000000ce: MLOAD 000000cf: PUSH1 0x01 000000d1: PUSH1 0x01 000000d3: PUSH1 0xa0 000000d5: SHL 000000d6: SUB 000000d7: DUP4 000000d8: AND 000000d9: SWAP1 000000da: PUSH32 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b 000000fb: SWAP1 000000fc: PUSH1 0x00 000000fe: SWAP1 000000ff: LOG2 00000100: DUP1 00000101: MLOAD 00000102: ISZERO 00000103: PUSH2 0x0115 00000106: JUMPI 00000107: PUSH2 0x0110 0000010a: DUP3 0000010b: DUP3 0000010c: PUSH2 0x01f5 0000010f: JUMP 00000110: JUMPDEST 00000111: POP 00000112: POP 00000113: POP 00000114: JUMP 00000115: JUMPDEST 00000116: PUSH2 0x00ae 00000119: PUSH2 0x026b 0000011c: JUMP 0000011d: JUMPDEST 0000011e: PUSH1 0x00 00000120: PUSH2 0x0150 00000123: PUSH32 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc 00000144: SLOAD 00000145: PUSH1 0x01 00000147: PUSH1 0x01 00000149: PUSH1 0xa0 0000014b: SHL 0000014c: SUB 0000014d: AND 0000014e: SWAP1 0000014f: JUMP 00000150: JUMPDEST 00000151: SWAP1 00000152: POP 00000153: SWAP1 00000154: JUMP 00000155: JUMPDEST 00000156: CALLDATASIZE 00000157: PUSH1 0x00 00000159: DUP1 0000015a: CALLDATACOPY 0000015b: PUSH1 0x00 0000015d: DUP1 0000015e: CALLDATASIZE 0000015f: PUSH1 0x00 00000161: DUP5 00000162: GAS 00000163: DELEGATECALL 00000164: RETURNDATASIZE 00000165: PUSH1 0x00 00000167: DUP1 00000168: RETURNDATACOPY 00000169: DUP1 0000016a: DUP1 0000016b: ISZERO 0000016c: PUSH2 0x0174 0000016f: JUMPI 00000170: RETURNDATASIZE 00000171: PUSH1 0x00 00000173: RETURN 00000174: JUMPDEST 00000175: RETURNDATASIZE 00000176: PUSH1 0x00 00000178: REVERT 00000179: JUMPDEST 0000017a: DUP1 0000017b: PUSH1 0x01 0000017d: PUSH1 0x01 0000017f: PUSH1 0xa0 00000181: SHL 00000182: SUB 00000183: AND 00000184: EXTCODESIZE 00000185: PUSH1 0x00 00000187: SUB 00000188: PUSH2 0x01b4 0000018b: JUMPI 0000018c: PUSH1 0x40 0000018e: MLOAD 0000018f: PUSH4 0x4c9c8ce3 00000194: PUSH1 0xe0 00000196: SHL 00000197: DUP2 00000198: MSTORE 00000199: PUSH1 0x01 0000019b: PUSH1 0x01 0000019d: PUSH1 0xa0 0000019f: SHL 000001a0: SUB 000001a1: DUP3 000001a2: AND 000001a3: PUSH1 0x04 000001a5: DUP3 000001a6: ADD 000001a7: MSTORE 000001a8: PUSH1 0x24 000001aa: ADD 000001ab: JUMPDEST 000001ac: PUSH1 0x40 000001ae: MLOAD 000001af: DUP1 000001b0: SWAP2 000001b1: SUB 000001b2: SWAP1 000001b3: REVERT 000001b4: JUMPDEST 000001b5: PUSH32 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc 000001d6: DUP1 000001d7: SLOAD 000001d8: PUSH1 0x01 000001da: PUSH1 0x01 000001dc: PUSH1 0xa0 000001de: SHL 000001df: SUB 000001e0: NOT 000001e1: AND 000001e2: PUSH1 0x01 000001e4: PUSH1 0x01 000001e6: PUSH1 0xa0 000001e8: SHL 000001e9: SUB 000001ea: SWAP3 000001eb: SWAP1 000001ec: SWAP3 000001ed: AND 000001ee: SWAP2 000001ef: SWAP1 000001f0: SWAP2 000001f1: OR 000001f2: SWAP1 000001f3: SSTORE 000001f4: JUMP 000001f5: JUMPDEST 000001f6: PUSH1 0x60 000001f8: PUSH1 0x00 000001fa: DUP1 000001fb: DUP5 000001fc: PUSH1 0x01 000001fe: PUSH1 0x01 00000200: PUSH1 0xa0 00000202: SHL 00000203: SUB 00000204: AND 00000205: DUP5 00000206: PUSH1 0x40 00000208: MLOAD 00000209: PUSH2 0x0212 0000020c: SWAP2 0000020d: SWAP1 0000020e: PUSH2 0x0422 00000211: JUMP 00000212: JUMPDEST 00000213: PUSH1 0x00 00000215: PUSH1 0x40 00000217: MLOAD 00000218: DUP1 00000219: DUP4 0000021a: SUB 0000021b: DUP2 0000021c: DUP6 0000021d: GAS 0000021e: DELEGATECALL 0000021f: SWAP2 00000220: POP 00000221: POP 00000222: RETURNDATASIZE 00000223: DUP1 00000224: PUSH1 0x00 00000226: DUP2 00000227: EQ 00000228: PUSH2 0x024d 0000022b: JUMPI 0000022c: PUSH1 0x40 0000022e: MLOAD 0000022f: SWAP2 00000230: POP 00000231: PUSH1 0x1f 00000233: NOT 00000234: PUSH1 0x3f 00000236: RETURNDATASIZE 00000237: ADD 00000238: AND 00000239: DUP3 0000023a: ADD 0000023b: PUSH1 0x40 0000023d: MSTORE 0000023e: RETURNDATASIZE 0000023f: DUP3 00000240: MSTORE 00000241: RETURNDATASIZE 00000242: PUSH1 0x00 00000244: PUSH1 0x20 00000246: DUP5 00000247: ADD 00000248: RETURNDATACOPY 00000249: PUSH2 0x0252 0000024c: JUMP 0000024d: JUMPDEST 0000024e: PUSH1 0x60 00000250: SWAP2 00000251: POP 00000252: JUMPDEST 00000253: POP 00000254: SWAP2 00000255: POP 00000256: SWAP2 00000257: POP 00000258: PUSH2 0x0262 0000025b: DUP6 0000025c: DUP4 0000025d: DUP4 0000025e: PUSH2 0x028a 00000261: JUMP 00000262: JUMPDEST 00000263: SWAP6 00000264: SWAP5 00000265: POP 00000266: POP 00000267: POP 00000268: POP 00000269: POP 0000026a: JUMP 0000026b: JUMPDEST 0000026c: CALLVALUE 0000026d: ISZERO 0000026e: PUSH2 0x0079 00000271: JUMPI 00000272: PUSH1 0x40 00000274: MLOAD 00000275: PUSH4 0xb398979f 0000027a: PUSH1 0xe0 0000027c: SHL 0000027d: DUP2 0000027e: MSTORE 0000027f: PUSH1 0x04 00000281: ADD 00000282: PUSH1 0x40 00000284: MLOAD 00000285: DUP1 00000286: SWAP2 00000287: SUB 00000288: SWAP1 00000289: REVERT 0000028a: JUMPDEST 0000028b: PUSH1 0x60 0000028d: DUP3 0000028e: PUSH2 0x029f 00000291: JUMPI 00000292: PUSH2 0x029a 00000295: DUP3 00000296: PUSH2 0x02e9 00000299: JUMP 0000029a: JUMPDEST 0000029b: PUSH2 0x02e2 0000029e: JUMP 0000029f: JUMPDEST 000002a0: DUP2 000002a1: MLOAD 000002a2: ISZERO 000002a3: DUP1 000002a4: ISZERO 000002a5: PUSH2 0x02b6 000002a8: JUMPI 000002a9: POP 000002aa: PUSH1 0x01 000002ac: PUSH1 0x01 000002ae: PUSH1 0xa0 000002b0: SHL 000002b1: SUB 000002b2: DUP5 000002b3: AND 000002b4: EXTCODESIZE 000002b5: ISZERO 000002b6: JUMPDEST 000002b7: ISZERO 000002b8: PUSH2 0x02df 000002bb: JUMPI 000002bc: PUSH1 0x40 000002be: MLOAD 000002bf: PUSH4 0x9996b315 000002c4: PUSH1 0xe0 000002c6: SHL 000002c7: DUP2 000002c8: MSTORE 000002c9: PUSH1 0x01 000002cb: PUSH1 0x01 000002cd: PUSH1 0xa0 000002cf: SHL 000002d0: SUB 000002d1: DUP6 000002d2: AND 000002d3: PUSH1 0x04 000002d5: DUP3 000002d6: ADD 000002d7: MSTORE 000002d8: PUSH1 0x24 000002da: ADD 000002db: PUSH2 0x01ab 000002de: JUMP 000002df: JUMPDEST 000002e0: POP 000002e1: DUP1 000002e2: JUMPDEST 000002e3: SWAP4 000002e4: SWAP3 000002e5: POP 000002e6: POP 000002e7: POP 000002e8: JUMP 000002e9: JUMPDEST 000002ea: DUP1 000002eb: MLOAD 000002ec: ISZERO 000002ed: PUSH2 0x02f9 000002f0: JUMPI 000002f1: DUP1 000002f2: MLOAD 000002f3: DUP1 000002f4: DUP3 000002f5: PUSH1 0x20 000002f7: ADD 000002f8: REVERT 000002f9: JUMPDEST 000002fa: PUSH1 0x40 000002fc: MLOAD 000002fd: PUSH4 0x0a12f521 00000302: PUSH1 0xe1 00000304: SHL 00000305: DUP2 00000306: MSTORE 00000307: PUSH1 0x04 00000309: ADD 0000030a: PUSH1 0x40 0000030c: MLOAD 0000030d: DUP1 0000030e: SWAP2 0000030f: SUB 00000310: SWAP1 00000311: REVERT 00000312: JUMPDEST 00000313: PUSH1 0x00 00000315: DUP1 00000316: DUP6 00000317: DUP6 00000318: GT 00000319: ISZERO 0000031a: PUSH2 0x0322 0000031d: JUMPI 0000031e: PUSH1 0x00 00000320: DUP1 00000321: REVERT 00000322: JUMPDEST 00000323: DUP4 00000324: DUP7 00000325: GT 00000326: ISZERO 00000327: PUSH2 0x032f 0000032a: JUMPI 0000032b: PUSH1 0x00 0000032d: DUP1 0000032e: REVERT 0000032f: JUMPDEST 00000330: POP 00000331: POP 00000332: DUP3 00000333: ADD 00000334: SWAP4 00000335: SWAP2 00000336: SWAP1 00000337: SWAP3 00000338: SUB 00000339: SWAP2 0000033a: POP 0000033b: JUMP 0000033c: JUMPDEST 0000033d: PUSH4 0x4e487b71 00000342: PUSH1 0xe0 00000344: SHL 00000345: PUSH1 0x00 00000347: MSTORE 00000348: PUSH1 0x41 0000034a: PUSH1 0x04 0000034c: MSTORE 0000034d: PUSH1 0x24 0000034f: PUSH1 0x00 00000351: REVERT 00000352: JUMPDEST 00000353: PUSH1 0x00 00000355: DUP1 00000356: PUSH1 0x40 00000358: DUP4 00000359: DUP6 0000035a: SUB 0000035b: SLT 0000035c: ISZERO 0000035d: PUSH2 0x0365 00000360: JUMPI 00000361: PUSH1 0x00 00000363: DUP1 00000364: REVERT 00000365: JUMPDEST 00000366: DUP3 00000367: CALLDATALOAD 00000368: PUSH1 0x01 0000036a: PUSH1 0x01 0000036c: PUSH1 0xa0 0000036e: SHL 0000036f: SUB 00000370: DUP2 00000371: AND 00000372: DUP2 00000373: EQ 00000374: PUSH2 0x037c 00000377: JUMPI 00000378: PUSH1 0x00 0000037a: DUP1 0000037b: REVERT 0000037c: JUMPDEST 0000037d: SWAP2 0000037e: POP 0000037f: PUSH1 0x20 00000381: DUP4 00000382: ADD 00000383: CALLDATALOAD 00000384: PUSH8 0xffffffffffffffff 0000038d: DUP1 0000038e: DUP3 0000038f: GT 00000390: ISZERO 00000391: PUSH2 0x0399 00000394: JUMPI 00000395: PUSH1 0x00 00000397: DUP1 00000398: REVERT 00000399: JUMPDEST 0000039a: DUP2 0000039b: DUP6 0000039c: ADD 0000039d: SWAP2 0000039e: POP 0000039f: DUP6 000003a0: PUSH1 0x1f 000003a2: DUP4 000003a3: ADD 000003a4: SLT 000003a5: PUSH2 0x03ad 000003a8: JUMPI 000003a9: PUSH1 0x00 000003ab: DUP1 000003ac: REVERT 000003ad: JUMPDEST 000003ae: DUP2 000003af: CALLDATALOAD 000003b0: DUP2 000003b1: DUP2 000003b2: GT 000003b3: ISZERO 000003b4: PUSH2 0x03bf 000003b7: JUMPI 000003b8: PUSH2 0x03bf 000003bb: PUSH2 0x033c 000003be: JUMP 000003bf: JUMPDEST 000003c0: PUSH1 0x40 000003c2: MLOAD 000003c3: PUSH1 0x1f 000003c5: DUP3 000003c6: ADD 000003c7: PUSH1 0x1f 000003c9: NOT 000003ca: SWAP1 000003cb: DUP2 000003cc: AND 000003cd: PUSH1 0x3f 000003cf: ADD 000003d0: AND 000003d1: DUP2 000003d2: ADD 000003d3: SWAP1 000003d4: DUP4 000003d5: DUP3 000003d6: GT 000003d7: DUP2 000003d8: DUP4 000003d9: LT 000003da: OR 000003db: ISZERO 000003dc: PUSH2 0x03e7 000003df: JUMPI 000003e0: PUSH2 0x03e7 000003e3: PUSH2 0x033c 000003e6: JUMP 000003e7: JUMPDEST 000003e8: DUP2 000003e9: PUSH1 0x40 000003eb: MSTORE 000003ec: DUP3 000003ed: DUP2 000003ee: MSTORE 000003ef: DUP9 000003f0: PUSH1 0x20 000003f2: DUP5 000003f3: DUP8 000003f4: ADD 000003f5: ADD 000003f6: GT 000003f7: ISZERO 000003f8: PUSH2 0x0400 000003fb: JUMPI 000003fc: PUSH1 0x00 000003fe: DUP1 000003ff: REVERT 00000400: JUMPDEST 00000401: DUP3 00000402: PUSH1 0x20 00000404: DUP7 00000405: ADD 00000406: PUSH1 0x20 00000408: DUP4 00000409: ADD 0000040a: CALLDATACOPY 0000040b: PUSH1 0x00 0000040d: PUSH1 0x20 0000040f: DUP5 00000410: DUP4 00000411: ADD 00000412: ADD 00000413: MSTORE 00000414: DUP1 00000415: SWAP6 00000416: POP 00000417: POP 00000418: POP 00000419: POP 0000041a: POP 0000041b: POP 0000041c: SWAP3 0000041d: POP 0000041e: SWAP3 0000041f: SWAP1 00000420: POP 00000421: JUMP 00000422: JUMPDEST 00000423: PUSH1 0x00 00000425: DUP3 00000426: MLOAD 00000427: PUSH1 0x00 00000429: JUMPDEST 0000042a: DUP2 0000042b: DUP2 0000042c: LT 0000042d: ISZERO 0000042e: PUSH2 0x0443 00000431: JUMPI 00000432: PUSH1 0x20 00000434: DUP2 00000435: DUP7 00000436: ADD 00000437: DUP2 00000438: ADD 00000439: MLOAD 0000043a: DUP6 0000043b: DUP4 0000043c: ADD 0000043d: MSTORE 0000043e: ADD 0000043f: PUSH2 0x0429 00000442: JUMP 00000443: JUMPDEST 00000444: POP 00000445: PUSH1 0x00 00000447: SWAP3 00000448: ADD 00000449: SWAP2 0000044a: DUP3 0000044b: MSTORE 0000044c: POP 0000044d: SWAP2 0000044e: SWAP1 0000044f: POP 00000450: JUMP 00000451: INVALID 00000452: LOG2 00000453: PUSH5 0x6970667358 00000459: UNKNOWN(0x22) 0000045a: SLT 0000045b: KECCAK256 0000045c: UNKNOWN(0x27) 0000045d: UNKNOWN(0xF8) 0000045e: ADD 0000045f: PUSH23 0x059c85af4ee8751015dd9e5db167736b2eedc4be4d13ff 00000477: UNKNOWN(0xC8) 00000478: PUSH16
| Severity | Category | Tool | Title | Confidence | Validated | Confirmed | Details |
|---|---|---|---|---|---|---|---|
| critical | upgradeability | detector | Untrusted DELEGATECALL target reachable | 70 | no | no |
viewtrace_id: 0x75bc2b5d15ab733d800efe7a141bce1d96b7097e2d94474a6d71b6dedb6598ac
call_targets:
0x3203e813930bd710043c1d899fe38dd359307352
target_varies:
no
classification:
constant_target
validation_json{
"sink": "DELEGATECALL",
"errors": 0,
"status": "sink_reached",
"attempts": 1,
"trace_id": "0x75bc2b5d15ab733d800efe7a141bce1d96b7097e2d94474a6d71b6dedb6598ac",
"confirmed": false,
"trace_mode": "callTracer",
"call_targets": [
"0x3203e813930bd710043c1d899fe38dd359307352"
],
"matched_probe": null,
"target_varies": false,
"classification": "constant_target"
}
witness_json{
"notes": "heuristic witness; entrypoints are best-effort selectors with zeroed calldata",
"constraints": [],
"entrypoints": [
{
"calldata": "0x278f7943",
"selector": "0x278f7943",
"calldata_variants": [
"0x278f7943",
"0x278f79430000000000000000000000000000000000000000000000000000000000000000"
]
}
]
}
evidence_json{
"examples": [
{
"pc": 355,
"opcode": "DELEGATECALL",
"returndata_checked": null,
"value_expression_category": "n/a",
"target_expression_category": "computed"
},
{
"pc": 542,
"opcode": "DELEGATECALL",
"returndata_checked": null,
"value_expression_category": "n/a",
"target_expression_category": "computed"
}
],
"guards_detected": [],
"delegatecall_count": 2,
"untrusted_delegatecall_count": 2
}
|
| high | upgradeability | codex | Unrestricted initialize allows proxy takeover if not initialized atomically | 85 | no | — |
viewevidence_json{
"evidence": "`initialize` is `external payable` and only guarded by `reentrancyGuardInitializer`, with no caller restriction or admin check.",
"locations": [
"contracts/zksync/l1-contracts/bridge/L1ERC20Bridge.sol:82"
],
"recommendation": "Require an access control check on `initialize` (e.g., onlyProxyAdmin/onlyOwner) or ensure the proxy is deployed with initialization calldata in the same transaction so no external caller can front-run the first initialization.",
"exploit_scenario": "If the proxy is deployed without atomically calling `initialize`, any account can call it first and set `l2Bridge`, `l2TokenBeacon`, and `l2TokenProxyBytecodeHash` to attacker-controlled values. The attacker can then deploy or point `l2Bridge` to a malicious L2 contract that emits withdrawal messages to drain L1 token balances or permanently disrupt deposits/withdrawals."
}
|
| medium | upgradeability | cast | DELEGATECALL present | 80 | no | — |
viewevidence_json{
"notes": "delegatecall enables execution in caller context; common in proxies/upgradeable patterns",
"delegatecall_count": 2
}
|
| low | other | codex | Refund recipient aliasing not enforced for explicit L1 contract addresses | 65 | no | — |
viewevidence_json{
"evidence": "Aliasing is only applied when `_refundRecipient == address(0)`; there is no contract-detection branch for explicit `_refundRecipient` values despite the comment implying it.",
"locations": [
"contracts/zksync/l1-contracts/bridge/L1ERC20Bridge.sol:264"
],
"recommendation": "If `_refundRecipient` is a contract on L1, automatically apply `AddressAliasHelper.applyL1ToL2Alias`, or explicitly require callers to pass an already-aliased L2 address and update the documentation accordingly.",
"exploit_scenario": "A user (or UI) may pass an L1 contract address as `_refundRecipient` expecting automatic aliasing. The code only aliases when `_refundRecipient == address(0)`, so the refund will be sent to the same address on L2, which the L1 contract does not control. If the L2 transaction fails, the refund can be lost or claimed by whoever controls that L2 address."
}
|