The L2GraphToken contract, serving as an upgradeable ERC-20 token on Arbitrum, exhibits a critical flaw in its initialization mechanism, preventing it from being properly set up after deployment or upgrade. This issue, combined with significant centralization risks associated with the governor role, elevates the overall risk level to High. While the contract incorporates standard ERC-20 features, OpenZeppelin upgradeable patterns, and a two-step ownership transfer, the uncallable initialize function and the broad powers of the governor require immediate attention. Additional concerns include front-running susceptibility in the permit function and misleading custom upgrade logic.
On-chain security analysis — is it a scam or legit?
Is Graph Token Safe?
Not verified
This record has not gone through deep verification and is not being monitored. The score is a dated snapshot — the token’s risk can change at any time.Own this token? Put it under verification →The L2GraphToken contract, serving as an upgradeable ERC-20 token on Arbitrum, exhibits a critical flaw in its initialization mechanism, preventing it from being properly set up after deployment or upgrade.
Read the full analysis- Ownership not renounced
- Mintable supply — no cap found, dilution unbounded
- Proxy contract
- Admin is EOA
- Top-10 concentration > 30%
- Liquidity not locked, but no owner/deployer address holds LP — market-depth risk, not rug risk
- 1 Critical finding(s) from audit
- 2 High finding(s) from audit
- 1 Medium finding(s) from audit
- 2 Low finding(s) from audit
Exact factors
- Ownership NOT renounced (admin/mint authority retained)
- Mintable supply — no cap found, dilution unbounded
- Proxy contract (upgradeable — admin can replace logic)
- Admin is EOA (single key controls upgrades)
- Top-10 concentration > 30% (87.1% total → 48.9% effective; 23.3% in EOAs, 63.8% in contracts — moderate)
- Liquidity not locked, but no owner/deployer address holds LP — market-depth risk, not rug risk
- 1 Critical finding(s) from audit
- 2 High finding(s) from audit
- 1 Medium finding(s) from audit
- 2 Low finding(s) from audit
Each factor is an on-chain fact recorded at the time of this analysis. The score is computed from them by a deterministic function, so the same contract returns the same score for anyone who runs the audit. How scores are computed
Security Checklist
as of audit · Sep 6Holders
as of audit · Sep 6, 2026- Contractstreasury, vesting, bridge, staking63.8%
- Walletsanonymous holders, free to sell23.3%
- Everyone elsethe rest of the supply12.9%
How holder types affect the score
Share held by contracts — treasury, vesting, bridge or staking — is discounted against share held by wallets when the score is computed: a contract cannot decide to sell the way an anonymous holder can, though it can still be drained or voted to sell. Effective concentration is the figure the risk score is actually calculated from.
Holder balances as recorded by the audit of Sep 6, 2026
Liquidity
as of audit · Sep 6- UniswapWETHAnalysed$67.1K87.6%
- UniswapWETH$9.5K12.4%
- SushiswapWETH$80.0%
- KyberswapWETH$40.0%
- Uniswap v2WETH$30.0%
- UniswapARB$10.0%
Show 1 more pair
The risk score reads depth across every pair.
The LP tokens of the analysed pool are not locked or burned, so whoever holds them can withdraw that liquidity.
0xe86d…fce00xabd5…b67e0x085a…5fb00x3399…01fb0x0cc5…149c0x94bf…54250x25cd…658d0x1179…c0bb0x9418…f3b30xcbf8…06f2No privileged address appears among these holders: the unlocked liquidity sits with independent providers, not with the deployer.
Security Findings
CriticalCritical: initialize function cannot be called via proxy due to onlyImpl modifier
The initialize function in L2GraphToken is marked with external onlyImpl initializer. The onlyImpl modifier requires msg.sender == _implementation(). The _implementation() function reads the IMPLEMENTATION_SLOT from the *current contract's storage*. For an implementation contract (like L2GraphToken itself), this slot is typically empty (0), meaning _implementation() would return address(0). Consequently, onlyImpl would require msg.sender == address(0), which is impossible for any transaction. This prevents the initialize function from ever being called through the proxy, leaving the contract uninitialized. An uninitialized contract would have its governor, `gatew…C-01
initialize function cannot be called via proxy due to onlyImpl modifierIssue
The initialize function in L2GraphToken is marked with external onlyImpl initializer. The onlyImpl modifier requires msg.sender == _implementation(). The _implementation() function reads the IMPLEMENTATION_SLOT from the *current contract's storage*. For an implementation contract (like L2GraphToken itself), this slot is typically empty (0), meaning _implementation() would return address(0). Consequently, onlyImpl would require msg.sender == address(0), which is impossible for any transaction. This prevents the initialize function from ever being called through the proxy, leaving the contract uninitialized. An uninitialized contract would have its governor, `gatew…
Fix
Remove the onlyImpl modifier from the initialize function. The initializer modifier from OpenZeppelin is sufficient to ensure it's called only once. If onlyImpl was intended for a specific custom proxy interaction, this logic must be thoroughly re-evaluated and corrected to allow proxy-based initialization.
Status
Unresolved
HighHigh: Centralization Risk with Governor Role
The governor role holds extensive control over critical contract functionalities. The governor can set the gateway (which controls bridgeMint and bridgeBurn), set the l1Address, and add/remove minters (who can mint new tokens). If the governor's private key is compromised, it could lead to unauthorized minting, burning, and manipulation of bridge operations, posing a significant centralization risk (7.3 Access Control, 7.4 Economic). While a two-step ownership transfer exists, the power remains concentrated in a single entity (or multisig).H-01
Issue
The governor role holds extensive control over critical contract functionalities. The governor can set the gateway (which controls bridgeMint and bridgeBurn), set the l1Address, and add/remove minters (who can mint new tokens). If the governor's private key is compromised, it could lead to unauthorized minting, burning, and manipulation of bridge operations, posing a significant centralization risk (7.3 Access Control, 7.4 Economic). While a two-step ownership transfer exists, the power remains concentrated in a single entity (or multisig).
Fix
Implement a robust multi-signature wallet (e.g., Gnosis Safe) for the governor address to distribute control and reduce the risk of a single point of failure. Consider implementing time-locks for highly sensitive actions (e.g., setGateway, addMinter) to provide a window for intervention if a malicious transaction is initiated.
Status
Unresolved
HighHigh: permit function susceptible to front-running for transaction failure
The EIP-2612 permit function allows users to approve token transfers off-chain. While the nonce mechanism prevents replay attacks, the function is susceptible to front-running. A malicious actor could observe a pending permit transaction and submit their own transaction with a higher gas price, causing the legitimate user's transaction to fail due to the nonce being incremented by the front-running transaction. Although this does not lead to direct fund loss for the _owner, it results in wasted gas and a poor user experience (7.2 Code Security).H-02
permit function susceptible to front-running for transaction failureIssue
The EIP-2612 permit function allows users to approve token transfers off-chain. While the nonce mechanism prevents replay attacks, the function is susceptible to front-running. A malicious actor could observe a pending permit transaction and submit their own transaction with a higher gas price, causing the legitimate user's transaction to fail due to the nonce being incremented by the front-running transaction. Although this does not lead to direct fund loss for the _owner, it results in wasted gas and a poor user experience (7.2 Code Security).
Fix
While inherent to the permit design, users should be aware of this behavior. Consider implementing a mechanism where the permit signature includes a minGasPrice or similar parameter, or advise users to use a transaction relayer that can handle such scenarios gracefully. Ensure clear documentation for users regarding potential transaction failures.
Status
Unresolved
MediumMedium: Misleading _implementation() logic in GraphUpgradeable
The _implementation() function in GraphUpgradeable reads the IMPLEMENTATION_SLOT from the *current contract's storage*. For an implementation contract, this slot is not used to store its own address or the proxy's implementation address; it's the proxy that stores the implementation address in this slot. This function will likely return address(0) or an incorrect address when called on the implementation contract, making it unsuitable for determining the actual implementation address in a standard EIP-1967 proxy context. This misleading logic contributes to the critical initialize issue (C-01) and can cause confusion or unexpected behavior in custom upgrade mechanisms (7.1 Archite…M-01
_implementation() logic in GraphUpgradeableIssue
The _implementation() function in GraphUpgradeable reads the IMPLEMENTATION_SLOT from the *current contract's storage*. For an implementation contract, this slot is not used to store its own address or the proxy's implementation address; it's the proxy that stores the implementation address in this slot. This function will likely return address(0) or an incorrect address when called on the implementation contract, making it unsuitable for determining the actual implementation address in a standard EIP-1967 proxy context. This misleading logic contributes to the critical initialize issue (C-01) and can cause confusion or unexpected behavior in custom upgrade mechanisms (7.1 Archite…
Fix
Refactor _implementation() to correctly retrieve the proxy's implementation address if it's intended to be used by the implementation contract to query its proxy. Alternatively, remove this function if its current implementation is not serving a valid purpose within the upgrade pattern, and ensure any modifiers relying on it (like onlyImpl) are corrected.
Status
Unresolved
LowLow: Hardcoded DOMAIN_SALT
The DOMAIN_SALT used in the EIP-712 DOMAIN_SEPARATOR calculation is a hardcoded bytes32 value. While not a direct vulnerability, hardcoding this value can limit flexibility if the contract were to be deployed in multiple environments or chains where a unique salt might be desired to prevent potential domain collisions or improve clarity (7.2 Code Security).L-01
DOMAIN_SALTIssue
The DOMAIN_SALT used in the EIP-712 DOMAIN_SEPARATOR calculation is a hardcoded bytes32 value. While not a direct vulnerability, hardcoding this value can limit flexibility if the contract were to be deployed in multiple environments or chains where a unique salt might be desired to prevent potential domain collisions or improve clarity (7.2 Code Security).
Fix
Consider making the DOMAIN_SALT configurable during initialization or deriving it from a unique, immutable contract parameter (e.g., block.chainid combined with a deployment-specific value) to enhance flexibility and ensure uniqueness across different deployments or networks.
Status
Unresolved
LowLow: _initialSupply in _initialize is always zero for L2GraphToken
The GraphTokenUpgradeable._initialize function accepts an _initialSupply parameter to mint tokens to the owner upon initialization. However, the L2GraphToken.initialize function explicitly calls GraphTokenUpgradeable._initialize(_owner, 0);, always passing 0 for the initial supply. This means no tokens are minted to the owner during the contract's initialization, making the _initialSupply parameter in the base _initialize function effectively unused by this concrete implementation. While this might be intentional for a bridge token where supply is managed via bridging, it could be misleading or an oversight if initial supply was expected (7.8 Operations).L-02
_initialSupply in _initialize is always zero for L2GraphTokenIssue
The GraphTokenUpgradeable._initialize function accepts an _initialSupply parameter to mint tokens to the owner upon initialization. However, the L2GraphToken.initialize function explicitly calls GraphTokenUpgradeable._initialize(_owner, 0);, always passing 0 for the initial supply. This means no tokens are minted to the owner during the contract's initialization, making the _initialSupply parameter in the base _initialize function effectively unused by this concrete implementation. While this might be intentional for a bridge token where supply is managed via bridging, it could be misleading or an oversight if initial supply was expected (7.8 Operations).
Fix
Confirm if the intention is indeed to start with zero initial supply and manage all token issuance via the bridge. If so, consider removing the _initialSupply parameter from GraphTokenUpgradeable._initialize or clearly documenting its intended use for other implementations. If an initial supply is desired, adjust the L2GraphToken.initialize call accordingly.
Status
Unresolved
Analysis
AI Copilotas of audit · Sep 6The contract leverages OpenZeppelin's upgradeable ERC-20 standards, including ERC20BurnableUpgradeable and EIP-2612 permit functionality, enhancing token utility and security. It employs clear role-based access control with onlyGovernor, onlyGateway, and onlyMinter modifiers, ensuring specific actions are restricted to authorized entities. However, a critical flaw exists where the initialize function cannot be called due to the onlyImpl modifier, preventing proper contract setup after deployment or upgrade (7.7 Upgrades). Additionally, the permit function is susceptible to front-running, potentially causing user transactions to fail (7.2 Code Security). The custom _implementation() logic in GraphUpgradeable is also misleading and contributes to the initialization issue (7.1 Architecture).
The contract implements a two-step ownership transfer mechanism, requiring a pendingGovernor to accept ownership, which is a good practice for mitigating immediate risks of accidental transfers (7.3 Access Control). It defines distinct roles for governor, gateway, and minters, allowing for granular control over critical token operations like minting and bridging. However, the governor role holds significant centralized power, capable of setting the gateway, l1Address, and managing minters, posing a high centralization risk if compromised (7.3 Access Control, 7.4 Economic). This single point of control could lead to unauthorized minting or manipulation of bridge operations.
The contract is designed to be upgradeable, utilizing OpenZeppelin's Upgradeable pattern with a __gap storage variable to ensure future compatibility (7.7 Upgrades). It includes custom acceptProxy and acceptProxyAndCall functions for a controlled upgrade process, requiring the proxy admin's explicit acceptance. However, a critical vulnerability prevents the initialize function from being called through the proxy due to an incorrect onlyImpl modifier, rendering the contract uninitializable after deployment or upgrade (7.7 Upgrades). This flaw fundamentally breaks the upgradeability model and prevents the contract from functioning correctly.
Address the critical initialize function flaw by removing or correcting the onlyImpl modifier to allow proper contract initialization via the proxy.
Re-evaluate the _implementation() logic in GraphUpgradeable to ensure it aligns with standard proxy patterns or is clearly documented for its custom behavior. Implement robust multi-signature control for the governor role to mitigate centralization risks and consider time-locks for critical administrative actions. Additionally, review the permit function for potential front-running mitigations to improve user experience and prevent transaction failures.
Contract & Record
Free for any token on the record. It links to this page and always shows the current score and the date it was checked.
- Sep 6, 202663Medium
Score changes are recorded as dated snapshots — history is append-only and cannot be edited.
Would You Like a More Detailed Audit of Graph Token?
Our AI-powered scanner gives you a deeper, real-time smart contract analysis — free, with every scoring factor shown.
Get Detailed Audit Is this your token? Publish your own audit on this page