Skip to content

feat(policy): add compositePolicyChildIds view for composite child sets - #183

Merged
rayyan224 merged 5 commits into
mainfrom
feat/policy-composite-child-ids-view
Jul 30, 2026
Merged

feat(policy): add compositePolicyChildIds view for composite child sets#183
rayyan224 merged 5 commits into
mainfrom
feat/policy-composite-child-ids-view

Conversation

@rayyan224

Copy link
Copy Markdown
Collaborator

Summary

Composite policies (UNION/INTERSECT) shipped in #174 with no on-chain read path for their child set. The only ways to recover it today are decoding the CompositePolicyUpdated event or a raw eth_getStorageAt against slot 4 — the smoke harness does the former (chain.py: composite_children_from).

This adds the missing getter to IPolicyRegistry, implements it in MockPolicyRegistry, and covers it with unit tests.

function compositePolicyChildIds(uint64 policyId) external view returns (uint64[] memory);

Semantics

Total — it never reverts, matching the contract every existing getter documents explicitly (policyExists, policyAdmin, pendingPolicyAdmin all say "Never reverts" and degrade to a zero value). An empty array is the array-typed analogue of address(0):

Input Returns
Existing composite Stored child set, in the order last written
Simple policy (ALLOWLIST / BLOCKLIST) []
Built-in sentinel (ALWAYS_ALLOW / ALWAYS_BLOCK) []
Well-formed but uncreated ID []
Malformed ID (type byte > INTERSECT) []

Returning [] rather than reverting IncompatiblePolicyType on a non-composite is safe because an empty return is unambiguous in practice: both write paths reject a set outside [2, 4] with ChildPoliciesOutsideOfRange, and there is no clear-the-list path, so a composite that exists always holds at least two children. There's a test pinning that invariant so it can't silently regress.

Ordering is preserved verbatim; the registry neither sorts nor de-duplicates, so a UNION over [a, a] reads back as [a, a].

On the name

childPolicyIds was the first choice, for exact symmetry with the write path and the event field. It doesn't work: a function of that name collides with the childPolicyIds parameter on createCompositePolicy and updateComposite, and solc emits warning 8760 ("This declaration has the same name as another declaration") in MockPolicyRegistry. Verified against solc 0.8.30 — compositePolicyChildIds compiles clean. The only alternative fix was renaming the mock's parameters away from the interface's, which destroys the symmetry that motivated the name in the first place.

It also reads better at the call site given that a non-composite returns empty.

Reviewer note

In the mock, the _isWellFormed guard must precede _isComposite. That helper routes through _typeOf, whose PolicyType(uint8(...)) conversion panics with 0x21 on a type byte above INTERSECT. The two guards look collapsible but aren't. The Rust precompile compares raw type bytes, so it's safe without the guard — this is a Solidity-side hazard only. Caught by test_compositePolicyChildIds_success_emptyForMalformedId, which failed before the guard was added.

Testing

forge test — 136 passed, 0 failed, 4 skipped. forge fmt --check clean. 10 new tests in test/unit/PolicyRegistry/compositePolicyChildIds.t.sol covering creation-order readback, updateComposite full replacement, event/view agreement, duplicate preservation, survival across renounceAdmin, the four empty cases (fuzzed for uncreated and malformed IDs), and the never-empty invariant.

Cross-repo

base-std is the spec forerunner here, so this lands first and base/base catches up. The advisory fork-test job (continue-on-error: true) is expected to flag compositePolicyChildIds as missing until the base/base precompile PR merges — the Rust implementation is written and green locally, and I'll link it here once it's open.

Generated with Claude Code

Composite policies (UNION/INTERSECT) had no on-chain read path for their
child set. The only ways to recover it were decoding the
CompositePolicyUpdated event or a raw eth_getStorageAt against slot 4.

Adds a total view returning the stored child set:

    function compositePolicyChildIds(uint64 policyId)
        external view returns (uint64[] memory);

