From a0fa023855d8c794aa50c45a0734b4cd2f55fb15 Mon Sep 17 00:00:00 2001 From: Eric Shenghsiung Liu Date: Fri, 29 May 2026 08:28:50 -0400 Subject: [PATCH 1/3] fix(factory): MissingRequiredField includes field name (BOP-155) Update IB20Factory interface and tests to match the precompile change that adds the missing field name to the MissingRequiredField error. --- src/interfaces/IB20Factory.sol | 6 +++--- test/lib/mocks/MockB20Factory.sol | 2 +- test/unit/B20Factory/createToken.t.sol | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/interfaces/IB20Factory.sol b/src/interfaces/IB20Factory.sol index ff031318..201fa398 100644 --- a/src/interfaces/IB20Factory.sol +++ b/src/interfaces/IB20Factory.sol @@ -181,9 +181,9 @@ interface IB20Factory { /// any known encoding for the requested variant. error UnsupportedVersion(uint8 version, B20Variant variant); - /// @notice A required string argument was the empty string (e.g. - /// security `isin`). - error MissingRequiredField(); + /// @notice A required string argument was the empty string. + /// @param field Name of the missing field (e.g. `"isin"`, `"currency"`). + error MissingRequiredField(string field); /// @notice The stablecoin `currency` contained a non-`A`–`Z` byte. error InvalidCurrency(string code); diff --git a/test/lib/mocks/MockB20Factory.sol b/test/lib/mocks/MockB20Factory.sol index 7cdec8f7..1c4b7285 100644 --- a/test/lib/mocks/MockB20Factory.sol +++ b/test/lib/mocks/MockB20Factory.sol @@ -134,7 +134,7 @@ contract MockB20Factory is IB20Factory { if (p.version != B20FactoryLib.B20_SECURITY_CREATE_PARAMS_VERSION) { revert UnsupportedVersion(p.version, variant); } - if (bytes(p.isin).length == 0) revert MissingRequiredField(); + if (bytes(p.isin).length == 0) revert MissingRequiredField("isin"); name_ = p.name; symbol_ = p.symbol; admin = p.initialAdmin; diff --git a/test/unit/B20Factory/createToken.t.sol b/test/unit/B20Factory/createToken.t.sol index 30fed407..e76851df 100644 --- a/test/unit/B20Factory/createToken.t.sol +++ b/test/unit/B20Factory/createToken.t.sol @@ -127,12 +127,12 @@ contract B20FactoryCreateB20Test is B20FactoryTest { } /// @notice Verifies security createToken reverts when isin is the empty string - /// @dev Per-variant required-field check; checks MissingRequiredField() error + /// @dev Per-variant required-field check; checks MissingRequiredField(string) error function test_createB20_revert_missingIsin(address caller, bytes32 salt) public { _assumeValidCaller(caller); IB20Factory.B20SecurityCreateParams memory p = _securityParams("Security Test", "SEC", admin, "", 0); vm.prank(caller); - vm.expectRevert(IB20Factory.MissingRequiredField.selector); + vm.expectRevert(abi.encodeWithSelector(IB20Factory.MissingRequiredField.selector, "isin")); factory.createB20(IB20Factory.B20Variant.SECURITY, salt, abi.encode(p), new bytes[](0)); } From d551a21725f1bc7f8427270d11dba1ce397ede56 Mon Sep 17 00:00:00 2001 From: Eric Shenghsiung Liu Date: Fri, 29 May 2026 09:40:51 -0400 Subject: [PATCH 2/3] fix(factory): add MissingRequiredField currency check and test --- test/lib/mocks/MockB20Factory.sol | 2 ++ test/unit/B20Factory/createToken.t.sol | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/test/lib/mocks/MockB20Factory.sol b/test/lib/mocks/MockB20Factory.sol index 1c4b7285..26caaa72 100644 --- a/test/lib/mocks/MockB20Factory.sol +++ b/test/lib/mocks/MockB20Factory.sol @@ -119,6 +119,8 @@ contract MockB20Factory is IB20Factory { if (p.version != B20FactoryLib.B20_STABLECOIN_CREATE_PARAMS_VERSION) { revert UnsupportedVersion(p.version, variant); } + // Required: currency must be non-empty. + if (bytes(p.currency).length == 0) revert MissingRequiredField("currency"); // Format check: every byte must be an uppercase ASCII letter (A-Z). bytes memory cb = bytes(p.currency); for (uint256 i = 0; i < cb.length; ++i) { diff --git a/test/unit/B20Factory/createToken.t.sol b/test/unit/B20Factory/createToken.t.sol index e76851df..f59d3605 100644 --- a/test/unit/B20Factory/createToken.t.sol +++ b/test/unit/B20Factory/createToken.t.sol @@ -126,6 +126,16 @@ contract B20FactoryCreateB20Test is B20FactoryTest { factory.createB20(IB20Factory.B20Variant.SECURITY, salt, abi.encode(p), new bytes[](0)); } + /// @notice Verifies stablecoin createToken reverts when currency is the empty string + /// @dev Per-variant required-field check; checks MissingRequiredField(string) error + function test_createB20_revert_missingCurrency(address caller, bytes32 salt) public { + _assumeValidCaller(caller); + IB20Factory.B20StablecoinCreateParams memory p = _stablecoinParams("Stable Test", "STB", admin, ""); + vm.prank(caller); + vm.expectRevert(abi.encodeWithSelector(IB20Factory.MissingRequiredField.selector, "currency")); + factory.createB20(IB20Factory.B20Variant.STABLECOIN, salt, abi.encode(p), new bytes[](0)); + } + /// @notice Verifies security createToken reverts when isin is the empty string /// @dev Per-variant required-field check; checks MissingRequiredField(string) error function test_createB20_revert_missingIsin(address caller, bytes32 salt) public { From 4e7a5fae11e672ddcad4fdc78230bf3e73a6e7d3 Mon Sep 17 00:00:00 2001 From: Amie Corso Date: Fri, 29 May 2026 11:16:02 -0700 Subject: [PATCH 3/3] docs(factory): drop stale currency references after merge resolution Post-merge cleanup: now that empty currency is rejected via InvalidCurrency("") on main, the MissingRequiredField natspec and a cross-reference in createToken.t.sol still pointed at the dropped currency path. Updated both to use isin as the example (the field that actually still uses MissingRequiredField) and to point at the live test_createB20_revert_emptyCurrency. --- src/interfaces/IB20Factory.sol | 3 ++- test/unit/B20Factory/createToken.t.sol | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/interfaces/IB20Factory.sol b/src/interfaces/IB20Factory.sol index 201fa398..269aad21 100644 --- a/src/interfaces/IB20Factory.sol +++ b/src/interfaces/IB20Factory.sol @@ -182,7 +182,8 @@ interface IB20Factory { error UnsupportedVersion(uint8 version, B20Variant variant); /// @notice A required string argument was the empty string. - /// @param field Name of the missing field (e.g. `"isin"`, `"currency"`). + /// @param field Name of the missing field (e.g. `"isin"`). Empty + /// `currency` is rejected separately via `InvalidCurrency("")`. error MissingRequiredField(string field); /// @notice The stablecoin `currency` contained a non-`A`–`Z` byte. diff --git a/test/unit/B20Factory/createToken.t.sol b/test/unit/B20Factory/createToken.t.sol index 0147cb36..ec0540b4 100644 --- a/test/unit/B20Factory/createToken.t.sol +++ b/test/unit/B20Factory/createToken.t.sol @@ -93,7 +93,7 @@ contract B20FactoryCreateB20Test is B20FactoryTest { /// @notice Any non-empty string containing a non-`A`–`Z` byte reverts with `InvalidCurrency(code)`. /// @dev Subsumes every point case (lowercase, digits, symbols, multi-byte UTF-8) via /// `vm.assume(!_isValidFiatCode)`. Empty input is covered by - /// `test_createB20_revert_missingCurrency` (rejected with MissingRequiredField). + /// `test_createB20_revert_emptyCurrency` (rejected with `InvalidCurrency("")`). function test_createB20_revert_currency_rejectsInvalidFormat(string memory code, address caller, bytes32 salt) public {