Skip to content

feat(BOP-440): add transferFromSeizableWithMemo and burnBlockedWithMemo seize surface - #177

Merged
stephancill merged 13 commits into
mainfrom
stephancilliers/bop-440-base-std-seize
Jul 28, 2026
Merged

feat(BOP-440): add transferFromSeizableWithMemo and burnBlockedWithMemo seize surface#177
stephancill merged 13 commits into
mainfrom
stephancilliers/bop-440-base-std-seize

Conversation

@stephancill

@stephancill stephancill commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the seize operation class to the base-std reference + ABI (the source of truth the fork harness cross-validates against base/base), per the BOP-439 PPS. Implements BOP-440 and, per review, also folds in the BOP-471 burnBlocked repoint (base-std portion).

New surface:

  • transferFromSeizableWithMemo(address from, address to, uint256 amount, bytes32 memo) — admin seize that reassigns a blocked account's balance in one call. Skips allowance and the transfer policies; the only membership check is that from is blocked under the new SEIZABLE_ACCOUNT_POLICY. to is not policy-checked (admin op). Emits Transfer + Memo + TransferredFromSeizable.
  • burnBlockedWithMemo(address from, uint256 amount, bytes32 memo) — memo variant of burn-based seizure.
  • New SEIZABLE_ACCOUNT_POLICY scope, TRANSFER_FROM_SEIZABLE_ROLE, and SEIZE PausableFeature.

Seize operation class (all three gate uniformly on SEIZABLE_ACCOUNT_POLICY + the SEIZE pause vector):

  • burnBlocked repointed from TRANSFER_SENDER_POLICY/BURN pause to SEIZABLE_ACCOUNT_POLICY/SEIZE pause. This is a breaking change, acceptable because burnBlocked is not yet in use; intent is a single uniform seize gate rather than a transient split.

Design notes

  • Storage: dedicated slot at offset 14. The seizable id lives in its own SeizePolicyIds slot placed before the mock-only initialized flag (which stays trailing at offset 15). This matters for the Rust mirror: the native precompile checks factory-init via deployed marker bytecode and stores no initialized field, so it omits that trailing mock-only slot and lands seizable_policy_id at offset 14 — with no filler on either side. Offsets 0–13 are untouched, so already-deployed tokens are undisturbed. Seize is a distinct per-operation group (cold-path, spanning transfer + burn), mirroring how mint policies are grouped separately from transfer policies.
  • Explicit guards, no privileged reuse. Seize applies its own guard sequence (SEIZE pause → role → SEIZABLE_ACCOUNT_POLICY block check) and moves balance via a shared _moveBalance primitive extracted from transfer_inner; it does not reuse the factory-bootstrap privileged path.
  • Memo ordering. Memo is emitted immediately after Transfer (before TransferredFromSeizable / BurnedBlocked), per the IB20 Memo invariant.

Testing

  • forge test (non-fork): all pass (715+), forge fmt --check clean.
  • Existing burnBlocked tests updated to the seize gating; new happy-path + revert-order suites for both new functions.
  • Storage layout pinned via B20FullLayout (seize at slot 14, initialized at slot 15) and MockB20SlotHelpers round-trip tests; B20Removals regression updated for the new SEIZE feature (ALL_FEATURES_PAUSED 7 → 15, enum boundary index 4).

@github-actions

Copy link
Copy Markdown

Interface Coverage

✅ All interface functions have test coverage.

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown

📊 Forge Coverage (src/lib/)

🟡 ≥95% across all metrics — some metrics below 99%.

File Lines Stmts Branches Funcs
🔴 B20FactoryLib.sol 95.40% 96.00% 100.00% 90.00%
🔴 test/lib/ForceFeeder.sol 0.00% 0.00% 100.00% 0.00%
🔴 test/lib/PrecompileProbe.sol 0.00% 0.00% 0.00% 0.00%
🟢 MockActivationRegistry.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockActivationRegistryStorage.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockB20.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockB20Asset.sol 100.00% 100.00% 100.00% 100.00%
🟡 MockB20Factory.sol 98.96% 99.10% 100.00% 100.00%
🟢 MockB20Stablecoin.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockB20Storage.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockPolicyRegistry.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockPolicyRegistryStorage.sol 100.00% 100.00% 100.00% 100.00%
Total 96.72% 97.32% 98.67% 96.44%

Full report: download artifact. To browse locally: make coverage (runs forge coverage + genhtml + opens the HTML report).

Comment thread src/interfaces/IB20.sol Outdated
Comment thread src/interfaces/IB20.sol Outdated
Comment thread src/interfaces/IB20.sol Outdated
Comment thread src/lib/B20Constants.sol Outdated
Comment thread test/lib/mocks/MockB20.sol
Comment thread test/lib/mocks/MockB20Storage.sol Outdated
Comment on lines +58 to +60
/// bits 192..255 : reserved (implicit, no field declared)
/// bits 192..255 : seizable (`SEIZABLE_POLICY`)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does adding seizable here use up the reserved bits? no more reserved bits after adding it?

@stephancill stephancill Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI: Yes — this fills the slot. TransferPolicyIds is one 256-bit slot = 4 × uint64 lanes; sender/receiver/executor used lanes 0–2, and seizable takes lane 3 (bits 192..255), which was the last reserved lane. No reserved bits remain in this slot. A future transfer-side policy would need a new appended slot. I picked this lane specifically because it avoids shifting any existing field offset on the already-shipped layout.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These bits were reserved for exactly this kind of purpose. Good to flag that we won't have any headroom after this lands, but it's correct to consume them like this.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rayyan224 another candidate for automated agent fuzzing would be any changes to storage layout (including appending)

