OpenZeppelin Blog

Crown BRL Stablecoin Audit

Written by OpenZeppelin Security | July 28, 2025

Table of Contents

Summary

Type
Stablecoins
Timeline
From 2025-07-15
To 2025-07-18
Languages
Solidity
Total Issues
8 (8 resolved)
Critical Severity Issues
0 (0 resolved)
High Severity Issues
0 (0 resolved)
Medium Severity Issues
0 (0 resolved)
Low Severity Issues
3 (3 resolved)
Notes & Additional Information
5 (5 resolved)

Scope

OpenZeppelin audited the crown-xyz/crown-contracts repository at commit 13631ab.

In scope were the following files:

 contracts
├── BRLV.sol
├── BRLY.sol
├── YieldStripping.sol
└── wBRLY.sol

BRLY.sol was diff-audited against USDM.sol at commit 2f0e2f0, while wBRLY.sol was diff-audited against wUSDM.sol at commit 78c6556.

System Overview

This is a brief audit of 4 files, with 2 of them being diff-audited against the existing USDM codebase. BRLY is a rebasing token similar to the USDM token with added functionality such as a maximum reward multiplier increment ratio and its setter function. This function ensures that the new reward ratio is not over the existing maximum value to avoid profitable just-in-time deposit attacks. The BRLY contract also adds a function that rounds up the value when converting to shares during burning. This helps ensure that rounding errors favor protocol solvency.

BRLV is a tokenized vault that has BRLY as the underlying asset. It inherits the YieldStripping contract, allowing for the equal-amount exchange of an underlying asset that is supposed to be a yield-bearing ERC-20 token. BRLV can be considered a simplified version of an ERC-4626 vault, since any overflowing token supply generated by rebasing will be held for distribution afterwards. The additional yield is distributed by a permissioned claim workflow.

Security Model and Trust Assumptions

The contracts under review are built on fairly standard modules such ERC-4626 vault and ERC-20. In addition, they use OpenZeppelin's UUPS upgradeability pattern to allow for logical upgrades in the future.

Privileged Roles

Throughout the codebase, multiple privileged roles were identified:

  • CLAIM_ROLE
  • UPGRADE_ROLE
  • PAUSE_ROLE
  • DEFAULT_ADMIN_ROLE
  • ORACLE_ADMIN_ROLE
  • MINTER_ROLE
  • BURNER_ROLE

It is assumed that the above-listed roles will be granted to trusted accounts that will always behave properly.

Low Severity

Incorrect Comment

In BRLV.sol, the comment in line 123 states that a token transfer will be reverted if either the from or to address is "blocked". However, the function only checks if the from address is blocked. This is also reflected by the comment in line 127, which states that only the sender is blocked.

Consider either changing the comment in line 123 to reflect the actual behavior of the function, or implementing a separate check for the to address and removing the comment in line 127.

Update: Resolved in commit 3d0a80 in pull request #19

Missing Docstrings

Throughout the in-scope codebase, multiple instances of missing docstrings were identified. For instance, in line 107 of YieldStripping.sol

Consider thoroughly documenting all functions (and their parameters) that are part of any contract's public API. Functions implementing sensitive functionality, even if not public, should be clearly documented as well. When writing docstrings, consider following the Ethereum Natural Specification Format (NatSpec).

Update: Resolved in commit fb9260 in pull request #20

Rounding Errors in _burn May Leave Dust in Accounts

The _burn function is needed for both the withdraw and access-controlled burn flows. Within the withdraw flow, users must request an amount of assets to withdraw, which will be converted to shares that are then burned from their accounts. However, due to the maxWithdrawal limit and the use of convertToSharesRoundUp (which still causes some rounding errors), it is nearly impossible for a user to withdraw their total balance of shares. The access-controlled burn function is also prone to leaving some dust in accounts when attempting to burn all shares in accounts. This is partially due to the convertToSharesRoundUp function.

In both cases, due to the nature of rebasing tokens, the assets specified for an account may correspond to a different amount of shares than intended by the time a transaction is included in a block. Finally, the check within _burn that accountShares < shares means that over-estimating assets is not a viable way to burn tokens, as execution will revert if over-estimation is done.

Consider implementing some flow that burns all shares in a user's account. This will allow the contract to function more cleanly and reduce lost tokens. This could be achieved, for example, by implementing a different check in _burn which allows over-estimation, or by implementing special withdrawAll or burnAll functions.

Update: Resolved in commit 20d92a of pull request #21.

Notes & Additional Information

YieldStrippable.sol Should Be Renamed

YieldStrippable.sol only contains the IYieldStrippable interface.

To better follow the best-practice naming standards and improve the clarity of the codebase, consider renaming YieldStrippable.sol to IYieldStrippable.sol and changing the import within YieldStripping.sol to match the new filename.

Update: Resolved in commit 596b36 in pull request #22

IBRLY Should Be in a Separate File

The IBRLY interface is declared within BRLV.sol alongside the BRLV contract.

Consider separating the IBRLY interface into its own file, IBRLY.sol. Doing so will help improve the clarity and maintainability of the codebase.

Update: Resolved in commit 404523 in pull request #23

_tryGetAssetDecimals Is Unused

The _tryGetAssetDecimals function in YieldStripping.sol is unused.

Consider removing any unused code to improve the clarity and maintainability of the codebase.

Update: Resolved in commit 9f4098 in pull request #24

Claiming 0 Rewards Possible

The claimRewards function will successfully execute when rewards == 0. This is effectively a no-op, but will pollute event logs with unnecessary event emissions.

Consider implementing a sanity check for the rewards amount within claimRewards.

Update: Resolved in commit 0de1f8 in pull request #25

Unused Imports

Throughout the codebase, multiple instances of unused imports were identified:

Consider removing any unused imports to improve the overall clarity and maintainability of the codebase.

Update: Resolved in commit e69fa0 in pull request #26 

Conclusion

The codebase implements a minimal, yield-bearing, rebasing BRL stablecoin token. It features role-based access controls, pausability, and a blocklist mechanism to ensure regulatory compliance.

The audit identified no critical-, high-, or medium-severity issues, with only a few low- and note-severity findings. Recommendations were provided to align the codebase with Solidity best practices and ensure clean, intended functionality. Safe system operation relies on trusted roles acting in the protocol’s best interest, along with real-time monitoring to manage the blocklist effectively.

The audit team commended the minimal design and the reuse of battle-tested contracts, such as Mountain Protocol’s USDM and OpenZeppelin’s libraries, which help minimize potential vulnerabilities.