test(roles): comprehensive storage and ops test suites for role management#2855
test(roles): comprehensive storage and ops test suites for role management#2855rayyan224 wants to merge 8 commits into
Conversation
…allowlist Removes the `iso_currency` external dependency and replaces its usage with a self-contained `IsoCurrency` struct that validates against the 155-code allowlist defined in `b20_stablecoin/currency.rs`. The match- based lookup is no_std-compatible, so the `#[cfg(feature = "std")]` guards around currency validation in `storage.rs` are also removed — validation now runs unconditionally in all build targets. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Move role hash literals into B20TokenRole::id() and add tests that verify each identifier matches the canonical keccak256 digest from B20Constants. Co-authored-by: Cursor <cursoragent@cursor.com>
…anagement Adds 9 storage-layer tests in b20/storage.rs covering role_member_count set/get for DEFAULT_ADMIN_ROLE, has_role/set_role true and false paths, and role_admin/set_role_admin including the default-to-DEFAULT_ADMIN_ROLE behaviour. Adds 11 ops-layer tests in common/ops/roles.rs covering the previously untested paths: revoke_role happy path and RoleRevoked event, revoke_role no-op semantics, grant_role no-op on duplicate, privileged bypass, renounce_role happy path and RoleRevoked event, AccessControlBadConfirmation, renounce_last_admin error paths (NotSoleAdmin, AccessControlUnauthorizedAccount), and the required dual-event ordering (RoleRevoked then LastAdminRenounced). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
🟡 Heimdall Review Status
|
|
|
||
| impl IsoCurrency { | ||
| /// Returns `Some(())` if `code` is on the supported currency allowlist, `None` otherwise. | ||
| pub fn from_code(code: &str) -> Option<()> { |
There was a problem hiding this comment.
from_code returning Option<()> is an unusual API. A method named from_* conventionally returns Option<Self> (see FromStr, from_utf8, etc.). Since the struct is a unit struct with no data, Option<()> is functionally a bool.
Consider renaming to is_valid(code: &str) -> bool to better express intent, or have from_code return Option<Self> so the type carries meaning (e.g., could later hold the validated code or metadata).
| pub fn from_code(code: &str) -> Option<()> { | |
| pub fn from_code(code: &str) -> Option<Self> { |
(with Some(Self) on the match arm instead of Some(()))
| use super::accounting::StablecoinAccounting; | ||
| use super::{IB20Stablecoin, IsoCurrency}; |
There was a problem hiding this comment.
Previously, the iso_currency import and the validation in initialize() were gated behind #[cfg(feature = "std")], meaning no_std builds skipped currency validation entirely. This PR removes that gate, so validation now runs unconditionally.
This is a correctness improvement (no reason to skip validation in no_std), but it's a behavioral change worth calling out: no_std consumers that previously passed invalid currency codes will now get InvalidCurrency reverts. If that's intentional (it should be), a note in the PR description would be helpful.
| "AED" | "AFN" | "ALL" | "AMD" | "ANG" | "AOA" | "ARS" | "AUD" | "AWG" | "AZN" | ||
| | "BAM" | "BBD" | "BDT" | "BGN" | "BHD" | "BIF" | "BMD" | "BND" | "BOB" | "BRL" | ||
| | "BSD" | "BTN" | "BWP" | "BYN" | "BZD" | "CAD" | "CDF" | "CHF" | "CNY" | "COP" | ||
| | "CRC" | "CUP" | "CVE" | "CZK" | "DJF" | "DKK" | "DOP" | "DZD" | "EGP" | "ERN" | ||
| | "ETB" | "EUR" | "FJD" | "FKP" | "GBP" | "GEL" | "GHS" | "GIP" | "GMD" | "GNF" | ||
| | "GTQ" | "GYD" | "HKD" | "HNL" | "HTG" | "HUF" | "IDR" | "ILS" | "INR" | "IQD" | ||
| | "IRR" | "ISK" | "JMD" | "JOD" | "JPY" | "KES" | "KGS" | "KHR" | "KMF" | "KPW" | ||
| | "KRW" | "KWD" | "KYD" | "KZT" | "LAK" | "LBP" | "LKR" | "LRD" | "LSL" | "LYD" | ||
| | "MAD" | "MDL" | "MGA" | "MKD" | "MMK" | "MNT" | "MOP" | "MRU" | "MUR" | "MVR" | ||
| | "MWK" | "MXN" | "MYR" | "MZN" | "NAD" | "NGN" | "NIO" | "NOK" | "NPR" | "NZD" | ||
| | "OMR" | "PAB" | "PEN" | "PGK" | "PHP" | "PKR" | "PLN" | "PYG" | "QAR" | "RON" | ||
| | "RSD" | "RUB" | "RWF" | "SAR" | "SBD" | "SCR" | "SDG" | "SEK" | "SGD" | "SHP" | ||
| | "SLE" | "SOS" | "SRD" | "SSP" | "STN" | "SVC" | "SYP" | "SZL" | "THB" | "TJS" | ||
| | "TMT" | "TND" | "TOP" | "TRY" | "TTD" | "TWD" | "TZS" | "UAH" | "UGX" | "USD" | ||
| | "UYU" | "UZS" | "VED" | "VES" | "VND" | "VUV" | "WST" | "XAF" | "XCD" | "XOF" | ||
| | "XPF" | "YER" | "ZAR" | "ZMW" | "ZWG" => Some(()), |
There was a problem hiding this comment.
This hardcoded list replaces the iso_currency crate's full ISO 4217 dataset. The curated approach makes sense for a stablecoin allowlist, but CLP (Chilean Peso) appears to be missing — it's an active circulating fiat currency (ISO 4217: 152). Worth double-checking if this omission is intentional.
Also, since this is a static allowlist that will need manual updates when ISO 4217 changes (new codes like ZWG Zimbabwe Gold were only added in 2024), consider adding a comment noting where the canonical source is and when it was last synced.
There was a problem hiding this comment.
https://github.com/base/base-std/blob/main/test/lib/ISO4217.sol#L28
List here, manually checking now
There was a problem hiding this comment.
Having my agent do another pass and find out why it missed and if there are more
Option<()> is effectively a bool with an unusual name. Returning Option<Self> follows the from_* convention (FromStr, from_utf8, etc.) and allows the type to carry meaning if metadata is added later. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Review SummaryThis PR adds comprehensive test coverage for the role management storage and ops layers, replaces the What looks good
Findings from inline commentsThree inline comments were posted in a prior run and remain relevant:
No additional issues found. The test logic is correct, the refactoring is safe, and the Cargo.toml cleanup (removing |

Summary
b20/storage.rscoveringrole_member_count/set_role_member_countforDEFAULT_ADMIN_ROLE(default zero, write/read-back, increment),has_role/set_roletrue and false paths, androle_admin/set_role_adminincluding the default-to-DEFAULT_ADMIN_ROLEand self-referential behaviourcommon/ops/roles.rsfilling gaps identified in an OpenZeppelin AccessControl v5 compliance audit:revoke_rolehappy path +RoleRevokedevent,revoke_roleno-op semantics,grant_roleno-op on duplicate, privileged bypass,renounce_rolehappy path +RoleRevokedevent,AccessControlBadConfirmation,renounce_last_adminerror paths (NotSoleAdmin,AccessControlUnauthorizedAccount), and the required dual-event ordering (RoleRevokedthenLastAdminRenounced)Test plan
cargo test -p base-common-precompiles --lib b20::storage— 12 tests passcargo test -p base-common-precompiles --lib common::ops::roles— 17 tests passcargo test -p base-common-precompiles --lib— full 271 test suite passes🤖 Generated with Claude Code