@ilikesymmetry ilikesymmetry Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit concerned to take the last space here. Main thing on my mind is if we have some other idea that definitely needs to be on the hotpath transfer. The seizing operations are cold-path and we won't be using any of the other policies here in its execution, so I'm not seeing as clear of a benefit tbh. I'd opt to make a new slot for seize policy entirely by default.

We also recognize seize as an operation applies to burning too. Placing this policy in the "transfer policies" group is not consistent with our separation of "mint policies" as a distinct group by operation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aligned with moving to a dedicated slot

@stephancill
stephancill marked this pull request as draft July 27, 2026 15:57
@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown

⚠️ Fork tests: 146 failed, 632 passed

These failures indicate divergences where base/base needs to catch up to the base-std spec. This check is advisory and does not block merging.

Failing tests
  • test_burnBlockedWithMemo_revertOrder_blocked_beats_balance(address): Error != expected error: custom error 0x6f872692 != AccountNotBlocked(0xB50E2A06E2db34867085bF544B82cF6a6B63A14E); counterexample: calldata=0x2cf250e8000000000000000000000000b50e2a06e2db34867085bf544b82cf6a6b63a14e args=[0xB50E2A06E2db34867085bF544B82cF6a6B63A14E]
  • test_burnBlockedWithMemo_revertOrder_pause_beats_blocked(address): custom error 0xa290249c: type check failed for "(uint8[])" with data: 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003; counterexample: calldata=0x26d48986000000000000000000000000c4208c768599831571f1130d88c08e507c2c9677 args=[0xc4208c768599831571F1130d88c08E507C2C9677]
  • test_burnBlockedWithMemo_revertOrder_pause_beats_role(address,address): custom error 0xa290249c: type check failed for "(uint8[])" with data: 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003; counterexample: calldata=0x53c0e66c000000000000000000000000246c65cb2db6551313d37d835ca0db259a2f23d60000000000000000000000001f5f0dee293e8ba68479d48d2e5845b466efc6bd args=[0x246c65Cb2DB6551313D37D835CA0DB259a2F23d6, 0x1F5f0DEE293E8bA68479d48d2e5845B466eFc6bD]
  • test_burnBlockedWithMemo_revertOrder_role_beats_blocked(address,address): Error != expected error: custom error 0x6f872692 != AccessControlUnauthorizedAccount(0x727157Cd17feB14d8691fA9909FD3370fA0bD757, 0x7408fdc0d31c7bcb349eab611f5d1168acd4303574993f8cdc98b1cd18c41cae); counterexample: calldata=0xbd3f8b6b000000000000000000000000727157cd17feb14d8691fa9909fd3370fa0bd7570000000000000000000000008a370ae5ca0299d326a08fd71451b52ff0df6102 args=[0x727157Cd17feB14d8691fA9909FD3370fA0bD757, 0x8A370aE5ca0299D326A08fD71451b52fF0Df6102]
  • test_burnBlockedWithMemo_revert_accountNotBlocked(address,uint256): Error != expected error: custom error 0x6f872692 != AccountNotBlocked(0x90A0B00A85F5c40c3f10370013d559041C27dc83); counterexample: calldata=0xf0e9ee6a00000000000000000000000090a0b00a85f5c40c3f10370013d559041c27dc83000000000000000000000000000000000000000000000000000000000003cf42 args=[0x90A0B00A85F5c40c3f10370013d559041C27dc83, 249666 [2.496e5]]
  • test_burnBlockedWithMemo_revert_insufficientBalance(address,uint256): UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25); counterexample: calldata=0xeb275e770000000000000000000000000000000000000000000000000000000097a406500000000000000000000000000000000000000000000000000000000000000005 args=[0x0000000000000000000000000000000097a40650, 5]
  • test_burnBlockedWithMemo_revert_unauthorized(address,address,uint256): Error != expected error: custom error 0x6f872692 != AccessControlUnauthorizedAccount(0x5aBD114D202b37743478E86C3198eBB455ac8e9b, 0x7408fdc0d31c7bcb349eab611f5d1168acd4303574993f8cdc98b1cd18c41cae); counterexample: calldata=0xb8e4c85b0000000000000000000000005abd114d202b37743478e86c3198ebb455ac8e9b00000000000000000000000049a255136336ddbc301943bb36760a905c4b7be6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff args=[0x5aBD114D202b37743478E86C3198eBB455ac8e9b, 0x49a255136336DdBc301943Bb36760a905C4B7Be6, 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]]
  • test_burnBlockedWithMemo_revert_whenSeizePaused(address,uint256): UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25); counterexample: calldata=0xfce2f39a000000000000000000000000fe23f7375d89a274d4a6d2a1b567652eab01096b00000000000000000000000000000000009bd79bf5c245f13ba4b7dad3e0266d args=[0xfE23F7375D89a274d4a6D2A1B567652eab01096b, 809179087561562411020884351257683565 [8.091e35]]
  • test_burnBlockedWithMemo_success_debitsAndDecreasesSupply(address,uint256): UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25); counterexample: calldata=0x189a0bed000000000000000000000000a277e463ff9b8da8d8390c2d0fa30767f32514ebffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff args=[0xA277e463fF9b8DA8D8390c2D0fA30767F32514eB, 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]]
  • test_burnBlockedWithMemo_success_emitsTransferBurnedBlockedAndMemo(address,uint256,bytes32): UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25); counterexample: calldata=0x25c74b370000000000000000000000000000000000000000000000000000000000003fb50000000000000000000000000000000000000000000000000000000000000f7c0000000000000000000000000000000000000000000000000000000000003570 args=[0x0000000000000000000000000000000000003fb5, 3964, 0x0000000000000000000000000000000000000000000000000000000000003570]
  • test_burnBlocked_revertOrder_pause_beats_balance(address,uint256): custom error 0xa290249c: type check failed for "(uint8[])" with data: 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003; counterexample: calldata=0xca82fe43000000000000000000000000cb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 args=[0xCB00000000000000000000000000000000000000, 1]
  • test_burnBlocked_revertOrder_pause_beats_blocked(address,uint256): custom error 0xa290249c: type check failed for "(uint8[])" with data: 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003; counterexample: calldata=0x4c29bb130000000000000000000000003284f726110b1ab1582d7905bc5ee9e1d28d518d0000000000001c47fcc2f0c13b3999c7b921d11ef050a1903297f11ec9b63f88 args=[0x3284F726110B1aB1582D7905bc5ee9e1d28d518d, 45446137156070454840796224586539703406286034005925253828984712 [4.544e61]]
  • test_burnBlocked_revertOrder_pause_beats_role(address,address,uint256): custom error 0xa290249c: type check failed for "(uint8[])" with data: 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003; counterexample: calldata=0x022471a50000000000000000000000000000000000000000000000000000000000000c3800000000000000000000000000000000000000000000000000000000e2517d3f0000000000000000000000000000000000000000000000000000000000001bf7 args=[0x0000000000000000000000000000000000000c38, 0x00000000000000000000000000000000E2517d3f, 7159]
  • test_burnBlocked_revert_insufficientBalance(address,uint256): UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25); counterexample: calldata=0x15f3e92c0000000000000000000000005cdfb393623d45b05e0bfe0e58adac82779e3b6c000000000000000000000000000000001c672555eb5f7a45fb6dd72c3caf027f args=[0x5cdfB393623D45b05e0bFE0E58AdAC82779e3B6c, 37753947714800283191852725874866455167 [3.775e37]]
  • test_burnBlocked_revert_whenSeizePaused(address,uint256): UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25); counterexample: calldata=0xf55efb32000000000000000000000000000000000000000000000000000000000000079600000000000000000000000000000000000000000000021e19e0c9bab2400000 args=[0x0000000000000000000000000000000000000796, 10000000000000000000000 [1e22]]
  • test_burnBlocked_success_debitsTarget(address,uint256): UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25); counterexample: calldata=0x485c308e000000000000000000000000f0c332e4f3d38ce694c47cd7c88d81546a4a2d210000000000e42570b540e4514990435d5f7bb7433d59add8e087dc1554657b5c args=[0xF0c332e4f3D38Ce694c47cd7C88D81546a4a2D21, 93853923954663182591525390670538742676233157644566496793445366620 [9.385e64]]
  • test_burnBlocked_success_decreasesTotalSupply(address,uint256): UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25); counterexample: calldata=0x8bb754f20000000000000000000000001e6251883f7d7db0d82f1855c5c56633c124430f000000000000000000000000000000000000000000000000000001226165fcef args=[0x1E6251883F7d7DB0d82F1855C5c56633C124430F, 1247174589679 [1.247e12]]
  • test_burnBlocked_success_emitsTransferAndBurnedBlocked(address,uint256): UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25); counterexample: calldata=0x89de84f800000000000000000000000000000000000000000000000000000000095ea7b200000000000000000000000000000000000000000000000000000000deadbeef args=[0x00000000000000000000000000000000095eA7B2, 3735928559 [3.735e9]]
  • test_childrenSlot_success_locatesChildArray(uint8): custom error 0x6fdd1491; counterexample: calldata=0x69fce776000000000000000000000000000000000000000000000000000000000000003c args=[60]
  • test_createCompositePolicy_revertOrder(uint8): custom error 0x6fdd1491; counterexample: calldata=0x5fafe4f70000000000000000000000000000000000000000000000000000000000000002 args=[2]
  • test_createCompositePolicy_revert_builtinChild(address,address,uint8): Error != expected error: custom error 0x6fdd1491 != InvalidChildPolicy(0); counterexample: calldata=0x87108a4b0000000000000000000000006d254e7b07e51ee076a80a11a69ea2dcabb30808000000000000000000000000c97de05cfcb6ab8a82e8cd64238198dbf9e2b27d00000000000000000000000000000000000000000000000000000000000000fe args=[0x6d254e7b07e51Ee076a80A11a69Ea2DcABb30808, 0xC97dE05cFCb6aB8A82E8CD64238198DBf9E2B27D, 254]
  • test_createCompositePolicy_revert_childPoliciesOutsideOfRange(address,address,uint8,uint8): Error != expected error: custom error 0x6fdd1491 != ChildPoliciesOutsideOfRange(2, 4); counterexample: calldata=0xb84664360000000000000000000000004488f6bf99bde0dbb72fdb58610ca2bad15287d700000000000000000000000055774cd9bfa4b58134169c0da1471b03b8e05fae00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008 args=[0x4488F6bF99Bde0dBB72fdb58610ca2bad15287d7, 0x55774cD9bfa4b58134169C0da1471b03b8E05Fae, 0, 8]
  • test_createCompositePolicy_revert_incompatiblePolicyType(address,address,uint8): Error != expected error: custom error 0x6fdd1491 != IncompatiblePolicyType(); counterexample: calldata=0x97326925000000000000000000000000000000000000000000000000000000000000406c000000000000000000000000b20f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006f args=[0x000000000000000000000000000000000000406C, 0xB20f000000000000000000000000000000000000, 111]
  • test_createCompositePolicy_revert_invalidChildPolicy(address,address,uint8): custom error 0x6fdd1491; counterexample: calldata=0x7416f459000000000000000000000000cb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a78000000000000000000000000000000000000000000000000000000000000004b args=[0xCB00000000000000000000000000000000000000, 0x0000000000000000000000000000000000002A78, 75]
  • test_createCompositePolicy_revert_policyNotFound(address,address,uint8,uint64): Error != expected error: custom error 0x6fdd1491 != PolicyNotFound(); counterexample: calldata=0x1803f6050000000000000000000000001928cc5f8885806f0a0eaa496bc25d2b28b4f6e6000000000000000000000000d3ce18a41cd370c7824dd1fce768d59a559d78d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 args=[0x1928cC5f8885806F0a0eaa496BC25d2b28B4f6e6, 0xD3cE18A41Cd370c7824Dd1fCE768d59a559D78d0, 0, 1]
  • test_createCompositePolicy_revert_zeroAdmin(address,uint8): Error != expected error: custom error 0x6fdd1491 != ZeroAddress(); counterexample: calldata=0xb18b5cb100000000000000000000000091da96de596ba9ea4e9d873930ce1b6ef0539d4c0000000000000000000000000000000000000000000000000000000000000013 args=[0x91da96De596bA9ea4e9d873930Ce1B6ef0539d4c, 19]
  • test_createCompositePolicy_success_advancesNextPolicyId(address,uint8,uint8): custom error 0x6fdd1491; counterexample: calldata=0xf9a1ca77000000000000000000000000729949b3f8d0f7c9d13c72e15c545c4d6ed3389a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b args=[0x729949B3F8d0F7C9D13C72E15c545c4d6eD3389A, 0, 107]
  • test_createCompositePolicy_success_atMaxChildren(address,address,uint8): custom error 0x6fdd1491; counterexample: calldata=0x296e3341000000000000000000000000d0ec2a69ed1da1d071d3f7eb8b1e02bd4a349fab000000000000000000000000c39c68563cba2829beb25e6921872b91551d77260000000000000000000000000000000000000000000000000000000000000008 args=[0xd0EC2A69Ed1DA1d071d3f7eb8b1E02bD4A349Fab, 0xc39c68563cBA2829Beb25e6921872B91551d7726, 8]
  • test_createCompositePolicy_success_emitsCompositePolicyUpdated(address,address,uint8): log != expected log; counterexample: calldata=0x1694cfe00000000000000000000000001c9607db877b6b592dc78d04dcbe7500bb0f19620000000000000000000000002c54d242eeb7dcc90569fa787601a2b457e88bae000000000000000000000000000000000000000000000000000000000000008b args=[0x1C9607DB877B6B592dC78D04dcbE7500bb0F1962, 0x2C54d242EEb7DCc90569fA787601A2B457e88BaE, 139]
  • test_createCompositePolicy_success_emitsInitialPolicyAdminUpdated(address,address,uint8): log != expected log; counterexample: calldata=0xd6a6c2b200000000000000000000000079301e6f3a04a1556009fba135bb5cdcadf734c60000000000000000000000009e12924582ce851401c1aec19a40d510259458870000000000000000000000000000000000000000000000000000000000000025 args=[0x79301E6f3A04a1556009fBA135bb5CDCaDf734C6, 0x9E12924582CE851401c1aec19A40D51025945887, 37]
  • test_createCompositePolicy_success_emitsPolicyCreated(address,address,uint8): log != expected log; counterexample: calldata=0x89031165000000000000000000000000000000000000000000000000000000006fdd149200000000000000000000000084530000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000007 args=[0x000000000000000000000000000000006FDD1492, 0x8453000000000000000000000000000000000001, 7]
  • test_createCompositePolicy_success_intersect(address,address): custom error 0x6fdd1491; counterexample: calldata=0xccbe0bd4000000000000000000000000899799a325e619c5856de52bb3f20d175a6b831900000000000000000000000059e05c9dd4106e478b1d523087ed0de45f95526f args=[0x899799a325E619c5856DE52Bb3f20D175a6b8319, 0x59e05c9Dd4106e478B1D523087ED0dE45F95526F]
  • test_createCompositePolicy_success_union(address,address): custom error 0x6fdd1491; counterexample: calldata=0xd748b1fe00000000000000000000000089fc4223c828b020a8c295289b96ffd62c0266ff000000000000000000000000b244db7167cd0c756f23fe4f256927ac33141727 args=[0x89Fc4223c828B020A8c295289b96FFd62c0266FF, 0xb244Db7167CD0c756f23Fe4f256927ac33141727]
  • test_isAuthorized_success_composite_mixedChildTypes(address): custom error 0x6fdd1491; counterexample: calldata=0xca56e88d000000000000000000000000f0c49d21bec7306aee417ef146b4894a12dcef22 args=[0xf0c49D21bEC7306aee417ef146b4894a12dCeF22]
  • test_isAuthorized_success_composite_reflectsChildMembershipChange(address): custom error 0x6fdd1491; counterexample: calldata=0x9154ecaf000000000000000000000000466e9e940ae13f8703855835e355ea7f9e145652 args=[0x466E9e940Ae13F8703855835E355EA7f9e145652]
  • test_isAuthorized_success_intersect_allChildrenAuthorize(address): custom error 0x6fdd1491; counterexample: calldata=0x12f37cef000000000000000000000000b20f000000000000000000000000000000000000 args=[0xB20f000000000000000000000000000000000000]
  • test_isAuthorized_success_intersect_oneChildDenies(address): custom error 0x6fdd1491; counterexample: calldata=0x70fa07e600000000000000000000000000000000000000000000000000000000000013e6 args=[0x00000000000000000000000000000000000013e6]
  • test_isAuthorized_success_union_anyChildAuthorizes(address): custom error 0x6fdd1491; counterexample: calldata=0x1ab54113000000000000000000000000f7b0d88419012d6d9e927269a8e2a660554db224 args=[0xf7B0d88419012D6d9E927269a8e2A660554db224]
  • test_isAuthorized_success_union_noChildAuthorizes(address): custom error 0x6fdd1491; counterexample: calldata=0x18ea2b87000000000000000000000000863f64a728fa21c5ec0d2e388974d693d1ce0368 args=[0x863f64A728fA21C5ec0D2E388974d693d1cE0368]
  • test_policyId_success_reflectsUpdatePolicy(uint8,uint64): UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25); counterexample: calldata=0xa21d943e0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000fffffffffffffffd args=[3, 18446744073709551613 [1.844e19]]
  • test_transferFromSeizableWithMemo_revertOrder_blocked_beats_balance(address,address): Error != expected error: custom error 0x5dd87dfc != AccountNotBlocked(0x70C8006469D48Fca90c10B418b2250703a95EaE5); counterexample: calldata=0xa308af8800000000000000000000000070c8006469d48fca90c10b418b2250703a95eae50000000000000000000000003b5624eb717a71a42ecec1aa153d413d4d2ef335 args=[0x70C8006469D48Fca90c10B418b2250703a95EaE5, 0x3b5624Eb717A71A42eceC1aa153d413D4d2ef335]
  • test_transferFromSeizableWithMemo_revertOrder_pause_beats_blocked(address,address): custom error 0xa290249c: type check failed for "(uint8[])" with data: 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003; counterexample: calldata=0x99aeda8c00000000000000000000000044726cfa6eee8275842a5c961f0015841bc553c4000000000000000000000000bc15f2d6940551da0dc7299656901513de5fc9cc args=[0x44726cFA6eEe8275842A5c961F0015841bC553c4, 0xBc15f2d6940551dA0DC7299656901513De5fC9cc]
  • test_transferFromSeizableWithMemo_revertOrder_pause_beats_role(address,address,address): custom error 0xa290249c: type check failed for "(uint8[])" with data: 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003; counterexample: calldata=0xcc85f153000000000000000000000000cfe0584e286206adb6e7b0e3bc3ce8ebbb307d85000000000000000000000000581eb61b92df1f65de4c4b87ae6750e9b3c8194d000000000000000000000000eb4e9a499c9dd0c21a19cb6a12cab6c12baac03d args=[0xCFe0584e286206AdB6e7b0e3bC3CE8eBbb307D85, 0x581Eb61b92dF1f65DE4C4B87ae6750e9B3c8194d, 0xeb4e9a499C9dd0C21A19CB6a12CAB6c12bAaC03D]
  • test_transferFromSeizableWithMemo_revertOrder_role_beats_zeroActors(address,address): Error != expected error: custom error 0x5dd87dfc != AccessControlUnauthorizedAccount(0xaF088BFcF6d82BE67DE540cf335cD2406D3D4A2F, 0x466d0fa97b99b3315998c229880e8a3a6aa5f5586b46f762a35e210facb4ea63); counterexample: calldata=0x9797d0e2000000000000000000000000af088bfcf6d82be67de540cf335cd2406d3d4a2f000000000000000000000000a3922312e40b58035567ed2a1be1929d9083d1bd args=[0xaF088BFcF6d82BE67DE540cf335cD2406D3D4A2F, 0xA3922312E40b58035567ed2A1Be1929d9083d1bD]
  • test_transferFromSeizableWithMemo_revertOrder_zeroActors_beats_blocked(address): Error != expected error: custom error 0x5dd87dfc != InvalidReceiver(0x0000000000000000000000000000000000000000); counterexample: calldata=0x289bd19b000000000000000000000000000000000000000000000000000000000000187e args=[0x000000000000000000000000000000000000187e]
  • test_transferFromSeizableWithMemo_revert_accountNotBlocked(address,address,uint256): Error != expected error: custom error 0x5dd87dfc != AccountNotBlocked(0x78E1D5E5A95589d8F1beb0ad653E04229fa88660); counterexample: calldata=0x608e309d00000000000000000000000078e1d5e5a95589d8f1beb0ad653e04229fa88660000000000000000000000000a9760018ffb6753a27452f0d8806be274edbadc400000000000000000000000000000000000530a932aa68e6b5d1a75b8524b944 args=[0x78E1D5E5A95589d8F1beb0ad653E04229fa88660, 0xa9760018ffB6753A27452f0d8806BE274eDBaDc4, 26948445193377591364587853388757316 [2.694e34]]
  • test_transferFromSeizableWithMemo_revert_insufficientBalance(address,address,uint256): UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25); counterexample: calldata=0x2c2ee054000000000000000000000000c56684ffa4421afd6db2999e54766801ec66506b000000000000000000000000603052dce8048a81d8520fee863c5f85bc9810190000001cbaab4e12439bd753a57c45a0a664ffc257bee0c240ae6eb00967b90d args=[0xc56684FFA4421aFd6DB2999E54766801ec66506B, 0x603052Dce8048A81d8520feE863C5f85bC981019, 774537063706208077806652927121195788459286429510147607899752574204173 [7.745e68]]
  • test_transferFromSeizableWithMemo_revert_invalidReceiver(address,uint256): UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25); counterexample: calldata=0x4247020600000000000000000000000000000000000000000000000000000000e5450ef40000000000000000000000000000000000000000000000000000000000000050 args=[0x00000000000000000000000000000000e5450eF4, 80]
  • test_transferFromSeizableWithMemo_revert_unauthorized(address,address,address,uint256): Error != expected error: custom error 0x5dd87dfc != AccessControlUnauthorizedAccount(0xE7B75a29aa64d8070F7772de089B689c02197130, 0x466d0fa97b99b3315998c229880e8a3a6aa5f5586b46f762a35e210facb4ea63); counterexample: calldata=0xf1180915000000000000000000000000e7b75a29aa64d8070f7772de089b689c02197130000000000000000000000000dc045c8060e66e876e48d3c9b3dc482e50cdc9f200000000000000000000000097770e1a2b9d32130895471065461fe60aec5d9200000000000000000000000000000000000000000000000000004e4ad91c5366 args=[0xE7B75a29aa64d8070F7772de089B689c02197130, 0xDC045c8060e66E876e48D3C9B3dc482e50cDc9f2, 0x97770E1A2B9d32130895471065461fe60aec5D92, 86083377058662 [8.608e13]]
  • test_transferFromSeizableWithMemo_revert_whenSeizePaused(address,address,uint256): UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25); counterexample: calldata=0x420274fb00000000000000000000000087341650fbc85f55a87916d6da098ff485c2aaf60000000000000000000000005f7668a3869135af1a86e6dcdc21dc19d44ec07e000000000000000000000000f458791618f54877b3a30355748a1825a301c2c3 args=[0x87341650Fbc85F55a87916D6dA098ff485c2aaf6, 0x5F7668A3869135af1A86E6DCdC21DC19D44Ec07E, 1394966761764472408446249390323277715490889384643 [1.394e48]]
  • test_transferFromSeizableWithMemo_success_emitsEvents(address,address,uint256,bytes32): UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25); counterexample: calldata=0x52d397380000000000000000000000000000000000000000000000000000000000003de500000000000000000000000000000000000000000000000000000000000011d400000000000000000000000000000000000000000000000000000000b2851ef4000000000000000000000000000000000000000000000000000000000000009f args=[0x0000000000000000000000000000000000003De5, 0x00000000000000000000000000000000000011D4, 2995068660 [2.995e9], 0x000000000000000000000000000000000000000000000000000000000000009f]
  • test_transferFromSeizableWithMemo_success_ignoresReceiverPolicy(address,address,uint256): UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25); counterexample: calldata=0xe5450ef30000000000000000000000004d524cb2e7bfef726c115ac2963159078bc4565400000000000000000000000099c45f837a0407bc9a58fdaad3c0668c6cfc5d1b00000000000000000000000000000000000000000000000000000902def5df11 args=[0x4d524cb2E7bfEF726c115aC2963159078BC45654, 0x99c45f837a0407bC9A58fDaaD3C0668c6CFc5d1B, 9907935239953 [9.907e12]]
  • test_transferFromSeizableWithMemo_success_movesBalance(address,address,uint256): UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25); counterexample: calldata=0xdf16afc900000000000000000000000000000000000000000000000000000000000009f90000000000000000000000000000000000000000000000000000000000003c12000000000000000000000000000000000000000000000000000000000000140c args=[0x00000000000000000000000000000000000009f9, 0x0000000000000000000000000000000000003c12, 5132]
  • test_transferFromSeizableWithMemo_success_noAllowanceRequired(address,address,uint256): UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25); counterexample: calldata=0x16a6a880000000000000000000000000e56f971da67ca4a66ad8c777194ad74fe910ca2700000000000000000000000030df9885edd1079b6bb7d9f48ed9dbe3c533b4580000000000000000000000000000000000000000b22a2ec9d6099728ed1d4ba3 args=[0xe56F971da67cA4A66aD8C777194AD74FE910CA27, 0x30Df9885edd1079B6BB7d9F48ED9DBE3C533B458, 55139327584700032078587972515 [5.513e28]]
  • test_updateComposite_revertOrder(uint8): custom error 0x6fdd1491; counterexample: calldata=0x46b240f9000000000000000000000000000000000000000000000000000000000000002a args=[42]
  • test_updateComposite_revert_builtinChild(uint8): custom error 0x6fdd1491; counterexample: calldata=0x44e9729e0000000000000000000000000000000000000000000000000000000000000001 args=[1]
  • test_updateComposite_revert_childPoliciesOutsideOfRange(uint8,uint8): custom error 0x6fdd1491; counterexample: calldata=0x25c4476000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000004 args=[7, 4]
  • test_updateComposite_revert_incompatiblePolicyType(uint8): Error != expected error: custom error 0xbfe142c0 != IncompatiblePolicyType(); counterexample: calldata=0xbe9d9a400000000000000000000000000000000000000000000000000000000000000013 args=[19]
  • test_updateComposite_revert_invalidChildPolicy(uint8): custom error 0x6fdd1491; counterexample: calldata=0xfc6d02c60000000000000000000000000000000000000000000000000000000000000001 args=[1]
  • test_updateComposite_revert_policyNotFound(address,uint64): Error != expected error: custom error 0xbfe142c0 != PolicyNotFound(); counterexample: calldata=0xbc492f7a000000000000000000000000cb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001004 args=[0xCB00000000000000000000000000000000000000, 4100]
  • test_updateComposite_revert_policyNotFoundChild(uint8,uint64): custom error 0x6fdd1491; counterexample: calldata=0x5d96e02c000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000002 args=[15, 2]
  • test_updateComposite_revert_renouncedComposite(uint8): custom error 0x6fdd1491; counterexample: calldata=0x9c795a420000000000000000000000000000000000000000000000000000000000000080 args=[128]
  • test_updateComposite_revert_unauthorized(address,uint8): custom error 0x6fdd1491; counterexample: calldata=0x13f7deab0000000000000000000000002775edc5549fc5a43ed2138f93f41005f4c3f57f0000000000000000000000000000000000000000000000000000000000000000 args=[0x2775EdC5549fC5a43eD2138F93F41005F4C3F57F, 0]
  • test_updateComposite_success_atMaxChildren(uint8): custom error 0x6fdd1491; counterexample: calldata=0x9b8e06050000000000000000000000000000000000000000000000000000000000000006 args=[6]
  • test_updateComposite_success_emitsCompositePolicyUpdated(uint8): custom error 0x6fdd1491; counterexample: calldata=0xc975d54c00000000000000000000000000000000000000000000000000000000000000bb args=[187]
  • test_updateComposite_success_replacesChildSet(address): custom error 0x6fdd1491; counterexample: calldata=0xbb14993300000000000000000000000004146d113f81aeec360897565d6b2414d284e88d args=[0x04146d113F81aeEC360897565D6B2414D284e88d]
  • test_updatePolicy_revert_policyNotFound(uint8,uint64): Error != expected error: UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25) != PolicyNotFound(72057691460982398 [7.205e16]); counterexample: calldata=0x74fd7f390000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000016aeddd67e45 args=[48, 24940301942341 [2.494e13]]
  • test_updatePolicy_success_builtinAllow(uint8): UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25); counterexample: calldata=0xde997e8b0000000000000000000000000000000000000000000000000000000000000012 args=[18]
  • test_updatePolicy_success_emitsPolicyUpdated(uint8,uint64): log != expected log; counterexample: calldata=0x59afa2fa000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000dd62ed3f args=[3, 3714247999 [3.714e9]]
  • test_updatePolicy_success_writesSlot(uint8,uint64): UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25); counterexample: calldata=0x9746e8e1000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000072497e4ab1 args=[3, 490859285169 [4.908e11]]
    [FAIL: UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25)] test_b20Layout_success_populatedSnapshotMatchesAllSlots() (gas: 569463)
    [FAIL: UnsupportedPolicyType(0x3efcaab33335f8757bc9054f42ac0ae92950f9727a52ff4db8681fc9521c9e25)] test_seizePolicyIdsSlot_success_decodesSeizableLane() (gas: 12560)
    [FAIL: isPaused must still accept enum index 3 (SEIZE)] test_isPaused_revert_noFifthFeature() (gas: 9532)