Semantics follow the existing read surface, which documents "Never
reverts" on every getter: simple policies, built-in sentinels, unknown
IDs, and malformed IDs all return an empty array rather than reverting
with IncompatiblePolicyType. An empty return is unambiguous in practice
because both write paths reject a set outside [2, 4], so a composite
that exists always holds at least two children and there is no
clear-the-list path.

Naming: childPolicyIds was the first choice for symmetry with the write
path, but a function of that name collides with the childPolicyIds
parameter on createCompositePolicy and updateComposite, emitting solc
warning 8760 in MockPolicyRegistry. compositePolicyChildIds compiles
clean and keeps "composite" at the call site, which reads better given
that a non-composite returns empty.

The mock's well-formedness guard must precede _isComposite: that helper
routes through _typeOf, whose PolicyType(uint8(...)) conversion panics
with 0x21 on a type byte above INTERSECT. The Rust precompile compares
raw type bytes and is safe without the guard.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Interface Coverage

✅ All interface functions have test coverage.

@github-actions

github-actions Bot commented Jul 29, 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.74% 97.34% 98.68% 96.46%

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

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

⚠️ Fork tests: 78 failed, 671 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(0x642eb269253c8fa8AF9bb7522DC8637a21Bd84bD); counterexample: calldata=0x2cf250e8000000000000000000000000642eb269253c8fa8af9bb7522dc8637a21bd84bd args=[0x642eb269253c8fa8AF9bb7522DC8637a21Bd84bD]
  • test_burnBlockedWithMemo_revertOrder_pause_beats_blocked(address): custom error 0xa290249c: type check failed for "(uint8[])" with data: 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003; counterexample: calldata=0x26d4898600000000000000000000000019a8c5ea43e6f55db151efe111230be1e00b3614 args=[0x19A8C5ea43e6f55dB151EFE111230BE1e00B3614]
  • test_burnBlockedWithMemo_revertOrder_pause_beats_role(address,address): custom error 0xa290249c: type check failed for "(uint8[])" with data: 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003; counterexample: calldata=0x53c0e66c000000000000000000000000b66a14a24c72fe4beb1759581427420925fd1c5600000000000000000000000075e2f93de77faa984d0e12229ccff65b98a71d5e args=[0xB66A14a24C72fE4bEb1759581427420925FD1C56, 0x75e2F93dE77Faa984d0e12229cCfF65b98A71D5E]
  • test_burnBlockedWithMemo_revertOrder_role_beats_blocked(address,address): Error != expected error: custom error 0x6f872692 != AccessControlUnauthorizedAccount(0x00000000000000000000000000000000000009Bb, 0x7408fdc0d31c7bcb349eab611f5d1168acd4303574993f8cdc98b1cd18c41cae); counterexample: calldata=0xbd3f8b6b00000000000000000000000000000000000000000000000000000000000009bb0000000000000000000000000000000000000000000000000000000000000998 args=[0x00000000000000000000000000000000000009Bb, 0x0000000000000000000000000000000000000998]
  • test_burnBlockedWithMemo_revert_accountNotBlocked(address,uint256): Error != expected error: custom error 0x6f872692 != AccountNotBlocked(0xF3A79F3CE49B2826aE215861126020A20762f360); counterexample: calldata=0xf0e9ee6a000000000000000000000000f3a79f3ce49b2826ae215861126020a20762f360000000000000000000000000000da66eeab90335dd03d795707aed01ae254aa8 args=[0xF3A79F3CE49B2826aE215861126020A20762f360, 304408070105922652235798471298663945108540072 [3.044e44]]
  • test_burnBlockedWithMemo_revert_insufficientBalance(address,uint256): Error != expected error: custom error 0x6f872692 != InsufficientBalance(0x000000000000000000000000000000000000347e, 0, 2835717307 [2.835e9]); counterexample: calldata=0xeb275e77000000000000000000000000000000000000000000000000000000000000347e00000000000000000000000000000000000000000000000000000000a9059cbb args=[0x000000000000000000000000000000000000347e, 2835717307 [2.835e9]]
  • test_burnBlockedWithMemo_revert_unauthorized(address,address,uint256): Error != expected error: custom error 0x6f872692 != AccessControlUnauthorizedAccount(0x8d5cd23cbc377852824ccF7586D0f9dAb67Cd7FC, 0x7408fdc0d31c7bcb349eab611f5d1168acd4303574993f8cdc98b1cd18c41cae); counterexample: calldata=0xb8e4c85b0000000000000000000000008d5cd23cbc377852824ccf7586d0f9dab67cd7fc000000000000000000000000c4d967af1fd5bff4710c1787858bef0f082bf88dfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd args=[0x8d5cd23cbc377852824ccF7586D0f9dAb67Cd7FC, 0xc4D967Af1Fd5bFF4710c1787858bEf0f082bF88D, 115792089237316195423570985008687907853269984665640564039457584007913129639933 [1.157e77]]
  • test_burnBlockedWithMemo_revert_whenSeizePaused(address,uint256): custom error 0xa290249c: type check failed for "(uint8[])" with data: 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003; counterexample: calldata=0xfce2f39a0000000000000000000000005f1fcbadd9fd6aa3dfd3f4d8c61fa2c522de4f4c0000036c0e52c97d52fe9792860d03c8fa290eee84c72dd948a0c1e8461e3129 args=[0x5f1FcbAdd9fd6AA3dFd3F4d8C61fa2C522De4F4C, 23618421709131964977672599843783055969276437769122637615255323594862889 [2.361e70]]
  • test_burnBlockedWithMemo_success_debitsAndDecreasesSupply(address,uint256): custom error 0x6f872692; counterexample: calldata=0x189a0bed0000000000000000000000000099e79f596afdfa42c53147fb6144f48f489d9f0000000000000000000000000000000000000000000000000000000000b60941 args=[0x0099E79f596AFdFa42C53147Fb6144f48F489D9f, 11929921 [1.192e7]]
  • test_burnBlockedWithMemo_success_emitsTransferBurnedBlockedAndMemo(address,uint256,bytes32): log != expected log; counterexample: calldata=0x25c74b3700000000000000000000000037f1f3de9eaa7ade5f0771db56ac2cc1779b19df0012c36507be843b1e414f548f3b24800a2a7d09bb5f6c4624f2d340ba6b541f696ab024f0062be4d537749c9ba2ff4233e4f1b723192c234fdc8878c53602ef args=[0x37F1F3dE9eaA7adE5F0771Db56aC2cC1779B19DF, 33151811473808455151023865936714022777774890419090867299981920931217626143 [3.315e73], 0x696ab024f0062be4d537749c9ba2ff4233e4f1b723192c234fdc8878c53602ef]
  • test_burnBlocked_revertOrder_pause_beats_balance(address,uint256): custom error 0xa290249c: type check failed for "(uint8[])" with data: 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003; counterexample: calldata=0xca82fe43000000000000000000000000c7cf686602f3253e199370fc38df7dfbe754b9d8000000000000000000000000000000000000000000000008831913791ee12556 args=[0xc7CF686602f3253E199370fc38DF7dfbe754b9D8, 157020555693992781142 [1.57e20]]
  • test_burnBlocked_revertOrder_pause_beats_blocked(address,uint256): custom error 0xa290249c: type check failed for "(uint8[])" with data: 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003; counterexample: calldata=0x4c29bb130000000000000000000000007377f80d93bfe0c7aeca8f5693f9018a2ae81dc0000000000000000000000000000000000000000000000000000000ba95450e28 args=[0x7377f80D93bFE0C7aeCa8f5693f9018a2Ae81DC0, 801368247848 [8.013e11]]
  • test_burnBlocked_revertOrder_pause_beats_role(address,address,uint256): custom error 0xa290249c: type check failed for "(uint8[])" with data: 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003; counterexample: calldata=0x022471a5000000000000000000000000bcbe4dc7a82b082add6f8004e9713728f603156c00000000000000000000000021724a8b6b4f9526b27f775e551f87ab9c387408000000000000000000000000003027b2b5e14e9c708c795b43a9ec86d42b711c args=[0xBcBE4Dc7a82B082adD6F8004E9713728f603156C, 0x21724a8B6b4f9526B27f775E551f87aB9c387408, 1073893960702248940074119361996536895834386716 [1.073e45]]
  • test_burnBlocked_revert_insufficientBalance(address,uint256): Error != expected error: AccountNotBlocked(0x82d290A6CAA8bada8DFA2C785515F6d429018D28) != InsufficientBalance(0x82d290A6CAA8bada8DFA2C785515F6d429018D28, 0, 1496685869446313807408509630718643727927671875008115216578741417824 [1.496e66]); counterexample: calldata=0x15f3e92c00000000000000000000000082d290a6caa8bada8dfa2c785515f6d429018d28000000000e363de7364d42b46fc0c0f3da26cabeba51866768aa1d6a2f1e3760 args=[0x82d290A6CAA8bada8DFA2C785515F6d429018D28, 1496685869446313807408509630718643727927671875008115216578741417824 [1.496e66]]
  • test_burnBlocked_revert_whenSeizePaused(address,uint256): custom error 0xa290249c: type check failed for "(uint8[])" with data: 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003; counterexample: calldata=0xf55efb320000000000000000000000000000000000000000000000000000000000000963000000000000000000000000000000000000000000000000000000000000001f args=[0x0000000000000000000000000000000000000963, 31]
  • test_burnBlocked_success_debitsTarget(address,uint256): AccountNotBlocked(0x407cb170C7281c2614570fE78D2217A77D2Ef673); counterexample: calldata=0x485c308e000000000000000000000000407cb170c7281c2614570fe78d2217a77d2ef673000000000000000000000000000000000000000000000001f651552726fbcd01 args=[0x407cb170C7281c2614570fE78D2217A77D2Ef673, 36195805306799508737 [3.619e19]]
  • test_burnBlocked_success_decreasesTotalSupply(address,uint256): AccountNotBlocked(0x7df74D6A6F110aA46dc600908dF9468591162D0d); counterexample: calldata=0x8bb754f20000000000000000000000007df74d6a6f110aa46dc600908df9468591162d0d00000001332612f0cb516a18748198700805c86e98bcfbd255e97e68d9433ada args=[0x7df74D6A6F110aA46dc600908dF9468591162D0d, 32346536271916164398747293268380776359147886574771718223785942530778 [3.234e67]]
  • test_burnBlocked_success_emitsTransferAndBurnedBlocked(address,uint256): log != expected log; counterexample: calldata=0x89de84f80000000000000000000000000000000000000000000000000000000000001cab00000000000000000000000000000000000000000000000000000000d547741f args=[0x0000000000000000000000000000000000001CAb, 3578229791 [3.578e9]]
  • test_compositePolicyChildIds_success_emptyForMalformedId(uint64): custom error 0x7c40df74; counterexample: calldata=0x1ae49a5d000000000000000000000000000000000000000000000000fffffffffffffffe args=[18446744073709551614 [1.844e19]]
  • test_compositePolicyChildIds_success_emptyForUncreated(uint64): custom error 0x7c40df74; counterexample: calldata=0x26541667000000000000000000000000000000000000000000000000000000003ceec563 args=[1022281059 [1.022e9]]
  • test_transferFromSeizableWithMemo_revertOrder_blocked_beats_balance(address,address): Error != expected error: custom error 0x5dd87dfc != AccountNotBlocked(0xCB00000000000000000000000000000000000000); counterexample: calldata=0xa308af88000000000000000000000000cb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f1a args=[0xCB00000000000000000000000000000000000000, 0x0000000000000000000000000000000000000F1a]
  • test_transferFromSeizableWithMemo_revertOrder_pause_beats_blocked(address,address): custom error 0xa290249c: type check failed for "(uint8[])" with data: 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003; counterexample: calldata=0x99aeda8c0000000000000000000000002e11a3d375218cee134aecee381c34f2c1b6fbff0000000000000000000000005e69480d0b6c42c8858dded0a2d6f5103bd34172 args=[0x2E11a3D375218cEE134AECeE381C34f2C1B6FBfF, 0x5E69480d0b6C42C8858DdEd0A2d6F5103Bd34172]
  • test_transferFromSeizableWithMemo_revertOrder_pause_beats_role(address,address,address): custom error 0xa290249c: type check failed for "(uint8[])" with data: 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003; counterexample: calldata=0xcc85f15300000000000000000000000001045c6d6aa8c8fc9553bb8bc6c47c64344c685b000000000000000000000000b0159e2280bb6e725fd06cabd462bd5cd431c62f000000000000000000000000c4d0d321228cc54bcdc6c27973e03cbcf1391f17 args=[0x01045C6D6Aa8C8Fc9553Bb8Bc6c47c64344c685B, 0xB0159E2280BB6e725FD06cAbd462BD5cD431C62f, 0xC4d0d321228CC54BcDC6c27973E03cBcf1391f17]
  • test_transferFromSeizableWithMemo_revertOrder_role_beats_zeroActors(address,address): Error != expected error: custom error 0x5dd87dfc != AccessControlUnauthorizedAccount(0x3E0588ec3A8bd22791E790B12c45e29e6CA29C26, 0x466d0fa97b99b3315998c229880e8a3a6aa5f5586b46f762a35e210facb4ea63); counterexample: calldata=0x9797d0e20000000000000000000000003e0588ec3a8bd22791e790b12c45e29e6ca29c260000000000000000000000006355ef410ab00c7878e725395ffd96f2769759f6 args=[0x3E0588ec3A8bd22791E790B12c45e29e6CA29C26, 0x6355Ef410AB00C7878E725395fFd96f2769759F6]
  • test_transferFromSeizableWithMemo_revertOrder_zeroActors_beats_blocked(address): Error != expected error: custom error 0x5dd87dfc != InvalidReceiver(0x0000000000000000000000000000000000000000); counterexample: calldata=0x289bd19b000000000000000000000000eceb16a99ed885fd6c42a93afa969d0b528e4ace args=[0xEceb16A99Ed885Fd6c42A93aFa969d0B528E4aCE]
  • test_transferFromSeizableWithMemo_revert_accountNotBlocked(address,address,uint256): Error != expected error: custom error 0x5dd87dfc != AccountNotBlocked(0x0000000000000000000000000000000000000D14); counterexample: calldata=0x608e309d0000000000000000000000000000000000000000000000000000000000000d140000000000000000000000000000000000000000000000000000000000000809c78b71fee795ddd74aff64ea9b2474194c938c3196430e10bb5f01ed48434000 args=[0x0000000000000000000000000000000000000D14, 0x0000000000000000000000000000000000000809, 90256635379626638402994590225948139774634157376374926850808936957150668996608 [9.025e76]]
  • test_transferFromSeizableWithMemo_revert_insufficientBalance(address,address,uint256): Error != expected error: custom error 0x5dd87dfc != InsufficientBalance(0xC7911F3bE243dED9B39211B22D06B4FC134B3522, 0, 389233793813209468833864180809248750276710645523292780497335927633444 [3.892e68]); counterexample: calldata=0x2c2ee054000000000000000000000000c7911f3be243ded9b39211b22d06b4fc134b3522000000000000000000000000b1c1a4c04133eeab43f39d05a9b18b7177eabba30000000e6ffef08e52738541e0eb093b526ef141e4504e5bd4743fd4ec0af624 args=[0xC7911F3bE243dED9B39211B22D06B4FC134B3522, 0xB1c1a4c04133EeAB43F39d05a9B18b7177EABba3, 389233793813209468833864180809248750276710645523292780497335927633444 [3.892e68]]
  • test_transferFromSeizableWithMemo_revert_invalidReceiver(address,uint256): Error != expected error: custom error 0x5dd87dfc != InvalidReceiver(0x0000000000000000000000000000000000000000); counterexample: calldata=0x42470206000000000000000000000000643f7daa119623610d9a4a64443d24b511238c390000000000000000000000000000000000000000002d0c5474b785b26ae840a7 args=[0x643f7dAA119623610D9a4a64443D24b511238c39, 54459888217292770688647335 [5.445e25]]
  • test_transferFromSeizableWithMemo_revert_unauthorized(address,address,address,uint256): Error != expected error: custom error 0x5dd87dfc != AccessControlUnauthorizedAccount(0x00000000000000000000000000000000cA950391, 0x466d0fa97b99b3315998c229880e8a3a6aa5f5586b46f762a35e210facb4ea63); counterexample: calldata=0xf118091500000000000000000000000000000000000000000000000000000000ca950391000000000000000000000000b20f000000000000000000000000000000000000000000000000000000000000b20f000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff args=[0x00000000000000000000000000000000cA950391, 0xB20f000000000000000000000000000000000000, 0xB20f000000000000000000000000000000000000, 18446744073709551615 [1.844e19]]
  • test_transferFromSeizableWithMemo_revert_whenSeizePaused(address,address,uint256): custom error 0xa290249c: type check failed for "(uint8[])" with data: 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003; counterexample: calldata=0x420274fb000000000000000000000000f19b7ed8a5f12dc929a2c43413788b6ed78d6ff300000000000000000000000016fb2784b4b70e93f9266f33b759ab0f9bb6248500000000000000000000000000000000000000000000000000000000000dbef6 args=[0xF19B7eD8A5f12DC929A2c43413788B6ED78D6ff3, 0x16fB2784b4B70E93F9266F33B759aB0F9bb62485, 900854 [9.008e5]]
  • test_transferFromSeizableWithMemo_success_emitsEvents(address,address,uint256,bytes32): log != expected log; counterexample: calldata=0x52d397380000000000000000000000004e69a3769547c9e9c0d085aa05868ec3e2eb7bc200000000000000000000000001523d20c717161757f4140545730be39959a66b0000000000000000000000000000708b865b2dde1e86f7197045ea1b385172752c6513fc912f3f9a30d9cb267826ba485a42bd2540d23d09854410192ad3c34b args=[0x4E69a3769547c9E9c0d085aA05868eC3E2EB7bC2, 0x01523D20c717161757f4140545730be39959A66b, 9804053863339913908195309221874035061781109 [9.804e42], 0x2c6513fc912f3f9a30d9cb267826ba485a42bd2540d23d09854410192ad3c34b]
  • test_transferFromSeizableWithMemo_success_ignoresReceiverPolicy(address,address,uint256): custom error 0x5dd87dfc; counterexample: calldata=0xe5450ef3000000000000000000000000a1284182b4dc67a3dc11199a12532777a04494cd0000000000000000000000000417aa6d3c0985cb6f0c3da2753ca6db3218e73f00000000000000000000000000000000000000000000000001f104a0ff927c30 args=[0xA1284182B4dC67A3Dc11199A12532777a04494CD, 0x0417AA6D3c0985cb6f0c3DA2753CA6db3218e73f, 139898152954264624 [1.398e17]]
  • test_transferFromSeizableWithMemo_success_movesBalance(address,address,uint256): custom error 0x5dd87dfc; counterexample: calldata=0xdf16afc900000000000000000000000043384bc11eeba7f5835ac23af15fb497e1321a7a000000000000000000000000755507bb04531d42f661ff3c7913d56920cfbfdb0000000000000000000000000000000000000000ecf1d9980cf5b54c747be5f9 args=[0x43384Bc11eEbA7f5835AC23aF15Fb497E1321A7A, 0x755507bb04531d42F661Ff3C7913d56920CFbFDB, 73330840998730295604741400057 [7.333e28]]
  • test_transferFromSeizableWithMemo_success_noAllowanceRequired(address,address,uint256): custom error 0x5dd87dfc; counterexample: calldata=0x16a6a88000000000000000000000000001bcb1939e6f4855b574bbef6099aa68e7532f5f0000000000000000000000009fc52e4177e00550466ecac401b8a5b493e813710000000000000000000000000000000000000000000000000000000000007e22 args=[0x01bcb1939E6f4855B574BBef6099AA68e7532F5f, 0x9FC52e4177E00550466ECaC401b8A5b493E81371, 32290 [3.229e4]]
    [FAIL: custom error 0x7c40df74] test_compositePolicyChildIds_success_emptyAfterFailedCreation() (gas: 133282)
    [FAIL: custom error 0x7c40df74] test_compositePolicyChildIds_success_returnsCreationSet() (gas: 231818)
    [FAIL: custom error 0x7c40df74] test_compositePolicyChildIds_success_tracksUpdateComposite() (gas: 259213)
    [FAIL: custom error 0xa290249c: type check failed for "(uint8[])" with data: 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003] test_b20Layout_success_populatedSnapshotMatchesAllSlots() (gas: 682840)
    [FAIL: isPaused must still accept enum index 3 (SEIZE)] test_isPaused_revert_noFifthFeature() (gas: 9532)

