Security
Test suite layout, invariants, and the adapter's structural defenses.
Test suite layout#
The repository ships with a layered Foundry test suite:
test/
├── Adapter8004.t.sol # original unit tests
└── security/
├── Adapter8004.security.t.sol # coverage-gap tests
├── Adapter8004.adversarial.t.sol # hostile tokens + registries
├── Adapter8004.fuzz.t.sol # property tests
└── mocks/
├── MaliciousERC721.sol
├── MaliciousERC1155.sol
├── MaliciousERC6909.sol
├── RevertingToken.sol
└── OverflowRegistry.sol
Run everything:
forge test
forge coverage --no-match-coverage "test/|script/"
Expected result: 100% line, statement, branch, and function coverage on
src/Adapter8004.sol.
Core invariants#
The fuzz suite exercises these under 256 random inputs each:
- Register lookup round-trip — a successful
registeralways makesagentIdForBindingreturn the sameagentIdwithexists = true. - Controller follows owner (ERC-721) — transferring the bound token moves control atomically with no extra call to the adapter.
- Shared control (ERC-1155 / ERC-6909) — every current holder passes
isController, every non-holder fails. - Non-controllers are always rejected — random attacker addresses cannot call any controller-gated function.
- Admin gating — only the owner can swap the registry or upgrade.
- agentId encoding — the internal
agentId + 1storage never confuses slot 0 with "unset" for any(standard, contract, id)tuple.
Adversarial scenarios#
The adversarial suite covers hostile behavior from both sides of the adapter:
Malformed token contracts#
- Reverting tokens — every revert path propagates; no stale binding is left behind.
- EOA as tokenContract — Solidity's
extcodesizecheck aborts the call before anything else happens.
Attacker-controlled token contracts#
- Forced
ownerOf/balanceOf— documents the trust boundary. A malicious token can name whoever it wants as the controller. This is the known trust assumption of the adapter. - Control flips between calls — whoever the token currently names is the controller. The adapter reads fresh on every gated call.
View-time reentrancy#
The adapter's _hasBindingControl is internal view, and the ERC-721 /
ERC-1155 / ERC-6909 interface reads are declared view. Solidity compiles
those calls as STATICCALL, so any attempt by a hostile token to write
state or make a non-static CALL from inside ownerOf / balanceOf kills
the whole transaction.
This is a structural defense that depends on never relaxing the view
declarations in the adapter. Any future refactor that downgrades them from
view to non-view should be treated as a security-critical change.
Hostile registry#
agentId + 1overflow — a stub registry returningtype(uint256).maxfromregistermakes the adapter revert with an arithmetic error instead of silently wrapping to zero.- Registry swap hazard — old bindings become read-only artifacts and writes to them revert at the new registry's auth check. This is documented, not prevented.
Admin self-footgun#
- Owner renouncement —
renounceOwnership()permanently locks bothsetIdentityRegistryandupgradeToAndCall. Deliberate, but worth documenting as a point of no return.
Audit notes#
- Trust boundary: the adapter trusts the bound token contract to report
honest
ownerOf/balanceOf. Only bind to token contracts you trust. - Shared control is intentional: ERC-1155 / ERC-6909 can produce multiple equally-valid controllers. This is a design choice, not a bug.
- EIP-712
ownerfield is the adapter address, not the token holder. Off-chain signers must sign against the adapter address. - Upgrade power is concentrated in a single owner. Renounce after setup if that's unacceptable for your threat model.
- No ReentrancyGuard is needed because the adapter's external calls to
the bound token happen through
viewinterface methods, which Solidity compiles asSTATICCALL. The external call to the registry happens after all local state writes, so even if the registry reentered the adapter, it would see consistent state.
Reporting issues#
Security reports should go to the adapter maintainers via the channels
listed in the repository's SECURITY.md. Do not file public issues for
vulnerabilities.