diff --git a/src/quark-core-scripts/src/Cancel.sol b/src/quark-core-scripts/src/Cancel.sol new file mode 100644 index 00000000..ecc20f2e --- /dev/null +++ b/src/quark-core-scripts/src/Cancel.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.27; + +import {IQuarkWallet} from "quark-core/src/QuarkWallet.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; + +/** + * @title Cancel Core Script + * @notice Core transaction script that can be used to cancel quark operations. + * @author Compound Labs, Inc. + */ +contract Cancel { + + /** + * @notice May cancel a script by being run as a no-op (no operation). + */ + function nop() external pure {} + + /** + * @notice Cancels a script by calling into nonce manager to cancel the script's nonce. + * @param nonce The nonce of the quark operation to cancel (exhaust) + */ + function cancel(bytes32 nonce) external { + nonceManager().cancel(nonce); + } + + /** + * @notice Cancels many scripts by calling into nonce manager to cancel each script's nonce. + * @param nonces A list of nonces of the quark operations to cancel (exhaust) + */ + function cancelMany(bytes32[] calldata nonces) external { + QuarkNonceManager manager = nonceManager(); + for (uint256 i = 0; i < nonces.length; i++) { + bytes32 nonce = nonces[i]; + manager.cancel(nonce); + } + } + + function nonceManager() internal view returns (QuarkNonceManager) { + return QuarkNonceManager(IQuarkWallet(address(this)).nonceManager()); + } +} diff --git a/test/lib/CancelOtherScript.sol b/test/lib/CheckNonceScript.sol similarity index 63% rename from test/lib/CancelOtherScript.sol rename to test/lib/CheckNonceScript.sol index d9e62ec0..f7b1919a 100644 --- a/test/lib/CancelOtherScript.sol +++ b/test/lib/CheckNonceScript.sol @@ -4,19 +4,7 @@ pragma solidity 0.8.27; import "quark-core/src/QuarkWallet.sol"; import "quark-core/src/QuarkScript.sol"; -contract CancelOtherScript is QuarkScript { - event Nop(); - event CancelNonce(bytes32 nonce); - - function nop() public { - emit Nop(); - } - - function run(bytes32 nonce) public { - nonceManager().cancel(nonce); - emit CancelNonce(nonce); - } - +contract CheckNonceScript is QuarkScript { function checkNonce() public view returns (bytes32) { return getActiveNonce(); } diff --git a/test/lib/QuarkOperationHelper.sol b/test/lib/QuarkOperationHelper.sol index 6e2bbf26..8683badd 100644 --- a/test/lib/QuarkOperationHelper.sol +++ b/test/lib/QuarkOperationHelper.sol @@ -44,6 +44,18 @@ contract QuarkOperationHelper is Test { ); } + function newBasicOpWithCalldata( + QuarkWallet wallet, + bytes memory scriptSource, + bytes memory scriptCalldata, + ScriptType scriptType, + bytes32 nonce + ) public returns (QuarkWallet.QuarkOperation memory) { + return newBasicOpWithCalldata( + wallet, scriptSource, scriptCalldata, new bytes[](0), scriptType, nonce + ); + } + function newBasicOpWithCalldata( QuarkWallet wallet, bytes memory scriptSource, @@ -144,7 +156,7 @@ contract QuarkOperationHelper is Test { returns (QuarkWallet.QuarkOperation memory) { return getCancelOperation( - wallet, semiRandomNonce(wallet), abi.encodeWithSignature("run(bytes32)", quarkOperation.nonce) + wallet, semiRandomNonce(wallet), abi.encodeWithSignature("cancel(bytes32)", quarkOperation.nonce) ); } @@ -152,10 +164,10 @@ contract QuarkOperationHelper is Test { public returns (QuarkWallet.QuarkOperation memory) { - bytes memory cancelOtherScript = new YulHelper().getCode("CancelOtherScript.sol/CancelOtherScript.json"); - address scriptAddress = wallet.codeJar().saveCode(cancelOtherScript); + bytes memory cancelScript = new YulHelper().getCode("Cancel.sol/Cancel.json"); + address scriptAddress = wallet.codeJar().saveCode(cancelScript); bytes[] memory scriptSources = new bytes[](1); - scriptSources[0] = cancelOtherScript; + scriptSources[0] = cancelScript; return QuarkWallet.QuarkOperation({ scriptAddress: scriptAddress, scriptSources: scriptSources, diff --git a/test/quark-core/Noncer.t.sol b/test/quark-core/Noncer.t.sol index 1067df32..35bdc77f 100644 --- a/test/quark-core/Noncer.t.sol +++ b/test/quark-core/Noncer.t.sol @@ -357,21 +357,26 @@ contract NoncerTest is Test { assertEq(replayCount, 2); } - function testGetActiveReplayCountWithCancel() public { + function testGetActiveReplayCountWithNonReplayCancel() public { // gas: do not meter set-up vm.pauseGasMetering(); bytes memory noncerScript = new YulHelper().getCode("Noncer.sol/Noncer.json"); + bytes memory checkNonceScript = new YulHelper().getCode("CheckNonceScript.sol/CheckNonceScript.json"); (QuarkWallet.QuarkOperation memory op, bytes32[] memory submissionTokens) = new QuarkOperationHelper() .newReplayableOpWithCalldata( aliceWallet, noncerScript, abi.encodeWithSignature("checkReplayCount()"), ScriptType.ScriptSource, 2 ); (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, op); - QuarkWallet.QuarkOperation memory cancelOp = new QuarkOperationHelper().getCancelOperation( - aliceWallet, op.nonce, abi.encodeWithSignature("checkReplayCount()") + QuarkWallet.QuarkOperation memory checkReplayCountOp = new QuarkOperationHelper().newBasicOpWithCalldata( + aliceWallet, + checkNonceScript, + abi.encodeWithSignature("checkReplayCount()"), + ScriptType.ScriptSource, + op.nonce ); (uint8 cancelV, bytes32 cancelR, bytes32 cancelS) = - new SignatureHelper().signOp(alicePrivateKey, aliceWallet, cancelOp); + new SignatureHelper().signOp(alicePrivateKey, aliceWallet, checkReplayCountOp); // gas: meter execute vm.resumeGasMetering(); @@ -381,7 +386,7 @@ contract NoncerTest is Test { assertEq(replayCount, 0); result = aliceWallet.executeQuarkOperationWithSubmissionToken( - cancelOp, submissionTokens[1], cancelV, cancelR, cancelS + checkReplayCountOp, submissionTokens[1], cancelV, cancelR, cancelS ); (replayCount) = abi.decode(result, (uint256)); diff --git a/test/quark-core/QuarkWallet.t.sol b/test/quark-core/QuarkWallet.t.sol index 46390c2d..9a3e93e2 100644 --- a/test/quark-core/QuarkWallet.t.sol +++ b/test/quark-core/QuarkWallet.t.sol @@ -28,7 +28,7 @@ import {Incrementer} from "test/lib/Incrementer.sol"; import {PrecompileCaller} from "test/lib/PrecompileCaller.sol"; import {MaxCounterScript} from "test/lib/MaxCounterScript.sol"; import {GetMessageDetails} from "test/lib/GetMessageDetails.sol"; -import {CancelOtherScript} from "test/lib/CancelOtherScript.sol"; +import {CheckNonceScript} from "test/lib/CheckNonceScript.sol"; contract QuarkWalletTest is Test { enum ExecutionType { @@ -592,8 +592,6 @@ contract QuarkWalletTest is Test { (uint8 cancelV, bytes32 cancelR, bytes32 cancelS) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, cancelOtherOp); vm.resumeGasMetering(); - vm.expectEmit(true, true, true, true); - emit CancelOtherScript.Nop(); aliceWallet.executeQuarkOperationWithSubmissionToken( cancelOtherOp, submissionTokens[1], cancelV, cancelR, cancelS ); @@ -638,15 +636,15 @@ contract QuarkWalletTest is Test { // can cancel the replayable nonce... vm.pauseGasMetering(); - QuarkWallet.QuarkOperation memory cancelOtherOp = + QuarkWallet.QuarkOperation memory cancelOp = new QuarkOperationHelper().cancelReplayableByNewOp(aliceWallet, op); (uint8 cancelV, bytes32 cancelR, bytes32 cancelS) = - new SignatureHelper().signOp(alicePrivateKey, aliceWallet, cancelOtherOp); + new SignatureHelper().signOp(alicePrivateKey, aliceWallet, cancelOp); vm.resumeGasMetering(); vm.expectEmit(true, true, true, true); - emit CancelOtherScript.CancelNonce(op.nonce); + emit QuarkNonceManager.NonceCanceled(address(aliceWallet), op.nonce); aliceWallet.executeQuarkOperationWithSubmissionToken( - cancelOtherOp, submissionTokens[1], cancelV, cancelR, cancelS + cancelOp, submissionTokens[1], cancelV, cancelR, cancelS ); // and now you can no longer replay