From e4c05ada7d31f20e5548e4c091431a95c32a1f4f Mon Sep 17 00:00:00 2001 From: Amie Corso Date: Fri, 29 May 2026 08:11:31 -0700 Subject: [PATCH] fix(activation): add B20_STABLECOIN to feature id constants + pin all 5 to preimage (BOP-159) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the missing B20_STABLECOIN feature id (keccak256("base.b20_stablecoin") = 0xecfa0def…0b8601) to ActivationRegistryFeatureList. The Rust precompile's ActivationFeature enum (base/base@crates/common/precompiles/src/activation/ storage.rs) defines this id and uses it for B20StablecoinToken dispatch; the Solidity-side canonical constants library was missing the entry. Adds 5 pinning tests (ActivationRegistryFeatureListTest) that assert each constant equals its keccak256 preimage, so silent drift in either the hex literal or the string is caught locally. --- .../mocks/ActivationRegistryFeatureList.sol | 3 + .../unit/ActivationRegistry/featureList.t.sol | 64 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 test/unit/ActivationRegistry/featureList.t.sol diff --git a/test/lib/mocks/ActivationRegistryFeatureList.sol b/test/lib/mocks/ActivationRegistryFeatureList.sol index 1f54d1c9..c70c1f69 100644 --- a/test/lib/mocks/ActivationRegistryFeatureList.sol +++ b/test/lib/mocks/ActivationRegistryFeatureList.sol @@ -14,4 +14,7 @@ library ActivationRegistryFeatureList { /// @dev keccak256("base.policy_registry") bytes32 internal constant POLICY_REGISTRY = 0xb582ebae03f16fee49a6763f78df482fb11ae73f103ed0d330bbe556aa90a43f; + + /// @dev keccak256("base.b20_stablecoin") + bytes32 internal constant B20_STABLECOIN = 0xecfa0def2c10020caaf65e6155aa69c84b24892aaef76eeac52e0e2b3a0b8601; } diff --git a/test/unit/ActivationRegistry/featureList.t.sol b/test/unit/ActivationRegistry/featureList.t.sol new file mode 100644 index 00000000..2c792f45 --- /dev/null +++ b/test/unit/ActivationRegistry/featureList.t.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {ActivationRegistryFeatureList} from "test/lib/mocks/ActivationRegistryFeatureList.sol"; + +/// @notice Pins each feature id constant in `ActivationRegistryFeatureList` to +/// its canonical `keccak256("base.")` preimage. +/// +/// @dev These constants are the cross-language contract between the +/// Solidity mock surface and the Rust precompile's `ActivationFeature` +/// enum (`crates/common/precompiles/src/activation/storage.rs` in +/// base/base): both sides must hash the same string and arrive at the +/// same id. The pinning tests below catch silent drift in either +/// direction — a typo in the hex literal or in the string preimage +/// surfaces here before it can desync against the Rust source of +/// truth. +contract ActivationRegistryFeatureListTest is Test { + /// @notice `B20_SECURITY` equals `keccak256("base.b20_security")`. + function test_B20_SECURITY_pinnedToKeccak() public pure { + assertEq( + ActivationRegistryFeatureList.B20_SECURITY, + keccak256("base.b20_security"), + "B20_SECURITY must equal keccak256(\"base.b20_security\")" + ); + } + + /// @notice `B20_TOKEN` equals `keccak256("base.b20_token")`. + function test_B20_TOKEN_pinnedToKeccak() public pure { + assertEq( + ActivationRegistryFeatureList.B20_TOKEN, + keccak256("base.b20_token"), + "B20_TOKEN must equal keccak256(\"base.b20_token\")" + ); + } + + /// @notice `B20_FACTORY` equals `keccak256("base.b20_factory")`. + function test_B20_FACTORY_pinnedToKeccak() public pure { + assertEq( + ActivationRegistryFeatureList.B20_FACTORY, + keccak256("base.b20_factory"), + "B20_FACTORY must equal keccak256(\"base.b20_factory\")" + ); + } + + /// @notice `POLICY_REGISTRY` equals `keccak256("base.policy_registry")`. + function test_POLICY_REGISTRY_pinnedToKeccak() public pure { + assertEq( + ActivationRegistryFeatureList.POLICY_REGISTRY, + keccak256("base.policy_registry"), + "POLICY_REGISTRY must equal keccak256(\"base.policy_registry\")" + ); + } + + /// @notice `B20_STABLECOIN` equals `keccak256("base.b20_stablecoin")`. + function test_B20_STABLECOIN_pinnedToKeccak() public pure { + assertEq( + ActivationRegistryFeatureList.B20_STABLECOIN, + keccak256("base.b20_stablecoin"), + "B20_STABLECOIN must equal keccak256(\"base.b20_stablecoin\")" + ); + } +}