diff --git a/src/quark-core/src/QuarkWallet.sol b/src/quark-core/src/QuarkWallet.sol index c154cdda..1aebe699 100644 --- a/src/quark-core/src/QuarkWallet.sol +++ b/src/quark-core/src/QuarkWallet.sol @@ -503,6 +503,9 @@ contract QuarkWallet is IERC1271 { // Transiently store the active submission token tstore(activeSubmissionTokenSlot, submissionToken) + // Transiently set the callback slot to 0 + tstore(callbackSlot, 0) + // Note: CALLCODE is used to set the QuarkWallet as the `msg.sender` success := callcode(gas(), scriptAddress, /* value */ 0, add(scriptCalldata, 0x20), scriptCalldataLen, 0x0, 0) diff --git a/test/lib/GetCallbackDetails.sol b/test/lib/GetCallbackDetails.sol new file mode 100644 index 00000000..7fe3cd6d --- /dev/null +++ b/test/lib/GetCallbackDetails.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.27; + +import {QuarkWalletMetadata} from "quark-core/src/QuarkWallet.sol"; +import {QuarkScript} from "quark-core/src/QuarkScript.sol"; + +contract GetCallbackDetails is QuarkScript { + function getCallbackAddress() public returns (address) { + bytes32 callbackSlot = QuarkWalletMetadata.CALLBACK_SLOT; + address callbackAddress; + assembly { + callbackAddress := tload(callbackSlot) + } + return callbackAddress; + } +} diff --git a/test/quark-core/Callbacks.t.sol b/test/quark-core/Callbacks.t.sol index 2cbef133..cf546b3f 100644 --- a/test/quark-core/Callbacks.t.sol +++ b/test/quark-core/Callbacks.t.sol @@ -138,9 +138,44 @@ contract CallbacksTest is Test { // gas: meter execute vm.resumeGasMetering(); aliceWallet.executeQuarkOperation(parentOp, v, r, s); + assertEq(counter.number(), 11); } + function testNestedCallbackResetsCallbackSlot() public { + // gas: do not meter set-up + vm.pauseGasMetering(); + bytes memory getCallbackDetails = new YulHelper().getCode("GetCallbackDetails.sol/GetCallbackDetails.json"); + bytes memory executeOtherScript = + new YulHelper().getCode("ExecuteOtherOperation.sol/ExecuteOtherOperation.json"); + + QuarkWallet.QuarkOperation memory nestedOp = new QuarkOperationHelper().newBasicOpWithCalldata( + aliceWallet, getCallbackDetails, abi.encodeWithSignature("getCallbackAddress()"), ScriptType.ScriptAddress + ); + + (uint8 v_, bytes32 r_, bytes32 s_) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, nestedOp); + + QuarkWallet.QuarkOperation memory parentOp = new QuarkOperationHelper().newBasicOpWithCalldata( + aliceWallet, + executeOtherScript, + abi.encodeWithSelector(ExecuteOtherOperation.run.selector, nestedOp, v_, r_, s_), + ScriptType.ScriptAddress + ); + + parentOp.nonce = new QuarkOperationHelper().incrementNonce(nestedOp.nonce); + + (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, parentOp); + + // gas: meter execute + vm.resumeGasMetering(); + bytes memory result = aliceWallet.executeQuarkOperation(parentOp, v, r, s); + // We decode twice because the result is encoded twice due to the nested operation + address innerCallbackAddress = abi.decode(abi.decode(result, (bytes)), (address)); + + // The inner callback address should be 0 + assertEq(innerCallbackAddress, address(0)); + } + function testNestedCallWithNoCallbackSucceeds() public { // gas: do not meter set-up vm.pauseGasMetering();