Download audit report
This document presents the results of a security review conducted by Nethermind Security for BlueGoldOne's StandardGoldCoin Token contract.
Standard Gold Coin is a digitized commodity token designed to track the value of physical gold (1 gram per token). The system is a UUPS upgradeable ERC20 that implements: a unique issuance and redemption workflow to verify off-chain backing, a 0.02% transfer fee mechanism, regulatory blacklisting capabilities, emergency pause functionality, and EIP2612 Permit features.
The audit comprises 161 lines of the Solidity code. The audit was performed using (a) manual analysis of the codebase, and (b) automated analysis tools.
Findings by severity
| # | Contract | LoC | Comments | Ratio | Blank | Total |
| 1 | src/StandardGoldCoin.sol | 161 | 295 | 183.2% | 63 | 519 |
| Total | 161 | 295 | 183.2% | 63 | 519 |
| # | Finding | Severity | Update |
| 1 | Blacklisted spenders can still execute transfers via transferFrom(...) | Medium | Fixed |
| 2 | The EOA check in initialize(...) can be bypassed by contracts during construction | Info | Mitigated |
| 3 | supportsInterface returns false for ERC20, Metadata, and Permit interfaces | Info | Fixed |
| 4 | The implementation contract does not call _disableInitializers(...) in the constructor | Best Practices | Fixed |
The Standard Gold Coin (SGC) protocol implements a digitized commodity system where each SGC token represents one gram of physical gold. The system is architected as a UUPS Upgradeable ERC20 token that integrates regulatory compliance features, a transaction fee mechanism, and a strict issuance model to maintain a 1:1 correlation with off-chain gold reserves.
The protocol consists of a single primary implementation contract, StandardGoldCoin, which utilizes OpenZeppelin's upgradeable contracts library for core standards including ERC20Permit, AccessControl, and Pausable.
The system distinguishes itself from standard ERC20 tokens by enforcing a strict “Declared Backing” model. The total supply is managed through restricted roles to ensure on-chain tokens match off-chain physical assets.
mintWithIssuance function allows the MINTER_ROLE to create tokens. Crucially, this operation requires a unique issuanceId. This ID links the on-chain minting event to a specific off-chain gold deposit, preventing duplicate processing of the same physical asset. The global totalDeclaredBacking counter is incremented upon success.burnForRedemption function (executed by the BURNER_ROLE). Similar to minting, this requires a unique redemptionId. This ensures the totalDeclaredBacking is decremented correctly and provides an immutable record of the redemption. Users may also burn their own tokens voluntarily via the public burn function.The protocol overrides the standard _update function to inject custom logic into every token transfer.
feeCollector.feeCollector are processed without fees to prevent circular charging.feeCollector to be an Externally Owned Account (EOA). The system explicitly checks code.length to prevent contract addresses from receiving fees.To adhere to regulatory requirements and ensure system safety, SGC implements centralized control mechanisms comparable to major stablecoins.
BLACKLIST_MANAGER_ROLE can freeze specific addresses via addBlackList and removeBlackList. Blacklisted addresses are strictly prohibited from sending or receiving tokens. The implementation follows the standard isBlackListed interface for compatibility with external compliance tools.PAUSER_ROLE can trigger a global pause, halting all token transfers, minting, and burning operations in the event of a security breach or system migration.UPGRADER_ROLE), managing fees (DEFAULT_ADMIN_ROLE), and operational tasks (Minting/Burning).The token has been deployed at the following proxy address 0x4e5A9fDb7F3473CDB86661AAd2b084B853D18B78 on Base Mainnet, with the following implementation address 0x80990084304cf47ed27159fd685992e9066a4584. Upon deployment the contract was initialized, granting the following roles DEFAULT_ADMIN_ROLE, PAUSER_ROLE, MINTER_ROLE, BURNER_ROLE, UPGRADER_ROLE, and BLACKLIST_MANAGER_ROLE to an address owned by the protocol 0x2F54Db274570b8222583Ef3eB8397e40EDf1B176 and setting the feeCollector at 0x2E27573b37127Ae773cd280B645f29298D1B55B2.
The token implements a fee-of-transfer mechanism. The contract is upgradable, meaning that the admin can change the logic of the token if necessary.
The bytecode of the deployed implementation can be verified to match the compiled code provided in the audited repository at this specific commit bbd7923e3a08bb23e8f85e5e27e158e3818d56fb by comparing a keccak hash of the locally compiled deployment bytecode against the deployment bytecode registered onchain:
keccak(deployed_code) == 0xcc73f7876c8c5817d65b21205207bee397f217bb515c125ea91175c82c4433b5keccak(deployed_code) == 0xcc73f7876c8c5817d65b21205207bee397f217bb515c125ea91175c82c4433b5Given that both deployment binaries match when hashed, it is proven that the code provided in the respository is the same as the code deployed on-chain. Additionally, from the on-chain contract code, we can determine that the Solidity compiler version used was 0.8.24.
The risk rating methodology used by Nethermind Security follows the principles established by the OWASP Foundation. The severity of each finding is determined by two factors: Likelihood and Impact.
Likelihood measures how likely the finding is to be uncovered and exploited by an attacker. This factor will be one of the following values:
When defining the likelihood of a finding, other factors are also considered. These can include but are not limited to motive, opportunity, exploit accessibility, ease of discovery, and ease of exploit.
Impact is a measure of the damage that may be caused if an attacker exploits the finding. This factor will be one of the following values:
When defining the impact of a finding, other factors are also considered. These can include but are not limited to data and state integrity, loss of availability, financial loss, and reputation damage. After defining the likelihood and impact of an issue, the severity is determined according to the table below.
| Impact / Likelihood | Low | Medium | High |
| High | Medium | High | Critical |
| Medium | Low | Medium | High |
| Low | Info / Best Practices | Low | Medium |
| Undetermined | Undetermined | Undetermined | Undetermined |
To address issues that do not fit a High, Medium or Low severity, Nethermind Security also uses three more finding severities: Informational, Best Practices, and Undetermined.
transferFrom(...)| File | Fix commit | Type | Status |
src/StandardGoldCoin.sol | — | Medium | Fixed |
The StandardGoldCoin contract implements a blacklist mechanism designed to freeze malicious accounts and prevent them from participating in transactions. This restriction is enforced by overriding the _update(...) function to check both the sender ( from) and the recipient ( to).
However, the contract inherits the standard ERC20 transferFrom(...) logic without overriding it or _spendAllowance(...) to validate the status of the spender.
In a transferFrom(...) operation, the msg.sender is the spender. Since _update(...) only verifies the token owner and the recipient, a blacklisted address acting as a spender can still move funds on behalf of other users. This bypasses the intent of the blacklist, which is to isolate the malicious account from the protocol.
// @audit The _spendAllowance function doesn't check if the spender is blacklisted or not
function _update(address from, address to, uint256 value) internal override whenNotPaused {
// Enforce blacklist (skip for mint/burn)
if (from != address(0)) require(!_blacklisted[from], "SGC: sender blacklisted");
if (to != address(0)) require(!_blacklisted[to], "SGC: recipient blacklisted");
// ...
}Recommendation
Consider overriding the _spendAllowance(...) function to explicitly require that msg.sender (the spender) is not blacklisted.
initialize(...) can be bypassed by contracts during construction| File | Fix commit | Type | Status |
/src/StandardGoldCoin.sol | — | Info | Mitigated |
The initialize(...) function attempts to distinguish whether the provided admin address is an Externally Owned Account (EOA) or a smart contract to correctly assign the feeCollector. The protocol explicitly intends for the feeCollector to be address(0) if the admin is a contract to avoid assigning a contract as a fee collector.
However, the logic relies on admin.code.length == 0 to verify EOA status. In Solidity, a smart contract returns a code size of 0 while it is being executed inside its own constructor.
This creates an edge case where a smart contract could call initialize(...) during its construction phase. The check would falsely identify it as an EOA, resulting in the contract address being assigned as the feeCollector, contrary to the protocol's intended design constraints.
function initialize(address admin) public initializer {
// ...
// @audit-issue Returns 0 if admin is a contract currently being constructed
if (admin.code.length == 0) {
feeCollector = admin;
emit FeeCollectorChanged(address(0), admin);
} else {
feeCollector = address(0);
emit FeeCollectorChanged(address(0), address(0));
}
}Recommendation
Consider documenting this behavior to ensure operational teams are aware of it.
supportsInterface returns false for ERC20, Metadata, and Permit interfaces| File | Fix commit | Type | Status |
/src/StandardGoldCoin.sol | — | Info | Fixed |
The contract overrides supportsInterface to ensure compatibility with AccessControlUpgradeable. However, the implementation simply delegates to super.supportsInterface.
Since OpenZeppelin's ERC20Upgradeable and ERC20PermitUpgradeable do not implement ERC165 by default, the super call chain does not account for them. Consequently, the contract reports that it does not support the following standard interfaces, despite fully implementing them IERC20, IERC20Metadata, and IERC20Permit.
This creates a discrepancy where the contract functions as an ERC20/Permit token but denies supporting these interfaces.
// ...
function supportsInterface(bytes4 interfaceId) public view override(AccessControlUpgradeable) returns (bool) {
// @audit Returns false for IERC20, IERC20Metadata, and IERC20Permit
return super.supportsInterface(interfaceId);
}Recommendation
Consider manually checking for these interface IDs to provide accurate contract information.
Update from Nethermind Security
You don't need to manually calculate interface ID. You can do something like:
return interfaceId == type(IERC20).interfaceId ||
interfaceId == type(IERC20Metadata).interfaceId ||
interfaceId == type(IERC20Permit).interfaceId ||
super.supportsInterface(interfaceId);_disableInitializers(...) in the constructor| File | Fix commit | Type | Status |
src/StandardGoldCoin.sol | — | Best Practices | Fixed |
The StandardGoldCoin contract is designed as an upgradeable contract that inherits from Initializable and UUPSUpgradeable. It relies on the initialize(...) function to set up the initial state, such as granting the DEFAULT_ADMIN_ROLE and other roles to the admin address.
However, the contract does not implement a constructor that calls _disableInitializers().
This means that the logic contract (the implementation) can be initialized by any third party after deployment. According to OpenZeppelin's best practices for upgradeable contracts, the implementation contract should be "locked" in the constructor to prevent it from being used or initialized directly. Leaving it open can allow an attacker to claim ownership of the implementation contract.
contract StandardGoldCoin is
Initializable,
ERC20Upgradeable,
ERC20PermitUpgradeable,
AccessControlUpgradeable,
PausableUpgradeable,
UUPSUpgradeable
{
// ...
// @audit-issue The contract lacks a constructor calling _disableInitializers().
//..
}Recommendation
Consider adding a constructor to the contract that calls _disableInitializers(). This ensures that the implementation contract is locked upon deployment and cannot be initialized by an attacker later on.
Software documentation refers to the written or visual information that describes the functionality, architecture, design, and implementation of software. It provides a comprehensive overview of the software system and helps users, developers, and stakeholders understand how the software works, how to use it, and how to maintain it. Software documentation can take different forms, such as user manuals, system manuals, technical specifications, requirements documents, design documents, and code comments. Software documentation is critical in software development, enabling effective communication between developers, testers, users, and other stakeholders. It helps to ensure that everyone involved in the development process has a shared understanding of the software system and its functionality. Moreover, software documentation can improve software maintenance by providing a clear and complete understanding of the software system, making it easier for developers to maintain, modify, and update the software over time. Smart contracts can use various types of software documentation. Some of the most common types include:
These types of documentation are essential for smart contract development and maintenance. They help ensure that the contract is properly designed, implemented, and tested, and they provide a reference for developers who need to modify or maintain the contract in the future.
The documentation for the BlueGoldOne protocol was provided through Natspec. Moreover, the team addressed all questions and concerns raised by the Nethermind Security team, providing valuable insights and a comprehensive understanding of the project's technical aspects.
The AuditAgent is an AI-powered smart contract auditing tool that analyses code, detects vulnerabilities, and provides actionable fixes. It accelerates the security analysis process, complementing human expertise with advanced AI models to deliver efficient and comprehensive smart contract audits. Available at https://app.auditagent.nethermind.io.
| 20Tests passed | 0Failed |
The complete test output is in the full report.
As auditors, we recommend that any changes or updates made to the audited codebase undergo a re-audit or security review to address potential vulnerabilities or risks introduced by the modifications. By conducting a re-audit or security review of the modified codebase, you can significantly enhance the overall security of your system and reduce the likelihood of exploitation. However, we do not possess the authority or right to impose obligations or restrictions on our clients regarding codebase updates, modifications, or subsequent audits. Accordingly, the decision to seek a re-audit or security review lies solely with you.
This report is based on the scope of materials and documentation provided by you to Nethermind in order that Nethermind could conduct the security review outlined in 1. Executive Summary and 2. Audited Files. The results set out in this report may not be complete nor inclusive of all vulnerabilities. Nethermind has provided the review and this report on an as-is, where-is, and as-available basis. You agree that your access and/or use, including but not limited to any associated services, products, protocols, platforms, content, and materials, will be at your sole risk. Blockchain technology remains under development and is subject to unknown risks and flaws. The review does not extend to the compiler layer, or any other areas beyond the programming language, or other programming aspects that could present security risks. This report does not indicate the endorsement of any particular project or team, nor guarantee its security. No third party should rely on this report in any way, including for the purpose of making any decisions to buy or sell a product, service or any other asset. To the fullest extent permitted by law, Nethermind disclaims any liability in connection with this report, its content, and any related services and products and your use thereof, including, without limitation, the implied warranties of merchantability, fitness for a particular purpose, and non-infringement. Nethermind does not warrant, endorse, guarantee, or assume responsibility for any product or service advertised or offered by a third party through the product, any open source or third-party software, code, libraries, materials, or information linked to, called by, referenced by or accessible through the report, its content, and the related services and products, any hyperlinked websites, any websites or mobile applications appearing on any advertising, and Nethermind will not be a party to or in any way be responsible for monitoring any transaction between you and any third-party providers of products or services. As with the purchase or use of a product or service through any medium or in any environment, you should use your best judgment and exercise caution where appropriate. FOR AVOIDANCE OF DOUBT, THE REPORT, ITS CONTENT, ACCESS, AND/OR USAGE THEREOF, INCLUDING ANY ASSOCIATED SERVICES OR MATERIALS, SHALL NOT BE CONSIDERED OR RELIED UPON AS ANY FORM OF FINANCIAL, INVESTMENT, TAX, LEGAL, REGULATORY, OR OTHER ADVICE.
The Leading Engineers of Blockchain Infrastructure