Quantum Audit Logo

Is Curve DAO Token Safe?

On-chain security analysis — is it a scam or legit?

Curve DAO Token CRV
0x11cd…4978
Arbitrum Not verifiedLast checked 3d ago 1 audit on record
Executive SummaryAI Copilot

The audit of the StandardArbERC20 contract, serving as an L2 token implementation for the Arbitrum bridge, identified critical and high-severity issues primarily related to initialization and proxy compatibility. The `bridgeInit` function lacks proper access control, allowing potential front-running to seize control of minting/burning. A critical flaw exists in the `Cloneable` contract's interaction with the proxy pattern, potentially enabling self-destruction of proxy instances. Several minor issues related to code quality and getter behavior were also noted.

1 Critical1 High1 Medium1 Low1 Informational
Volume 24h
$195.7K
Liquidity
$495.4K
Price
$0.3683
Token Age
4y
Top 10 Holders
63.3%

Security Findings

Critical

`Cloneable` contract's `isMasterCopy` state variable is not correctly initialized in proxy context

C-01The `Cloneable` contract uses a constructor to set `isMasterCopy = true`. In a proxy pattern (like `ClonableBeaconProxy`), the constructor is only executed on the *implementation* contract, not on the *proxy* contract. State variables for the proxy are stored in the proxy's storage. Therefore, `isMasterCopy` in the proxy's storage will remain its default value (`false`). This means the `safeSelfDestruct` function, which has a `require(!isMasterCopy, NOT_CLONE);`, will *not* revert for a proxy. A malicious actor could potentially call `safeSelfDestruct` on a proxy contract, leading to its destruction and loss of all associated funds/functionality.
IssueThe `Cloneable` contract uses a constructor to set `isMasterCopy = true`. In a proxy pattern (like `ClonableBeaconProxy`), the constructor is only executed on the *implementation* contract, not on the *proxy* contract. State variables for the proxy are stored in the proxy's storage. Therefore, `isMasterCopy` in the proxy's storage will remain its default value (`false`). This means the `safeSelfDestruct` function, which has a `require(!isMasterCopy, NOT_CLONE);`, will *not* revert for a proxy. A malicious actor could potentially call `safeSelfDestruct` on a proxy contract, leading to its destruction and loss of all associated funds/functionality.
FixThe `Cloneable` pattern is incompatible with standard proxy patterns for managing `isMasterCopy` as a state variable. The `safeSelfDestruct` function should be removed from the proxy's callable interface, or the `isMasterCopy` check needs to be re-architected to be compatible with upgradeable proxies (e.g., by using a storage slot explicitly initialized in the `_initialize` function).
StatusUnresolved
High

Unprotected `bridgeInit` allows anyone to set `l2Gateway`

H-01The `bridgeInit` function is `public virtual` and can be called by anyone. It calls `L2GatewayToken._initialize`, which sets the critical `l2Gateway` and `l1Address` parameters. While `_initialize` includes an `ALREADY_INIT` check, a malicious actor could front-run the legitimate initializer and set themselves as the `l2Gateway`. This would grant them full control over `bridgeMint` and `bridgeBurn`, allowing them to mint tokens to arbitrary addresses or burn existing tokens.
IssueThe `bridgeInit` function is `public virtual` and can be called by anyone. It calls `L2GatewayToken._initialize`, which sets the critical `l2Gateway` and `l1Address` parameters. While `_initialize` includes an `ALREADY_INIT` check, a malicious actor could front-run the legitimate initializer and set themselves as the `l2Gateway`. This would grant them full control over `bridgeMint` and `bridgeBurn`, allowing them to mint tokens to arbitrary addresses or burn existing tokens.
FixImplement a robust access control mechanism for the `bridgeInit` function, such as an `onlyOwner` modifier or a specific `initializer` role, to ensure that only a trusted entity can call it. This is crucial to prevent unauthorized control of the L2 gateway.
StatusUnresolved
Medium

Potential for `decimals`, `name`, `symbol` getters to revert

M-01The `StandardArbERC20` contract allows `ignoreDecimals`, `ignoreName`, `ignoreSymbol` to be set to `true` during `bridgeInit` if the parsing of the L1 token metadata fails. If these flags are true, calling the respective standard ERC20 getter (`decimals()`, `name()`, `symbol()`) will cause a `revert()`. This behavior, while potentially intended to signal invalid L1 data, can lead to unexpected reverts for users or dApps trying to interact with the token, potentially breaking integrations that rely on these standard ERC20 getters.
IssueThe `StandardArbERC20` contract allows `ignoreDecimals`, `ignoreName`, `ignoreSymbol` to be set to `true` during `bridgeInit` if the parsing of the L1 token metadata fails. If these flags are true, calling the respective standard ERC20 getter (`decimals()`, `name()`, `symbol()`) will cause a `revert()`. This behavior, while potentially intended to signal invalid L1 data, can lead to unexpected reverts for users or dApps trying to interact with the token, potentially breaking integrations that rely on these standard ERC20 getters.
FixConsider returning default or empty values (e.g., `0` for decimals, empty string for name/symbol) instead of reverting when `ignore*` flags are set. Alternatively, ensure that the L1 data parsing is robust enough to minimize failures, or provide a clear error message in the revert reason.
StatusUnresolved
Low

`BytesParser.toString` logic for `input.length == 32` is complex and potentially error-prone

