Quantum Audit Logo

Is Frax USD Safe?

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

Is this your token? Publish your own audit on this page →

Frax USD FRXUSD
0xcacd…6e29
Ethereum Not verifiedLast checked 3d ago 1 audit on record
Executive SummaryAI Copilot

The audit of the FrxUSD token contract, an ERC-20 implementation utilizing a TransparentUpgradeableProxy, identified a critical vulnerability related to its constructor initialization. The implementation contract's owner is incorrectly set to address(1) due to constructor logic, rendering all owner-restricted functions inaccessible to the legitimate proxy owner. Additionally, the contract exhibits a high degree of centralization with extensive owner and freezer privileges, and the owner can bypass pause and freeze mechanisms. While the contract leverages well-vetted OpenZeppelin libraries for core functionality and EIP standards, the upgradeability and access control flaws pose significant risks.

1 Critical1 High1 Medium1 Low2 Informational
Volume 24h
$1.86M
Liquidity
$14.77M
Price
$0.9999
Token Age
1y
Top 10 Holders
85.6%

Security Findings

Critical

Critical: Constructor Initialization in Upgradeable Contract Locks Out Owner

C-01The `FrxUSD3` contract, which serves as the implementation for a TransparentUpgradeableProxy, initializes its `Ownable` base contract via its constructor: `constructor() FrxUSD2(address(1), "Frax USD", "frxUSD") {}`. In an upgradeable proxy pattern, the constructor of the implementation contract is called only once when the implementation is deployed, not when the proxy is initialized. This means the `owner` state variable within the implementation contract is permanently set to `address(1)`. Consequently, any function protected by the `onlyOwner` modifier will only be callable by `address(1)`, rendering the legitimate proxy owner (e.g., the multisig `0xb898ad2976b4d8f2e21521c9db16b7497825e…
IssueThe `FrxUSD3` contract, which serves as the implementation for a TransparentUpgradeableProxy, initializes its `Ownable` base contract via its constructor: `constructor() FrxUSD2(address(1), "Frax USD", "frxUSD") {}`. In an upgradeable proxy pattern, the constructor of the implementation contract is called only once when the implementation is deployed, not when the proxy is initialized. This means the `owner` state variable within the implementation contract is permanently set to `address(1)`. Consequently, any function protected by the `onlyOwner` modifier will only be callable by `address(1)`, rendering the legitimate proxy owner (e.g., the multisig `0xb898ad2976b4d8f2e21521c9db16b7497825e…
FixFor upgradeable contracts, avoid using constructors for state initialization. Instead, implement an `initialize` function (e.g., `function initialize(address _ownerAddress, string memory _name, string memory _symbol) public initializer`) that is called once by the proxy's admin after deployment. This function should set the owner and other initial parameters. Ensure the `initialize` function uses an `initializer` modifier (e.g., from OpenZeppelin's `UUPSUpgradeable` or `Initializable`) to preve…
StatusUnresolved
High

High: Extensive Centralized Control by Owner and Freezers

H-01The contract grants significant centralized control to the `owner` and designated `freezer` roles. The owner has the power to add/remove minters, add/remove freezers, pause/unpause all transfers, and burn arbitrary amounts of tokens from any address, including burning an entire balance if `_amount` is zero. Freezers can freeze/thaw any user account. This high degree of centralization means that a compromise of the owner's or a freezer's private keys could lead to severe consequences, including censorship, denial of service, or arbitrary token destruction.
IssueThe contract grants significant centralized control to the `owner` and designated `freezer` roles. The owner has the power to add/remove minters, add/remove freezers, pause/unpause all transfers, and burn arbitrary amounts of tokens from any address, including burning an entire balance if `_amount` is zero. Freezers can freeze/thaw any user account. This high degree of centralization means that a compromise of the owner's or a freezer's private keys could lead to severe consequences, including censorship, denial of service, or arbitrary token destruction.
FixWhile some centralization is inherent in stablecoin designs, consider implementing additional safeguards. For critical functions like `burnMany`, `addMinter`, `removeMinter`, `addFreezer`, `removeFreezer`, `pause`, and `unpause`, consider adding time-locks to allow users to react to pending changes. Implement multi-signature requirements for all sensitive administrative actions if not already enforced at the owner wallet level. Clearly document the responsibilities and security procedures for m…
StatusUnresolved
Medium