Comment thread src/lib/B20Constants.sol Outdated

@ilikesymmetry ilikesymmetry left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeking alignment on policy name, storage location, and revisiting behavior of only supporting specific policy scopes.

Comment thread test/lib/mocks/MockB20Storage.sol Outdated
Comment on lines +65 to +66
/// `burnBlocked`, and `burnBlockedWithMemo`). A distinct per-operation group — seize is
/// cold-path and applies to burns too, so it does not belong in the transfer slot.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the "A distinct per-operation group..." note

@stephancill

stephancill commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

@ilikesymmetry implemented the following

Naming

policy: SEIZABLE_ACCOUNT_POLICY
fn: transferFromSeizableWithMemo(from, to, amount, memo)
role: TRANSFER_FROM_SEIZABLE_ROLE
event: TransferredFromSeizable

Policy slot

New slot

Policy scope support behavior

Concern to be investigated separately

@stephancill stephancill changed the title feat(BOP-440): add transferFromBlockedWithMemo and burnBlockedWithMemo seize surface feat(BOP-440): add transferFromSeizableWithMemo and burnBlockedWithMemo seize surface Jul 28, 2026
@stephancill
stephancill requested a review from robriks July 28, 2026 18:24
Comment thread src/interfaces/IB20.sol Outdated
Comment on lines +260 to +264
/// @notice Policy slot consulted against `from` by the seize operations (`transferFromSeizableWithMemo`,
/// `burnBlockedWithMemo`). A `from` is seizable only when it is NOT authorized by this policy
/// (mirroring the `burnBlocked` "blocked" semantics). An unset slot reads as `0` (always-allow),
/// which makes no account seizable until an issuer configures the slot.
/// @return Policy scope constant.

