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
and PRIMARY_CONDITIONAL_MULTIPLIER_ADDRESS
ChecksThe 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 usingpermit
explicitly, 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.sol
initialize
function in L2LineaToken.sol
constructor
function in TokenAirdrop.sol
Consider 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.sol
L1TotalSupplySyncStarted
event of ILineaToken.sol
L1LineaTokenTotalSupplySynced
event of IL2LineaToken.sol
TokenBalanceWithdrawn
event of TokenAirdrop.sol
To 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.,
TokenBalanceWithdrawn
could 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.sol
Consider 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.