Comment thread src/interfaces/IPolicyRegistry.sol Outdated
Comment thread src/interfaces/IPolicyRegistry.sol Outdated
Comment on lines +120 to +123
/// @notice Verifies an existing composite never reports fewer than MIN_CHILD_POLICIES children
/// @dev This is what makes an empty return unambiguously mean "not a composite": there is no
/// clear-the-list path, so a created composite can never decay to an empty set.
function test_compositePolicyChildIds_success_createdCompositeIsNeverEmpty() public {

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.

Weird to me that we call this a success test when this is the only instance of testing the revert case for ChildPoliciesOutsideOfRange.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed

rayyan224 and others added 4 commits July 30, 2026 09:28
Co-authored-by: katzman <steve.katzman@coinbase.com>
Co-authored-by: katzman <steve.katzman@coinbase.com>
Addresses review feedback on #183.

test_..._success_createdCompositeIsNeverEmpty asserted a
ChildPoliciesOutsideOfRange revert under a _success_ name, against the
repo's _success_/_revert_ convention, and duplicated coverage already
pinned thoroughly by updateComposite.t.sol (under-range 0 and 1,
over-range 5..8) plus three other files.

Scopes the file to the selector's own behavior, 10 tests -> 5:

  - returnsCreationSet        newly created
  - tracksUpdateComposite     exists and updated
  - emptyForUncreated         does not exist
  - emptyAfterFailedCreation  creation failed, nothing persisted (new)
  - emptyForMalformedId       malformed id (regression guard)

The new failed-creation test uses a bare vm.expectRevert() rather than
the typed selector: the failure is setup for the read, not the assertion
under test, which is what made the old test read as a revert test in
disguise. It also pins that the predicted ID is the one the failed call
would have taken, by showing the next valid creation claims exactly it —
otherwise the empty read would pass for free on an unrelated ID.

Dropped matchesEmittedEvent, preservesDuplicates, survivesRenounce,
emptyForSimplePolicy, emptyForBuiltins, liveCompositeIsNeverEmpty. Guard
coverage survives: emptyForUncreated fuzzes a top byte across the whole
PolicyType range, so it exercises the !_isComposite branch that
emptyForSimplePolicy covered, and emptyForMalformedId still covers
!_isWellFormed.

Also applies the two natspec suggestions from review.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

@robriks robriks 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.

good stuff LGTM!

@rayyan224
rayyan224 merged commit 8927f89 into main Jul 30, 2026
10 checks passed
@rayyan224
rayyan224 deleted the feat/policy-composite-child-ids-view branch July 30, 2026 14:56
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.

3 participants