From f386169251ed5f74ee9fc9b74abfd12608d92976 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 24 Sep 2025 10:59:12 +0200 Subject: [PATCH 01/11] Expect a message-less revert when the proofs are too short. --- contracts/test/ProtocolAdapterMock.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/test/ProtocolAdapterMock.t.sol b/contracts/test/ProtocolAdapterMock.t.sol index 98fa6ec3..16499634 100644 --- a/contracts/test/ProtocolAdapterMock.t.sol +++ b/contracts/test/ProtocolAdapterMock.t.sol @@ -380,8 +380,8 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { truncatedProof[k] = proof[k]; } complianceVerifierInputs[params.inputIdx].proof = truncatedProof; - // With a short proof, we expect failure - vm.expectRevert(address(_router)); + // With a short proof, we expect an EVM error (which is message-less) + vm.expectRevert(bytes(""), address(_router)); // Finally, execute the transaction to make sure that it fails this.execute(transaction); } From 128590d9a3e1b25824042decb8593a5d1aba2328 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 25 Sep 2025 07:43:20 +0200 Subject: [PATCH 02/11] Reduce comments and remove inheritance. --- contracts/test/ProtocolAdapterMock.t.sol | 145 ++++++----------------- 1 file changed, 36 insertions(+), 109 deletions(-) diff --git a/contracts/test/ProtocolAdapterMock.t.sol b/contracts/test/ProtocolAdapterMock.t.sol index 16499634..9dabe208 100644 --- a/contracts/test/ProtocolAdapterMock.t.sol +++ b/contracts/test/ProtocolAdapterMock.t.sol @@ -18,14 +18,9 @@ import {ForwarderExample} from "./examples/Forwarder.e.sol"; import {INPUT, EXPECTED_OUTPUT} from "./examples/ForwarderTarget.e.sol"; import {TxGen} from "./libs/TxGen.sol"; import {DeployRiscZeroContractsMock} from "./script/DeployRiscZeroContractsMock.s.sol"; +import {CommitmentAccumulator} from "./../src/state/CommitmentAccumulator.sol"; -struct ProtocolAdapterTestArgs { - RiscZeroVerifierRouter router; - RiscZeroVerifierEmergencyStop emergencyStop; - RiscZeroMockVerifier verifier; -} - -contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { +contract ProtocolAdapterMockVerifierTest is Test { using MerkleTree for bytes32[]; using TxGen for Action[]; using TxGen for Action; @@ -33,9 +28,6 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { using RiscZeroUtils for Logic.VerifierInput; /// @notice The parameters necessary to make a failing mutation to a transaction - /// @param The index of the action to mutate - /// @param The index of the compliance verifier input of the action to mutate - /// @param The value to mutate the action tree root to struct NonExistingRootFailsParams { uint256 actionIdx; uint256 inputIdx; @@ -43,17 +35,12 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { } /// @notice The parameters necessary to make a failing mutation to a transaction - /// @param The index of the action to mutate - /// @param The index of the compliance verifier input of the action to mutate struct ShortProofFailsParams { uint256 actionIdx; uint256 inputIdx; } /// @notice The parameters necessary to make a failing mutation to a transaction - /// @param The index of the action to mutate - /// @param The index of the compliance verifier input of the action to mutate - /// @param The proof to overwrite with struct UnknownSelectorFailsParams { uint256 actionIdx; uint256 inputIdx; @@ -61,9 +48,6 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { } /// @notice The parameters necessary to make a failing mutation to a transaction - /// @param The index of the action to mutate - /// @param The index of the compliance verifier input of the action to mutate - /// @param The tag to overwrite with struct UnknownTagFailsParams { uint256 actionIdx; uint256 inputIdx; @@ -71,17 +55,12 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { } /// @notice The parameters necessary to make a failing mutation to a transaction - /// @param The index of the action to mutate - /// @param The index of the compliance verifier input of the action to mutate struct MismatchingResourcesFailParams { uint256 actionIdx; uint256 inputIdx; } /// @notice The parameters necessary to make a failing mutation to a transaction - /// @param The index of the action to mutate - /// @param The index of the logic verifier input of the action to mutate - /// @param The logic reference to overwrite with struct MismatchingLogicRefsFailParams { uint256 actionIdx; uint256 inputIdx; @@ -89,10 +68,6 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { } /// @notice The parameters necessary to make a failing mutation to a transaction - /// @param The index of the action to mutate - /// @param The index of the logic verifier input of the action to mutate - /// @param The index of the external payload to mutate - /// @param The output to overwrite with struct MismatchingForwarderCallOutputsFailParams { uint256 actionIdx; uint256 inputIdx; @@ -113,13 +88,9 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { bytes32 internal _carrierLabelRef; - constructor(ProtocolAdapterTestArgs memory args) - ProtocolAdapter(args.router, args.verifier.SELECTOR(), _EMERGENCY_COMMITTEE) - { - _router = args.router; - _emergencyStop = args.emergencyStop; - _mockVerifier = args.verifier; - _verifierSelector = args.verifier.SELECTOR(); + constructor() { + (_router, _emergencyStop, _mockVerifier) = new DeployRiscZeroContractsMock().run(); + _verifierSelector = _mockVerifier.SELECTOR(); _mockPa = new ProtocolAdapter(_router, _mockVerifier.SELECTOR(), _EMERGENCY_COMMITTEE); } @@ -318,11 +289,7 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { _mockPa.execute(txn); } - /// @notice Take a transaction that would execute successfully and make it - /// fail by giving one of its compliance verifier inputs an incorrect - /// commitment tree root. - /// @param transaction A successful transaction - /// @param params A failure inducing modification + /// @notice Make transaction fail by giving it an incorrect commitment tree root. function mutationTestExecuteNonExistingRootFails( Transaction memory transaction, NonExistingRootFailsParams memory params @@ -332,15 +299,17 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { Compliance.VerifierInput[] memory complianceVerifierInputs = transaction.actions[params.actionIdx].complianceVerifierInputs; // Assume the proposed commitment tree root is not already contained - vm.assume(!_containsRoot(params.commitmentTreeRoot)); + vm.assume(!_mockPa.containsRoot(params.commitmentTreeRoot)); // Wrap the compliance verifier input index into range params.inputIdx = params.inputIdx % complianceVerifierInputs.length; // Finally assign the proposed commitment tree root into the transaction complianceVerifierInputs[params.inputIdx].instance.consumed.commitmentTreeRoot = params.commitmentTreeRoot; // With an incorrect commitment tree root, we expect failure - vm.expectRevert(abi.encodeWithSelector(NonExistingRoot.selector, params.commitmentTreeRoot)); + vm.expectRevert( + abi.encodeWithSelector(CommitmentAccumulator.NonExistingRoot.selector, params.commitmentTreeRoot) + ); // Finally, execute the transaction to make sure that it fails - this.execute(transaction); + _mockPa.execute(transaction); } /// @notice Test that transactions with nonexistent rotts fail @@ -358,11 +327,7 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { mutationTestExecuteNonExistingRootFails(txn, params); } - /// @notice Take a transaction that would execute successfully and make it - /// fail by giving one of its compliance verifier inputs a proof that's too - /// short. - /// @param transaction A successful transaction - /// @param params A failure inducing modification + /// @notice Make transaction fail by giving it a proof that's too short function mutationTestExecuteShortProofFails(Transaction memory transaction, ShortProofFailsParams memory params) public { @@ -383,7 +348,7 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { // With a short proof, we expect an EVM error (which is message-less) vm.expectRevert(bytes(""), address(_router)); // Finally, execute the transaction to make sure that it fails - this.execute(transaction); + _mockPa.execute(transaction); } /// @notice Test that transactions with short proofs fail @@ -401,11 +366,7 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { mutationTestExecuteShortProofFails(txn, params); } - /// @notice Take a transaction that would execute successfully and make it - /// fail by giving one of its compliance verifier inputs a proof with an - /// unknown selector. - /// @param transaction A successful transaction - /// @param params A failure inducing modification + /// @notice Make transaction fail by giving it an unknown selector. function mutationTestExecuteUnknownSelectorFails( Transaction memory transaction, UnknownSelectorFailsParams memory params @@ -428,7 +389,7 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { address(_router) ); // Finally, execute the transaction to make sure that it fails - this.execute(transaction); + _mockPa.execute(transaction); } /// @notice Test that transactions with unknown selectors fail @@ -446,11 +407,7 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { mutationTestExecuteUnknownSelectorFails(txn, params); } - /// @notice Take a transaction that would execute successfully and make it - /// fail by ensuring that the nullifier of one of its compliance verifier - /// inputs is not found in the logic verifier inputs. - /// @param transaction A successful transaction - /// @param params A failure inducing modification + /// @notice Make transaction fail by ensuring unknown nullifier function mutationTestExecuteUnknownNullifierTagFails( Transaction memory transaction, UnknownTagFailsParams memory params @@ -481,7 +438,7 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { ) ); // Finally, execute the transaction to make sure that it fails - this.execute(transaction); + _mockPa.execute(transaction); } /// @notice Test that transactions with unknown nullifier tags fail @@ -499,11 +456,7 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { mutationTestExecuteUnknownNullifierTagFails(txn, params); } - /// @notice Take a transaction that would execute successfully and make it - /// fail by ensuring that the commitment of one of its compliance verifier - /// inputs is not found in the logic verifier inputs. - /// @param transaction A successful transaction - /// @param params A failure inducing modification + /// @notice Make transaction fail by ensuring unknown commitment function mutationTestExecuteUnknownCommitmentTagFails( Transaction memory transaction, UnknownTagFailsParams memory params @@ -534,7 +487,7 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { ) ); // Finally, execute the transaction to make sure that it fails - this.execute(transaction); + _mockPa.execute(transaction); } /// @notice Test that transactions with unknown commitment tags fail @@ -552,11 +505,7 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { mutationTestExecuteUnknownCommitmentTagFails(txn, params); } - /// @notice Take a transaction that would execute successfully and make it - /// fail by ensuring that it has less compliance verifier inputs than half - /// the logic verifier inputs. - /// @param transaction A successful transaction - /// @param params A failure inducing modification + /// @notice Make transaction fail by ensuring that it has less compliance verifier inputs function mutationTestExecuteMissingComplianceVerifierInputFail( Transaction memory transaction, MismatchingResourcesFailParams memory params @@ -579,10 +528,12 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { transaction.actions[params.actionIdx].complianceVerifierInputs = shorter; // With mismatching resource counts, we expect failure vm.expectRevert( - abi.encodeWithSelector(TagCountMismatch.selector, action.logicVerifierInputs.length, shorter.length * 2) + abi.encodeWithSelector( + ProtocolAdapter.TagCountMismatch.selector, action.logicVerifierInputs.length, shorter.length * 2 + ) ); // Finally, execute the transaction to make sure that it fails - this.execute(transaction); + _mockPa.execute(transaction); } /// @notice Test that transactions with a missing compliance verifier input fail @@ -600,11 +551,7 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { mutationTestExecuteMissingComplianceVerifierInputFail(txn, params); } - /// @notice Take a transaction that would execute successfully and make it - /// fail by ensuring that it has less logic verifier inputs than the number - /// of compliance verifier inputs doubled. - /// @param transaction A successful transaction - /// @param params A failure inducing modification + /// @notice Make transaction fail by ensuring that it has less logic verifier inputs function mutationTestExecuteMissingLogicVerifierInputFail( Transaction memory transaction, MismatchingResourcesFailParams memory params @@ -628,11 +575,11 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { // With mismatching resource counts, we expect failure vm.expectRevert( abi.encodeWithSelector( - TagCountMismatch.selector, shorter.length, action.complianceVerifierInputs.length * 2 + ProtocolAdapter.TagCountMismatch.selector, shorter.length, action.complianceVerifierInputs.length * 2 ) ); // Finally, execute the transaction to make sure that it fails - this.execute(transaction); + _mockPa.execute(transaction); } /// @notice Test that transactions with a missing logic verifier input fail @@ -650,12 +597,7 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { mutationTestExecuteMissingLogicVerifierInputFail(txn, params); } - /// @notice Take a transaction that would execute successfully and make it - /// fail by ensuring that the verifying key of one of its logic verifier - /// inputs does not match the nullifier or commitment in the corresponding - /// compliance verifier input. - /// @param transaction A successful transaction - /// @param params A failure inducing modification + /// @notice Make transaction fail by making logic reference mismatch function mutationTestExecuteMismatchingLogicRefsFail( Transaction memory transaction, MismatchingLogicRefsFailParams memory params @@ -669,9 +611,9 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { vm.assume(logicVerifierInputs[params.inputIdx].verifyingKey != params.logicRef); logicVerifierInputs[params.inputIdx].verifyingKey = params.logicRef; // With mismatching logic references, we expect failure - vm.expectPartialRevert(LogicRefMismatch.selector); + vm.expectPartialRevert(ProtocolAdapter.LogicRefMismatch.selector); // Finally, execute the transaction to make sure that it fails - this.execute(transaction); + _mockPa.execute(transaction); } /// @notice Test that transactions with mismatching logic references fail @@ -689,10 +631,7 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { mutationTestExecuteMismatchingLogicRefsFail(txn, params); } - /// @notice Take a transaction that would execute successfully and make it - /// fail by ensuring that one of its forwarder call outputs mismatch. - /// @param transaction A successful transaction - /// @param params A failure inducing modification + /// @notice Make transaction fail by ensuring that one of its forwarder call outputs mismatch. function mutationTestExecuteMismatchingForwarderCallOutputsFail( Transaction memory transaction, MismatchingForwarderCallOutputsFailParams memory params @@ -723,9 +662,7 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { for (uint256 i = 0; i < complianceVerifierInputs.length; i++) { if (complianceVerifierInputs[i].instance.consumed.nullifier == logicVerifierInput.tag) { isConsumed = true; - } else if ( - complianceVerifierInputs[i].instance.created.commitment == logicVerifierInputs[params.inputIdx].tag - ) { + } else if (complianceVerifierInputs[i].instance.created.commitment == logicVerifierInput.tag) { isConsumed = false; } } @@ -737,9 +674,11 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { journalDigest: logicVerifierInputs[params.inputIdx].toJournalDigest(actionTreeRoot, isConsumed) }).seal; // With mismatching forwarder call outputs, we expect failure - vm.expectRevert(abi.encodeWithSelector(ForwarderCallOutputMismatch.selector, params.output, expectedOutput)); + vm.expectRevert( + abi.encodeWithSelector(ProtocolAdapter.ForwarderCallOutputMismatch.selector, params.output, expectedOutput) + ); // Finally, execute the transaction to make sure that it fails - this.execute(transaction); + _mockPa.execute(transaction); } /// @notice Test that transactions with mismatching forwarder call outputs fails @@ -844,15 +783,3 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { data[0].appData.externalPayload = externalBlobs; } } - -contract ProtocolAdapterTest is ProtocolAdapterMockVerifierTest { - constructor() ProtocolAdapterMockVerifierTest(baseArgs()) {} - - function baseArgs() public returns (ProtocolAdapterTestArgs memory args) { - RiscZeroVerifierRouter router; - RiscZeroVerifierEmergencyStop emergencyStop; - RiscZeroMockVerifier verifier; - (router, emergencyStop, verifier) = new DeployRiscZeroContractsMock().run(); - return ProtocolAdapterTestArgs({router: router, emergencyStop: emergencyStop, verifier: verifier}); - } -} From 231ca815fd83e6227f184183be16571d6166b0db Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 25 Sep 2025 09:34:16 +0200 Subject: [PATCH 03/11] Tighten the logic in mutationTestExecuteMismatchingForwarderCallOutputsFail. --- contracts/src/ProtocolAdapter.sol | 13 +----- contracts/src/proving/Compliance.sol | 20 ++++++++ contracts/test/ProtocolAdapterMock.t.sol | 58 +++++++----------------- 3 files changed, 38 insertions(+), 53 deletions(-) diff --git a/contracts/src/ProtocolAdapter.sol b/contracts/src/ProtocolAdapter.sol index 8997520a..d71e60c2 100644 --- a/contracts/src/ProtocolAdapter.sol +++ b/contracts/src/ProtocolAdapter.sol @@ -45,6 +45,7 @@ contract ProtocolAdapter is using RiscZeroUtils for Logic.VerifierInput; using Logic for Logic.VerifierInput[]; using Delta for uint256[2]; + using Compliance for Compliance.VerifierInput[]; RiscZeroVerifierRouter internal immutable _TRUSTED_RISC_ZERO_VERIFIER_ROUTER; bytes4 internal immutable _RISC_ZERO_VERIFIER_SELECTOR; @@ -366,16 +367,6 @@ contract ProtocolAdapter is pure returns (bytes32 root) { - bytes32[] memory actionTreeTags = new bytes32[](complianceUnitCount * 2); - - // The order in which the tags are added to the tree is provided by the compliance units. - for (uint256 j = 0; j < complianceUnitCount; ++j) { - Compliance.VerifierInput calldata complianceVerifierInput = action.complianceVerifierInputs[j]; - - actionTreeTags[2 * j] = complianceVerifierInput.instance.consumed.nullifier; - actionTreeTags[(2 * j) + 1] = complianceVerifierInput.instance.created.commitment; - } - - root = actionTreeTags.computeRoot(); + root = action.complianceVerifierInputs.computeActionTreeTags(complianceUnitCount).computeRoot(); } } diff --git a/contracts/src/proving/Compliance.sol b/contracts/src/proving/Compliance.sol index cc6daecc..aa6b8a23 100644 --- a/contracts/src/proving/Compliance.sol +++ b/contracts/src/proving/Compliance.sol @@ -48,4 +48,24 @@ library Compliance { /// @notice The compliance verifying key. /// @dev The key is fixed as long as the compliance circuit binary is not changed. bytes32 internal constant _VERIFYING_KEY = 0x706468196fd92568220f5271e843c608126f7a8f204205d42ceef1f2c69f91df; + + /// @notice Computes the action tree root of an action constituted by all its nullifiers and commitments. + /// @param complianceVerifierInputs Compliance verifier inputs. + /// @param complianceUnitCount The number of compliance units in the action. + /// @return actionTreeTags The action tree tags corresponding to the compliance verifier inputs. + function computeActionTreeTags(Compliance.VerifierInput[] calldata complianceVerifierInputs, uint256 complianceUnitCount) + internal + pure + returns (bytes32[] memory actionTreeTags) + { + actionTreeTags = new bytes32[](complianceUnitCount * 2); + + // The order in which the tags are added to the tree is provided by the compliance units. + for (uint256 j = 0; j < complianceUnitCount; ++j) { + Compliance.VerifierInput calldata complianceVerifierInput = complianceVerifierInputs[j]; + + actionTreeTags[2 * j] = complianceVerifierInput.instance.consumed.nullifier; + actionTreeTags[(2 * j) + 1] = complianceVerifierInput.instance.created.commitment; + } + } } diff --git a/contracts/test/ProtocolAdapterMock.t.sol b/contracts/test/ProtocolAdapterMock.t.sol index 9dabe208..6a04e574 100644 --- a/contracts/test/ProtocolAdapterMock.t.sol +++ b/contracts/test/ProtocolAdapterMock.t.sol @@ -26,6 +26,8 @@ contract ProtocolAdapterMockVerifierTest is Test { using TxGen for Action; using TxGen for Vm; using RiscZeroUtils for Logic.VerifierInput; + using Logic for Logic.VerifierInput[]; + using Compliance for Compliance.VerifierInput[]; /// @notice The parameters necessary to make a failing mutation to a transaction struct NonExistingRootFailsParams { @@ -73,6 +75,7 @@ contract ProtocolAdapterMockVerifierTest is Test { uint256 inputIdx; uint256 payloadIdx; bytes output; + bool consumed; } address internal constant _EMERGENCY_COMMITTEE = address(uint160(1)); @@ -633,16 +636,19 @@ contract ProtocolAdapterMockVerifierTest is Test { /// @notice Make transaction fail by ensuring that one of its forwarder call outputs mismatch. function mutationTestExecuteMismatchingForwarderCallOutputsFail( - Transaction memory transaction, + Transaction calldata transaction_calldata, MismatchingForwarderCallOutputsFailParams memory params - ) public { + ) public {Transaction memory transaction = transaction_calldata; // Wrap the action index into range params.actionIdx = params.actionIdx % transaction.actions.length; + Action calldata action_calldata = transaction_calldata.actions[params.actionIdx]; Action memory action = transaction.actions[params.actionIdx]; - Logic.VerifierInput[] memory logicVerifierInputs = action.logicVerifierInputs; + Compliance.VerifierInput[] memory complianceVerifierInputs = action.complianceVerifierInputs; // Wrap the logic verifier input index into range - params.inputIdx = params.inputIdx % logicVerifierInputs.length; - Logic.VerifierInput memory logicVerifierInput = logicVerifierInputs[params.inputIdx]; + params.inputIdx = params.inputIdx % complianceVerifierInputs.length; + Compliance.VerifierInput memory complianceVerifierInput = complianceVerifierInputs[params.inputIdx]; + bytes32 tag = params.consumed ? complianceVerifierInput.instance.consumed.nullifier : complianceVerifierInput.instance.created.commitment; + Logic.VerifierInput memory logicVerifierInput = action_calldata.logicVerifierInputs.lookup(tag); Logic.ExpirableBlob[] memory externalPayloads = logicVerifierInput.appData.externalPayload; // Cannot do the mutation if transaction has no external payloads vm.assume(externalPayloads.length > 0); @@ -656,22 +662,12 @@ contract ProtocolAdapterMockVerifierTest is Test { ); // Re-encode the calldata and replace the value in the external payloads externalPayloads[params.payloadIdx].blob = abi.encode(untrustedForwarder, input, params.output); - // Now determine whether the current resource is being created or consumed - Compliance.VerifierInput[] memory complianceVerifierInputs = action.complianceVerifierInputs; - bool isConsumed; - for (uint256 i = 0; i < complianceVerifierInputs.length; i++) { - if (complianceVerifierInputs[i].instance.consumed.nullifier == logicVerifierInput.tag) { - isConsumed = true; - } else if (complianceVerifierInputs[i].instance.created.commitment == logicVerifierInput.tag) { - isConsumed = false; - } - } // Compute the action tree root - bytes32 actionTreeRoot = _computeActionTreeRootMemory(action, action.complianceVerifierInputs.length); + bytes32[] memory actionTreeTags = action_calldata.complianceVerifierInputs.computeActionTreeTags(action.complianceVerifierInputs.length); // Recompute the logic verifier input proof - logicVerifierInputs[params.inputIdx].proof = _mockVerifier.mockProve({ - imageId: logicVerifierInputs[params.inputIdx].verifyingKey, - journalDigest: logicVerifierInputs[params.inputIdx].toJournalDigest(actionTreeRoot, isConsumed) + logicVerifierInput.proof = _mockVerifier.mockProve({ + imageId: logicVerifierInput.verifyingKey, + journalDigest: logicVerifierInput.toJournalDigest(actionTreeTags.computeRoot(), params.consumed) }).seal; // With mismatching forwarder call outputs, we expect failure vm.expectRevert( @@ -702,29 +698,7 @@ contract ProtocolAdapterMockVerifierTest is Test { TxGen.ResourceLists[] memory resourceLists = new TxGen.ResourceLists[](1); resourceLists[0] = TxGen.ResourceLists({consumed: consumed, created: created}); Transaction memory txn = vm.transaction(_mockVerifier, resourceLists); - mutationTestExecuteMismatchingForwarderCallOutputsFail(txn, params); - } - - /// @notice Computes the action tree root of an action constituted by all its nullifiers and commitments. - /// @param action The action whose root we compute. - /// @param complianceUnitCount The number of compliance units in the action. - /// @return root The root of the corresponding tree. - function _computeActionTreeRootMemory(Action memory action, uint256 complianceUnitCount) - internal - pure - returns (bytes32 root) - { - bytes32[] memory actionTreeTags = new bytes32[](complianceUnitCount * 2); - - // The order in which the tags are added to the tree are provided by the compliance units - for (uint256 j = 0; j < complianceUnitCount; ++j) { - Compliance.VerifierInput memory complianceVerifierInput = action.complianceVerifierInputs[j]; - - actionTreeTags[2 * j] = complianceVerifierInput.instance.consumed.nullifier; - actionTreeTags[(2 * j) + 1] = complianceVerifierInput.instance.created.commitment; - } - - root = actionTreeTags.computeRoot(); + this.mutationTestExecuteMismatchingForwarderCallOutputsFail(txn, params); } function _exampleResourceAndEmptyAppData(uint256 nonce) From 7bf6d5e92a313a659b29ebfb68be32b7d390abbb Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 25 Sep 2025 10:12:11 +0200 Subject: [PATCH 04/11] Fix forwarder calldata test. --- contracts/src/ProtocolAdapter.sol | 4 +- contracts/src/proving/Compliance.sol | 9 ++-- contracts/src/proving/Logic.sol | 8 +-- contracts/test/ProtocolAdapterMock.t.sol | 67 ++++++++++++------------ 4 files changed, 42 insertions(+), 46 deletions(-) diff --git a/contracts/src/ProtocolAdapter.sol b/contracts/src/ProtocolAdapter.sol index d71e60c2..7b16d6d2 100644 --- a/contracts/src/ProtocolAdapter.sol +++ b/contracts/src/ProtocolAdapter.sol @@ -136,7 +136,7 @@ contract ProtocolAdapter is // Check the consumed resource. // slither-disable-next-line reentrancy-benign _processResourceLogicContext({ - input: action.logicVerifierInputs.lookup(nf), + input: action.logicVerifierInputs[action.logicVerifierInputs.lookup(nf)], logicRef: complianceVerifierInput.instance.consumed.logicRef, actionTreeRoot: actionTreeRoot, consumed: true @@ -145,7 +145,7 @@ contract ProtocolAdapter is // Check the created resource. // slither-disable-next-line reentrancy-benign _processResourceLogicContext({ - input: action.logicVerifierInputs.lookup(cm), + input: action.logicVerifierInputs[action.logicVerifierInputs.lookup(cm)], logicRef: complianceVerifierInput.instance.created.logicRef, actionTreeRoot: actionTreeRoot, consumed: false diff --git a/contracts/src/proving/Compliance.sol b/contracts/src/proving/Compliance.sol index aa6b8a23..fe2251f6 100644 --- a/contracts/src/proving/Compliance.sol +++ b/contracts/src/proving/Compliance.sol @@ -53,11 +53,10 @@ library Compliance { /// @param complianceVerifierInputs Compliance verifier inputs. /// @param complianceUnitCount The number of compliance units in the action. /// @return actionTreeTags The action tree tags corresponding to the compliance verifier inputs. - function computeActionTreeTags(Compliance.VerifierInput[] calldata complianceVerifierInputs, uint256 complianceUnitCount) - internal - pure - returns (bytes32[] memory actionTreeTags) - { + function computeActionTreeTags( + Compliance.VerifierInput[] calldata complianceVerifierInputs, + uint256 complianceUnitCount + ) internal pure returns (bytes32[] memory actionTreeTags) { actionTreeTags = new bytes32[](complianceUnitCount * 2); // The order in which the tags are added to the tree is provided by the compliance units. diff --git a/contracts/src/proving/Logic.sol b/contracts/src/proving/Logic.sol index 3aa39ba2..e782264e 100644 --- a/contracts/src/proving/Logic.sol +++ b/contracts/src/proving/Logic.sol @@ -53,15 +53,11 @@ library Logic { /// @param list The list of verifier inputs. /// @param tag The tag to look up. /// @return foundElement The found `VerifierInput` element. - function lookup(VerifierInput[] calldata list, bytes32 tag) - internal - pure - returns (VerifierInput calldata foundElement) - { + function lookup(VerifierInput[] calldata list, bytes32 tag) internal pure returns (uint256) { uint256 len = list.length; for (uint256 i = 0; i < len; ++i) { if (list[i].tag == tag) { - return foundElement = list[i]; + return i; } } revert TagNotFound(tag); diff --git a/contracts/test/ProtocolAdapterMock.t.sol b/contracts/test/ProtocolAdapterMock.t.sol index 6a04e574..d6508e0f 100644 --- a/contracts/test/ProtocolAdapterMock.t.sol +++ b/contracts/test/ProtocolAdapterMock.t.sol @@ -1,24 +1,24 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.30; -import {RiscZeroVerifierEmergencyStop} from "@risc0-ethereum/RiscZeroVerifierEmergencyStop.sol"; -import {RiscZeroVerifierRouter} from "@risc0-ethereum/RiscZeroVerifierRouter.sol"; -import {RiscZeroMockVerifier} from "@risc0-ethereum/test/RiscZeroMockVerifier.sol"; -import {Test} from "forge-std/Test.sol"; -import {Vm} from "forge-std/Vm.sol"; -import {IProtocolAdapter} from "./../src/interfaces/IProtocolAdapter.sol"; -import {MerkleTree} from "./../src/libs/MerkleTree.sol"; -import {RiscZeroUtils} from "./../src/libs/RiscZeroUtils.sol"; -import {ProtocolAdapter} from "./../src/ProtocolAdapter.sol"; -import {Compliance} from "./../src/proving/Compliance.sol"; -import {Logic} from "./../src/proving/Logic.sol"; -import {NullifierSet} from "./../src/state/NullifierSet.sol"; -import {Transaction, Action} from "./../src/Types.sol"; -import {ForwarderExample} from "./examples/Forwarder.e.sol"; -import {INPUT, EXPECTED_OUTPUT} from "./examples/ForwarderTarget.e.sol"; -import {TxGen} from "./libs/TxGen.sol"; -import {DeployRiscZeroContractsMock} from "./script/DeployRiscZeroContractsMock.s.sol"; -import {CommitmentAccumulator} from "./../src/state/CommitmentAccumulator.sol"; +import { RiscZeroVerifierEmergencyStop } from "@risc0-ethereum/RiscZeroVerifierEmergencyStop.sol"; +import { RiscZeroVerifierRouter } from "@risc0-ethereum/RiscZeroVerifierRouter.sol"; +import { RiscZeroMockVerifier } from "@risc0-ethereum/test/RiscZeroMockVerifier.sol"; +import { Test } from "forge-std/Test.sol"; +import { Vm } from "forge-std/Vm.sol"; +import { IProtocolAdapter } from "./../src/interfaces/IProtocolAdapter.sol"; +import { MerkleTree } from "./../src/libs/MerkleTree.sol"; +import { RiscZeroUtils } from "./../src/libs/RiscZeroUtils.sol"; +import { ProtocolAdapter } from "./../src/ProtocolAdapter.sol"; +import { Compliance } from "./../src/proving/Compliance.sol"; +import { Logic } from "./../src/proving/Logic.sol"; +import { CommitmentAccumulator } from "./../src/state/CommitmentAccumulator.sol"; +import { NullifierSet } from "./../src/state/NullifierSet.sol"; +import { Transaction, Action } from "./../src/Types.sol"; +import { ForwarderExample } from "./examples/Forwarder.e.sol"; +import { INPUT, EXPECTED_OUTPUT } from "./examples/ForwarderTarget.e.sol"; +import { TxGen } from "./libs/TxGen.sol"; +import { DeployRiscZeroContractsMock } from "./script/DeployRiscZeroContractsMock.s.sol"; contract ProtocolAdapterMockVerifierTest is Test { using MerkleTree for bytes32[]; @@ -91,13 +91,11 @@ contract ProtocolAdapterMockVerifierTest is Test { bytes32 internal _carrierLabelRef; - constructor() { + function setUp() public { (_router, _emergencyStop, _mockVerifier) = new DeployRiscZeroContractsMock().run(); _verifierSelector = _mockVerifier.SELECTOR(); - _mockPa = new ProtocolAdapter(_router, _mockVerifier.SELECTOR(), _EMERGENCY_COMMITTEE); - } + _mockPa = new ProtocolAdapter(_router, _verifierSelector, _EMERGENCY_COMMITTEE); - function setUp() public { _fwd = address( new ForwarderExample({protocolAdapter: address(_mockPa), calldataCarrierLogicRef: _CARRIER_LOGIC_REF}) ); @@ -636,19 +634,23 @@ contract ProtocolAdapterMockVerifierTest is Test { /// @notice Make transaction fail by ensuring that one of its forwarder call outputs mismatch. function mutationTestExecuteMismatchingForwarderCallOutputsFail( - Transaction calldata transaction_calldata, + Transaction calldata transactionCalldata, MismatchingForwarderCallOutputsFailParams memory params - ) public {Transaction memory transaction = transaction_calldata; + ) public { + Transaction memory transaction = transactionCalldata; // Wrap the action index into range params.actionIdx = params.actionIdx % transaction.actions.length; - Action calldata action_calldata = transaction_calldata.actions[params.actionIdx]; + Action calldata actionCalldata = transactionCalldata.actions[params.actionIdx]; Action memory action = transaction.actions[params.actionIdx]; Compliance.VerifierInput[] memory complianceVerifierInputs = action.complianceVerifierInputs; // Wrap the logic verifier input index into range params.inputIdx = params.inputIdx % complianceVerifierInputs.length; Compliance.VerifierInput memory complianceVerifierInput = complianceVerifierInputs[params.inputIdx]; - bytes32 tag = params.consumed ? complianceVerifierInput.instance.consumed.nullifier : complianceVerifierInput.instance.created.commitment; - Logic.VerifierInput memory logicVerifierInput = action_calldata.logicVerifierInputs.lookup(tag); + bytes32 tag = params.consumed + ? complianceVerifierInput.instance.consumed.nullifier + : complianceVerifierInput.instance.created.commitment; + uint256 logicVerifierInputIdx = actionCalldata.logicVerifierInputs.lookup(tag); + Logic.VerifierInput memory logicVerifierInput = action.logicVerifierInputs[logicVerifierInputIdx]; Logic.ExpirableBlob[] memory externalPayloads = logicVerifierInput.appData.externalPayload; // Cannot do the mutation if transaction has no external payloads vm.assume(externalPayloads.length > 0); @@ -663,7 +665,8 @@ contract ProtocolAdapterMockVerifierTest is Test { // Re-encode the calldata and replace the value in the external payloads externalPayloads[params.payloadIdx].blob = abi.encode(untrustedForwarder, input, params.output); // Compute the action tree root - bytes32[] memory actionTreeTags = action_calldata.complianceVerifierInputs.computeActionTreeTags(action.complianceVerifierInputs.length); + bytes32[] memory actionTreeTags = + actionCalldata.complianceVerifierInputs.computeActionTreeTags(action.complianceVerifierInputs.length); // Recompute the logic verifier input proof logicVerifierInput.proof = _mockVerifier.mockProve({ imageId: logicVerifierInput.verifyingKey, @@ -682,14 +685,12 @@ contract ProtocolAdapterMockVerifierTest is Test { MismatchingForwarderCallOutputsFailParams memory params ) public { bytes32 carrierLogicRef = bytes32(uint256(123)); - address fwd = - address(new ForwarderExample({protocolAdapter: address(this), calldataCarrierLogicRef: carrierLogicRef})); address fwd2 = - address(new ForwarderExample({protocolAdapter: address(this), calldataCarrierLogicRef: carrierLogicRef})); - assertNotEq(fwd, fwd2); + address(new ForwarderExample({protocolAdapter: address(_mockPa), calldataCarrierLogicRef: carrierLogicRef})); + assertNotEq(_fwd, fwd2); address[] memory fwdList = new address[](2); - fwdList[0] = fwd; + fwdList[0] = _fwd; fwdList[1] = fwd2; TxGen.ResourceAndAppData[] memory consumed = _exampleResourceAndEmptyAppData({nonce: 0}); From 628c558dd4edc89068a8375257de330e83fd387c Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 25 Sep 2025 12:33:59 +0200 Subject: [PATCH 05/11] Use Logic.lookup in order to find logic verifier inputs. --- contracts/test/ProtocolAdapterMock.t.sol | 45 ++++++++++-------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/contracts/test/ProtocolAdapterMock.t.sol b/contracts/test/ProtocolAdapterMock.t.sol index d6508e0f..b328b31b 100644 --- a/contracts/test/ProtocolAdapterMock.t.sol +++ b/contracts/test/ProtocolAdapterMock.t.sol @@ -410,28 +410,24 @@ contract ProtocolAdapterMockVerifierTest is Test { /// @notice Make transaction fail by ensuring unknown nullifier function mutationTestExecuteUnknownNullifierTagFails( - Transaction memory transaction, + Transaction calldata transactionCalldata, UnknownTagFailsParams memory params ) public { + Transaction memory transaction = transactionCalldata; // Wrap the action index into range params.actionIdx = params.actionIdx % transaction.actions.length; - Compliance.VerifierInput[] memory complianceVerifierInputs = - transaction.actions[params.actionIdx].complianceVerifierInputs; + Action calldata actionCalldata = transactionCalldata.actions[params.actionIdx]; + Action memory action = transaction.actions[params.actionIdx]; + Compliance.VerifierInput[] memory complianceVerifierInputs = action.complianceVerifierInputs; // Wrap the compliance verifier input index into range params.inputIdx = params.inputIdx % complianceVerifierInputs.length; Compliance.VerifierInput memory complianceVerifierInput = complianceVerifierInputs[params.inputIdx]; // Make sure that the planned corruption will change something - vm.assume(complianceVerifierInput.instance.consumed.nullifier != params.tag); + bytes32 tag = complianceVerifierInput.instance.consumed.nullifier; + vm.assume(tag != params.tag); // Finally, corrupt the corresponding logic verifier input tag - Logic.VerifierInput[] memory logicVerifierInputs = transaction.actions[params.actionIdx].logicVerifierInputs; - // Do a linear search to identify the corresponding logic verifier input - for (uint256 i = 0; i < logicVerifierInputs.length; i++) { - // Select the logic verifier input with a tag matching the nullifier - if (logicVerifierInputs[i].tag == complianceVerifierInput.instance.consumed.nullifier) { - // Finally, corrupt the logic verifier input tag so it can no longer be found - logicVerifierInputs[i].tag = params.tag; - } - } + uint256 logicVerifierInputIdx = actionCalldata.logicVerifierInputs.lookup(tag); + action.logicVerifierInputs[logicVerifierInputIdx].tag = params.tag; // With an unknown tag, we expect failure vm.expectRevert( abi.encodeWithSelector( @@ -454,33 +450,30 @@ contract ProtocolAdapterMockVerifierTest is Test { }); (Transaction memory txn,) = vm.transaction({mockVerifier: _mockVerifier, nonce: 0, configs: configs}); - mutationTestExecuteUnknownNullifierTagFails(txn, params); + this.mutationTestExecuteUnknownNullifierTagFails(txn, params); } /// @notice Make transaction fail by ensuring unknown commitment function mutationTestExecuteUnknownCommitmentTagFails( - Transaction memory transaction, + Transaction calldata transactionCalldata, UnknownTagFailsParams memory params ) public { + Transaction memory transaction = transactionCalldata; // Wrap the action index into range params.actionIdx = params.actionIdx % transaction.actions.length; + Action calldata actionCalldata = transactionCalldata.actions[params.actionIdx]; + Action memory action = transaction.actions[params.actionIdx]; Compliance.VerifierInput[] memory complianceVerifierInputs = transaction.actions[params.actionIdx].complianceVerifierInputs; // Wrap the compliance verifier input index into range params.inputIdx = params.inputIdx % complianceVerifierInputs.length; Compliance.VerifierInput memory complianceVerifierInput = complianceVerifierInputs[params.inputIdx]; // Make sure that the planned corruption will change something - vm.assume(complianceVerifierInput.instance.created.commitment != params.tag); + bytes32 tag = complianceVerifierInput.instance.created.commitment; + vm.assume(tag != params.tag); // Finally, corrupt the corresponding logic verifier input tag - Logic.VerifierInput[] memory logicVerifierInputs = transaction.actions[params.actionIdx].logicVerifierInputs; - // Do a linear search to identify the corresponding logic verifier input - for (uint256 i = 0; i < logicVerifierInputs.length; i++) { - // Select the logic verifier input with a tag matching the commitment - if (logicVerifierInputs[i].tag == complianceVerifierInput.instance.created.commitment) { - // Finally, corrupt the logic verifier input tag so it can no longer be found - logicVerifierInputs[i].tag = params.tag; - } - } + uint256 logicVerifierInputIdx = actionCalldata.logicVerifierInputs.lookup(tag); + action.logicVerifierInputs[logicVerifierInputIdx].tag = params.tag; // With an unknown tag, we expect failure vm.expectRevert( abi.encodeWithSelector( @@ -503,7 +496,7 @@ contract ProtocolAdapterMockVerifierTest is Test { }); (Transaction memory txn,) = vm.transaction({mockVerifier: _mockVerifier, nonce: 0, configs: configs}); - mutationTestExecuteUnknownCommitmentTagFails(txn, params); + this.mutationTestExecuteUnknownCommitmentTagFails(txn, params); } /// @notice Make transaction fail by ensuring that it has less compliance verifier inputs From 40446611849a2f7d28a1e50a0116a11b21081f76 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 25 Sep 2025 12:40:41 +0200 Subject: [PATCH 06/11] Fuse together the unknown commitment and unknown nullifier tests. --- contracts/src/proving/Logic.sol | 6 +- contracts/test/ProtocolAdapterMock.t.sol | 103 ++++++----------------- 2 files changed, 31 insertions(+), 78 deletions(-) diff --git a/contracts/src/proving/Logic.sol b/contracts/src/proving/Logic.sol index e782264e..f6194561 100644 --- a/contracts/src/proving/Logic.sol +++ b/contracts/src/proving/Logic.sol @@ -52,12 +52,12 @@ library Logic { /// @notice Looks up a `VerifierInput` element from a list by its tag. /// @param list The list of verifier inputs. /// @param tag The tag to look up. - /// @return foundElement The found `VerifierInput` element. - function lookup(VerifierInput[] calldata list, bytes32 tag) internal pure returns (uint256) { + /// @return foundElementIdx The index of the found `VerifierInput` element. + function lookup(VerifierInput[] calldata list, bytes32 tag) internal pure returns (uint256 foundElementIdx) { uint256 len = list.length; for (uint256 i = 0; i < len; ++i) { if (list[i].tag == tag) { - return i; + return foundElementIdx = i; } } revert TagNotFound(tag); diff --git a/contracts/test/ProtocolAdapterMock.t.sol b/contracts/test/ProtocolAdapterMock.t.sol index b328b31b..89dd51f8 100644 --- a/contracts/test/ProtocolAdapterMock.t.sol +++ b/contracts/test/ProtocolAdapterMock.t.sol @@ -1,24 +1,24 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.30; -import { RiscZeroVerifierEmergencyStop } from "@risc0-ethereum/RiscZeroVerifierEmergencyStop.sol"; -import { RiscZeroVerifierRouter } from "@risc0-ethereum/RiscZeroVerifierRouter.sol"; -import { RiscZeroMockVerifier } from "@risc0-ethereum/test/RiscZeroMockVerifier.sol"; -import { Test } from "forge-std/Test.sol"; -import { Vm } from "forge-std/Vm.sol"; -import { IProtocolAdapter } from "./../src/interfaces/IProtocolAdapter.sol"; -import { MerkleTree } from "./../src/libs/MerkleTree.sol"; -import { RiscZeroUtils } from "./../src/libs/RiscZeroUtils.sol"; -import { ProtocolAdapter } from "./../src/ProtocolAdapter.sol"; -import { Compliance } from "./../src/proving/Compliance.sol"; -import { Logic } from "./../src/proving/Logic.sol"; -import { CommitmentAccumulator } from "./../src/state/CommitmentAccumulator.sol"; -import { NullifierSet } from "./../src/state/NullifierSet.sol"; -import { Transaction, Action } from "./../src/Types.sol"; -import { ForwarderExample } from "./examples/Forwarder.e.sol"; -import { INPUT, EXPECTED_OUTPUT } from "./examples/ForwarderTarget.e.sol"; -import { TxGen } from "./libs/TxGen.sol"; -import { DeployRiscZeroContractsMock } from "./script/DeployRiscZeroContractsMock.s.sol"; +import {RiscZeroVerifierEmergencyStop} from "@risc0-ethereum/RiscZeroVerifierEmergencyStop.sol"; +import {RiscZeroVerifierRouter} from "@risc0-ethereum/RiscZeroVerifierRouter.sol"; +import {RiscZeroMockVerifier} from "@risc0-ethereum/test/RiscZeroMockVerifier.sol"; +import {Test} from "forge-std/Test.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {IProtocolAdapter} from "./../src/interfaces/IProtocolAdapter.sol"; +import {MerkleTree} from "./../src/libs/MerkleTree.sol"; +import {RiscZeroUtils} from "./../src/libs/RiscZeroUtils.sol"; +import {ProtocolAdapter} from "./../src/ProtocolAdapter.sol"; +import {Compliance} from "./../src/proving/Compliance.sol"; +import {Logic} from "./../src/proving/Logic.sol"; +import {CommitmentAccumulator} from "./../src/state/CommitmentAccumulator.sol"; +import {NullifierSet} from "./../src/state/NullifierSet.sol"; +import {Transaction, Action} from "./../src/Types.sol"; +import {ForwarderExample} from "./examples/Forwarder.e.sol"; +import {INPUT, EXPECTED_OUTPUT} from "./examples/ForwarderTarget.e.sol"; +import {TxGen} from "./libs/TxGen.sol"; +import {DeployRiscZeroContractsMock} from "./script/DeployRiscZeroContractsMock.s.sol"; contract ProtocolAdapterMockVerifierTest is Test { using MerkleTree for bytes32[]; @@ -54,6 +54,7 @@ contract ProtocolAdapterMockVerifierTest is Test { uint256 actionIdx; uint256 inputIdx; bytes32 tag; + bool consumed; } /// @notice The parameters necessary to make a failing mutation to a transaction @@ -408,8 +409,8 @@ contract ProtocolAdapterMockVerifierTest is Test { mutationTestExecuteUnknownSelectorFails(txn, params); } - /// @notice Make transaction fail by ensuring unknown nullifier - function mutationTestExecuteUnknownNullifierTagFails( + /// @notice Make transaction fail by ensuring unknown tag + function mutationTestExecuteUnknownTagFails( Transaction calldata transactionCalldata, UnknownTagFailsParams memory params ) public { @@ -423,69 +424,21 @@ contract ProtocolAdapterMockVerifierTest is Test { params.inputIdx = params.inputIdx % complianceVerifierInputs.length; Compliance.VerifierInput memory complianceVerifierInput = complianceVerifierInputs[params.inputIdx]; // Make sure that the planned corruption will change something - bytes32 tag = complianceVerifierInput.instance.consumed.nullifier; - vm.assume(tag != params.tag); - // Finally, corrupt the corresponding logic verifier input tag - uint256 logicVerifierInputIdx = actionCalldata.logicVerifierInputs.lookup(tag); - action.logicVerifierInputs[logicVerifierInputIdx].tag = params.tag; - // With an unknown tag, we expect failure - vm.expectRevert( - abi.encodeWithSelector( - Logic.TagNotFound.selector, complianceVerifierInputs[params.inputIdx].instance.consumed.nullifier - ) - ); - // Finally, execute the transaction to make sure that it fails - _mockPa.execute(transaction); - } - - /// @notice Test that transactions with unknown nullifier tags fail - function testFuzz_execute_unknown_nullifier_tag_fails( - uint8 actionCount, - uint8 complianceUnitCount, - UnknownTagFailsParams memory params - ) public { - TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({ - actionCount: uint8(bound(actionCount, 1, 5)), - complianceUnitCount: uint8(bound(complianceUnitCount, 1, 5)) - }); - - (Transaction memory txn,) = vm.transaction({mockVerifier: _mockVerifier, nonce: 0, configs: configs}); - this.mutationTestExecuteUnknownNullifierTagFails(txn, params); - } - - /// @notice Make transaction fail by ensuring unknown commitment - function mutationTestExecuteUnknownCommitmentTagFails( - Transaction calldata transactionCalldata, - UnknownTagFailsParams memory params - ) public { - Transaction memory transaction = transactionCalldata; - // Wrap the action index into range - params.actionIdx = params.actionIdx % transaction.actions.length; - Action calldata actionCalldata = transactionCalldata.actions[params.actionIdx]; - Action memory action = transaction.actions[params.actionIdx]; - Compliance.VerifierInput[] memory complianceVerifierInputs = - transaction.actions[params.actionIdx].complianceVerifierInputs; - // Wrap the compliance verifier input index into range - params.inputIdx = params.inputIdx % complianceVerifierInputs.length; - Compliance.VerifierInput memory complianceVerifierInput = complianceVerifierInputs[params.inputIdx]; - // Make sure that the planned corruption will change something - bytes32 tag = complianceVerifierInput.instance.created.commitment; + bytes32 tag = params.consumed + ? complianceVerifierInput.instance.consumed.nullifier + : complianceVerifierInput.instance.created.commitment; vm.assume(tag != params.tag); // Finally, corrupt the corresponding logic verifier input tag uint256 logicVerifierInputIdx = actionCalldata.logicVerifierInputs.lookup(tag); action.logicVerifierInputs[logicVerifierInputIdx].tag = params.tag; // With an unknown tag, we expect failure - vm.expectRevert( - abi.encodeWithSelector( - Logic.TagNotFound.selector, complianceVerifierInputs[params.inputIdx].instance.created.commitment - ) - ); + vm.expectRevert(abi.encodeWithSelector(Logic.TagNotFound.selector, tag)); // Finally, execute the transaction to make sure that it fails _mockPa.execute(transaction); } - /// @notice Test that transactions with unknown commitment tags fail - function testFuzz_execute_unknown_commitment_tag_fails( + /// @notice Test that transactions with unknown tags fail + function testFuzz_execute_unknown_tag_fails( uint8 actionCount, uint8 complianceUnitCount, UnknownTagFailsParams memory params @@ -496,7 +449,7 @@ contract ProtocolAdapterMockVerifierTest is Test { }); (Transaction memory txn,) = vm.transaction({mockVerifier: _mockVerifier, nonce: 0, configs: configs}); - this.mutationTestExecuteUnknownCommitmentTagFails(txn, params); + this.mutationTestExecuteUnknownTagFails(txn, params); } /// @notice Make transaction fail by ensuring that it has less compliance verifier inputs From d6401619f483462dec6a8fc508454102b8176f33 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 25 Sep 2025 13:13:43 +0200 Subject: [PATCH 07/11] Remove the duplicated failure structure. --- contracts/test/ProtocolAdapterMock.t.sol | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/contracts/test/ProtocolAdapterMock.t.sol b/contracts/test/ProtocolAdapterMock.t.sol index 89dd51f8..eb1542f0 100644 --- a/contracts/test/ProtocolAdapterMock.t.sol +++ b/contracts/test/ProtocolAdapterMock.t.sol @@ -36,12 +36,6 @@ contract ProtocolAdapterMockVerifierTest is Test { bytes32 commitmentTreeRoot; } - /// @notice The parameters necessary to make a failing mutation to a transaction - struct ShortProofFailsParams { - uint256 actionIdx; - uint256 inputIdx; - } - /// @notice The parameters necessary to make a failing mutation to a transaction struct UnknownSelectorFailsParams { uint256 actionIdx; @@ -58,7 +52,7 @@ contract ProtocolAdapterMockVerifierTest is Test { } /// @notice The parameters necessary to make a failing mutation to a transaction - struct MismatchingResourcesFailParams { + struct GenericFailParams { uint256 actionIdx; uint256 inputIdx; } @@ -330,7 +324,7 @@ contract ProtocolAdapterMockVerifierTest is Test { } /// @notice Make transaction fail by giving it a proof that's too short - function mutationTestExecuteShortProofFails(Transaction memory transaction, ShortProofFailsParams memory params) + function mutationTestExecuteShortProofFails(Transaction memory transaction, GenericFailParams memory params) public { uint256 minProofLen = 4; @@ -357,7 +351,7 @@ contract ProtocolAdapterMockVerifierTest is Test { function testFuzz_execute_short_proof_fails( uint8 actionCount, uint8 complianceUnitCount, - ShortProofFailsParams memory params + GenericFailParams memory params ) public { TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({ actionCount: uint8(bound(actionCount, 1, 5)), @@ -455,7 +449,7 @@ contract ProtocolAdapterMockVerifierTest is Test { /// @notice Make transaction fail by ensuring that it has less compliance verifier inputs function mutationTestExecuteMissingComplianceVerifierInputFail( Transaction memory transaction, - MismatchingResourcesFailParams memory params + GenericFailParams memory params ) public { // Wrap the action index into range params.actionIdx = params.actionIdx % transaction.actions.length; @@ -487,7 +481,7 @@ contract ProtocolAdapterMockVerifierTest is Test { function testFuzz_execute_missing_compliance_verifier_input_fail( uint8 actionCount, uint8 complianceUnitCount, - MismatchingResourcesFailParams memory params + GenericFailParams memory params ) public { TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({ actionCount: uint8(bound(actionCount, 1, 5)), @@ -501,7 +495,7 @@ contract ProtocolAdapterMockVerifierTest is Test { /// @notice Make transaction fail by ensuring that it has less logic verifier inputs function mutationTestExecuteMissingLogicVerifierInputFail( Transaction memory transaction, - MismatchingResourcesFailParams memory params + GenericFailParams memory params ) public { // Wrap the action index into range params.actionIdx = params.actionIdx % transaction.actions.length; @@ -533,7 +527,7 @@ contract ProtocolAdapterMockVerifierTest is Test { function testFuzz_execute_missing_logic_verifier_input_fail( uint8 actionCount, uint8 complianceUnitCount, - MismatchingResourcesFailParams memory params + GenericFailParams memory params ) public { TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({ actionCount: uint8(bound(actionCount, 1, 5)), From 28b5ad8fa8e917a0c6d81f9f8a9ec91e8611f68e Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 25 Sep 2025 14:39:44 +0200 Subject: [PATCH 08/11] Use array slicing to truncate compliance verifier input proof. --- contracts/test/ProtocolAdapterMock.t.sol | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/contracts/test/ProtocolAdapterMock.t.sol b/contracts/test/ProtocolAdapterMock.t.sol index eb1542f0..a343d985 100644 --- a/contracts/test/ProtocolAdapterMock.t.sol +++ b/contracts/test/ProtocolAdapterMock.t.sol @@ -324,9 +324,11 @@ contract ProtocolAdapterMockVerifierTest is Test { } /// @notice Make transaction fail by giving it a proof that's too short - function mutationTestExecuteShortProofFails(Transaction memory transaction, GenericFailParams memory params) - public - { + function mutationTestExecuteShortProofFails( + Transaction calldata transactionCalldata, + GenericFailParams memory params + ) public { + Transaction memory transaction = transactionCalldata; uint256 minProofLen = 4; // Wrap the action index into range params.actionIdx = params.actionIdx % transaction.actions.length; @@ -335,12 +337,9 @@ contract ProtocolAdapterMockVerifierTest is Test { // Wrap the compliance verifier input index into range params.inputIdx = params.inputIdx % complianceVerifierInputs.length; // Finally truncate the compliance proof to below the minimum - bytes memory proof = complianceVerifierInputs[params.inputIdx].proof; - bytes memory truncatedProof = new bytes(proof.length % minProofLen); - for (uint256 k = 0; k < truncatedProof.length; k++) { - truncatedProof[k] = proof[k]; - } - complianceVerifierInputs[params.inputIdx].proof = truncatedProof; + bytes calldata proof = + transactionCalldata.actions[params.actionIdx].complianceVerifierInputs[params.inputIdx].proof; + complianceVerifierInputs[params.inputIdx].proof = proof[0:(proof.length % minProofLen)]; // With a short proof, we expect an EVM error (which is message-less) vm.expectRevert(bytes(""), address(_router)); // Finally, execute the transaction to make sure that it fails @@ -359,7 +358,7 @@ contract ProtocolAdapterMockVerifierTest is Test { }); (Transaction memory txn,) = vm.transaction({mockVerifier: _mockVerifier, nonce: 0, configs: configs}); - mutationTestExecuteShortProofFails(txn, params); + this.mutationTestExecuteShortProofFails(txn, params); } /// @notice Make transaction fail by giving it an unknown selector. From 95fa2868c130c362478c8ac0896b58b0b69d6f90 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 1 Oct 2025 17:17:40 +0200 Subject: [PATCH 09/11] Generalized transaction generation. --- contracts/test/ProtocolAdapterMock.t.sol | 4 + contracts/test/libs/TxGen.sol | 193 +++++++++++++++++++++++ 2 files changed, 197 insertions(+) diff --git a/contracts/test/ProtocolAdapterMock.t.sol b/contracts/test/ProtocolAdapterMock.t.sol index a343d985..1de66dcc 100644 --- a/contracts/test/ProtocolAdapterMock.t.sol +++ b/contracts/test/ProtocolAdapterMock.t.sol @@ -246,6 +246,10 @@ contract ProtocolAdapterMockVerifierTest is Test { _mockPa.execute(txn); } + function test_random_transactions_execute(TxGen.TransactionParams memory params) public { + _mockPa.execute(vm.transaction(_mockVerifier, params)); + } + function test_execute_reverts_on_pre_existing_nullifier() public { TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({actionCount: 1, complianceUnitCount: 1}); diff --git a/contracts/test/libs/TxGen.sol b/contracts/test/libs/TxGen.sol index a20af6f3..a70e3ab9 100644 --- a/contracts/test/libs/TxGen.sol +++ b/contracts/test/libs/TxGen.sol @@ -19,6 +19,12 @@ library TxGen { using Logic for Logic.VerifierInput[]; using Delta for uint256[2]; + uint256 public constant MAX_ACTIONS = 4; + uint256 public constant MAX_RESOURCES = 5; + // The order of the secp256k1 curve. + uint256 internal constant SECP256K1_ORDER = + 115792089237316195423570985008687907852837564279074904382605163141518161494337; + struct ActionConfig { uint256 complianceUnitCount; } @@ -33,6 +39,18 @@ library TxGen { ResourceAndAppData[] created; } + struct ActionParams { + Resource[2][MAX_RESOURCES] resources; + uint256[MAX_RESOURCES] bijection; + uint256 targetResourcesLen; + uint256[2][MAX_RESOURCES] valueCommitmentRandomness; + } + + struct TransactionParams { + ActionParams[MAX_ACTIONS] actionParams; + uint256 targetActionsLen; + } + error ConsumedCreatedCountMismatch(uint256 nConsumed, uint256 nCreated); function complianceVerifierInput( @@ -257,6 +275,142 @@ library TxGen { txn = Transaction({actions: actions, deltaProof: proof}); } + function generateAction(VmSafe vm, RiscZeroMockVerifier mockVerifier, ActionParams memory params) + internal + returns (Action memory action, uint256 totalValueCommitmentRandomness) + { + Resource[2][] memory truncatedResources = + truncateResources(params.resources, params.targetResourcesLen % MAX_RESOURCES); + action.logicVerifierInputs = new Logic.VerifierInput[](truncatedResources.length * 2); + action.complianceVerifierInputs = new Compliance.VerifierInput[](truncatedResources.length); + // Created empty app data for all the resources + Logic.AppData memory appData = Logic.AppData({ + resourcePayload: new Logic.ExpirableBlob[](0), + discoveryPayload: new Logic.ExpirableBlob[](0), + externalPayload: new Logic.ExpirableBlob[](0), + applicationPayload: new Logic.ExpirableBlob[](0) + }); + // Match the created and consumed resources + uint256[] memory bijection = generateBijection(params.bijection, truncatedResources.length); + for (uint256 i = 0; i < truncatedResources.length; ++i) { + truncatedResources[bijection[i]][1].quantity = truncatedResources[i][0].quantity; + truncatedResources[bijection[i]][1].logicRef = truncatedResources[i][0].logicRef; + truncatedResources[bijection[i]][1].labelRef = truncatedResources[i][0].labelRef; + } + // Compute action tree tags and action tree root + bytes32[] memory actionTreeTags = new bytes32[](2 * truncatedResources.length); + totalValueCommitmentRandomness = 0; + for (uint256 i = 0; i < truncatedResources.length; ++i) { + uint256 index = (i * 2); + + actionTreeTags[index] = nullifier(truncatedResources[i][0], 0); + actionTreeTags[index + 1] = commitment(truncatedResources[i][1]); + // Adjust and accumulate the value randomness commitments + params.valueCommitmentRandomness[i][0] = + 1 + (params.valueCommitmentRandomness[i][0] % (SECP256K1_ORDER - 1)); + params.valueCommitmentRandomness[i][1] = + 1 + (params.valueCommitmentRandomness[i][1] % (SECP256K1_ORDER - 1)); + totalValueCommitmentRandomness = + addmod(totalValueCommitmentRandomness, params.valueCommitmentRandomness[i][0], SECP256K1_ORDER); + totalValueCommitmentRandomness = + addmod(totalValueCommitmentRandomness, params.valueCommitmentRandomness[i][1], SECP256K1_ORDER); + } + bytes32 actionTreeRoot = actionTreeTags.computeRoot(); + // Create logic and compliance verifier inputs + for (uint256 i = 0; i < truncatedResources.length; i++) { + Resource memory consumedResource = truncatedResources[i][0]; + Resource memory createdResource = truncatedResources[i][1]; + bytes32 nf = nullifier(consumedResource, 0); + bytes32 cm = commitment(createdResource); + + // Created logic verifier input for a consumed resource + action.logicVerifierInputs[2 * i] = + Logic.VerifierInput({tag: nf, verifyingKey: consumedResource.logicRef, proof: "", appData: appData}); + action.logicVerifierInputs[2 * i].proof = mockVerifier.mockProve({ + imageId: consumedResource.logicRef, + journalDigest: action.logicVerifierInputs[2 * i].toJournalDigest(actionTreeRoot, true) + }).seal; + // Create logic verifier input for a created resource + action.logicVerifierInputs[2 * i + 1] = + Logic.VerifierInput({tag: cm, verifyingKey: createdResource.logicRef, proof: "", appData: appData}); + action.logicVerifierInputs[2 * i + 1].proof = mockVerifier.mockProve({ + imageId: createdResource.logicRef, + journalDigest: action.logicVerifierInputs[2 * i + 1].toJournalDigest(actionTreeRoot, false) + }).seal; + // Create the delta for the consumed resource + uint256[2] memory unitDelta = DeltaGen.generateInstance( + vm, + DeltaGen.InstanceInputs({ + kind: kind(consumedResource), + quantity: consumedResource.quantity, + consumed: true, + valueCommitmentRandomness: params.valueCommitmentRandomness[i][0] + }) + ); + + // Add the delta for the created resource + unitDelta = Delta.add( + unitDelta, + DeltaGen.generateInstance( + vm, + DeltaGen.InstanceInputs({ + kind: kind(createdResource), + quantity: createdResource.quantity, + consumed: false, + valueCommitmentRandomness: params.valueCommitmentRandomness[i][1] + }) + ) + ); + // Create the compliance verifier input + Compliance.Instance memory instance = Compliance.Instance({ + unitDeltaX: bytes32(unitDelta[0]), + unitDeltaY: bytes32(unitDelta[1]), + consumed: Compliance.ConsumedRefs({ + nullifier: nf, + logicRef: consumedResource.logicRef, + commitmentTreeRoot: initialRoot() + }), + created: Compliance.CreatedRefs({commitment: cm, logicRef: createdResource.logicRef}) + }); + action.complianceVerifierInputs[i] = Compliance.VerifierInput({ + instance: instance, + proof: mockVerifier.mockProve({ + imageId: Compliance._VERIFYING_KEY, + journalDigest: instance.toJournalDigest() + }).seal + }); + } + } + + function transaction(VmSafe vm, RiscZeroMockVerifier mockVerifier, TransactionParams memory params) + internal + returns (Transaction memory txn) + { + // Generate actions + Action[] memory actions = new Action[](params.targetActionsLen % MAX_ACTIONS); + uint256 totalValueCommitmentRandomness = 0; + for (uint256 i = 0; i < actions.length; i++) { + uint256 valueCommitmentRandomness; + (actions[i], valueCommitmentRandomness) = generateAction(vm, mockVerifier, params.actionParams[i]); + totalValueCommitmentRandomness = + addmod(totalValueCommitmentRandomness, valueCommitmentRandomness, SECP256K1_ORDER); + } + // Generate delta proof + bytes memory proof = ""; + bytes32[] memory tags = TxGen.collectTags(actions); + if (tags.length != 0) { + proof = DeltaGen.generateProof( + vm, + DeltaGen.ProofInputs({ + valueCommitmentRandomness: totalValueCommitmentRandomness, + verifyingKey: Delta.computeVerifyingKey(tags) + }) + ); + } + // Generate transaction + txn = Transaction({actions: actions, deltaProof: proof}); + } + function logicVerifierInput( RiscZeroMockVerifier mockVerifier, bytes32 actionTreeRoot, @@ -378,4 +532,43 @@ library TxGen { function initialRoot() internal pure returns (bytes32 root) { root = SHA256.EMPTY_HASH; } + + function truncateResources(Resource[2][MAX_RESOURCES] memory resources, uint256 len) + internal + pure + returns (Resource[2][] memory truncatedResources) + { + truncatedResources = new Resource[2][](len); + for (uint256 i = 0; i < len; i++) { + truncatedResources[i][0] = resources[i][0]; + truncatedResources[i][1] = resources[i][1]; + } + } + + function generateBijection(uint256[MAX_RESOURCES] memory input, uint256 len) + internal + pure + returns (uint256[] memory output) + { + output = new uint256[](len); + uint256[] memory duplicates = new uint256[](len); + uint256 duplicateCount = 0; + for (uint256 i = 0; i < len; i++) { + output[i] = duplicates[i] = len; + input[i] %= len; + } + for (uint256 i = 0; i < len; i++) { + if (output[input[i]] == len) { + output[input[i]] = i; + } else { + duplicates[duplicateCount++] = i; + } + } + duplicateCount = 0; + for (uint256 i = 0; i < len; i++) { + if (output[i] == len) { + output[i] = duplicates[duplicateCount++]; + } + } + } } From 988e7d37a3dd0b57d9d4447c11cb1da389061eef Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 1 Oct 2025 17:45:17 +0200 Subject: [PATCH 10/11] Integrated generalized transaction generation into other tests. --- contracts/test/ProtocolAdapterMock.t.sol | 91 ++++++------------------ 1 file changed, 21 insertions(+), 70 deletions(-) diff --git a/contracts/test/ProtocolAdapterMock.t.sol b/contracts/test/ProtocolAdapterMock.t.sol index 1de66dcc..09285359 100644 --- a/contracts/test/ProtocolAdapterMock.t.sol +++ b/contracts/test/ProtocolAdapterMock.t.sol @@ -314,17 +314,10 @@ contract ProtocolAdapterMockVerifierTest is Test { /// @notice Test that transactions with nonexistent rotts fail function testFuzz_execute_non_existing_root_fails( - uint8 actionCount, - uint8 complianceUnitCount, - NonExistingRootFailsParams memory params + TxGen.TransactionParams memory txParams, + NonExistingRootFailsParams memory mutParams ) public { - TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({ - actionCount: uint8(bound(actionCount, 1, 5)), - complianceUnitCount: uint8(bound(complianceUnitCount, 1, 5)) - }); - - (Transaction memory txn,) = vm.transaction({mockVerifier: _mockVerifier, nonce: 0, configs: configs}); - mutationTestExecuteNonExistingRootFails(txn, params); + mutationTestExecuteNonExistingRootFails(vm.transaction(_mockVerifier, txParams), mutParams); } /// @notice Make transaction fail by giving it a proof that's too short @@ -352,17 +345,10 @@ contract ProtocolAdapterMockVerifierTest is Test { /// @notice Test that transactions with short proofs fail function testFuzz_execute_short_proof_fails( - uint8 actionCount, - uint8 complianceUnitCount, - GenericFailParams memory params + TxGen.TransactionParams memory txParams, + GenericFailParams memory mutParams ) public { - TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({ - actionCount: uint8(bound(actionCount, 1, 5)), - complianceUnitCount: uint8(bound(complianceUnitCount, 1, 5)) - }); - - (Transaction memory txn,) = vm.transaction({mockVerifier: _mockVerifier, nonce: 0, configs: configs}); - this.mutationTestExecuteShortProofFails(txn, params); + this.mutationTestExecuteShortProofFails(vm.transaction(_mockVerifier, txParams), mutParams); } /// @notice Make transaction fail by giving it an unknown selector. @@ -393,17 +379,10 @@ contract ProtocolAdapterMockVerifierTest is Test { /// @notice Test that transactions with unknown selectors fail function testFuzz_execute_unknown_selector_fails( - uint8 actionCount, - uint8 complianceUnitCount, - UnknownSelectorFailsParams memory params + TxGen.TransactionParams memory txParams, + UnknownSelectorFailsParams memory mutParams ) public { - TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({ - actionCount: uint8(bound(actionCount, 1, 5)), - complianceUnitCount: uint8(bound(complianceUnitCount, 1, 5)) - }); - - (Transaction memory txn,) = vm.transaction({mockVerifier: _mockVerifier, nonce: 0, configs: configs}); - mutationTestExecuteUnknownSelectorFails(txn, params); + mutationTestExecuteUnknownSelectorFails(vm.transaction(_mockVerifier, txParams), mutParams); } /// @notice Make transaction fail by ensuring unknown tag @@ -436,17 +415,10 @@ contract ProtocolAdapterMockVerifierTest is Test { /// @notice Test that transactions with unknown tags fail function testFuzz_execute_unknown_tag_fails( - uint8 actionCount, - uint8 complianceUnitCount, - UnknownTagFailsParams memory params + TxGen.TransactionParams memory txParams, + UnknownTagFailsParams memory mutParams ) public { - TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({ - actionCount: uint8(bound(actionCount, 1, 5)), - complianceUnitCount: uint8(bound(complianceUnitCount, 1, 5)) - }); - - (Transaction memory txn,) = vm.transaction({mockVerifier: _mockVerifier, nonce: 0, configs: configs}); - this.mutationTestExecuteUnknownTagFails(txn, params); + this.mutationTestExecuteUnknownTagFails(vm.transaction(_mockVerifier, txParams), mutParams); } /// @notice Make transaction fail by ensuring that it has less compliance verifier inputs @@ -482,17 +454,10 @@ contract ProtocolAdapterMockVerifierTest is Test { /// @notice Test that transactions with a missing compliance verifier input fail function testFuzz_execute_missing_compliance_verifier_input_fail( - uint8 actionCount, - uint8 complianceUnitCount, - GenericFailParams memory params + TxGen.TransactionParams memory txParams, + GenericFailParams memory mutParams ) public { - TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({ - actionCount: uint8(bound(actionCount, 1, 5)), - complianceUnitCount: uint8(bound(complianceUnitCount, 1, 5)) - }); - - (Transaction memory txn,) = vm.transaction({mockVerifier: _mockVerifier, nonce: 0, configs: configs}); - mutationTestExecuteMissingComplianceVerifierInputFail(txn, params); + mutationTestExecuteMissingComplianceVerifierInputFail(vm.transaction(_mockVerifier, txParams), mutParams); } /// @notice Make transaction fail by ensuring that it has less logic verifier inputs @@ -528,17 +493,10 @@ contract ProtocolAdapterMockVerifierTest is Test { /// @notice Test that transactions with a missing logic verifier input fail function testFuzz_execute_missing_logic_verifier_input_fail( - uint8 actionCount, - uint8 complianceUnitCount, - GenericFailParams memory params + TxGen.TransactionParams memory txParams, + GenericFailParams memory mutParams ) public { - TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({ - actionCount: uint8(bound(actionCount, 1, 5)), - complianceUnitCount: uint8(bound(complianceUnitCount, 1, 5)) - }); - - (Transaction memory txn,) = vm.transaction({mockVerifier: _mockVerifier, nonce: 0, configs: configs}); - mutationTestExecuteMissingLogicVerifierInputFail(txn, params); + mutationTestExecuteMissingLogicVerifierInputFail(vm.transaction(_mockVerifier, txParams), mutParams); } /// @notice Make transaction fail by making logic reference mismatch @@ -562,17 +520,10 @@ contract ProtocolAdapterMockVerifierTest is Test { /// @notice Test that transactions with mismatching logic references fail function testFuzz_execute_mismatching_logic_refs_fail( - uint8 actionCount, - uint8 complianceUnitCount, - MismatchingLogicRefsFailParams memory params + TxGen.TransactionParams memory txParams, + MismatchingLogicRefsFailParams memory mutParams ) public { - TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({ - actionCount: uint8(bound(actionCount, 1, 5)), - complianceUnitCount: uint8(bound(complianceUnitCount, 1, 5)) - }); - - (Transaction memory txn,) = vm.transaction({mockVerifier: _mockVerifier, nonce: 0, configs: configs}); - mutationTestExecuteMismatchingLogicRefsFail(txn, params); + mutationTestExecuteMismatchingLogicRefsFail(vm.transaction(_mockVerifier, txParams), mutParams); } /// @notice Make transaction fail by ensuring that one of its forwarder call outputs mismatch. From e8453d3fa93f61dbb7b6ab1d031b9cc4378c0d5e Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 1 Oct 2025 17:53:51 +0200 Subject: [PATCH 11/11] Added some assumptions to make the existing fuzz testing code work with generalized transaction generation. --- contracts/test/ProtocolAdapterMock.t.sol | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/contracts/test/ProtocolAdapterMock.t.sol b/contracts/test/ProtocolAdapterMock.t.sol index 09285359..b7795643 100644 --- a/contracts/test/ProtocolAdapterMock.t.sol +++ b/contracts/test/ProtocolAdapterMock.t.sol @@ -295,12 +295,14 @@ contract ProtocolAdapterMockVerifierTest is Test { NonExistingRootFailsParams memory params ) public { // Wrap the action index into range + vm.assume(transaction.actions.length > 0); params.actionIdx = params.actionIdx % transaction.actions.length; Compliance.VerifierInput[] memory complianceVerifierInputs = transaction.actions[params.actionIdx].complianceVerifierInputs; // Assume the proposed commitment tree root is not already contained vm.assume(!_mockPa.containsRoot(params.commitmentTreeRoot)); // Wrap the compliance verifier input index into range + vm.assume(complianceVerifierInputs.length > 0); params.inputIdx = params.inputIdx % complianceVerifierInputs.length; // Finally assign the proposed commitment tree root into the transaction complianceVerifierInputs[params.inputIdx].instance.consumed.commitmentTreeRoot = params.commitmentTreeRoot; @@ -328,10 +330,12 @@ contract ProtocolAdapterMockVerifierTest is Test { Transaction memory transaction = transactionCalldata; uint256 minProofLen = 4; // Wrap the action index into range + vm.assume(transaction.actions.length > 0); params.actionIdx = params.actionIdx % transaction.actions.length; Compliance.VerifierInput[] memory complianceVerifierInputs = transaction.actions[params.actionIdx].complianceVerifierInputs; // Wrap the compliance verifier input index into range + vm.assume(complianceVerifierInputs.length > 0); params.inputIdx = params.inputIdx % complianceVerifierInputs.length; // Finally truncate the compliance proof to below the minimum bytes calldata proof = @@ -361,10 +365,12 @@ contract ProtocolAdapterMockVerifierTest is Test { vm.assume(params.proof.length >= minProofLen); vm.assume(address(_router.verifiers(bytes4(params.proof))) == address(0)); // Wrap the action index into range + vm.assume(transaction.actions.length > 0); params.actionIdx = params.actionIdx % transaction.actions.length; Compliance.VerifierInput[] memory complianceVerifierInputs = transaction.actions[params.actionIdx].complianceVerifierInputs; // Wrap the compliance verifier input index into range + vm.assume(complianceVerifierInputs.length > 0); params.inputIdx = params.inputIdx % complianceVerifierInputs.length; // Finally, corrupt the verifier selector complianceVerifierInputs[params.inputIdx].proof = params.proof; @@ -392,11 +398,13 @@ contract ProtocolAdapterMockVerifierTest is Test { ) public { Transaction memory transaction = transactionCalldata; // Wrap the action index into range + vm.assume(transaction.actions.length > 0); params.actionIdx = params.actionIdx % transaction.actions.length; Action calldata actionCalldata = transactionCalldata.actions[params.actionIdx]; Action memory action = transaction.actions[params.actionIdx]; Compliance.VerifierInput[] memory complianceVerifierInputs = action.complianceVerifierInputs; // Wrap the compliance verifier input index into range + vm.assume(complianceVerifierInputs.length > 0); params.inputIdx = params.inputIdx % complianceVerifierInputs.length; Compliance.VerifierInput memory complianceVerifierInput = complianceVerifierInputs[params.inputIdx]; // Make sure that the planned corruption will change something @@ -427,10 +435,12 @@ contract ProtocolAdapterMockVerifierTest is Test { GenericFailParams memory params ) public { // Wrap the action index into range + vm.assume(transaction.actions.length > 0); params.actionIdx = params.actionIdx % transaction.actions.length; Action memory action = transaction.actions[params.actionIdx]; Compliance.VerifierInput[] memory complianceVerifierInputs = action.complianceVerifierInputs; // Wrap the compliance verifier input index into range + vm.assume(complianceVerifierInputs.length > 0); params.inputIdx = params.inputIdx % complianceVerifierInputs.length; // Now delete the array entry // Replace the target position with the last element @@ -466,10 +476,12 @@ contract ProtocolAdapterMockVerifierTest is Test { GenericFailParams memory params ) public { // Wrap the action index into range + vm.assume(transaction.actions.length > 0); params.actionIdx = params.actionIdx % transaction.actions.length; Action memory action = transaction.actions[params.actionIdx]; Logic.VerifierInput[] memory logicVerifierInputs = action.logicVerifierInputs; // Wrap the logic verifier input index into range + vm.assume(logicVerifierInputs.length > 0); params.inputIdx = params.inputIdx % logicVerifierInputs.length; // Now delete the array entry // Replace the target position with the last element @@ -505,9 +517,11 @@ contract ProtocolAdapterMockVerifierTest is Test { MismatchingLogicRefsFailParams memory params ) public { // Wrap the action index into range + vm.assume(transaction.actions.length > 0); params.actionIdx = params.actionIdx % transaction.actions.length; Logic.VerifierInput[] memory logicVerifierInputs = transaction.actions[params.actionIdx].logicVerifierInputs; // Wrap the logic verifier input index into range + vm.assume(logicVerifierInputs.length > 0); params.inputIdx = params.inputIdx % logicVerifierInputs.length; // Now corrupt the logic reference vm.assume(logicVerifierInputs[params.inputIdx].verifyingKey != params.logicRef); @@ -533,11 +547,13 @@ contract ProtocolAdapterMockVerifierTest is Test { ) public { Transaction memory transaction = transactionCalldata; // Wrap the action index into range + vm.assume(transaction.actions.length > 0); params.actionIdx = params.actionIdx % transaction.actions.length; Action calldata actionCalldata = transactionCalldata.actions[params.actionIdx]; Action memory action = transaction.actions[params.actionIdx]; Compliance.VerifierInput[] memory complianceVerifierInputs = action.complianceVerifierInputs; // Wrap the logic verifier input index into range + vm.assume(complianceVerifierInputs.length > 0); params.inputIdx = params.inputIdx % complianceVerifierInputs.length; Compliance.VerifierInput memory complianceVerifierInput = complianceVerifierInputs[params.inputIdx]; bytes32 tag = params.consumed