L-01The `BytesParser.toString` function contains specific logic for `input.length == 32` that attempts to truncate trailing null bytes. This logic involves a loop and an `assembly` block to create a new, truncated bytes array. While aiming to handle a specific encoding, this approach adds complexity and potential for off-by-one errors or misinterpretations of byte data compared to a simpler `abi.decode(input, (string))` for all non-zero length inputs.
IssueThe `BytesParser.toString` function contains specific logic for `input.length == 32` that attempts to truncate trailing null bytes. This logic involves a loop and an `assembly` block to create a new, truncated bytes array. While aiming to handle a specific encoding, this approach adds complexity and potential for off-by-one errors or misinterpretations of byte data compared to a simpler `abi.decode(input, (string))` for all non-zero length inputs.
FixSimplify the `toString` logic if possible, or add comprehensive unit tests specifically for the `input.length == 32` case with various byte patterns (e.g., all nulls, partial nulls, no nulls, non-ASCII characters) to ensure correctness and robustness.
StatusUnresolved
Info

Implicit assumption of valid ABI-encoded strings in `BytesParser.toString`

I-01The `BytesParser.toString` function uses `abi.decode(input, (string))` for `input.length != 0 && input.length != 32`. This implicitly assumes that the `input` bytes are always valid ABI-encoded strings. If `input` contains arbitrary bytes that are not valid ABI-encoded strings, `abi.decode` might revert or produce unexpected results, leading to runtime errors or incorrect data interpretation.
IssueThe `BytesParser.toString` function uses `abi.decode(input, (string))` for `input.length != 0 && input.length != 32`. This implicitly assumes that the `input` bytes are always valid ABI-encoded strings. If `input` contains arbitrary bytes that are not valid ABI-encoded strings, `abi.decode` might revert or produce unexpected results, leading to runtime errors or incorrect data interpretation.
FixAdd more robust validation or error handling around `abi.decode(input, (string))` to ensure the input bytes represent a valid string, or clarify the expected encoding of the `input` bytes in the function's documentation.
StatusUnresolved

Category Ratings

TechnicalMedium6/10

The technical architecture leverages OpenZeppelin upgradeable contracts and custom libraries for byte parsing, demonstrating a structured approach (7.1 Architecture). The `bridgeMint` and `bridgeBurn` functions are correctly protected by an `onlyGateway` modifier (7.3 Access Control). However, a critical vulnerability exists where the `Cloneable` contract's `isMasterCopy` state is not correctly initialized in the proxy context, potentially allowing proxies to be self-destructed (7.2 Code Security, 7.7 Upgrades). Additionally, the `bridgeInit` function lacks proper access control, enabling a front-running attack to control the L2 gateway (7.3 Access Control).

GovernanceHigh1/10

The economic model relies on the `l2Gateway` to control token minting and burning, which is standard for an L2 bridge token (7.4 Economic). The `l2Gateway` address is set during initialization, making its initial security paramount. The `StandardArbERC20` contract's `decimals`, `name`, and `symbol` getters can revert if L1 metadata parsing fails, which could negatively impact user experience and dApp integrations (7.4 Economic). There is no explicit governance mechanism beyond the `l2Gateway`'s control (7.5 Governance).

UpgradesHigh1/10

The contract utilizes a `ClonableBeaconProxy` pattern, indicating an intention for upgradeability (7.7 Upgrades). The `_initialize` pattern is correctly used for state initialization in the upgradeable context. However, the `Cloneable` base contract's `isMasterCopy` state variable is initialized via a constructor, which is incompatible with the proxy pattern. This critical flaw means `isMasterCopy` will be `false` in proxy instances, potentially allowing `safeSelfDestruct` to be called on live proxies, leading to catastrophic loss of functionality and assets (7.7 Upgrades).

Security Checklist

Contract VerifiedPass
Ownership Renounced?
No Mint FunctionPass
Liquidity LockedFail
Not a ProxyFail

Proxy Upgrade Controls

Proxy TypeBeacon
ImplementationVerified source
Upgrades (30d)0 · stable

Holder Composition

50.1% in wallets13.2% in contracts
Effective Concentration55.4%

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.

Liquidity Depth

Show 4 more pairsShow less

One more pair holds $4 and is not listed.

The risk score reads depth across every pair. The volume figure and the volume-to-liquidity ratio elsewhere on this page describe only the pair this audit analysed, so the two are not directly comparable.

LP Distribution

Top-1 Unlocked Holder24.2%
Top-3 Unlocked37.6%

Key Addresses

Deployer
0x3fe3…000f
Unlocked LP Held By
0x33ab…21fa0x7a48…911c0x55ac…e1490x60c6…10810x1540…813d0xf8d0…dd0c0xdec1…63760x1bac…d8070x4e24…25100x841e…153f

No privileged address appears among these holders: the unlocked liquidity sits with independent providers, not with the deployer.

What Raised This Score

  • Ownership status UNKNOWN (owner could not be resolved)
  • Proxy contract (upgradeable — admin can replace logic)
  • Complex proxy pattern (BEACON)
  • Top-10 concentration > 50% (63.3% total → 55.4% effective; 50.1% in EOAs, 13.2% in contracts — heavy)
  • Liquidity not locked, but no owner/deployer address holds LP — market-depth risk, not rug risk
  • 1 Critical finding(s) from audit
  • 1 High finding(s) from audit
  • 1 Medium finding(s) from audit
  • 1 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

Related Audits

ODYSHigh RiskEspresso (ESP)High RiskLivepeer Token (LPT)High RiskOrderly Network (ORDER)High RiskGraph Token (GRT)High RiskArbitrum Intern (INTERN)High Risk

Would You Like a More Detailed Audit of Curve DAO Token?

Our AI-powered scanner gives you a deeper, real-time smart contract analysis — free, with every scoring factor shown.

Get Detailed Audit