Medium: Owner Bypasses Pause and Freeze Checks

M-01The `_update` internal function, which is called during token transfers, includes a conditional check: `if (msg.sender != owner()) { ... }`. This logic allows the contract owner to bypass the `isPaused` and `isFrozen` checks. While this might be intended for emergency recovery or specific administrative actions, it means the owner is not subject to the same restrictions as other users. This could be perceived as a backdoor, potentially allowing the owner to transfer tokens even when the contract is paused or to/from frozen accounts, undermining the intended universal application of these security mechanisms.
IssueThe `_update` internal function, which is called during token transfers, includes a conditional check: `if (msg.sender != owner()) { ... }`. This logic allows the contract owner to bypass the `isPaused` and `isFrozen` checks. While this might be intended for emergency recovery or specific administrative actions, it means the owner is not subject to the same restrictions as other users. This could be perceived as a backdoor, potentially allowing the owner to transfer tokens even when the contract is paused or to/from frozen accounts, undermining the intended universal application of these security mechanisms.
FixRe-evaluate the necessity of the owner bypassing pause and freeze checks. If this functionality is critical for emergency scenarios, ensure it is clearly documented and understood. Consider implementing a separate, explicitly named emergency function for owner-initiated transfers that bypasses these checks, rather than embedding it within the general `_update` logic. This would make the intent clearer and reduce the risk of unintended use.
StatusUnresolved
Low

Low: Inefficient Minter Removal in `minters_array`

L-01The `removeMinter` function iterates through the `minters_array` to find the target address and sets `minters_array[i] = address(0)`. This approach does not actually remove the element from the array; it merely marks it as `address(0)`. Over time, if many minters are added and removed, the `minters_array` will grow indefinitely with `address(0)` entries. This can lead to increased gas costs for any future function that iterates over this array, and could potentially hit block gas limits if the array becomes excessively large.
IssueThe `removeMinter` function iterates through the `minters_array` to find the target address and sets `minters_array[i] = address(0)`. This approach does not actually remove the element from the array; it merely marks it as `address(0)`. Over time, if many minters are added and removed, the `minters_array` will grow indefinitely with `address(0)` entries. This can lead to increased gas costs for any future function that iterates over this array, and could potentially hit block gas limits if the array becomes excessively large.
FixTo efficiently remove elements from a dynamic array, consider using a 'swap and pop' pattern. When an element is to be removed, swap it with the last element in the array and then `pop()` the last element. This maintains array contiguity and avoids `address(0)` entries. Alternatively, if the order of minters is not critical, a `mapping(address => bool)` for `minters` is sufficient for checking membership, and `minters_array` could be removed or rebuilt periodically if iteration is truly necessa…
StatusUnresolved
Info

Informational: Use of `address(1)` in Constructor

I-01The constructor of `FrxUSD3` initializes the `Ownable` contract with `address(1)` as the owner. While the primary issue is the use of a constructor for initialization in an upgradeable contract (C-01), the specific choice of `address(1)` is unusual. This address is a valid, but generally unused, Ethereum address. Its use here might be confusing or lead to misinterpretations if not clearly documented as a placeholder or a symptom of the upgradeability issue.
IssueThe constructor of `FrxUSD3` initializes the `Ownable` contract with `address(1)` as the owner. While the primary issue is the use of a constructor for initialization in an upgradeable contract (C-01), the specific choice of `address(1)` is unusual. This address is a valid, but generally unused, Ethereum address. Its use here might be confusing or lead to misinterpretations if not clearly documented as a placeholder or a symptom of the upgradeability issue.
FixEnsure that any placeholder addresses or unusual initialization values are thoroughly documented within the code or in external specifications to prevent confusion or misinterpretation by future auditors or developers.
StatusUnresolved
Info

Informational: OpenZeppelin Contracts Version 5.3.0

