Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Cooperative Storage #211

Merged
merged 2 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions src/quark-core/src/QuarkScript.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ abstract contract QuarkScript {
error ReentrantCall();
error InvalidActiveNonce();
error InvalidActiveSubmissionToken();
error NoActiveNonce();

/// @notice Storage location for the re-entrancy guard
bytes32 internal constant REENTRANCY_FLAG_SLOT =
Expand Down Expand Up @@ -97,8 +98,9 @@ abstract contract QuarkScript {

function read(bytes32 key) internal view returns (bytes32) {
bytes32 value;
bytes32 isolatedKey = getNonceIsolatedKey(key);
assembly {
value := sload(key)
value := sload(isolatedKey)
}
return value;
}
Expand All @@ -114,11 +116,22 @@ abstract contract QuarkScript {
// TODO: Consider adding nonce-based scoping by TLOAD'ing the nonce and using
// that to hash the key.
function write(bytes32 key, bytes32 value) internal {
bytes32 isolatedKey = getNonceIsolatedKey(key);
assembly {
sstore(key, value)
sstore(isolatedKey, value)
}
}

// Returns a key isolated to the active nonce of a script
// This provide cooperative isolation of storage between scripts.
function getNonceIsolatedKey(bytes32 key) internal view returns (bytes32) {
bytes32 nonce = getActiveNonce();
if (nonce == bytes32(0)) {
revert NoActiveNonce();
}
return keccak256(abi.encodePacked(nonce, key));
}

hayesgm marked this conversation as resolved.
Show resolved Hide resolved
// Note: this may not be accurate after any nested calls from a script
function getActiveNonce() internal view returns (bytes32) {
bytes32 activeNonceSlot = QuarkWalletMetadata.ACTIVE_NONCE_SLOT;
Expand Down
8 changes: 8 additions & 0 deletions test/lib/Noncer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ contract Noncer is QuarkScript {
return (pre, post, result);
}

function postNestRead(QuarkWallet.QuarkOperation memory op, uint8 v, bytes32 r, bytes32 s)
public
returns (uint256)
{
QuarkWallet(payable(address(this))).executeQuarkOperation(op, v, r, s);
return readU256("count");
}

function nestedPlay(Stow stow) public returns (uint256) {
uint256 n = getActiveReplayCount();
if (n == 0) {
Expand Down
31 changes: 31 additions & 0 deletions test/quark-core/Noncer.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,37 @@ contract NoncerTest is Test {
assertEq(innerNonce, 0);
}

function testPostNestReadFailure() public {
// gas: do not meter set-up
vm.pauseGasMetering();
bytes memory noncerScript = new YulHelper().getCode("Noncer.sol/Noncer.json");
QuarkWallet.QuarkOperation memory nestedOp = new QuarkOperationHelper().newBasicOpWithCalldata(
aliceWallet, noncerScript, abi.encodeWithSignature("checkNonce()"), ScriptType.ScriptSource
);
nestedOp.nonce = bytes32(uint256(keccak256(abi.encodePacked(block.timestamp))) - 2); // Don't overlap on nonces
(uint8 nestedV, bytes32 nestedR, bytes32 nestedS) =
new SignatureHelper().signOp(alicePrivateKey, aliceWallet, nestedOp);

QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata(
aliceWallet,
noncerScript,
abi.encodeWithSignature(
"postNestRead((bytes32,bool,address,bytes[],bytes,uint256),uint8,bytes32,bytes32)",
nestedOp,
nestedV,
nestedR,
nestedS
),
ScriptType.ScriptSource
);
(uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, op);

// gas: meter execute
vm.resumeGasMetering();
vm.expectRevert(abi.encodeWithSelector(QuarkScript.NoActiveNonce.selector));
aliceWallet.executeQuarkOperation(op, v, r, s);
}

/*
* replayable
*/
Expand Down
89 changes: 65 additions & 24 deletions test/quark-core/QuarkWallet.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -405,34 +405,60 @@ contract QuarkWalletTest is Test {

/* ===== storage tests ===== */

// TODO: IMPLEMENT THIS NEW TEST (MOVED FROM STATEMANAGER)
// function testReadStorageForWallet() public {
// // gas: disable metering except while executing operations
// vm.pauseGasMetering();
function testReadStorageForWallet() public {
// gas: disable metering except while executing operations
vm.pauseGasMetering();

// Counter counter = new Counter();
// assertEq(counter.number(), 0);
assertEq(counter.number(), 0);

// bytes memory maxCounterScript = new YulHelper().getCode("MaxCounterScript.sol/MaxCounterScript.json");
// address maxCounterScriptAddress = codeJar.saveCode(maxCounterScript);
// bytes memory call = abi.encodeWithSignature("run(address)", address(counter));
bytes memory maxCounter = new YulHelper().getCode("MaxCounterScript.sol/MaxCounterScript.json");

// QuarkWallet wallet = new QuarkWalletStandalone(address(0), address(0), codeJar, nonceManager);
(QuarkWallet.QuarkOperation memory op, bytes32[] memory submissionTokens) = new QuarkOperationHelper()
.newReplayableOpWithCalldata(
aliceWallet,
maxCounter,
abi.encodeWithSignature("run(address)", address(counter)),
ScriptType.ScriptAddress,
4
);
(uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, op);

// vm.resumeGasMetering();
assertEq(
vm.load(address(aliceWallet), keccak256(abi.encodePacked(op.nonce, keccak256("count")))),
bytes32(uint256(0))
);

// assertEq(nonceManager.walletStorage(address(wallet), 0, keccak256("count")), bytes32(uint256(0)));
vm.resumeGasMetering();

// vm.prank(address(wallet));
// nonceManager.setActiveNonceAndCallback(0, maxCounterScriptAddress, call);
aliceWallet.executeQuarkOperationWithSubmissionToken(op, submissionTokens[0], v, r, s);
assertEq(
vm.load(address(aliceWallet), keccak256(abi.encodePacked(op.nonce, keccak256("count")))),
bytes32(uint256(1))
);
assertEq(counter.number(), 1);

// assertEq(nonceManager.walletStorage(address(wallet), 0, keccak256("count")), bytes32(uint256(1)));
aliceWallet.executeQuarkOperationWithSubmissionToken(op, submissionTokens[1], v, r, s);
assertEq(
vm.load(address(aliceWallet), keccak256(abi.encodePacked(op.nonce, keccak256("count")))),
bytes32(uint256(2))
);
assertEq(counter.number(), 2);

// vm.prank(address(wallet));
// nonceManager.setActiveNonceAndCallback(0, maxCounterScriptAddress, call);
aliceWallet.executeQuarkOperationWithSubmissionToken(op, submissionTokens[2], v, r, s);
assertEq(
vm.load(address(aliceWallet), keccak256(abi.encodePacked(op.nonce, keccak256("count")))),
bytes32(uint256(3))
);
assertEq(counter.number(), 3);

// assertEq(nonceManager.walletStorage(address(wallet), 0, keccak256("count")), bytes32(uint256(2)));
// }
vm.expectRevert(abi.encodeWithSelector(MaxCounterScript.EnoughAlready.selector));
aliceWallet.executeQuarkOperationWithSubmissionToken(op, submissionTokens[3], v, r, s);
assertEq(
vm.load(address(aliceWallet), keccak256(abi.encodePacked(op.nonce, keccak256("count")))),
bytes32(uint256(3))
);
assertEq(counter.number(), 3);
}

/* ===== replayability tests ===== */

Expand Down Expand Up @@ -881,7 +907,10 @@ contract QuarkWalletTest is Test {
vm.pauseGasMetering();

assertEq(counter.number(), 1);
assertEq(vm.load(address(aliceWallet), keccak256("count")), bytes32(uint256(1)));
assertEq(
vm.load(address(aliceWallet), keccak256(abi.encodePacked(op.nonce, keccak256("count")))),
bytes32(uint256(1))
);

// call twice
vm.resumeGasMetering();
Expand All @@ -893,7 +922,10 @@ contract QuarkWalletTest is Test {
vm.pauseGasMetering();

assertEq(counter.number(), 2);
assertEq(vm.load(address(aliceWallet), keccak256("count")), bytes32(uint256(2)));
assertEq(
vm.load(address(aliceWallet), keccak256(abi.encodePacked(op.nonce, keccak256("count")))),
bytes32(uint256(2))
);

// call thrice
vm.resumeGasMetering();
Expand All @@ -905,7 +937,10 @@ contract QuarkWalletTest is Test {
vm.pauseGasMetering();

assertEq(counter.number(), 3);
assertEq(vm.load(address(aliceWallet), keccak256("count")), bytes32(uint256(3)));
assertEq(
vm.load(address(aliceWallet), keccak256(abi.encodePacked(op.nonce, keccak256("count")))),
bytes32(uint256(3))
);

// revert because max has been hit
vm.expectRevert(abi.encodeWithSelector(MaxCounterScript.EnoughAlready.selector));
Expand All @@ -916,11 +951,17 @@ contract QuarkWalletTest is Test {
vm.pauseGasMetering();

assertEq(counter.number(), 3);
assertEq(vm.load(address(aliceWallet), keccak256("count")), bytes32(uint256(3)));
assertEq(
vm.load(address(aliceWallet), keccak256(abi.encodePacked(op.nonce, keccak256("count")))),
bytes32(uint256(3))
);

counter.increment();
assertEq(counter.number(), 4);
assertEq(vm.load(address(aliceWallet), keccak256("count")), bytes32(uint256(3)));
assertEq(
vm.load(address(aliceWallet), keccak256(abi.encodePacked(op.nonce, keccak256("count")))),
bytes32(uint256(3))
);

vm.resumeGasMetering();
vm.stopPrank();
Expand Down
Loading