@stevieraykatz stevieraykatz Jul 28, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably split into a notice and dev block to move the verbosity into the appropriate field.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also missing burnBlocked in @noice enumeration

Comment on lines +155 to +160
// ---------- Seize policies (PACKED, per-operation) ----------
// Appended after `initialized` so no pre-existing field offset shifts
// on already-deployed tokens. Distinct per-operation group (seize is
// cold-path and spans transfer + burn), mirroring how mint policies are
// grouped separately from transfer policies.
SeizePolicyIds seizePolicyIds;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// ---------- Seize policies (PACKED, per-operation) ----------
// Appended after `initialized` so no pre-existing field offset shifts
// on already-deployed tokens. Distinct per-operation group (seize is
// cold-path and spans transfer + burn), mirroring how mint policies are
// grouped separately from transfer policies.
SeizePolicyIds seizePolicyIds;
// ---------- Seize policies (PACKED, per-operation) ----------
// Appended after `initialized` so no pre-existing field offset shifts
// on already-deployed tokens.
SeizePolicyIds seizePolicyIds;

stephancill and others added 12 commits July 28, 2026 15:44
…o seize surface

Adds the additive seize operation class to the base-std reference and ABI
(source of truth for fork tests), per the BOP-439 PPS:

- transferFromBlockedWithMemo(from, to, amount, memo): admin seize that
  reassigns a blocked account's balance in one call. Skips allowance and
  the sender/receiver/executor transfer policies; the only membership check
  is that `from` is blocked under the new SEIZABLE_POLICY. `to` is not
  policy-checked (admin op). Emits Transfer + TransferredFromBlocked + Memo.
