Note: This version has not been audited.
Main theme: remediation of the Nethermind AuditAgent v3.3.0-rc2 automated review. Maintainer triage outcome: 12 fixed in code · 8 accepted as design (5 documented) · 4 rejected; no open item. Full per-finding dispositions in audit_agent_report_v3.3.0-rc2-feedback.md.
Smart contract
Fixed
- NM-15/17 — missing
address(0)guard in_setFrozenTokens(ERC20EnforcementModuleInternal): anERC20ENFORCER_ROLEholder could freeze tokens on the zero address and brick every mint path. A zero-address guard now rejects it. - NM-3/8 — allowance revocation blocked while paused / frozen: setting an allowance to
0is now always permitted._canAuthorizeAllowanceByModuleAndRevertreturns early whenvalue == 0, so an owner can revoke a spender even while the contract is paused or a party is frozen/delisted. - NM-22 —
setTerms(bytes32,string)overload erased the terms document name: the overload now preserves the existing documentnameinstead of silently clearing it (ERC7551Module/ExtraInformationModule). - NM-24 —
forcedTransferallowance accounting:forcedTransfernow emitsSpend(from, to, min(currentAllowance, value))and theIERC20Allowance.SpendNatSpec was corrected. - NM-5 — Light minter transfer:
_minterTransferOverridenow threads_msgSender()as the spender argument, aligning the Light variant with the other deployments.
Security
- NM-7/9/11/16/18 — reentrancy on the RuleEngine
transferredcallback: thetransferredcallback (invoked before balance effects) is now protected by a transient-storage reentrancy guard (OpenZeppelinReentrancyGuardTransient, EIP-1153) on the deployment variants with bytecode headroom._callRuleEngineTransferredisvirtualand only guards when a RuleEngine is set; size-constrained variants can override, and the limitation is documented. Added a malicious reentrant RuleEngine mock (RuleEngineReentrantMock) to cover the attack.
Changed
- Raised the Solidity source pragma floor to
^0.8.24across all contracts, required by the EIP-1153 transient storage used by the new reentrancy guard.
Testing
Added
- Malicious-engine reentrancy tests (
test/common/ValidationModule/RuleEngineReentrancyCommon.js) plus per-variant wiring. - Regression tests for the fixes above: zero-value permit/allowance revocation while paused or with a frozen owner/spender,
setTermsname preservation,forcedTransferSpendevent, the Light minter-transfer spender. - Coverage tests that a non-allowlisted minter can still mint, a frozen minter can still mint, and that freeze does not block
mint/batchMint(standard + light). - ERC-1643 document tests (
test/common/DocumentModule/DocumentModuleCommon.js): event emission onsetDocument/removeDocument, the zero-name case and the missing-document case, run against both the nativeDocumentERC1643Moduleand the externalDocumentEngineModulevariants. - RuleEngine spender-dispatch tests (
test/common/ValidationModule/RuleEngineSpenderDispatchCommon.js, wired for the standalone and proxy variants): assert through CMTAT thattransferFromroutes to the spender-aware 4-argumenttransferred(spender, from, to, value)and forwards the real spender, while a directtransferroutes to the legacy 3-argument overload. Uses a new recording mock,RuleEngineSpenderRecorderMock, and kills thespender != address(0)dispatch mutant. doc/test/Test.md— a hand-maintained test catalogue (module × deployment-version matrix, per-module scenario reference) to make missing tests easy to find; its maintenance is now required byCLAUDE.md/AGENTS.md.
Fixed
npm run coverageno longer fails withTransaction ran out of gason the largest deployment variants (ERC1363, ERC7551, DebtEngine, HolderList). The Hardhat network defaults to theosakahardfork, which enforces the EIP-7825 per-transaction gas cap of 2**24 (16,777,216 gas); solidity-coverage's instrumented bytecode exceeds it at deployment, and each failedbeforeEachskipped its whole suite.hardhat.config.jsnow runs the coverage task onprague(overridable withHARDHAT_HARDFORK) whilenpm run teststays onosaka. Deployed contracts are unaffected — they use about a third of the cap (~5.7M gas). Full coverage run: 6108 passing, 0 failing.- Flaky timestamp assertions in the snapshot scheduling and rescheduling suites (
test/common/SnapshotModuleCommon/):CMTAT_SnapshotModule_SnapshotScheduledInThePastwas checked against(await time.latest()) + 1read while the reverting call was in flight, which resolved to a different block depending on run speed. The executing block's timestamp is now pinned withtime.setNextBlockTimestamp.
Documentation
- Documented the accepted-as-design findings: NM-4 (raw
msg.senderbridge gate and theCROSS_CHAIN_ROLEforwarder constraint), NM-6 (permissionless zero-valuetransferredcallbacks in theIRuleEngineNatSpec), NM-20 (the ERC20Burn pause note —burnFrom/burn(value)carry the pause check as cross-chain/third-party operations), NM-21 (setNamedoes not update the EIP-712 domain; signers must readeip712Domain()), and an allowance-spend-event technical note (NM-24). - Expanded
doc/README.md: ERC-2771 per-deployment support table; split of the ERC-1404 version-support row into base (detectTransferRestriction) and reworked-only Extension (detectTransferRestrictionFrom); nativeDocumentERC1643Modulevs externalDocumentEngineModule; inheritance schemas for ERC-1363, Light, Debt, DebtEngine, Permit and Allowlist; a per-tool security-tools overview; and aCMTAT-Confidentialentry under official implementations. - Renamed
guideline-new-blockchain.md→cmtat-specification-analyse.md(rescoped to a CMTAT-specification-vs-implementation comparison); porting now points to the external CMTAT-equivalency-assessment repository. Removed the ERC-1450 links from the Technical Guides. - Corrected
holder-list.md(the gas cost of a transfer between existing holders, and off-chain balance-at-a-block viaeth_call). - ERC-1404 predictor scope —
detectTransferRestrictionanddetectTransferRestrictionFromdescribe the holder transfer path (transfer/transferFrom) only. Theaddress(0)encoding of the ERC-1404 rework draft (mint asfrom == address(0), burn asto == address(0)) is not supported, because CMTAT's pause rule differs between entry points of the same operation —MINTER_ROLEmint andBURNER_ROLEburn proceed while paused, whilecrosschainMint,crosschainBurn,burnFromandburn(uint256)do not — and the ERC-1404 signature carries no entry-point discriminator. CMTAT therefore designates no ERC-1404 predictor for supply-changing operations and designatescanTransfer/canTransferFrominstead. Documented inValidationModuleERC1404NatSpec,doc/README.md,doc/technical/ruleengine-integration.mdanddoc/modules/core/Pause/pause.md. - RuleEngine authoring obligations (
doc/technical/ruleengine-integration.md): an engine must return a non-emptymessageForTransferRestrictionstring for every code it can return — never one denoting the absence of a restriction for a non-zero code — and must implementIRuleEngineERC1404when the token exposes ERC-1404. The token forwards both the code and the message verbatim by design (trusted,DEFAULT_ADMIN_ROLE-set engine; bytecode headroom), which is now recorded as an explicit design choice. - Documented that
forcedTransfer/forcedBurnintentionally remain available after deactivation (ERC-8343 named privileged operations, e.g. to sweep a frozen or migrated position), and corrected the absolute "no operation after deactivation" wording indoc/README.md/doc/technical/stablecoin.mdand in the0_CMTATBaseCore/ERC20EnforcementModuleInternalNatSpec. - Corrected misleading access-control NatSpec: the cross-chain
burn(uint256)is gated byonlySelfBurn(BURNER_SELF_ROLE), not the burner role, andEnforcementModule'sonlyEnforcergates the address freeze. doc/README.md: refreshed the DocumentEngine version table (CMTAT v3.3.0 → DocumentEngine v0.4.0; v3.0.0–v3.2.0 marked as not developed) and added Permit (ERC-2612), Multicall (ERC-6357) and Holder List to the optional-features list.- Regenerated the Surya schema for rc3 — call graphs, inheritance graphs and reports for all 129 contracts, picking up
ReentrancyGuardTransientin the deployment-variant inheritance and the two new RuleEngine mocks — and re-synced the per-module report copies underdoc/modules/**with the canonical set (74 files, two ghosts from the deployment-filenames rename dropped, 13 missing reports added). - Documented the coverage hardfork in
doc/USAGE.md(withdoc/README.mdpointing to it): why the coverage run usespraguewhile the test run usesosaka, and the measured deployment gas of the largest variants against the EIP-7825 cap, showing that the cap constrains the instrumented build only and not the deployed contracts. - Refreshed the Slither and Aderyn reports and maintainer feedback against the rc3 source: Slither 158 results (0 High;
calls-looprises 28 → 76 purely because the RuleEngine hook was factored into thevirtual_callRuleEngineTransferred, not from any new external call), Aderyn unchanged at 2 High / 10 Low with identical instance counts (3736 → 3826 nSLOC). Nothing to fix in either; see AUDIT.md.
Dependencies
- Update the pinned Solidity compiler from 0.8.34 to 0.8.36 in
hardhat.config.jsandfoundry.toml. - Bump the
npmdevDependency to^12.0.1.