I-02The contract utilizes OpenZeppelin Contracts version 5.3.0. This is a relatively recent version, indicating a commitment to using up-to-date and potentially more secure library components. However, newer versions can sometimes introduce subtle behavioral changes or require careful integration. It's important to ensure that the development team is fully aware of any breaking changes or new features introduced in this version compared to previous ones they might have used.
IssueThe contract utilizes OpenZeppelin Contracts version 5.3.0. This is a relatively recent version, indicating a commitment to using up-to-date and potentially more secure library components. However, newer versions can sometimes introduce subtle behavioral changes or require careful integration. It's important to ensure that the development team is fully aware of any breaking changes or new features introduced in this version compared to previous ones they might have used.
FixMaintain a clear understanding of the OpenZeppelin Contracts changelog for version 5.3.0 and ensure all integrations are compatible. Regularly review OpenZeppelin's security advisories and consider upgrading to the latest stable version as new releases become available, after thorough testing.
StatusUnresolved

Category Ratings

TechnicalMedium6/10

The technical architecture benefits from inheriting battle-tested OpenZeppelin contracts (ERC20, ERC20Permit, Ownable2Step) and implementing EIP-3009 and EIP-2612 for enhanced token utility (7.1 Architecture). However, a critical flaw exists where the implementation contract's constructor initializes the owner to address(1), effectively locking out the actual proxy owner from all `onlyOwner` functions (7.3 Access Control). The `removeMinter` function also uses an inefficient soft-deletion mechanism, potentially leading to increased gas costs over time (7.2 Code Security).

GovernanceHigh1/10

The contract design grants significant centralized control to the owner and designated freezers (7.5 Governance). The owner can add/remove minters and freezers, pause/unpause transfers, and burn arbitrary amounts of tokens from any address, including full balances (7.4 Economic). Freezers can freeze/thaw accounts, which, combined with the owner's ability to bypass pause/freeze checks, creates a powerful and potentially exploitable control surface (7.3 Access Control). While the owner is a multisig, the extensive powers still represent a high risk if compromised.

UpgradesHigh1/10

The contract is deployed behind a TransparentUpgradeableProxy, but the implementation contract's constructor in `FrxUSD3` initializes `Ownable` with `address(1)` (7.7 Upgrades). This is a critical anti-pattern for upgradeable contracts, as constructors are only called once upon implementation deployment, not during proxy initialization. Consequently, the `owner()` function in the implementation will always return `address(1)`, making all `onlyOwner` functions inaccessible to the actual proxy owner, effectively bricking core administrative functionalities (7.3 Access Control).

Security Checklist

Contract VerifiedPass
Ownership RenouncedFail
No Mint FunctionPass
Liquidity LockedFail
Not a ProxyFail
HoneypotNoneBuy Tax0.0%Sell Tax0.0%

Proxy Upgrade Controls

Proxy TypeEip1967 Transparent
AdminOZ ProxyAdmin
ImplementationVerified source
Upgrades (30d)0 · stable

Holder Composition

44.3% in wallets41.2% in contracts
Effective Concentration60.8%

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

The 3 remaining pairs hold $706 between them and are 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 Holder68.0%
Top-3 Unlocked90.8%

Key Addresses

Deployer
0x5b35…9814
Unlocked LP Held By
0x5f07…832f0x3777…c2c80xc37a…88d30xf711…57930x83ee…26600xf5a5…9da30x714f…7f0a

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

What Raised This Score

  • Ownership NOT renounced — strong Multisig (4-of-7)
  • Proxy contract (upgradeable — admin can replace logic)
  • OZ ProxyAdmin -> Admin is unclassified contract
  • Top-10 concentration > 50% (85.6% total → 60.8% effective; 44.3% in EOAs, 41.2% in contracts — heavy)
  • Liquidity not locked, but no owner/deployer address holds LP — market-depth risk, not rug risk
  • LP top1 unlocked holder = 68.0% (independent LP — depth risk, pool = 52% of DEX liquidity)
  • LP top3 unlocked holders = 90.8% (independent LP — depth risk, pool = 52% of DEX liquidity)
  • 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

c8ntinuum (CTM)High RiskCurve DAO (CRV)High RiskEthena (ENA)High RiskEigenCloud (prev. EigenLayer) (EIGEN)High RiskCapHigh RiskYield Basis (YB)High Risk

Would You Like a More Detailed Audit of Frax USD?

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

Get Detailed Audit