Introduction
We express our gratitude to the Bullbit team for the collaborative engagement that enabled the execution of this Smart Contract Security Assessment.
BridgeGovernance is a typed function-route whitelist UUPS governance router for an EVM token bridge. A documented external Safe (per spec) is the intended owner that executes typed administrative actions on the sibling DepositFactory and Vault proxies, and Foundry deploy documentation targets Base and Base Sepolia on otherwise EVM-generic contracts (per spec).
Document | |
|---|---|
| Name | Smart Contract Code Review and Security Analysis Report for Bullbit |
| Audited By | Ivan Bondar |
| Approved By | Kornel Światłowski |
| Website | https://bullbit.ai/→ |
| Changelog | 08/09/2026 - Preliminary Report |
| 14/09/2026 - Final Report | |
| Platform | Base |
| Language | Solidity |
| Tags | Governance; Bridge; Proxy; Upgradable; |
| Methodology | https://docs.hacken.io/methodologies/smart-contracts→ |
Document
- Name
- Smart Contract Code Review and Security Analysis Report for Bullbit
- Audited By
- Ivan Bondar
- Approved By
- Kornel Światłowski
- Website
- https://bullbit.ai/→
- Changelog
- 08/09/2026 - Preliminary Report
- 14/09/2026 - Final Report
- Platform
- Base
- Language
- Solidity
- Tags
- Governance; Bridge; Proxy; Upgradable;
Review Scope | |
|---|---|
| Repository | https://github.com/bullbit/Bridge-Governance-Smart-Contract→ |
| Initial Commit | 1dc6a2e |
| Final Commit | d60b6c1 |
Review Scope
- Initial Commit
- 1dc6a2e
- Final Commit
- d60b6c1
Audit Summary
The system users should acknowledge all the risks summed up in the risks section of the report
Documentation quality
Functional requirements are partially missed.
Technical description is not provided. No architecture note, event catalog, or per-route NatSpec.
Code quality
Owner routes follow one template:
onlyOwner, optional zero check,_callTarget, event.The development environment is configured (Foundry 0.8.30, Makefile, remappings, fmt, coverage filter, deploy scripts).
Test coverage
Code coverage of the project is 100% (branch coverage).
Deployment and basic owner routes are covered.
Negative cases are present.
Multi-user flows are tested.
System Overview
BridgeGovernance is deployed behind a UUPS ERC-1967 proxy. initialize assigns initialOwner and is gated only by the initializer modifier, so any address may call it once on an uninitialized proxy. Initializable, Ownable2StepUpgradeable, and UUPSUpgradeable are inherited. The contract is not a voting Governor. A single owner address is stored in OpenZeppelin Ownable ERC-7201 storage. An external Safe and its signing threshold are deployment intent only (per spec). Typed routes and events are defined on this contract. renounceOwnership is overridden to revert with RenounceDisabled. Replacement of this proxy's implementation is performed through the inherited upgradeToAndCall entry, which is public, onlyProxy, and invokes the internal _authorizeUpgrade hook. That hook is onlyOwner and requires the new implementation to contain code.
BridgeGovernance is intended to become the owner of the sibling EVM bridge DepositFactory and Vault proxies after an Ownable2Step transfer (per spec). Those sibling contracts live outside this repository. Owner-restricted functions encode calldata against IDepositFactoryGovernanceTarget, IVaultGovernanceTarget, IOwnable2StepTarget, and IUUPSUpgradeableTarget, then forward it through _callTarget. No generic execute entry is present. upgradeTarget encodes the target's upgradeToAndCall with empty setup calldata. Post-upgrade configuration uses the typed factory and vault routes. Inherited upgradeToAndCall accepts setup calldata for this proxy. A target must be a nonzero address other than this contract and must contain code. An implementation must be nonzero and must contain code. _validateTarget does not read a stored factory or vault address and does not compare target, factory, or vault arguments to any allowlist. The whitelist is the set of typed function routes. BridgeGovernance declares no storage variables and no __gap. Inherited OpenZeppelin bases use ERC-7201 namespaced storage. Target ownership is accepted or started through acceptTargetOwnership and transferTargetOwnership. transferTargetOwnership rejects a zero newOwner. Target UUPS upgrades are performed through upgradeTarget.
Factory administration covers setFactoryVault, setFactorySupportedToken, setFactoryOperator, pauseFactory, unpauseFactory, and rescueUnsupportedForwarderToken. Vault administration covers setVaultValset, setVaultReleaseRelayer, setVaultOperator, setVaultColdVault, consolidateVault, pauseVault, and unpauseVault. DepositFactory.deployWallet has no route on this contract and remains callable on the factory proxy (per spec). Routine Factory and Vault actions are handled by operators (per spec). Releases through Vault.releaseWithAttestation are performed by a release relayer outside this contract (per spec).
Files in Scope
BridgeGovernance.sol — UUPS Ownable2Step governance router. Owner-restricted typed routes are forwarded through
_callTargetto caller-supplied addresses intended to be DepositFactory and Vault proxies, covering configuration, pause control, token rescue, valset and relayer updates, consolidation, ownership handover, and upgrades.renounceOwnershipis disabled. No factory or vault address is stored. InheritedupgradeToAndCallis the self-upgrade entry.upgradeTargetupgrades a target with empty setup calldata.IDepositFactoryGovernanceTarget.sol — Interface consumed by BridgeGovernance to type
setVault,setSupportedToken,updateOperator,pause,unpause, andrescueon a factory target. IOwnable2StepTarget is extended so factory ownership handover uses the same typed surface. The interface declares no access control.IOwnable2StepTarget.sol — Interface consumed by BridgeGovernance for Ownable2Step views and mutations:
owner,pendingOwner,transferOwnership, andacceptOwnership. Handover routes encodetransferOwnershipandacceptOwnershipagainst this surface. The interface declares no access control.IUUPSUpgradeableTarget.sol — Interface consumed by BridgeGovernance to type
upgradeToAndCallon a UUPS target.upgradeTargetencodes this function with empty data. The interface declares no access control.IVaultGovernanceTarget.sol — Interface consumed by BridgeGovernance to type
setValset,setReleaseRelayer,updateOperator,setColdVault,consolidate,pause, andunpauseon a vault target. AMemberstruct withattestorandpoweris defined for valset updates. IOwnable2StepTarget is extended. The interface declares no access control.
Privileged roles
BridgeGovernance.sol
owner (inherited from Ownable2StepUpgradeable): Every
onlyOwnerfunction is reserved to this role. The owner is a single address in Ownable ERC-7201 storage. An external Safe is documented as the intended owner (per spec) and is not enforced by this contract. Target ownership handover, target upgrades, DepositFactory and Vault governance, ownership transfer of BridgeGovernance, and UUPS upgrades of this proxy are included.Can call
acceptTargetOwnershipto accept pending ownership of an external Ownable2Step target.Can call
transferTargetOwnershipto start ownership transfer of an external Ownable2Step target to a nonzero new owner.Can call
upgradeTargetto upgrade an external UUPS target to a new implementation with empty calldata.Can call
setFactoryVaultto set the vault address on a DepositFactory.Can call
setFactorySupportedTokento enable or disable a supported token on a DepositFactory.Can call
setFactoryOperatorto set the operator on a DepositFactory.Can call
pauseFactoryto pause a DepositFactory.Can call
unpauseFactoryto unpause a DepositFactory.Can call
rescueUnsupportedForwarderTokento rescue a token from a forwarder through a DepositFactory to a recipient.Can call
setVaultValsetto set validator members and the power threshold on a Vault.Can call
setVaultReleaseRelayerto enable or disable a release relayer on a Vault.Can call
setVaultOperatorto set the operator on a Vault.Can call
setVaultColdVaultto set the cold vault address on a Vault.Can call
consolidateVaultto consolidate a token amount on a Vault.Can call
pauseVaultto pause a Vault.Can call
unpauseVaultto unpause a Vault.Can call
renounceOwnership, which is overridden to revert withRenounceDisabled.Can call
transferOwnershipto set or replace the pending owner of BridgeGovernance, including the zero address to cancel a pending transfer.Can call
upgradeToAndCallto replace this proxy's implementation and optionally execute setup calldata. Authorization is enforced by the internal_authorizeUpgradehook, which isonlyOwnerand requires the implementation to contain code.
pendingOwner (inherited from Ownable2StepUpgradeable): Ownership of BridgeGovernance is completed by this role after a transfer is started.
Can call
acceptOwnershipto become the owner of BridgeGovernance and clear the pending owner.
Potential Risks
Unrestricted State Modification by Owner: Owner routes forward parameter payloads without local range, membership, or operator-nonzero checks. setVaultValset passes members and powerThreshold unchanged, setFactorySupportedToken accepts any nonzero token and supported flag, and setFactoryVault and setVaultColdVault accept any nonzero address. setFactoryOperator and setVaultOperator forward operator without a zero-address check. A single owner transaction can replace the vault attestor set, retarget the factory vault pointer, or write a zero operator if the target allows it.
Absence of Timelock Mechanisms for Critical Operations: BridgeGovernance implements no enqueue, delay, or veto window. Owner invocations of upgradeTarget, setVaultValset, setVaultColdVault, rescueUnsupportedForwarderToken, consolidateVault, pauseVault, and unpauseFactory execute _callTarget immediately. Once the owner transaction is mined, the in-scope contract provides no function that can undo a valset replacement, implementation swap, or rescue except by a later owner call to the same targets.
Treasury or Rescue Authority: rescueUnsupportedForwarderToken requires nonzero forwarder, token, and to, then calls IDepositFactoryGovernanceTarget.rescue with those arguments. consolidateVault requires a nonzero token and forwards IVaultGovernanceTarget.consolidate with the owner-chosen amount. Neither route imposes an amount cap or a fixed recipient. Tokens held in a targeted forwarder or vault can be moved in one owner transaction when the out-of-repository target accepts the call.
Single Owner Control Path: The owner of BridgeGovernance is the only persistent privileged role that can upgrade this proxy, upgrade or reown factory and vault targets, replace the vault valset and release relayer, assign operators, move assets through rescueUnsupportedForwarderToken and consolidateVault, and pause or unpause both target types. A single onlyOwner gate covers that entire surface. The stored owner is one address in Ownable ERC-7201 storage. The bytecode does not require that address to be a Safe, a governor, or any other multi-signature module; the Safe owner described in project documentation is an off-chain deployment assumption (per spec).
Flexibility and Risk in Contract Upgrades: BridgeGovernance inherits UUPSUpgradeable, calls _disableInitializers in the constructor, and authorizes replacement logic in _authorizeUpgrade after _validateImplementation. Inherited upgradeToAndCall accepts caller data for this proxy. Separately, upgradeTarget passes empty bytes into IUUPSUpgradeableTarget.upgradeToAndCall on a caller-chosen target. A successful owner transaction can replace governance logic or the logic of any UUPS target that still recognizes BridgeGovernance as owner, including logic that later changes access control or asset handling.
Absence of Upgrade Window Constraints: Neither _authorizeUpgrade nor upgradeTarget records a future implementation or waits a mandatory period. _validateImplementation rejects only the zero address and empty code. The owner can replace the BridgeGovernance implementation or a factory or vault implementation in a single transaction.
Caller-Chosen Targets Without a Registry: _validateTarget on BridgeGovernance rejects the zero address, address(this), and accounts with no code. It does not load a stored DepositFactory or Vault and does not compare target, factory, or vault parameters to any allowlist. acceptTargetOwnership, setFactoryVault, setVaultValset, upgradeTarget, and the other owner routes therefore act on whichever contract the owner supplies. Events such as FactoryVaultSet and TargetUpgraded still fire after a non-reverting _callTarget, even when the address is not the intended bridge proxy.
Findings
Code ― | Title | Status | Severity | |
|---|---|---|---|---|
| F-2026-1922 | Documented Permissionless Forwarder Deployment Contradicts Operator-Gated Factory | fixed | Observation | |
| F-2026-1922 | Unrestricted Calldata In Target Upgrade Hook Bypasses The Typed Route Whitelist | fixed | Observation |
Appendix 1. Definitions
Severities
When auditing smart contracts, Hacken is using a risk-based approach that considers Likelihood, Impact, Exploitability and Complexity metrics to evaluate findings and score severities.
Reference on how risk scoring is done is available through the repository in our Github organization:
Severity | Description |
|---|---|
Critical | Critical vulnerabilities are usually straightforward to exploit and can lead to the loss of user funds or contract state manipulation. |
High | High vulnerabilities are usually harder to exploit, requiring specific conditions, or have a more limited scope, but can still lead to the loss of user funds or contract state manipulation. |
Medium | Medium vulnerabilities are usually limited to state manipulations and, in most cases, cannot lead to asset loss. Contradictions and requirements violations. Major deviations from best practices are also in this category. |
Low | Major deviations from best practices or major Gas inefficiency. These issues will not have a significant impact on code execution. |
Severity
- Critical
Description
- Critical vulnerabilities are usually straightforward to exploit and can lead to the loss of user funds or contract state manipulation.
Severity
- High
Description
- High vulnerabilities are usually harder to exploit, requiring specific conditions, or have a more limited scope, but can still lead to the loss of user funds or contract state manipulation.
Severity
- Medium
Description
- Medium vulnerabilities are usually limited to state manipulations and, in most cases, cannot lead to asset loss. Contradictions and requirements violations. Major deviations from best practices are also in this category.
Severity
- Low
Description
- Major deviations from best practices or major Gas inefficiency. These issues will not have a significant impact on code execution.
Potential Risks
The "Potential Risks" section identifies issues that are not direct security vulnerabilities but could still affect the project’s performance, reliability, or user trust. These risks arise from design choices, architectural decisions, or operational practices that, while not immediately exploitable, may lead to problems under certain conditions. Additionally, potential risks can impact the quality of the audit itself, as they may involve external factors or components beyond the scope of the audit, leading to incomplete assessments or oversight of key areas. This section aims to provide a broader perspective on factors that could affect the project's long-term security, functionality, and the comprehensiveness of the audit findings.
Appendix 2. Scope
The scope of the project includes the following smart contracts from the provided repository:
Scope Details | |
|---|---|
| Repository | https://github.com/bullbit/Bridge-Governance-Smart-Contract→ |
| Initial Commit | 1dc6a2e2fc53cbbbd3bff54818c00761dcf596be |
| Final Commit | d60b6c1a8d78d004a36e5ea48e227eca59afbb13 |
| Whitepaper | N/A |
| Requirements | README.md; NatSpec |
| Technical Requirements | README.md; NatSpec |
Scope Details
- Initial Commit
- 1dc6a2e2fc53cbbbd3bff54818c00761dcf596be
- Final Commit
- d60b6c1a8d78d004a36e5ea48e227eca59afbb13
- Whitepaper
- N/A
- Requirements
- README.md; NatSpec
- Technical Requirements
- README.md; NatSpec
Assets in Scope
Appendix 3. Additional Valuables
Additional Recommendations
The smart contracts in the scope of this audit could benefit from the introduction of automatic emergency actions for critical activities, such as unauthorized operations like ownership changes or proxy upgrades, as well as unexpected fund manipulations, including large withdrawals or minting events. Adding such mechanisms would enable the protocol to react automatically to unusual activity, ensuring that the contract remains secure and functions as intended.
To improve functionality, these emergency actions could be designed to trigger under specific conditions, such as:
Detecting changes to ownership or critical permissions.
Monitoring large or unexpected transactions and minting events.
Pausing operations when irregularities are identified.
These enhancements would provide an added layer of security, making the contract more robust and better equipped to handle unexpected situations while maintaining smooth operations.
Frameworks and Methodologies
This security assessment was conducted in alignment with recognised penetration testing standards, methodologies and guidelines, including the NIST SP 800-115 – Technical Guide to Information Security Testing and Assessment →, and the Penetration Testing Execution Standard (PTES) →, These assets provide a structured foundation for planning, executing, and documenting technical evaluations such as vulnerability assessments, exploitation activities, and security code reviews. Hacken’s internal penetration testing methodology extends these principles to Web2 and Web3 environments to ensure consistency, repeatability, and verifiable outcomes.