Skip to content

Commit

Permalink
Add Nonce and N Checking
Browse files Browse the repository at this point in the history
This patch adds support for setting the active nonce and submission token in temporary storage so scripts can read it and get the current nonce, submission token, and if they so choose, N. This is useful for certain scripts.

Patches:
  * TODO: Add tests showing scripts have access and how nesting works.
  * Add tests for checking nonce, submission token and replay count
  • Loading branch information
hayesgm committed Sep 10, 2024
1 parent b438f36 commit 93375ba
Show file tree
Hide file tree
Showing 6 changed files with 349 additions and 15 deletions.
52 changes: 52 additions & 0 deletions src/quark-core/src/QuarkScript.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {QuarkWallet, IHasSignerExecutor} from "quark-core/src/QuarkWallet.sol";
*/
abstract contract QuarkScript {
error ReentrantCall();
error InvalidActiveNonce();
error InvalidActiveSubmissionToken();

/// @notice Storage location for the re-entrancy guard
bytes32 internal constant REENTRANCY_FLAG_SLOT =
Expand Down Expand Up @@ -118,4 +120,54 @@ abstract contract QuarkScript {
sstore(key, value)
}
}

// Note: this may not be accurate after any nested calls from a script
function getActiveNonce() internal returns (bytes32) {
QuarkWallet self = QuarkWallet(payable(address(this)));

bytes32 activeNonceSlot = self.ACTIVE_NONCE_SLOT();
bytes32 value;
assembly {
value := sload(activeNonceSlot)
}
if (value == bytes32(0)) {
revert InvalidActiveNonce();
}

return value;
}

// Note: this may not be accurate after any nested calls from a script
function getActiveSubmissionToken() internal returns (bytes32) {
QuarkWallet self = QuarkWallet(payable(address(this)));

bytes32 activeSubmissionTokenSlot = self.ACTIVE_SUBMISSION_TOKEN_SLOT();
bytes32 value;
assembly {
value := sload(activeSubmissionTokenSlot)
}
if (value == bytes32(0)) {
revert InvalidActiveSubmissionToken();
}
return value;
}

// Note: this may not be accurate after any nested calls from a script
// Returns the active replay count of this script. Thus, the first submission should return 0,
// the second submission 1, and so on. This must be called before the script makes any external calls.
function getActiveReplayCount() internal returns (uint256) {
bytes32 nonce = getActiveNonce();
bytes32 submissionToken = getActiveSubmissionToken();
uint256 n;

if (submissionToken == bytes32(type(uint256).max)) {
return 0;
}

for (n = 0; submissionToken != nonce; n++) {
submissionToken = keccak256(abi.encodePacked(submissionToken));
}

return n;
}
}
49 changes: 40 additions & 9 deletions src/quark-core/src/QuarkWallet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ contract QuarkWallet is IERC1271 {

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

/// @notice Address of CodeJar contract used to deploy transaction script source code
Expand Down Expand Up @@ -113,6 +117,13 @@ contract QuarkWallet is IERC1271 {
/// @notice Well-known storage slot for the currently executing script's address (if any)
bytes32 public constant ACTIVE_SCRIPT_SLOT = bytes32(uint256(keccak256("quark.v1.active.script")) - 1);

/// @notice Well-known --
bytes32 public constant ACTIVE_NONCE_SLOT = bytes32(uint256(keccak256("quark.v1.active.nonce")) - 1);

/// @notice Well-known --
bytes32 public constant ACTIVE_SUBMISSION_TOKEN_SLOT =
bytes32(uint256(keccak256("quark.v1.active.submissionToken")) - 1);

/// @notice A nonce submission token that implies a Quark Operation is no longer replayable.
bytes32 public constant EXHAUSTED_TOKEN = bytes32(type(uint256).max);

Expand Down Expand Up @@ -271,9 +282,9 @@ contract QuarkWallet is IERC1271 {

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

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

return executeScriptInternal(op.scriptAddress, op.scriptCalldata);
return executeScriptInternal(op.scriptAddress, op.scriptCalldata, op.nonce, submissionToken);
}

/**
Expand Down Expand Up @@ -303,9 +314,9 @@ contract QuarkWallet is IERC1271 {

nonceManager.submit(nonce, false, EXHAUSTED_TOKEN);

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

return executeScriptInternal(scriptAddress, scriptCalldata);
return executeScriptInternal(scriptAddress, scriptCalldata, nonce, EXHAUSTED_TOKEN);
}

/**
Expand Down Expand Up @@ -438,12 +449,16 @@ contract QuarkWallet is IERC1271 {
* @notice Execute a script using the given calldata
* @param scriptAddress Address of script to execute
* @param scriptCalldata Encoded calldata for the call to execute on the scriptAddress
* @param nonce The nonce of the quark operation for this execution
* @param submissionToken The submission token for this quark execution
* @return Result of executing the script, encoded as bytes
*/
function executeScriptInternal(address scriptAddress, bytes memory scriptCalldata)
internal
returns (bytes memory)
{
function executeScriptInternal(
address scriptAddress,
bytes memory scriptCalldata,
bytes32 nonce,
bytes32 submissionToken
) internal returns (bytes memory) {
if (scriptAddress.code.length == 0) {
revert EmptyCode();
}
Expand All @@ -452,20 +467,36 @@ contract QuarkWallet is IERC1271 {
uint256 returnSize;
uint256 scriptCalldataLen = scriptCalldata.length;
bytes32 activeScriptSlot = ACTIVE_SCRIPT_SLOT;
bytes32 activeNonceSlot = ACTIVE_NONCE_SLOT;
bytes32 activeSubmissionTokenSlot = ACTIVE_SUBMISSION_TOKEN_SLOT;
assembly {
// TODO: TSTORE the callback slot to 0

// Store the active script
// TODO: Move to TSTORE after updating Solidity version to >=0.8.24
sstore(activeScriptSlot, scriptAddress)

// Store the active nonce
// TODO: Move to TSTORE after updating Solidity version to >=0.8.24
sstore(activeNonceSlot, nonce)

// Store the active submission token
// TODO: Move to TSTORE after updating Solidity version to >=0.8.24
sstore(activeSubmissionTokenSlot, submissionToken)

// Note: CALLCODE is used to set the QuarkWallet as the `msg.sender`
success :=
callcode(gas(), scriptAddress, /* value */ 0, add(scriptCalldata, 0x20), scriptCalldataLen, 0x0, 0)
returnSize := returndatasize()

// TODO: Move to TSTORE after updating Solidity version to >=0.8.24
sstore(activeScriptSlot, 0)

// TODO: Move to TSTORE after updating Solidity version to >=0.8.24
sstore(activeNonceSlot, 0)

// TODO: Move to TSTORE after updating Solidity version to >=0.8.24
sstore(activeSubmissionTokenSlot, 0)
}

bytes memory returnData = new bytes(returnSize);
Expand Down
15 changes: 14 additions & 1 deletion test/lib/CancelOtherScript.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,24 @@
pragma solidity 0.8.23;

import "quark-core/src/QuarkWallet.sol";
import "quark-core/src/QuarkScript.sol";

contract CancelOtherScript {
contract CancelOtherScript is QuarkScript {
event CancelNonce();

function run() public {
emit CancelNonce();
}

function checkNonce() public returns (bytes32) {
return getActiveNonce();
}

function checkSubmissionToken() public returns (bytes32) {
return getActiveSubmissionToken();
}

function checkReplayCount() public returns (uint256) {
return getActiveReplayCount();
}
}
18 changes: 18 additions & 0 deletions test/lib/Noncer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.23;

import "quark-core/src/QuarkScript.sol";

contract Noncer is QuarkScript {
function checkNonce() public returns (bytes32) {
return getActiveNonce();
}

function checkSubmissionToken() public returns (bytes32) {
return getActiveSubmissionToken();
}

function checkReplayCount() public returns (uint256) {
return getActiveReplayCount();
}
}
10 changes: 9 additions & 1 deletion test/lib/QuarkOperationHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,22 @@ contract QuarkOperationHelper is Test {
public
returns (QuarkWallet.QuarkOperation memory)
{
return cancelReplayable(wallet, quarkOperation, abi.encodeWithSignature("run()"));
}

function cancelReplayable(
QuarkWallet wallet,
QuarkWallet.QuarkOperation memory quarkOperation,
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 scriptSources = new bytes[](1);
scriptSources[0] = cancelOtherScript;
return QuarkWallet.QuarkOperation({
scriptAddress: scriptAddress,
scriptSources: scriptSources,
scriptCalldata: abi.encodeWithSignature("run()"),
scriptCalldata: callData,
nonce: quarkOperation.nonce,
isReplayable: false,
expiry: block.timestamp + 1000
Expand Down
Loading

0 comments on commit 93375ba

Please sign in to comment.