- burnBlockedWithMemo(from, amount, memo): memo variant of burn-based
  seizure, gated on SEIZABLE_POLICY and the new SEIZE pause vector.
- New SEIZABLE_POLICY scope (packed into the transfer slot's reserved
  lane 3, no storage-offset disruption), TRANSFER_FROM_BLOCKED_ROLE, and
  SEIZE PausableFeature.

Reference impl uses an explicit guard sequence and a shared _moveBalance
primitive; it does NOT reuse the factory-bootstrap privileged path.

Out of scope: repointing the legacy burnBlocked to SEIZABLE_POLICY (BOP-471).

Tests: 25 new (happy + revert-order for both functions); layout-pin, slot
codec, and removals regression tests updated for the new lane/feature.

Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
…TURES_PAUSED

Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
…emo natspec

Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
Folds the legacy burnBlocked into the seize operation class: its blocked
check now uses SEIZABLE_POLICY (was TRANSFER_SENDER_POLICY) and it gates on
the SEIZE pause vector (was BURN), matching burnBlockedWithMemo and
transferFromBlockedWithMemo. Breaking change, acceptable because burnBlocked
is not yet in use. Simplifies the AccountNotBlocked error to a single
seizable-policy statement.

Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
…k on seize

- Reorder seize emissions so Memo immediately follows Transfer (per the IB20
  Memo invariant): Transfer -> Memo -> BurnedBlocked/TransferredFromBlocked.
