Type: Layer 2 & Rollups
Timeline: July 22, 2025 → July 24, 2025
Languages: Solidity
Findings
Total issues: 10 (5 resolved, 1 partially resolved)
Critical: 0 (0 resolved) · High: 0 (0 resolved) · Medium: 0 (0 resolved) · Low: 3 (1 resolved, 1 partially resolved)
Notes & Additional Information
6 notes raised (3 resolved)
OpenZeppelin audited the Consensys/linea-tokens repository at commit 44640f0. In addition, pull rquest #10 was audited up to commit 86605a9 while pull request #11 was audited up to commit b9aad54.
Update: We have completed the review of all submissions and finalized the audit report. The final commit reviewed is 91036da. We note that the additional changes to in-scope files since 44640f0 are related to fixes from audit engagements conducted concurrently with ours.
In addition, we have verified the deployed bytecode matches the code at commit 91036da.
Lastly, we have verified that the TGE contract code and exported artifacts have been migrated from the private repository into the open-source linea-monorepo at release tag contract-audit-2025-07-30. The in-scope files and artifacts under contracts-tge/src and contracts-tge/exported-artifacts match exactly with those from the audited commit in the private repository.
In scope were the following files:
src
├── L1
│ ├── LineaToken.sol
│ └── interfaces
│ └── ILineaToken.sol
├── L2
│ ├── L2LineaToken.sol
│ └── interfaces
│ └── IL2LineaToken.sol
├── airdrops
│ └── TokenAirdrop.sol
└── interfaces
├── IGenericErrors.sol
└── IMessageService.sol
This system includes two ERC-20 token contracts deployed on Ethereum L1 and Linea L2, along with an airdrop contract deployed on L2. Together, these contracts enable cross-chain token minting, supply synchronization, and structured airdrop distribution based on predefined external signals.
LineaToken, is an upgradeable ERC-20 contract with burnable, permit-based approvals and role-based access control. It supports controlled minting via the MINTER_ROLE and uses Linea's message service to propagate total supply updates to the L2 counterpart.L2LineaToken, is the bridged version of the L1 token. It includes ERC20VotesUpgradeable, allowing voting power delegation and integration with applications that rely on token-weighted governance or identity-based utility.TokenAirdrop contract is deployed on L2 and allows eligible users to claim an allocation of L2 tokens. Rather than relying on an allowlist or merkle root, it calculates allocations dynamically based on a user’s balance across up to three external contracts. These external contracts do not represent transferable tokens and may serve purely as reference points for determining eligibility or proportional distribution. The design assumes that the balances of these contracts are immutable per user, which ensures airdrop fairness and resistance to Sybil or replay attacks.This structure enables a secure, modular, and upgradeable distribution system that connects L1-L2 token flows with transparent and programmable airdrop logic.
During the audit, the following trust assumptions were made:
The contracts referenced as PRIMARY_FACTOR_ADDRESS, PRIMARY_CONDITIONAL_MULTIPLIER_ADDRESS, and SECONDARY_FACTOR_ADDRESS in the airdrop must meet specific requirements to ensure allocation integrity:
balanceOf functionality.PRIMARY_FACTOR_ADDRESS and SECONDARY_FACTOR_ADDRESS must have the same number of decimals as the airdrop token.PRIMARY_CONDITIONAL_MULTIPLIER_ADDRESS must have 9 decimals, as it is used as a multiplier and divided by a fixed denominator of 1e9.PRIMARY_FACTOR_ADDRESS and PRIMARY_CONDITIONAL_MULTIPLIER_ADDRESS must not overflow a uint256 value. This is especially critical due to the multiplication done in this line.Transactions interacting with the system should consume less than 250,000 gas to remain compatible with Linea’s message service infrastructure and benefit from fee-free postman execution for L1 → L2 messages.
The deployment and configuration process must follow a specific sequence to ensure correctness:
setCustomContract to register the L1 token contract as the designated target for the corresponding L2 token.confirmDeployment function should be called on the Linea token bridge to notify the mainnet bridge that deployment on L2 is complete. This updates the nativeToBridgedToken mapping to reflect the DEPLOYED_STATUS.This deployment order guarantees safe airdrop initialization, consistent bridging logic, and secure synchronization between L1 and L2 states.
The following privileged roles were identified in the system:
LineaToken (L1) defines two roles:
DEFAULT_ADMIN_ROLE, used to manage roles and administrative settings.MINTER_ROLE, used to mint new tokens.L2LineaToken only accepts messages from the official token bridge and messaging service of Linea.TokenAirdrop is governed by a single owner that is assigned at deployment. This owner has the authority to withdraw unclaimed tokens after the claim window closes and can trigger the contract’s withdraw function after the claiming period has ended.MessageServiceBase modifiers (onlyMessagingService, onlyAuthorizedRemoteSender), ensuring that only authenticated L1/L2 message services and senders can invoke cross-chain sync logic.Throughout the codebase, the following instance of incomplete docstrings was identified:
IL2LineaToken.sol, within the L1LineaTokenTotalSupplySynced event, the l1BlockTimestamp and l1TotalSupply parameters are not documented.Consider thoroughly documenting the event (and its parameters) that are part of a contract's public API. When writing docstrings, consider following the Ethereum Natural Specification Format (NatSpec).
Update: Resolved in pull request #16.
Primary_Factor_Address & Primary_Conditional_Multiplier_Address Checks optimizationThe TokenAirdrop contract contains these two require statements. The intent is to ensure that either both _primaryFactorAddress and _primaryConditionalMultiplierAddress have been set (non-zero) or both are unset (zero). This guards against only one of them being initialized while the other is not. However, these two conditions are logically redundant and can be simplified. Moreover, in subsequent logic in the calculateAllocation function, the check can also be simplified based on the guarantee enforced by the constructor.
Consider replacing both of the require statements in the constructor of the TokenAirdrop with a single equivalent condition which ensures that both addresses are either set or unset. In addition, simplify the conditional in calculateAllocation to check just one of the two addresses.
Update: Partially resolved in pull request #10. While the check in calculateAllocation has been simplified, the require statements in the constructor can be simplified further.
Pragma directives should be fixed to clearly identify the Solidity version with which the contracts will be compiled.
Throughout the codebase, multiple instances of floating pragma directives were identified:
ILineaToken.sol has the solidity ^0.8.30 floating pragma directive.IL2LineaToken.sol has the solidity ^0.8.30 floating pragma directive.IGenericErrors.sol has the solidity ^0.8.30 floating pragma directive.IMessageService.sol has the solidity ^0.8.30 floating pragma directive.Consider using fixed pragma directives.
Update: Acknowledged, not resolved. The team stated:
Acknowledged: These have been left intentionally open for others to use for future/higher versions of their contracts. The top level deployables will dictate the compiler version which has been set without the floating pragma. No changes expected.
In the L2LineaToken contract, the super.nonces call is ambiguous.
Consider avoiding ambiguous calls to parent contracts and explicitly specifying which parent contract's function is being called.
Update: Acknowledged, not resolved. The team stated:
Acknowledged: When removing the
super.and usingpermitexplicitly, the Voting nonces is bypassed. It is safer to leave it in.
Throughout the codebase, multiple instances of functions updating the state without an event emission were identified:
initialize function in LineaToken.solinitialize function in L2LineaToken.solconstructor function in TokenAirdrop.solConsider emitting events whenever there are state changes to improve the clarity of the codebase and make it less error-prone.
Update: Resolved in pull request #16 and pull request #10.
Throughout the codebase, multiple instances of events not having indexed parameters were identified:
L2TokenAddressSet event of ILineaToken.solL1TotalSupplySyncStarted event of ILineaToken.solL1LineaTokenTotalSupplySynced event of IL2LineaToken.solTokenBalanceWithdrawn event of TokenAirdrop.solTo improve the ability of off-chain services to search and filter for specific events, consider indexing event parameters.
Update: Acknowledged, not resolved. The team stated:
Acknowledged. The event values themselves are variable (e.g.,
TokenBalanceWithdrawncould be anything), there wouldn't be event querying by that value. If they were, adding the indexing would be prudent. No changes will be made for these.
selfdestruct Does Not Delete CodeThe intended behavior is that calling the withdraw() function in TokenAirdrop.sol should remove the contract entirely, transferring any remaining ETH to the caller and preventing further interaction with the contract address. However, due to the behavior change introduced in EIP-6780, the use of selfdestruct(payable(msg.sender)) no longer removes the contract’s bytecode—it only transfers ETH.
Consider replacing selfdestruct with a direct ETH transfer for improved code clarity and better alignment with post-EIP-6780 behavior.
Update: Resolved in pull request #12. The team stated:
Acknowledged: Originally we expected the call to be on our network while the London EVM version applied. This has been removed.
Throughout the codebase, multiple instances of files having indecisive SPDX licenses were identified:
// SPDX-License-Identifier: Apache-2.0 OR MIT SPDX license in LineaToken.sol// SPDX-License-Identifier: Apache-2.0 OR MIT SPDX license in ILineaToken.sol// SPDX-License-Identifier: Apache-2.0 OR MIT SPDX license in L2LineaToken.sol// SPDX-License-Identifier: Apache-2.0 OR MIT SPDX license in IL2LineaToken.sol// SPDX-License-Identifier: Apache-2.0 OR MIT SPDX license in TokenAirdrop.sol// SPDX-License-Identifier: Apache-2.0 OR MIT SPDX license in IGenericErrors.sol// SPDX-License-Identifier: Apache-2.0 OR MIT SPDX license in IMessageService.solConsider specifying only one license to prevent possible licensing ambiguity.
Update: Acknowledged, not resolved. The team stated:
Acknowledged: This has been done on purpose to give consumers of our code flexibility when incorporating into their codebase. The intent is that whichever one their codebase uses applies.
In LineaToken.sol, the emit L1TotalSupplySyncStarted(block.timestamp, totalSupply); event emission includes unnecessary data fields such as block.number or block.timestamp.
To improve code clarity and maintainability, consider removing unnecessary data fields like block.number or block.timestamp from event emissions since they are already included in the block information.
Update: Resolved in pull request #15. The team stated:
Acknowledged: It was originally in for clarity, however, removing it is better practice and has been done.
L2LineaToken does not require ERC20BurnableUpgradeable inheritanceThe L2LineaToken contract includes a custom implementation of the burn function with additional authorization logic specific to the Linea token bridge. As a result, inheriting from ERC20BurnableUpgradeable was unnecessary and introduced extra functionality that the token does not utilize.
This issue was also identified by the Linea team during a parallel audit, and the inheritance from ERC20BurnableUpgradeable was removed in commit b9aad54.
Update: Resolved in commit b9aad54.
The audited system supports the Linea Token Generation Event (TGE), enabling cross-chain token minting, supply synchronization, and airdrop distribution. It includes a bridged ERC-20 token pair and an airdrop contract that calculates allocations based on balances in external reference contracts.
No high- or critical-severity issues were identified. Only minor observations were reported, primarily related to the handling of specific edge cases and documentation. The correctness of the bridging flow and the assumptions around allocation inputs are central to the system’s integrity and should be carefully monitored during deployment.
The Linea team is appreciated for being responsive and collaborative throughout the audit process.