Skip to content

Commit

Permalink
Cancel Core Script
Browse files Browse the repository at this point in the history
This patch simply adds a cancel core script that can be used (a) as a nop to cancel a chain, or (b) to directly call `nonceManager.cancel(nonce)` with one or more nonces. These are merely given as helpers for the end-users.

Patches:
  * Rename events, address feedback
  • Loading branch information
hayesgm committed Sep 12, 2024
1 parent c2aaa37 commit aa85fe4
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 53 deletions.
41 changes: 41 additions & 0 deletions src/quark-core-scripts/src/Cancel.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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 Legend 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());
}
}
9 changes: 6 additions & 3 deletions src/quark-core/src/QuarkWallet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,12 @@ contract QuarkWallet is IERC1271 {
}

/// @notice Event emitted when a Quark script is executed by this Quark wallet
event ExecuteQuarkScript(
event QuarkExecution(
address indexed executor,
address indexed scriptAddress,
bytes32 indexed nonce,
bytes32 submissionToken,
bool isReplayable,
ExecutionType executionType
);

Expand Down Expand Up @@ -292,7 +293,9 @@ contract QuarkWallet is IERC1271 {

nonceManager.submit(op.nonce, op.isReplayable, submissionToken);

emit ExecuteQuarkScript(msg.sender, op.scriptAddress, op.nonce, submissionToken, ExecutionType.Signature);
emit QuarkExecution(
msg.sender, op.scriptAddress, op.nonce, submissionToken, op.isReplayable, ExecutionType.Signature
);

return executeScriptInternal(op.scriptAddress, op.scriptCalldata, op.nonce, submissionToken);
}
Expand Down Expand Up @@ -324,7 +327,7 @@ contract QuarkWallet is IERC1271 {

nonceManager.submit(nonce, false, nonce);

emit ExecuteQuarkScript(msg.sender, scriptAddress, nonce, nonce, ExecutionType.Direct);
emit QuarkExecution(msg.sender, scriptAddress, nonce, nonce, false, ExecutionType.Direct);

return executeScriptInternal(scriptAddress, scriptCalldata, nonce, nonce);
}
Expand Down
14 changes: 1 addition & 13 deletions test/lib/CancelOtherScript.sol → test/lib/CheckNonceScript.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
18 changes: 14 additions & 4 deletions test/lib/QuarkOperationHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ 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,
Expand Down Expand Up @@ -144,18 +154,18 @@ 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)
);
}

