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.
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.
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.
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.
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
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
_burn
May Leave Dust in AccountsThe _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.
YieldStrippable.sol
Should Be RenamedYieldStrippable.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 FileThe 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 UnusedThe _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
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
Throughout the codebase, multiple instances of unused imports were identified:
ECDSAUpgradeable
of BRLV.sol
ERC4626Upgradeable
of BRLV.sol
ECDSAUpgradeable
of BRLY.sol
IERC4626Upgradeable
of YieldStripping.sol
ECDSAUpgradeable
of wBRLY.sol
Consider removing any unused imports to improve the overall clarity and maintainability of the codebase.
Update: Resolved in commit e69fa0 in pull request #26
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.