Skip to content

Commit

Permalink
Add Cooperative Storage (#211)
Browse files Browse the repository at this point in the history
This patch adds back cooperative storage, which simply isolates each
`read` and `write` call in `QuarkScript` to pulling the active nonce and
isolating storage keys on that value.

Notes:
* This is cooperative. A malicious script is free to change any stored
data.
* After a nested quark operation, the nonce value is cleared and thus
will not be properly read. We raise an error in this case.
  • Loading branch information
hayesgm authored Sep 12, 2024
1 parent 9924957 commit e0fea10
Show file tree
Hide file tree
Showing 89 changed files with 958 additions and 265 deletions.
4 changes: 2 additions & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[profile.default]
solc = "0.8.23"
evm_version = "paris"
solc = "0.8.27"
evm_version = "cancun"

libs = [ "./lib" ]

Expand Down
2 changes: 1 addition & 1 deletion script/DeployCodeJarFactory.s.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.23;
pragma solidity 0.8.27;

import "forge-std/Script.sol";
import "forge-std/console.sol";
Expand Down
2 changes: 1 addition & 1 deletion script/DeployQuarkWalletFactory.s.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.23;
pragma solidity 0.8.27;

import "forge-std/Script.sol";
import "forge-std/console.sol";
Expand Down
4 changes: 2 additions & 2 deletions src/codejar/foundry.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[profile.default]
solc = "0.8.23"
evm_version = "paris"
solc = "0.8.27"
evm_version = "cancun"

libs = [ "../../lib" ]

Expand Down
2 changes: 1 addition & 1 deletion src/codejar/src/CodeJar.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.23;
pragma solidity 0.8.27;

/**
* @title Code Jar
Expand Down
2 changes: 1 addition & 1 deletion src/codejar/src/CodeJarFactory.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.23;
pragma solidity 0.8.27;

import {CodeJar} from "codejar/src/CodeJar.sol";

Expand Down
4 changes: 2 additions & 2 deletions src/quark-core-scripts/foundry.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[profile.default]
solc = "0.8.23"
evm_version = "paris"
solc = "0.8.27"
evm_version = "cancun"

libs = [ "../../lib" ]

Expand Down
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());
}
}
2 changes: 1 addition & 1 deletion src/quark-core-scripts/src/ConditionalMulticall.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.23;
pragma solidity 0.8.27;

import "quark-core-scripts/src/lib/ConditionalChecker.sol";

Expand Down
2 changes: 1 addition & 1 deletion src/quark-core-scripts/src/Ethcall.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.23;
pragma solidity 0.8.27;

/**
* @title Ethcall Core Script
Expand Down
2 changes: 1 addition & 1 deletion src/quark-core-scripts/src/Multicall.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.23;
pragma solidity 0.8.27;

/**
* @title Multicall Core Script
Expand Down
2 changes: 1 addition & 1 deletion src/quark-core-scripts/src/Paycall.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.23;
pragma solidity 0.8.27;

import "quark-core-scripts/src/vendor/chainlink/AggregatorV3Interface.sol";
import "openzeppelin/token/ERC20/utils/SafeERC20.sol";
Expand Down
2 changes: 1 addition & 1 deletion src/quark-core-scripts/src/Quotecall.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.23;
pragma solidity 0.8.27;

import "quark-core-scripts/src/vendor/chainlink/AggregatorV3Interface.sol";
import "openzeppelin/token/ERC20/utils/SafeERC20.sol";
Expand Down
2 changes: 1 addition & 1 deletion src/quark-core-scripts/src/UniswapFlashLoan.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.23;
pragma solidity 0.8.27;

import "openzeppelin/token/ERC20/utils/SafeERC20.sol";
import "v3-core/contracts/interfaces/IUniswapV3Pool.sol";
Expand Down
2 changes: 1 addition & 1 deletion src/quark-core-scripts/src/UniswapFlashSwapExactOut.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.23;
pragma solidity 0.8.27;

import "openzeppelin/token/ERC20/utils/SafeERC20.sol";
import "v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol";
Expand Down
2 changes: 1 addition & 1 deletion src/quark-core-scripts/src/lib/ConditionalChecker.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.23;
pragma solidity 0.8.27;

library ConditionalChecker {
enum CheckType {
Expand Down
2 changes: 1 addition & 1 deletion src/quark-core-scripts/src/lib/UniswapFactoryAddress.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.23;
pragma solidity 0.8.27;

library UniswapFactoryAddress {
// Reference: https://docs.uniswap.org/contracts/v3/reference/deployments
Expand Down
2 changes: 1 addition & 1 deletion src/quark-core-scripts/src/vendor/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"newLines": 6,
"lines": [
" // SPDX-License-Identifier: GPL-2.0-or-later",
"-pragma solidity 0.8.23;",
"-pragma solidity 0.8.27;",
"+pragma solidity >=0.5.0;",
" ",
" /// @title Provides functions for deriving a pool address from the factory, tokens, and the fee",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.23;
pragma solidity 0.8.27;

/// @title Provides functions for deriving a pool address from the factory, tokens, and the fee
library PoolAddress {
Expand Down
4 changes: 2 additions & 2 deletions src/quark-core/foundry.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[profile.default]
solc = "0.8.23"
evm_version = "paris"
solc = "0.8.27"
evm_version = "cancun"

libs = [ "../../lib" ]

Expand Down
12 changes: 11 additions & 1 deletion src/quark-core/src/QuarkNonceManager.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.23;
pragma solidity 0.8.27;

import {IQuarkWallet} from "quark-core/src/interfaces/IQuarkWallet.sol";

Expand All @@ -22,6 +22,7 @@ contract QuarkNonceManager {
error InvalidSubmissionToken(address wallet, bytes32 nonce, bytes32 submissionToken);

event NonceSubmitted(address wallet, bytes32 nonce, bytes32 submissionToken);
event NonceCanceled(address wallet, bytes32 nonce);

/// @notice Represents the unclaimed bytes32 value.
bytes32 public constant FREE = QuarkNonceManagerMetadata.FREE;
Expand All @@ -32,6 +33,15 @@ contract QuarkNonceManager {
/// @notice Mapping from nonces to last used submission token.
mapping(address wallet => mapping(bytes32 nonce => bytes32 lastToken)) public submissions;

/**
* @notice Ensures a given nonce is canceled for sender. An un-used nonce will not be usable in the future, and a replayable nonce will no longer be replayable. This is a no-op for already canceled operations.
* @param nonce The nonce of the chain to cancel.
*/
function cancel(bytes32 nonce) external {
submissions[msg.sender][nonce] = EXHAUSTED;
emit NonceCanceled(msg.sender, nonce);
}

/**
* @notice Attempts a first or subsequent submission of a given nonce from a wallet.
* @param nonce The nonce of the chain to submit.
Expand Down
48 changes: 30 additions & 18 deletions src/quark-core/src/QuarkScript.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.23;
pragma solidity 0.8.27;

import {QuarkWallet, QuarkWalletMetadata, IHasSignerExecutor} from "quark-core/src/QuarkWallet.sol";
import {QuarkNonceManagerMetadata} from "quark-core/src/QuarkNonceManager.sol";
import {QuarkWallet, QuarkWalletMetadata, IHasSignerExecutor, IQuarkWallet} from "quark-core/src/QuarkWallet.sol";
import {QuarkNonceManager, QuarkNonceManagerMetadata} from "quark-core/src/QuarkNonceManager.sol";

/**
* @title Quark Script
Expand All @@ -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 All @@ -22,21 +23,20 @@ abstract contract QuarkScript {
modifier nonReentrant() {
bytes32 slot = REENTRANCY_FLAG_SLOT;
bytes32 flag;
// TODO: Move to TSTORE after updating Solidity version to >=0.8.24
assembly {
flag := sload(slot)
flag := tload(slot)
}
if (flag == bytes32(uint256(1))) {
revert ReentrantCall();
}
assembly {
sstore(slot, 1)
tstore(slot, 1)
}

_;

assembly {
sstore(slot, 0)
tstore(slot, 0)
}
}

Expand Down Expand Up @@ -69,21 +69,23 @@ abstract contract QuarkScript {
return IHasSignerExecutor(address(this)).executor();
}

function nonceManager() internal view returns (QuarkNonceManager) {
return QuarkNonceManager(IQuarkWallet(address(this)).nonceManager());
}

function allowCallback() internal {
bytes32 callbackSlot = QuarkWalletMetadata.CALLBACK_SLOT;
bytes32 activeScriptSlot = QuarkWalletMetadata.ACTIVE_SCRIPT_SLOT;
assembly {
// TODO: Move to TLOAD/TSTORE after updating Solidity version to >=0.8.24
let activeScript := sload(activeScriptSlot)
sstore(callbackSlot, activeScript)
let activeScript := tload(activeScriptSlot)
tstore(callbackSlot, activeScript)
}
}

function clearCallback() internal {
bytes32 callbackSlot = QuarkWalletMetadata.CALLBACK_SLOT;
assembly {
// TODO: Move to TSTORE after updating Solidity version to >=0.8.24
sstore(callbackSlot, 0)
tstore(callbackSlot, 0)
}
}

Expand All @@ -97,8 +99,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 @@ -111,20 +114,29 @@ abstract contract QuarkScript {
return write(keccak256(bytes(key)), value);
}

// 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));
}

// 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;
bytes32 value;
assembly {
value := sload(activeNonceSlot)
value := tload(activeNonceSlot)
}

return value;
Expand All @@ -135,7 +147,7 @@ abstract contract QuarkScript {
bytes32 activeSubmissionTokenSlot = QuarkWalletMetadata.ACTIVE_SUBMISSION_TOKEN_SLOT;
bytes32 value;
assembly {
value := sload(activeSubmissionTokenSlot)
value := tload(activeSubmissionTokenSlot)
}
return value;
}
Expand Down
Loading

0 comments on commit e0fea10

Please sign in to comment.