function getCancelOperation(QuarkWallet wallet, bytes32 selfNonce, bytes memory callData)
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,
Expand Down
6 changes: 2 additions & 4 deletions test/quark-core/Callbacks.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,7 @@ contract CallbacksTest is Test {
vm.pauseGasMetering();
bytes memory allowCallbacks = new YulHelper().getCode("AllowCallbacks.sol/AllowCallbacks.json");

(QuarkWallet.QuarkOperation memory op1, bytes32[] memory submissionTokens) = new QuarkOperationHelper()
.newReplayableOpWithCalldata(
(QuarkWallet.QuarkOperation memory op1,) = new QuarkOperationHelper().newReplayableOpWithCalldata(
aliceWallet, allowCallbacks, abi.encodeWithSignature("runWithoutAllow()"), ScriptType.ScriptSource, 1
);
(uint8 v1, bytes32 r1, bytes32 s1) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, op1);
Expand All @@ -218,8 +217,7 @@ contract CallbacksTest is Test {
vm.pauseGasMetering();
bytes memory allowCallbacks = new YulHelper().getCode("AllowCallbacks.sol/AllowCallbacks.json");

(QuarkWallet.QuarkOperation memory op1, bytes32[] memory submissionTokens) = new QuarkOperationHelper()
.newReplayableOpWithCalldata(
(QuarkWallet.QuarkOperation memory op1,) = new QuarkOperationHelper().newReplayableOpWithCalldata(
aliceWallet, allowCallbacks, abi.encodeWithSignature("runAllowThenClear()"), ScriptType.ScriptSource, 1
);
(uint8 v1, bytes32 r1, bytes32 s1) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, op1);
Expand Down
17 changes: 11 additions & 6 deletions test/quark-core/Noncer.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -357,21 +357,26 @@ contract NoncerTest is Test {
assertEq(replayCount, 2);
}

function testGetActiveReplayCountWithCancel() public {
function testGetActiveReplayCountWithNonReplaySoftCancel() 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);
(uint8 checkReplayCountOpV, bytes32 checkReplayCountOpR, bytes32 checkReplayCountOpS) =
new SignatureHelper().signOp(alicePrivateKey, aliceWallet, checkReplayCountOp);

// gas: meter execute
vm.resumeGasMetering();
Expand All @@ -381,7 +386,7 @@ contract NoncerTest is Test {
assertEq(replayCount, 0);

result = aliceWallet.executeQuarkOperationWithSubmissionToken(
cancelOp, submissionTokens[1], cancelV, cancelR, cancelS
checkReplayCountOp, submissionTokens[1], checkReplayCountOpV, checkReplayCountOpR, checkReplayCountOpS
);

(replayCount) = abi.decode(result, (uint256));
Expand Down
57 changes: 34 additions & 23 deletions test/quark-core/QuarkWallet.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -37,11 +37,12 @@ contract QuarkWalletTest is Test {
}

event Ping(uint256);
event ExecuteQuarkScript(
event QuarkExecution(
address indexed executor,
address indexed scriptAddress,
bytes32 indexed nonce,
bytes32 submissionToken,
bool isReplayable,
ExecutionType executionType
);

Expand Down Expand Up @@ -162,14 +163,24 @@ contract QuarkWalletTest is Test {
// gas: meter execute
vm.resumeGasMetering();
vm.expectEmit(true, true, true, true);
emit ExecuteQuarkScript(
address(this), scriptAddress, opWithScriptAddress.nonce, opWithScriptAddress.nonce, ExecutionType.Signature
emit QuarkExecution(
address(this),
scriptAddress,
opWithScriptAddress.nonce,
opWithScriptAddress.nonce,
false,
ExecutionType.Signature
);
aliceWallet.executeQuarkOperation(opWithScriptAddress, v, r, s);

vm.expectEmit(true, true, true, true);
emit ExecuteQuarkScript(
address(this), scriptAddress, opWithScriptSource.nonce, opWithScriptSource.nonce, ExecutionType.Signature
emit QuarkExecution(
address(this),
scriptAddress,
opWithScriptSource.nonce,
opWithScriptSource.nonce,
false,
ExecutionType.Signature
);
aliceWallet.executeQuarkOperation(opWithScriptSource, v2, r2, s2);
}
Expand All @@ -193,22 +204,27 @@ contract QuarkWalletTest is Test {
// gas: meter execute
vm.resumeGasMetering();
vm.expectEmit(true, true, true, true);
emit ExecuteQuarkScript(
address(this), scriptAddress, opWithScriptAddress.nonce, opWithScriptAddress.nonce, ExecutionType.Signature
emit QuarkExecution(
address(this),
scriptAddress,
opWithScriptAddress.nonce,
opWithScriptAddress.nonce,
true,
ExecutionType.Signature
);
aliceWallet.executeQuarkOperation(opWithScriptAddress, v, r, s);

// second execution
vm.expectEmit(true, true, true, true);
emit ExecuteQuarkScript(
address(this), scriptAddress, opWithScriptAddress.nonce, submissionTokens[1], ExecutionType.Signature
emit QuarkExecution(
address(this), scriptAddress, opWithScriptAddress.nonce, submissionTokens[1], true, ExecutionType.Signature
);
aliceWallet.executeQuarkOperationWithSubmissionToken(opWithScriptAddress, submissionTokens[1], v, r, s);

// third execution
vm.expectEmit(true, true, true, true);
emit ExecuteQuarkScript(
address(this), scriptAddress, opWithScriptAddress.nonce, submissionTokens[2], ExecutionType.Signature
emit QuarkExecution(
address(this), scriptAddress, opWithScriptAddress.nonce, submissionTokens[2], true, ExecutionType.Signature
);
aliceWallet.executeQuarkOperationWithSubmissionToken(opWithScriptAddress, submissionTokens[2], v, r, s);
}
Expand All @@ -227,7 +243,7 @@ contract QuarkWalletTest is Test {
// gas: meter execute
vm.resumeGasMetering();
vm.expectEmit(true, true, true, true);
emit ExecuteQuarkScript(address(aliceAccount), scriptAddress, nonce, nonce, ExecutionType.Direct);
emit QuarkExecution(address(aliceAccount), scriptAddress, nonce, nonce, false, ExecutionType.Direct);
aliceWalletExecutable.executeScript(nonce, scriptAddress, call, new bytes[](0));
}

Expand All @@ -251,7 +267,7 @@ contract QuarkWalletTest is Test {
// gas: meter execute
vm.resumeGasMetering();
vm.expectEmit(true, true, true, true);
emit ExecuteQuarkScript(address(aliceAccount), scriptAddress, nonce, nonce, ExecutionType.Direct);
emit QuarkExecution(address(aliceAccount), scriptAddress, nonce, nonce, false, ExecutionType.Direct);
aliceWalletExecutable.executeScript(nonce, scriptAddress, call, scriptSources);

assertEq(counter.number(), 1);
Expand Down Expand Up @@ -592,8 +608,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
);
Expand Down Expand Up @@ -638,16 +652,13 @@ contract QuarkWalletTest is Test {

// can cancel the replayable nonce...
vm.pauseGasMetering();
QuarkWallet.QuarkOperation memory cancelOtherOp =
new QuarkOperationHelper().cancelReplayableByNewOp(aliceWallet, op);
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);
aliceWallet.executeQuarkOperationWithSubmissionToken(
cancelOtherOp, submissionTokens[1], cancelV, cancelR, cancelS
);
emit QuarkNonceManager.NonceCanceled(address(aliceWallet), op.nonce);
aliceWallet.executeQuarkOperationWithSubmissionToken(cancelOp, submissionTokens[1], cancelV, cancelR, cancelS);

// and now you can no longer replay
vm.expectRevert(
Expand Down

0 comments on commit aa85fe4

Please sign in to comment.