- transferFromBlockedWithMemo: drop the from==address(0) check (keep to!=0),
  matching the burn-blocked family; a zero/empty from fails the seizable or
  balance check anyway. Removes the now-obsolete InvalidSender test.

Addresses review from @ilikesymmetry.

Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
… SEIZABLE_ACCOUNT_POLICY

Renames the transfer-based seize entrypoint and its policy/role/event to the
'seizable' vocabulary (the seize precondition is membership in the seizable
scope, not an enforced transfer block):
- SEIZABLE_POLICY -> SEIZABLE_ACCOUNT_POLICY
- transferFromBlockedWithMemo -> transferFromSeizableWithMemo
- TRANSFER_FROM_BLOCKED_ROLE -> TRANSFER_FROM_SEIZABLE_ROLE
- TransferredFromBlocked -> TransferredFromSeizable

Burn-blocked family (burnBlocked/burnBlockedWithMemo/BURN_BLOCKED_ROLE/
BurnedBlocked) keeps its established naming for selector continuity.

Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
…e slot

Per review, seize is a distinct cold-path operation that spans transfer + burn,
so its policy id no longer shares the transfer slot's reserved lane. Adds a
dedicated SeizePolicyIds slot appended after `initialized` (offset 15) so no
existing field offset shifts on already-deployed tokens; the transfer slot's
lane 3 is reserved again. Updates codecs, _read/_writePolicyId, and the
layout-pin + slot-helper tests.

Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
…t comment

Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
@stephancill
stephancill force-pushed the stephancilliers/bop-440-base-std-seize branch from a6288ac to 53b1f56 Compare July 28, 2026 19:45
The `initialized` flag is a mock-only field: the native precompile checks
factory-init via deployed marker bytecode, not a storage slot, so base/base's
B20CoreStorage has no such field. Appending seizePolicyIds after it forced the
Rust mirror to reserve an unused hole at offset 14 to keep seize at 15.

Keep `initialized` trailing and place seizePolicyIds before it, so seize sits at
offset 14 on both sides and the Rust impl omits the trailing mock-only slot for
free (no filler). Non-backward-compatible mock layout change, which is fine.

Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
@stephancill

stephancill commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

@ilikesymmetry going to merge - happy to follow up with changes

@stephancill
stephancill merged commit 9423b2e into main Jul 28, 2026
10 checks passed
@stephancill
stephancill deleted the stephancilliers/bop-440-base-std-seize branch July 28, 2026 20:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants