diff --git a/src/quark-core/src/QuarkStateManager.sol b/src/quark-core/src/QuarkStateManager.sol index e979cbea..97f9e8cb 100644 --- a/src/quark-core/src/QuarkStateManager.sol +++ b/src/quark-core/src/QuarkStateManager.sol @@ -15,13 +15,13 @@ contract QuarkStateManager { event NonceSubmitted(address wallet, bytes32 nonce, bytes32 replayToken); /// @notice Represents the unclaimed bytes32 value. - bytes32 public constant CLAIMABLE_TOKEN = bytes32(uint256(0)); + bytes32 public constant UNCLAIMED_TOKEN = bytes32(uint256(0)); /// @notice A token that implies a Quark Operation is no longer replayable. bytes32 public constant NO_REPLAY_TOKEN = bytes32(type(uint).max); /// @notice Mapping from nonces to last used replay token. - mapping(address wallet => mapping(bytes32 nonce => bytes32 lastToken)) public nonceTokens; + mapping(address wallet => mapping(bytes32 nonce => bytes32 lastToken)) public nonceReplayTokens; /** * @notice Returns the nonce token (last replay token) for a given nonce. For finalized scripts, this will be `uint256(-1)`. For unclaimed nonces, this will be `uint256(0)`. Otherwise, it will be the next value in the replay chain. @@ -29,8 +29,8 @@ contract QuarkStateManager { * @param nonce The nonce for the given request. * @return replayToken The last used replay token, or 0 if unused or -1 if finalized. */ - function getNonceToken(address wallet, bytes32 nonce) external view returns (bytes32 replayToken) { - return nonceTokens[wallet][nonce]; + function getNonceReplayToken(address wallet, bytes32 nonce) external view returns (bytes32 replayToken) { + return nonceReplayTokens[wallet][nonce]; } /** @@ -38,18 +38,19 @@ contract QuarkStateManager { * @param nonce The nonce of the chain to submit. * @param replayToken The replay token of the submission. For single-use operations, set `replayToken` to `uint256(-1)`. For first-use replayable operations, set `replayToken` = `nonce`. */ - function submitNonceToken(bytes32 nonce, bytes32 replayToken) external { - bytes32 lastToken = nonceTokens[msg.sender][nonce]; + function submitNonceReplayToken(bytes32 nonce, bool isReplayable, bytes32 replayToken) external { + bytes32 lastToken = nonceReplayTokens[msg.sender][nonce]; if (lastToken == NO_REPLAY_TOKEN) { revert NonReplayableNonce(msg.sender, nonce, replayToken); } - - bool validReplay = lastToken == CLAIMABLE_TOKEN || keccak256(abi.encodePacked(replayToken)) == lastToken; - if (!validReplay) { + bool validFirstPlay = lastToken == UNCLAIMED_TOKEN && (isReplayable ? replayToken == nonce : replayToken == NO_REPLAY_TOKEN); + /* validToken = validFirstPlay or ( validReplay ); */ + bool validToken = validFirstPlay || keccak256(abi.encodePacked(replayToken)) == lastToken; + if (!validToken) { revert InvalidReplayToken(msg.sender, nonce, replayToken); } - nonceTokens[msg.sender][nonce] = replayToken; + nonceReplayTokens[msg.sender][nonce] = replayToken; emit NonceSubmitted(msg.sender, nonce, replayToken); } } diff --git a/src/quark-core/src/QuarkWallet.sol b/src/quark-core/src/QuarkWallet.sol index 918deba3..27af7b5c 100644 --- a/src/quark-core/src/QuarkWallet.sol +++ b/src/quark-core/src/QuarkWallet.sol @@ -23,7 +23,7 @@ library QuarkWalletMetadata { /// @notice The EIP-712 typehash for authorizing an operation for this version of QuarkWallet bytes32 internal constant QUARK_OPERATION_TYPEHASH = keccak256( - "QuarkOperation(bytes32 nonce,address scriptAddress,bytes[] scriptSources,bytes scriptCalldata,uint256 expiry)" + "QuarkOperation(bytes32 nonce,bool isReplayable,address scriptAddress,bytes[] scriptSources,bytes scriptCalldata,uint256 expiry)" ); /// @notice The EIP-712 typehash for authorizing a MultiQuarkOperation for this version of QuarkWallet @@ -123,6 +123,8 @@ contract QuarkWallet is IERC1271 { struct QuarkOperation { /// @notice Nonce identifier for the operation bytes32 nonce; + /// @notice Whether this script is replayable or not. + bool isReplayable; /// @notice The address of the transaction script to run address scriptAddress; /// @notice Creation codes Quark must ensure are deployed before executing this operation @@ -155,10 +157,27 @@ contract QuarkWallet is IERC1271 { function executeQuarkOperation(QuarkOperation calldata op, uint8 v, bytes32 r, bytes32 s) external returns (bytes memory) + { + return executeQuarkOperationWithReplayToken(op, getInitialReplayToken(op), v, r, s); + } + + /** + * @notice Executes a first play or a replay of a QuarkOperation via signature + * @dev Can only be called with signatures from the wallet's signer + * @param op A QuarkOperation struct + * @param replayToken A replay token. For replayables, initial value should be `replayToken = op.nonce`, for non-replayables, `replayToken = bytes32(type(uint256).max)` + * @param v EIP-712 signature v value + * @param r EIP-712 signature r value + * @param s EIP-712 signature s value + * @return Return value from the executed operation + */ + function executeQuarkOperationWithReplayToken(QuarkOperation calldata op, bytes32 replayToken, uint8 v, bytes32 r, bytes32 s) + public + returns (bytes memory) { bytes32 opDigest = getDigestForQuarkOperation(op); - return verifySigAndExecuteQuarkOperation(op, opDigest, v, r, s); + return verifySigAndExecuteQuarkOperation(op, replayToken, opDigest, v, r, s); } /** @@ -178,60 +197,47 @@ contract QuarkWallet is IERC1271 { bytes32 r, bytes32 s ) public returns (bytes memory) { - bytes32 opDigest = getDigestForQuarkOperation(op); - - bool isValidOp = false; - for (uint256 i = 0; i < opDigests.length; ++i) { - if (opDigest == opDigests[i]) { - isValidOp = true; - break; - } - } - if (!isValidOp) { - revert InvalidMultiQuarkOperation(); - } - bytes32 multiOpDigest = getDigestForMultiQuarkOperation(opDigests); - - return verifySigAndExecuteQuarkOperation(op, multiOpDigest, v, r, s); + return executeMultiQuarkOperationWithReplayToken(op, getInitialReplayToken(op), opDigests, v, r, s); } /** - * @notice Verify a signature and execute a single-use QuarkOperation + * @notice Executes a first play or a replay of a QuarkOperation that is part of a MultiQuarkOperation via signature + * @dev Can only be called with signatures from the wallet's signer * @param op A QuarkOperation struct - * @param digest A EIP-712 digest for either a QuarkOperation or MultiQuarkOperation to verify the signature against + * @param replayToken A replay token. For replayables, initial value should be `replayToken = op.nonce`, for non-replayables, `replayToken = bytes32(type(uint256).max)` + * @param opDigests A list of EIP-712 digests for the operations in a MultiQuarkOperation * @param v EIP-712 signature v value * @param r EIP-712 signature r value * @param s EIP-712 signature s value * @return Return value from the executed operation */ - function verifySigAndExecuteQuarkOperation( + function executeMultiQuarkOperationWithReplayToken( QuarkOperation calldata op, - bytes32 digest, + bytes32 replayToken, + bytes32[] memory opDigests, uint8 v, bytes32 r, bytes32 s - ) internal returns (bytes memory) { - if (block.timestamp >= op.expiry) { - revert SignatureExpired(); - } - - // if the signature check does not revert, the signature is valid - checkValidSignatureInternal(IHasSignerExecutor(address(this)).signer(), digest, v, r, s); + ) public returns (bytes memory) { + bytes32 opDigest = getDigestForQuarkOperation(op); - // guarantee every script in scriptSources is deployed - for (uint256 i = 0; i < op.scriptSources.length; ++i) { - codeJar.saveCode(op.scriptSources[i]); + bool isValidOp = false; + for (uint256 i = 0; i < opDigests.length; ++i) { + if (opDigest == opDigests[i]) { + isValidOp = true; + break; + } } + if (!isValidOp) { + revert InvalidMultiQuarkOperation(); + } + bytes32 multiOpDigest = getDigestForMultiQuarkOperation(opDigests); - stateManager.submitNonceToken(op.nonce, NO_REPLAY_TOKEN); - - emit ExecuteQuarkScript(msg.sender, op.scriptAddress, op.nonce, ExecutionType.Signature); - - return executeScriptInternal(op.scriptAddress, op.scriptCalldata); + return verifySigAndExecuteQuarkOperation(op, replayToken, multiOpDigest, v, r, s); } /** - * @notice Verify a signature and execute a replayable QuarkOperation + * @notice Verify a signature and execute a single-use QuarkOperation * @param op A QuarkOperation struct * @param replayToken The replay token for the replayable quark operation. For the first submission, this is generally the `rootHash`. * @param digest A EIP-712 digest for either a QuarkOperation or MultiQuarkOperation to verify the signature against @@ -240,7 +246,7 @@ contract QuarkWallet is IERC1271 { * @param s EIP-712 signature s value * @return Return value from the executed operation */ - function verifySigAndExecuteReplayableQuarkOperation( + function verifySigAndExecuteQuarkOperation( QuarkOperation calldata op, bytes32 replayToken, bytes32 digest, @@ -260,7 +266,7 @@ contract QuarkWallet is IERC1271 { codeJar.saveCode(op.scriptSources[i]); } - stateManager.submitNonceToken(op.nonce, replayToken); + stateManager.submitNonceReplayToken(op.nonce, op.isReplayable, replayToken); emit ExecuteQuarkScript(msg.sender, op.scriptAddress, op.nonce, ExecutionType.Signature); @@ -292,7 +298,7 @@ contract QuarkWallet is IERC1271 { codeJar.saveCode(scriptSources[i]); } - stateManager.submitNonceToken(nonce, NO_REPLAY_TOKEN); + stateManager.submitNonceReplayToken(nonce, false, NO_REPLAY_TOKEN); emit ExecuteQuarkScript(msg.sender, scriptAddress, nonce, ExecutionType.Direct); @@ -324,6 +330,7 @@ contract QuarkWallet is IERC1271 { abi.encode( QUARK_OPERATION_TYPEHASH, op.nonce, + op.isReplayable, op.scriptAddress, keccak256(encodedScriptSources), keccak256(op.scriptCalldata), @@ -499,4 +506,9 @@ contract QuarkWallet is IERC1271 { /// @notice Fallback for receiving native token receive() external payable {} + + /// @dev Returns the expected initial replay token for an operation, which is either `op.nonce` for a replayable operation, or `bytes32(type(uint256).max)` (the no replay token) for a non-replayable operation. + function getInitialReplayToken(QuarkOperation memory op) internal pure returns (bytes32) { + return op.isReplayable ? op.nonce : NO_REPLAY_TOKEN; + } } diff --git a/test/lib/CancelOtherScript.sol b/test/lib/CancelOtherScript.sol index 7d0dac82..d49e582a 100644 --- a/test/lib/CancelOtherScript.sol +++ b/test/lib/CancelOtherScript.sol @@ -5,6 +5,6 @@ import "quark-core/src/QuarkWallet.sol"; contract CancelOtherScript { function run(bytes32 nonce) public { - return QuarkWallet(payable(address(this))).stateManager().submitNonceToken(nonce, bytes32(type(uint).max)); + return QuarkWallet(payable(address(this))).stateManager().submitNonceReplayToken(nonce, true, bytes32(type(uint).max)); } } diff --git a/test/lib/ExecuteWithRequirements.sol b/test/lib/ExecuteWithRequirements.sol index baf9d59b..b839136d 100644 --- a/test/lib/ExecuteWithRequirements.sol +++ b/test/lib/ExecuteWithRequirements.sol @@ -14,7 +14,7 @@ contract ExecuteWithRequirements { QuarkWallet wallet = QuarkWallet(payable(address(this))); QuarkStateManager stateManager = wallet.stateManager(); for (uint256 i = 0; i < requirements.length; i++) { - if (stateManager.getNonceToken(address(wallet), requirements[i]) == bytes32(uint256(0))) { + if (stateManager.getNonceReplayToken(address(wallet), requirements[i]) == bytes32(uint256(0))) { revert RequirementNotMet(requirements[i]); } } diff --git a/test/lib/QuarkOperationHelper.sol b/test/lib/QuarkOperationHelper.sol index 0068b4a5..3366f216 100644 --- a/test/lib/QuarkOperationHelper.sol +++ b/test/lib/QuarkOperationHelper.sol @@ -45,6 +45,7 @@ contract QuarkOperationHelper is Test { scriptSources: ensureScripts, scriptCalldata: scriptCalldata, nonce: semiRandomNonce(wallet), + isReplayable: false, expiry: block.timestamp + 1000 }); } else { @@ -53,6 +54,7 @@ contract QuarkOperationHelper is Test { scriptSources: ensureScripts, scriptCalldata: scriptCalldata, nonce: semiRandomNonce(wallet), + isReplayable: false, expiry: block.timestamp + 1000 }); } @@ -71,7 +73,7 @@ contract QuarkOperationHelper is Test { function semiRandomNonce(QuarkStateManager quarkStateManager, QuarkWallet wallet) public view returns (bytes32) { bytes32 nonce = bytes32(uint256(keccak256(abi.encodePacked(block.timestamp))) - 1); while (true) { - if (quarkStateManager.getNonceToken(address(wallet), nonce) == bytes32(uint256(0))) { + if (quarkStateManager.getNonceReplayToken(address(wallet), nonce) == bytes32(uint256(0))) { return nonce; } diff --git a/test/lib/SignatureHelper.sol b/test/lib/SignatureHelper.sol index 7f855f64..9a7f27b3 100644 --- a/test/lib/SignatureHelper.sol +++ b/test/lib/SignatureHelper.sol @@ -55,6 +55,7 @@ contract SignatureHelper is Test { abi.encode( QuarkWalletMetadata.QUARK_OPERATION_TYPEHASH, op.nonce, + op.isReplayable, op.scriptAddress, keccak256(encodedArray), keccak256(op.scriptCalldata), diff --git a/test/quark-core/EIP712.t.sol b/test/quark-core/EIP712.t.sol index d366926d..119d87e3 100644 --- a/test/quark-core/EIP712.t.sol +++ b/test/quark-core/EIP712.t.sol @@ -73,7 +73,7 @@ contract EIP712Test is Test { assertEq(counter.number(), 3); // nonce is spent - assertEq(stateManager.getNonceToken(address(wallet), op.nonce), bytes32(type(uint256).max)); + assertEq(stateManager.getNonceReplayToken(address(wallet), op.nonce), bytes32(type(uint256).max)); } function testRevertsForBadCode() public { @@ -99,7 +99,7 @@ contract EIP712Test is Test { assertEq(counter.number(), 0); // nonce is not spent - assertEq(stateManager.getNonceToken(address(wallet), op.nonce), bytes32(uint256(0))); + assertEq(stateManager.getNonceReplayToken(address(wallet), op.nonce), bytes32(uint256(0))); } function testStructHash() public { @@ -122,6 +122,7 @@ contract EIP712Test is Test { QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ nonce: nextNonce, + isReplayable: true, scriptAddress: incrementerAddress, scriptSources: scriptSources, scriptCalldata: scriptCalldata, @@ -138,6 +139,7 @@ contract EIP712Test is Test { }, { QuarkOperation: [ { name: 'nonce', type: 'bytes32' }, + { name: 'isReplayable', type: 'bool' }, { name: 'scriptAddress', type: 'address' }, { name: 'scriptSources', type: 'bytes[]' }, { name: 'scriptCalldata', type: 'bytes' }, @@ -145,6 +147,7 @@ contract EIP712Test is Test { ]}, { nonce: '0x0000000000000000000000000000000000000000000000000000000000000000', + isReplayable: true, scriptAddress: '0x5cB7957c702bB6BB8F22aCcf66657F0defd4550b', scriptSources: ['0x608060405234801561001057600080fd5b506102a7806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80636b582b7614610056578063e5910ae714610069575b73f62849f9a0b5bf2913b396098f7c7019b51a820a61005481610077565b005b610054610064366004610230565b610173565b610054610077366004610230565b806001600160a01b031663d09de08a6040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156100b257600080fd5b505af11580156100c6573d6000803e3d6000fd5b50505050806001600160a01b031663d09de08a6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561010557600080fd5b505af1158015610119573d6000803e3d6000fd5b50505050806001600160a01b031663d09de08a6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561015857600080fd5b505af115801561016c573d6000803e3d6000fd5b5050505050565b61017c81610077565b306001600160a01b0316632e716fb16040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101de9190610254565b6001600160a01b0316631913592a6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561015857600080fd5b6001600160a01b038116811461022d57600080fd5b50565b60006020828403121561024257600080fd5b813561024d81610218565b9392505050565b60006020828403121561026657600080fd5b815161024d8161021856fea26469706673582212200d71f9cd831b3c67d6f6131f807ee7fc47d21f07fe8f7b90a01dab56abb8403464736f6c63430008170033'], scriptCalldata: '0xe5910ae7000000000000000000000000f62849f9a0b5bf2913b396098f7c7019b51a820a', @@ -152,14 +155,16 @@ contract EIP712Test is Test { } ) - 0x1901ce5fced5138ae147492ff6ba56247e9d6f30bbbe45ae60eb0a0135d528a94be437302412583af420731c67963b8628682b151f38070c3c9142fc40054158666e + 0x1901 + ce5fced5138ae147492ff6ba56247e9d6f30bbbe45ae60eb0a0135d528a94be4 + 115a39f16a8c9e3e390e94dc858a17eba53b5358382af38b02f1ac31c2b5f9b0 */ bytes32 domainHash = new SignatureHelper().domainSeparator(wallet_); assertEq(domainHash, hex"ce5fced5138ae147492ff6ba56247e9d6f30bbbe45ae60eb0a0135d528a94be4"); bytes32 structHash = new SignatureHelper().opStructHash(op); - assertEq(structHash, hex"37302412583af420731c67963b8628682b151f38070c3c9142fc40054158666e"); + assertEq(structHash, hex"115a39f16a8c9e3e390e94dc858a17eba53b5358382af38b02f1ac31c2b5f9b0"); } function testRevertsForBadCalldata() public { @@ -182,7 +187,7 @@ contract EIP712Test is Test { assertEq(counter.number(), 0); // nonce is not spent - assertEq(stateManager.getNonceToken(address(wallet), op.nonce), bytes32(uint256(0))); + assertEq(stateManager.getNonceReplayToken(address(wallet), op.nonce), bytes32(uint256(0))); } function testRevertsForBadExpiry() public { @@ -205,7 +210,7 @@ contract EIP712Test is Test { assertEq(counter.number(), 0); // alice's nonce is not set - assertEq(stateManager.getNonceToken(address(wallet), op.nonce), bytes32(uint256(0))); + assertEq(stateManager.getNonceReplayToken(address(wallet), op.nonce), bytes32(uint256(0))); } function testRevertsOnReusedNonce() public { @@ -222,7 +227,7 @@ contract EIP712Test is Test { wallet.executeQuarkOperation(op, v, r, s); assertEq(counter.number(), 3); - assertEq(stateManager.getNonceToken(address(wallet), op.nonce), bytes32(type(uint256).max)); + assertEq(stateManager.getNonceReplayToken(address(wallet), op.nonce), bytes32(type(uint256).max)); // submitter tries to reuse the same signature twice, for a non-replayable operation vm.expectRevert(abi.encodeWithSelector(QuarkStateManager.NonReplayableNonce.selector, address(wallet), op.nonce, bytes32(type(uint256).max))); @@ -248,7 +253,7 @@ contract EIP712Test is Test { wallet.executeQuarkOperation(op, v, r, s); assertEq(counter.number(), 0); - assertEq(stateManager.getNonceToken(address(wallet), op.nonce), bytes32(uint256(0))); + assertEq(stateManager.getNonceReplayToken(address(wallet), op.nonce), bytes32(uint256(0))); } function testRevertsInvalidS() public { @@ -270,7 +275,7 @@ contract EIP712Test is Test { wallet.executeQuarkOperation(op, v, r, invalidS); assertEq(counter.number(), 0); - assertEq(stateManager.getNonceToken(address(wallet), op.nonce), bytes32(uint256(0))); + assertEq(stateManager.getNonceReplayToken(address(wallet), op.nonce), bytes32(uint256(0))); } // TODO: Uncomment when replay tokens are supported @@ -347,7 +352,7 @@ contract EIP712Test is Test { wallet.executeQuarkOperation(op, v, r, s); assertEq(counter.number(), 0); - assertEq(stateManager.getNonceToken(address(wallet), op.nonce), bytes32(uint256(0))); + assertEq(stateManager.getNonceReplayToken(address(wallet), op.nonce), bytes32(uint256(0))); } function testRequirements() public { diff --git a/test/quark-core/Nonce.t.sol b/test/quark-core/Nonce.t.sol index 0538e865..40b50ebc 100644 --- a/test/quark-core/Nonce.t.sol +++ b/test/quark-core/Nonce.t.sol @@ -13,7 +13,7 @@ contract NonceTest is Test { QuarkStateManager public stateManager; /// @notice Represents the unclaimed bytes32 value. - bytes32 public constant CLAIMABLE_TOKEN = bytes32(uint256(0)); + bytes32 public constant UNCLAIMED_TOKEN = bytes32(uint256(0)); /// @notice A token that implies a Quark Operation is no longer replayable. bytes32 public constant NO_REPLAY_TOKEN = bytes32(type(uint256).max); @@ -28,11 +28,11 @@ contract NonceTest is Test { function testIsSet() public { // nonce is unset by default - assertEq(stateManager.getNonceToken(address(this), NONCE_ZERO), CLAIMABLE_TOKEN); + assertEq(stateManager.getNonceReplayToken(address(this), NONCE_ZERO), UNCLAIMED_TOKEN); // it can be set - stateManager.submitNonceToken(NONCE_ZERO, NO_REPLAY_TOKEN); - assertEq(stateManager.getNonceToken(address(this), NONCE_ZERO), NO_REPLAY_TOKEN); + stateManager.submitNonceReplayToken(NONCE_ZERO, false, NO_REPLAY_TOKEN); + assertEq(stateManager.getNonceReplayToken(address(this), NONCE_ZERO), NO_REPLAY_TOKEN); } function testNonLinearNonce() public { @@ -40,11 +40,13 @@ contract NonceTest is Test { // long as it has not been set bytes32 nonce = bytes32(uint256(1234567890)); - assertEq(stateManager.getNonceToken(address(this), NONCE_ZERO), CLAIMABLE_TOKEN); + assertEq(stateManager.getNonceReplayToken(address(this), NONCE_ZERO), UNCLAIMED_TOKEN); - stateManager.submitNonceToken(nonce, NO_REPLAY_TOKEN); - assertEq(stateManager.getNonceToken(address(this), nonce), NO_REPLAY_TOKEN); + stateManager.submitNonceReplayToken(nonce, false, NO_REPLAY_TOKEN); + assertEq(stateManager.getNonceReplayToken(address(this), nonce), NO_REPLAY_TOKEN); } // TODO: ADD TESTS FOR NONCE CHAIN + // TODO: Really, more tests + // TODO: No cap. } diff --git a/test/quark-core/QuarkStateManager.t.sol b/test/quark-core/QuarkStateManager.t.sol index 5aeaddc4..c7479492 100644 --- a/test/quark-core/QuarkStateManager.t.sol +++ b/test/quark-core/QuarkStateManager.t.sol @@ -35,12 +35,12 @@ contract QuarkStateManagerTest is Test { bytes32 NO_REPLAY_TOKEN = stateManager.NO_REPLAY_TOKEN(); // by default, nonce 0 is not set - assertEq(stateManager.getNonceToken(address(0x123), nonce), stateManager.CLAIMABLE_TOKEN()); + assertEq(stateManager.getNonceReplayToken(address(0x123), nonce), stateManager.UNCLAIMED_TOKEN()); // nonce 0 can be set manually vm.prank(address(0x123)); - stateManager.submitNonceToken(nonce, NO_REPLAY_TOKEN); - assertEq(stateManager.getNonceToken(address(0x123), nonce), stateManager.NO_REPLAY_TOKEN()); + stateManager.submitNonceReplayToken(nonce, false, NO_REPLAY_TOKEN); + assertEq(stateManager.getNonceReplayToken(address(0x123), nonce), stateManager.NO_REPLAY_TOKEN()); } // TODO: We should really replace this test with one that @@ -69,9 +69,9 @@ contract QuarkStateManagerTest is Test { function testRevertsIfNonceIsAlreadySet() public { bytes32 NO_REPLAY_TOKEN = stateManager.NO_REPLAY_TOKEN(); bytes32 nonce = bytes32(uint256(0)); - stateManager.submitNonceToken(nonce, NO_REPLAY_TOKEN); + stateManager.submitNonceReplayToken(nonce, false, NO_REPLAY_TOKEN); vm.expectRevert(abi.encodeWithSelector(QuarkStateManager.NonReplayableNonce.selector, address(this), nonce, bytes32(type(uint256).max))); - stateManager.submitNonceToken(nonce, NO_REPLAY_TOKEN); + stateManager.submitNonceReplayToken(nonce, false, NO_REPLAY_TOKEN); } } diff --git a/test/quark-core/QuarkWallet.t.sol b/test/quark-core/QuarkWallet.t.sol index 97e4b5a4..3e0b45e6 100644 --- a/test/quark-core/QuarkWallet.t.sol +++ b/test/quark-core/QuarkWallet.t.sol @@ -188,6 +188,7 @@ contract QuarkWalletTest is Test { QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ nonce: new QuarkOperationHelper().semiRandomNonce(stateManager, aliceWallet), + isReplayable: false, scriptAddress: address(0), scriptSources: new bytes[](0), scriptCalldata: bytes(""), @@ -217,6 +218,7 @@ contract QuarkWalletTest is Test { // operation containing a valid empty script will revert QuarkWallet.QuarkOperation memory op2 = QuarkWallet.QuarkOperation({ nonce: new QuarkOperationHelper().semiRandomNonce(stateManager, aliceWallet), + isReplayable: false, scriptAddress: emptyCodeAddress, scriptSources: new bytes[](0), scriptCalldata: bytes(""), @@ -247,6 +249,7 @@ contract QuarkWalletTest is Test { QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ nonce: new QuarkOperationHelper().semiRandomNonce(stateManager, aliceWallet), + isReplayable: false, scriptAddress: address(0xc0c0), scriptSources: scriptSources, scriptCalldata: bytes("feefee"), @@ -422,7 +425,7 @@ contract QuarkWalletTest is Test { bytes memory call = abi.encodeWithSignature("incrementCounter(address)", counter); assertEq(counter.number(), 0); - assertEq(stateManager.getNonceToken(address(aliceWalletExecutable), nonce), bytes32(uint256(0))); + assertEq(stateManager.getNonceReplayToken(address(aliceWalletExecutable), nonce), bytes32(uint256(0))); // act as the executor for the wallet vm.startPrank(aliceAccount); @@ -432,7 +435,7 @@ contract QuarkWalletTest is Test { aliceWalletExecutable.executeScript(nonce, incrementerAddress, call, new bytes[](0)); assertEq(counter.number(), 3); - assertEq(stateManager.getNonceToken(address(aliceWalletExecutable), nonce), bytes32(type(uint256).max)); + assertEq(stateManager.getNonceReplayToken(address(aliceWalletExecutable), nonce), bytes32(type(uint256).max)); } function testDirectExecuteFromOtherQuarkWallet() public { @@ -462,14 +465,14 @@ contract QuarkWalletTest is Test { (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, op); assertEq(counter.number(), 0); - assertEq(stateManager.getNonceToken(address(aliceWalletExecutable), nonce), bytes32(uint256(0))); + assertEq(stateManager.getNonceReplayToken(address(aliceWalletExecutable), nonce), bytes32(uint256(0))); // gas: meter execute vm.resumeGasMetering(); aliceWallet.executeQuarkOperation(op, v, r, s); assertEq(counter.number(), 3); - assertEq(stateManager.getNonceToken(address(aliceWalletExecutable), nonce), bytes32(type(uint256).max)); + assertEq(stateManager.getNonceReplayToken(address(aliceWalletExecutable), nonce), bytes32(type(uint256).max)); } function testDirectExecuteWithScriptSources() public { @@ -484,7 +487,7 @@ contract QuarkWalletTest is Test { scriptSources[0] = incrementer; assertEq(counter.number(), 0); - assertEq(stateManager.getNonceToken(address(aliceWalletExecutable), nonce), bytes32(uint256(0))); + assertEq(stateManager.getNonceReplayToken(address(aliceWalletExecutable), nonce), bytes32(uint256(0))); // act as the executor for the wallet vm.startPrank(aliceAccount); @@ -494,7 +497,7 @@ contract QuarkWalletTest is Test { aliceWalletExecutable.executeScript(nonce, incrementerAddress, call, scriptSources); assertEq(counter.number(), 3); - assertEq(stateManager.getNonceToken(address(aliceWalletExecutable), nonce), bytes32(type(uint256).max)); + assertEq(stateManager.getNonceReplayToken(address(aliceWalletExecutable), nonce), bytes32(type(uint256).max)); } function testRevertsForDirectExecuteByNonExecutorSigner() public { @@ -816,6 +819,7 @@ contract QuarkWalletTest is Test { scriptSources: scriptSources, scriptCalldata: scriptCalldata, nonce: nonce, + isReplayable: false, expiry: expiry }); @@ -829,6 +833,7 @@ contract QuarkWalletTest is Test { }, { QuarkOperation: [ { name: 'nonce', type: 'bytes32' }, + { name: 'isReplayable', type: 'bool' }, { name: 'scriptAddress', type: 'address' }, { name: 'scriptSources', type: 'bytes[]' }, { name: 'scriptCalldata', type: 'bytes' }, @@ -836,6 +841,7 @@ contract QuarkWalletTest is Test { ]}, { nonce: '0x0000000000000000000000000000000000000000000000000000000000000001', + isReplayable: false, scriptAddress: '0x4a925cF75dcc5708671004d9bbFAf4DCF2C762B0', scriptSources: ['0x630000003080600e6000396000f36000356000527f48257dc961b6f792c2b78a080dacfed693b660960a702de21cee364e20270e2f60206000a1600080f3'], scriptCalldata: '0x00000000000000000000000000000000000000000000000000000000000000dd', @@ -845,7 +851,7 @@ contract QuarkWalletTest is Test { */ bytes memory sigHash = - hex"1901420cb4769bd47ac11897b8b69b8d80a84b9ec8b69437cd42529681d583a6b5211a9548fcdcb39c227cdd2ff9e13fbefc3db707f8e2216139758d6fc20328fcfd"; + hex"1901420cb4769bd47ac11897b8b69b8d80a84b9ec8b69437cd42529681d583a6b5218c7d870a6510d1840f2ec48a08d65eb874fa8af841e45e3c9b8e5c244bdc015f"; (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePrivateKey, keccak256(sigHash)); // gas: meter execute @@ -1231,6 +1237,7 @@ contract QuarkWalletTest is Test { scriptSources: new bytes[](0), scriptCalldata: hex"", nonce: nonce, + isReplayable: false, expiry: block.timestamp + 1000 }); } diff --git a/test/quark-proxy/QuarkWalletProxyFactory.t.sol b/test/quark-proxy/QuarkWalletProxyFactory.t.sol index 669bfa4b..608fd130 100644 --- a/test/quark-proxy/QuarkWalletProxyFactory.t.sol +++ b/test/quark-proxy/QuarkWalletProxyFactory.t.sol @@ -120,6 +120,7 @@ contract QuarkWalletProxyFactoryTest is Test { scriptSources: scriptSources, scriptCalldata: abi.encodeWithSignature("incrementCounter(address)", counter), nonce: nonce, + isReplayable: false, expiry: block.timestamp + 1000 }); @@ -142,7 +143,7 @@ contract QuarkWalletProxyFactoryTest is Test { assertEq(counter.number(), 3); // uses up the operation's nonce - assertEq(stateManager.getNonceToken(factory.walletAddressFor(alice, address(0)), nonce), bytes32(type(uint256).max)); + assertEq(stateManager.getNonceReplayToken(factory.walletAddressFor(alice, address(0)), nonce), bytes32(type(uint256).max)); } function testCreateAndExecuteWithSalt() public { @@ -161,6 +162,7 @@ contract QuarkWalletProxyFactoryTest is Test { scriptSources: scriptSources, scriptCalldata: abi.encodeWithSignature("incrementCounter(address)", counter), nonce: nonce, + isReplayable: false, expiry: block.timestamp + 1000 }); @@ -185,7 +187,7 @@ contract QuarkWalletProxyFactoryTest is Test { assertEq(counter.number(), 3); // uses up the operation's nonce - assertEq(stateManager.getNonceToken(factory.walletAddressForSalt(alice, address(0), salt), nonce), bytes32(type(uint256).max)); + assertEq(stateManager.getNonceReplayToken(factory.walletAddressForSalt(alice, address(0), salt), nonce), bytes32(type(uint256).max)); } function testExecuteOnExistingWallet() public { @@ -204,6 +206,7 @@ contract QuarkWalletProxyFactoryTest is Test { scriptSources: scriptSources, scriptCalldata: abi.encodeWithSignature("incrementCounter(address)", counter), nonce: nonce, + isReplayable: false, expiry: block.timestamp + 1000 }); @@ -228,7 +231,7 @@ contract QuarkWalletProxyFactoryTest is Test { assertEq(counter.number(), 3); // uses up the operation's nonce - assertEq(stateManager.getNonceToken(factory.walletAddressFor(alice, address(0)), nonce), bytes32(type(uint256).max)); + assertEq(stateManager.getNonceReplayToken(factory.walletAddressFor(alice, address(0)), nonce), bytes32(type(uint256).max)); } /* ===== create and execute MultiQuarkOperation tests ===== */ @@ -254,6 +257,7 @@ contract QuarkWalletProxyFactoryTest is Test { scriptSources: scriptSources, scriptCalldata: abi.encodeWithSignature("incrementCounter(address)", counter), nonce: nonce, + isReplayable: false, expiry: block.timestamp + 1000 }); bytes32 op1Digest = new SignatureHelper().opDigest(aliceWalletAddress, op1); @@ -263,6 +267,7 @@ contract QuarkWalletProxyFactoryTest is Test { scriptSources: scriptSources, scriptCalldata: abi.encodeWithSignature("incrementCounter(address)", counter), nonce: new QuarkOperationHelper().incrementNonce(nonce), + isReplayable: false, expiry: block.timestamp + 1000 }); op2.nonce = new QuarkOperationHelper().incrementNonce(op1.nonce); @@ -281,13 +286,13 @@ contract QuarkWalletProxyFactoryTest is Test { factory.createAndExecuteMulti(alice, address(0), op1, opDigests, v, r, s); assertEq(counter.number(), 3); - assertEq(stateManager.getNonceToken(aliceWalletAddress, op1.nonce), bytes32(type(uint256).max)); + assertEq(stateManager.getNonceReplayToken(aliceWalletAddress, op1.nonce), bytes32(type(uint256).max)); // call a second time factory.createAndExecuteMulti(alice, address(0), op2, opDigests, v, r, s); assertEq(counter.number(), 6); - assertEq(stateManager.getNonceToken(aliceWalletAddress, op2.nonce), bytes32(type(uint256).max)); + assertEq(stateManager.getNonceReplayToken(aliceWalletAddress, op2.nonce), bytes32(type(uint256).max)); } function testCreateAndExecuteMultiWithSalt() public { @@ -308,6 +313,7 @@ contract QuarkWalletProxyFactoryTest is Test { scriptSources: scriptSources, scriptCalldata: abi.encodeWithSignature("incrementCounter(address)", counter), nonce: nonce, + isReplayable: false, expiry: block.timestamp + 1000 }); bytes32 op1Digest = new SignatureHelper().opDigest(aliceWalletAddress, op1); @@ -317,6 +323,7 @@ contract QuarkWalletProxyFactoryTest is Test { scriptSources: scriptSources, scriptCalldata: abi.encodeWithSignature("incrementCounter(address)", counter), nonce: new QuarkOperationHelper().incrementNonce(nonce), + isReplayable: false, expiry: block.timestamp + 1000 }); op2.nonce = new QuarkOperationHelper().incrementNonce(op1.nonce); @@ -335,13 +342,13 @@ contract QuarkWalletProxyFactoryTest is Test { factory.createAndExecuteMulti(alice, address(0), salt, op1, opDigests, v, r, s); assertEq(counter.number(), 3); - assertEq(stateManager.getNonceToken(aliceWalletAddress, op1.nonce), bytes32(type(uint256).max)); + assertEq(stateManager.getNonceReplayToken(aliceWalletAddress, op1.nonce), bytes32(type(uint256).max)); // call a second time factory.createAndExecuteMulti(alice, address(0), salt, op2, opDigests, v, r, s); assertEq(counter.number(), 6); - assertEq(stateManager.getNonceToken(aliceWalletAddress, op2.nonce), bytes32(type(uint256).max)); + assertEq(stateManager.getNonceReplayToken(aliceWalletAddress, op2.nonce), bytes32(type(uint256).max)); } function testExecuteMultiOnExistingWallet() public { @@ -365,6 +372,7 @@ contract QuarkWalletProxyFactoryTest is Test { scriptSources: scriptSources, scriptCalldata: abi.encodeWithSignature("incrementCounter(address)", counter), nonce: nonce, + isReplayable: false, expiry: block.timestamp + 1000 }); bytes32 op1Digest = new SignatureHelper().opDigest(aliceWalletAddress, op1); @@ -374,6 +382,7 @@ contract QuarkWalletProxyFactoryTest is Test { scriptSources: scriptSources, scriptCalldata: abi.encodeWithSignature("incrementCounter(address)", counter), nonce: new QuarkOperationHelper().incrementNonce(nonce), + isReplayable: false, expiry: block.timestamp + 1000 }); op2.nonce = new QuarkOperationHelper().incrementNonce(op1.nonce); @@ -396,13 +405,13 @@ contract QuarkWalletProxyFactoryTest is Test { factory.createAndExecuteMulti(alice, address(0), op1, opDigests, v, r, s); assertEq(counter.number(), 3); - assertEq(stateManager.getNonceToken(aliceWalletAddress, op1.nonce), bytes32(type(uint256).max)); + assertEq(stateManager.getNonceReplayToken(aliceWalletAddress, op1.nonce), bytes32(type(uint256).max)); // call a second time factory.createAndExecuteMulti(alice, address(0), op2, opDigests, v, r, s); assertEq(counter.number(), 6); - assertEq(stateManager.getNonceToken(aliceWalletAddress, op2.nonce), bytes32(type(uint256).max)); + assertEq(stateManager.getNonceReplayToken(aliceWalletAddress, op2.nonce), bytes32(type(uint256).max)); } /* ===== msg.value and msg.sender tests ===== */ @@ -424,6 +433,7 @@ contract QuarkWalletProxyFactoryTest is Test { scriptSources: scriptSources, scriptCalldata: abi.encodeWithSignature("getMsgSenderAndValue()"), nonce: nonce, + isReplayable: false, expiry: block.timestamp + 1000 }); (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOpForAddress(alicePrivateKey, aliceWallet, op); @@ -442,7 +452,7 @@ contract QuarkWalletProxyFactoryTest is Test { assertEq(msgValue, 0); // uses up the operation's nonce - assertEq(stateManager.getNonceToken(aliceWallet, nonce), bytes32(type(uint256).max)); + assertEq(stateManager.getNonceReplayToken(aliceWallet, nonce), bytes32(type(uint256).max)); } function testCreateAndExecuteWithSaltSetsMsgSender() public { @@ -459,6 +469,7 @@ contract QuarkWalletProxyFactoryTest is Test { scriptSources: new bytes[](0), scriptCalldata: abi.encodeWithSignature("getMsgSenderAndValue()"), nonce: nonce, + isReplayable: false, expiry: block.timestamp + 1000 }); (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOpForAddress(alicePrivateKey, aliceWallet, op); @@ -492,7 +503,7 @@ contract QuarkWalletProxyFactoryTest is Test { assertEq(msgValue, 0); // uses up the operation's nonce - assertEq(stateManager.getNonceToken(aliceWallet, nonce), bytes32(type(uint256).max)); + assertEq(stateManager.getNonceReplayToken(aliceWallet, nonce), bytes32(type(uint256).max)); } /* ===== default wallet executor role tests ===== */ @@ -536,6 +547,7 @@ contract QuarkWalletProxyFactoryTest is Test { ) ), nonce: new QuarkOperationHelper().semiRandomNonce(stateManager, aliceWalletPrimary), + isReplayable: false, expiry: block.timestamp + 1000 }); (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, aliceWalletPrimary, op);