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.

On-chain security analysis — is it a scam or legit?
Is Frax USD 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 audit of the FrxUSD token contract, an ERC-20 implementation utilizing a TransparentUpgradeableProxy, identified a critical vulnerability related to its constructor initialization.
Read the full analysis- Ownership not renounced — strong Multisig
- Proxy contract
- OZ ProxyAdmin -> Admin is unclassified contract
- Top-10 concentration > 50%
- Liquidity not locked, but no owner/deployer address holds LP — market-depth risk, not rug risk
- LP top1 unlocked holder = 68.0%
- LP top3 unlocked holders = 90.8%
- 1 Critical finding(s) from audit
- 1 High finding(s) from audit
- 1 Medium finding(s) from audit
- 1 Low finding(s) from audit
Exact factors
- 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
Security Checklist
as of audit · Sep 4Holders
as of audit · Sep 4, 2026- Contractstreasury, vesting, bridge, staking41.2%
- Walletsanonymous holders, free to sell44.3%
- Everyone elsethe rest of the supply14.4%
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 4, 2026
Liquidity
as of audit · Sep 4- Curve stblcrvUSDAnalysed$14.77M51.9%
- Curve stbltrUSD$4.87M17.1%
- Curve stblFRAX$4.08M14.4%
- Curve stblUSG$2.32M8.2%
- Curve stblsUSDS$1.86M6.5%
- Uniswap v4USDe$245.1K0.9%
Show 4 more pairs
The 3 remaining pairs hold $706 between them and are not listed. 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.
0x5f07…832f0x3777…c2c80xc37a…88d30xf711…57930x83ee…26600xf5a5…9da30x714f…7f0aNo privileged address appears among these holders: the unlocked liquidity sits with independent providers, not with the deployer.
Security Findings
CriticalCritical: Constructor Initialization in Upgradeable Contract Locks Out Owner
The 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…C-01
Issue
The 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…
Fix
For 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…
Status
Unresolved
HighHigh: Extensive Centralized Control by Owner and Freezers
The 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.H-01
Issue
The 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.
Fix
While 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…
Status
Unresolved
MediumMedium: Owner Bypasses Pause and Freeze Checks
The _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.M-01
Issue
The _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.
Fix
Re-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.
Status
Unresolved
LowLow: Inefficient Minter Removal in minters_array
The 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.L-01
minters_arrayIssue
The 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.
Fix
To 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…
Status
Unresolved
InfoInformational: Use of address(1) in Constructor
The 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.I-01
address(1) in ConstructorIssue
The 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.
Fix
Ensure 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.
Status
Unresolved
InfoInformational: OpenZeppelin Contracts Version 5.3.0
The 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.I-02
Issue
The 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.
Fix
Maintain 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.
Status
Unresolved
Analysis
AI Copilotas of audit · Sep 4The 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).
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.
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).
Immediately address the critical constructor issue by deploying a new implementation contract that correctly initializes the Ownable role via an initialize function, rather than the constructor.
Review and potentially restrict the extensive centralized powers of the owner and freezer roles, considering multi-signature requirements for sensitive operations or time-locks. Improve the removeMinter function to efficiently remove elements from the minters_array to prevent unbounded growth and potential gas issues.
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 4, 202649Medium
- Sep 2, 202643Medium
Score changes are recorded as dated snapshots — history is append-only and cannot be edited.
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 Is this your token? Publish your own audit on this page