diff --git a/src/Test.sol b/src/Test.sol index 7b20508..86affec 100644 --- a/src/Test.sol +++ b/src/Test.sol @@ -10,8 +10,13 @@ interface ConfidentialInputsWrapperI { function resetConfidentialInputs() external; } +interface ConfidentialStoreI { + function reset() external; +} + contract SuaveEnabled is Test { ConfidentialInputsWrapperI constant confInputsWrapper = ConfidentialInputsWrapperI(Suave.CONFIDENTIAL_INPUTS); + ConfidentialStoreI constant confStoreWrapper = ConfidentialStoreI(Registry.confidentialStoreAddr); function setUp() public { string[] memory inputs = new string[](3); @@ -82,11 +87,6 @@ contract SuaveEnabled is Test { } function resetConfidentialStore() public { - string[] memory inputs = new string[](3); - inputs[0] = "suave-geth"; - inputs[1] = "forge"; - inputs[2] = "reset-conf-store"; - - vm.ffi(inputs); + confStoreWrapper.reset(); } } diff --git a/src/Transactions.sol b/src/Transactions.sol index fd01200..626a0d3 100644 --- a/src/Transactions.sol +++ b/src/Transactions.sol @@ -299,7 +299,6 @@ library Transactions { function signTxn(Transactions.EIP1559Request memory request, string memory signingKey) internal - view returns (Transactions.EIP1559 memory response) { bytes memory rlp = Transactions.encodeRLP(request); @@ -325,7 +324,6 @@ library Transactions { function signTxn(Transactions.EIP155Request memory request, string memory signingKey) internal - view returns (Transactions.EIP155 memory response) { bytes memory rlp = Transactions.encodeRLP(request); diff --git a/src/forge/ConfidentialStore.sol b/src/forge/ConfidentialStore.sol new file mode 100644 index 0000000..a3ca440 --- /dev/null +++ b/src/forge/ConfidentialStore.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.8; + +import "../suavelib/Suave.sol"; +import "forge-std/Test.sol"; + +// ConfidentialStore is an implementation of the confidential store in Solidity. +contract ConfidentialStore is Test { + mapping(bytes32 => Suave.DataRecord[]) private dataRecordsByConditionAndNamespace; + mapping(Suave.DataId => mapping(string => bytes)) private dataRecordsContent; + mapping(Suave.DataId => Suave.DataRecord) private dataRecords; + + uint64 private numRecords; + + type DataId is bytes16; + + constructor() { + vm.record(); + } + + function newDataRecord( + uint64 decryptionCondition, + address[] memory allowedPeekers, + address[] memory allowedStores, + string memory dataType + ) public returns (Suave.DataRecord memory) { + numRecords++; + + // Use a counter of the records to create a unique key + Suave.DataId id = Suave.DataId.wrap(bytes16(keccak256(abi.encodePacked(numRecords)))); + numRecords++; + + Suave.DataRecord memory newRecord; + newRecord.id = id; + newRecord.decryptionCondition = decryptionCondition; + newRecord.allowedPeekers = allowedPeekers; + newRecord.allowedStores = allowedStores; + newRecord.version = dataType; + + // Store the data record metadata + dataRecords[id] = newRecord; + + // Use a composite index to store the records for the 'fetchDataRecords' function + bytes32 key = keccak256(abi.encodePacked(decryptionCondition, dataType)); + dataRecordsByConditionAndNamespace[key].push(newRecord); + + return newRecord; + } + + function fetchDataRecords(uint64 cond, string memory namespace) public view returns (Suave.DataRecord[] memory) { + bytes32 key = keccak256(abi.encodePacked(cond, namespace)); + return dataRecordsByConditionAndNamespace[key]; + } + + function confidentialStore(Suave.DataId dataId, string memory key, bytes memory value) public { + address[] memory allowedStores = dataRecords[dataId].allowedStores; + for (uint256 i = 0; i < allowedStores.length; i++) { + if (allowedStores[i] == msg.sender || allowedStores[i] == Suave.ANYALLOWED) { + dataRecordsContent[dataId][key] = value; + return; + } + } + + revert("Not allowed to store"); + } + + function confidentialRetrieve(Suave.DataId dataId, string memory key) public view returns (bytes memory) { + address[] memory allowedPeekers = dataRecords[dataId].allowedPeekers; + for (uint256 i = 0; i < allowedPeekers.length; i++) { + if (allowedPeekers[i] == msg.sender || allowedPeekers[i] == Suave.ANYALLOWED) { + return dataRecordsContent[dataId][key]; + } + } + + revert("Not allowed to retrieve"); + } + + function reset() public { + (, bytes32[] memory writes) = vm.accesses(address(this)); + for (uint256 i = 0; i < writes.length; i++) { + vm.store(address(this), writes[i], 0); + } + } +} diff --git a/src/forge/ConfidentialStoreConnector.sol b/src/forge/ConfidentialStoreConnector.sol new file mode 100644 index 0000000..87a355e --- /dev/null +++ b/src/forge/ConfidentialStoreConnector.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.8; + +import "./ConfidentialStore.sol"; +import "../suavelib/Suave.sol"; + +contract ConfidentialStoreConnector { + fallback() external { + address confidentialStoreAddr = 0x0101010101010101010101010101010101010101; + + address addr = address(this); + bytes4 sig; + + if (addr == Suave.CONFIDENTIAL_STORE) { + sig = ConfidentialStore.confidentialStore.selector; + } else if (addr == Suave.CONFIDENTIAL_RETRIEVE) { + sig = ConfidentialStore.confidentialRetrieve.selector; + } else if (addr == Suave.FETCH_DATA_RECORDS) { + sig = ConfidentialStore.fetchDataRecords.selector; + } else if (addr == Suave.NEW_DATA_RECORD) { + sig = ConfidentialStore.newDataRecord.selector; + } else { + revert("function signature not found in the confidential store"); + } + + bytes memory input = msg.data; + + // call 'confidentialStore' with the selector and the input data. + (bool success, bytes memory output) = confidentialStoreAddr.call(abi.encodePacked(sig, input)); + if (!success) { + revert("Call to confidentialStore failed"); + } + + if (addr == Suave.CONFIDENTIAL_RETRIEVE) { + // special case we have to unroll the value from the abi + // since it comes encoded as tuple() but we return the value normally + // this was a special case that was not fixed yet in suave-geth. + output = abi.decode(output, (bytes)); + } + + assembly { + let location := output + let length := mload(output) + return(add(location, 0x20), length) + } + } +} diff --git a/src/forge/Registry.sol b/src/forge/Registry.sol index b82a092..2101166 100644 --- a/src/forge/Registry.sol +++ b/src/forge/Registry.sol @@ -6,6 +6,8 @@ import "../suavelib/Suave.sol"; import "./Connector.sol"; import "./ConfidentialInputs.sol"; import "./SuaveAddrs.sol"; +import "./ConfidentialStore.sol"; +import "./ConfidentialStoreConnector.sol"; interface registryVM { function etch(address, bytes calldata) external; @@ -13,6 +15,7 @@ interface registryVM { library Registry { registryVM constant vm = registryVM(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + address public constant confidentialStoreAddr = 0x0101010101010101010101010101010101010101; function enable() public { // enable all suave libraries @@ -22,7 +25,23 @@ library Registry { vm.etch(addrList[i], type(Connector).runtimeCode); } + // enable the confidential store + deployCodeTo(type(ConfidentialStore).creationCode, confidentialStoreAddr); + + // enable the confidential inputs wrapper + vm.etch(Suave.CONFIDENTIAL_RETRIEVE, type(ConfidentialStoreConnector).runtimeCode); + vm.etch(Suave.CONFIDENTIAL_STORE, type(ConfidentialStoreConnector).runtimeCode); + vm.etch(Suave.NEW_DATA_RECORD, type(ConfidentialStoreConnector).runtimeCode); + vm.etch(Suave.FETCH_DATA_RECORDS, type(ConfidentialStoreConnector).runtimeCode); + // enable is confidential wrapper vm.etch(Suave.CONFIDENTIAL_INPUTS, type(ConfidentialInputsWrapper).runtimeCode); } + + function deployCodeTo(bytes memory creationCode, address where) internal { + vm.etch(where, creationCode); + (bool success, bytes memory runtimeBytecode) = where.call(""); + require(success, "StdCheats deployCodeTo(string,bytes,uint256,address): Failed to create runtime bytecode."); + vm.etch(where, runtimeBytecode); + } } diff --git a/src/protocols/Bundle.sol b/src/protocols/Bundle.sol index 6d95ff9..68051f1 100644 --- a/src/protocols/Bundle.sol +++ b/src/protocols/Bundle.sol @@ -13,7 +13,7 @@ library Bundle { bytes[] txns; } - function sendBundle(string memory url, BundleObj memory bundle) internal view returns (bytes memory) { + function sendBundle(string memory url, BundleObj memory bundle) internal returns (bytes memory) { Suave.HttpRequest memory request = encodeBundle(bundle); request.url = url; return Suave.doHTTPRequest(request); diff --git a/src/protocols/MevShare.sol b/src/protocols/MevShare.sol index 2990a20..7fe21f5 100644 --- a/src/protocols/MevShare.sol +++ b/src/protocols/MevShare.sol @@ -71,7 +71,7 @@ library MevShare { return request; } - function sendBundle(string memory url, Bundle memory bundle) internal view { + function sendBundle(string memory url, Bundle memory bundle) internal { Suave.HttpRequest memory request = encodeBundle(bundle); request.url = url; Suave.doHTTPRequest(request); diff --git a/src/suavelib/Suave.sol b/src/suavelib/Suave.sol index bcaf758..9631364 100644 --- a/src/suavelib/Suave.sol +++ b/src/suavelib/Suave.sol @@ -95,8 +95,8 @@ library Suave { address public constant SUBMIT_ETH_BLOCK_TO_RELAY = 0x0000000000000000000000000000000042100002; // Returns whether execution is off- or on-chain - function isConfidential() internal view returns (bool b) { - (bool success, bytes memory isConfidentialBytes) = IS_CONFIDENTIAL_ADDR.staticcall(""); + function isConfidential() internal returns (bool b) { + (bool success, bytes memory isConfidentialBytes) = IS_CONFIDENTIAL_ADDR.call(""); if (!success) { revert PeekerReverted(IS_CONFIDENTIAL_ADDR, isConfidentialBytes); } @@ -110,10 +110,9 @@ library Suave { function buildEthBlock(BuildBlockArgs memory blockArgs, DataId dataId, string memory namespace) internal - view returns (bytes memory, bytes memory) { - (bool success, bytes memory data) = BUILD_ETH_BLOCK.staticcall(abi.encode(blockArgs, dataId, namespace)); + (bool success, bytes memory data) = BUILD_ETH_BLOCK.call(abi.encode(blockArgs, dataId, namespace)); if (!success) { revert PeekerReverted(BUILD_ETH_BLOCK, data); } @@ -121,8 +120,8 @@ library Suave { return abi.decode(data, (bytes, bytes)); } - function confidentialInputs() internal view returns (bytes memory) { - (bool success, bytes memory data) = CONFIDENTIAL_INPUTS.staticcall(abi.encode()); + function confidentialInputs() internal returns (bytes memory) { + (bool success, bytes memory data) = CONFIDENTIAL_INPUTS.call(abi.encode()); if (!success) { revert PeekerReverted(CONFIDENTIAL_INPUTS, data); } @@ -130,8 +129,8 @@ library Suave { return data; } - function confidentialRetrieve(DataId dataId, string memory key) internal view returns (bytes memory) { - (bool success, bytes memory data) = CONFIDENTIAL_RETRIEVE.staticcall(abi.encode(dataId, key)); + function confidentialRetrieve(DataId dataId, string memory key) internal returns (bytes memory) { + (bool success, bytes memory data) = CONFIDENTIAL_RETRIEVE.call(abi.encode(dataId, key)); if (!success) { revert PeekerReverted(CONFIDENTIAL_RETRIEVE, data); } @@ -139,15 +138,15 @@ library Suave { return data; } - function confidentialStore(DataId dataId, string memory key, bytes memory value) internal view { - (bool success, bytes memory data) = CONFIDENTIAL_STORE.staticcall(abi.encode(dataId, key, value)); + function confidentialStore(DataId dataId, string memory key, bytes memory value) internal { + (bool success, bytes memory data) = CONFIDENTIAL_STORE.call(abi.encode(dataId, key, value)); if (!success) { revert PeekerReverted(CONFIDENTIAL_STORE, data); } } - function doHTTPRequest(HttpRequest memory request) internal view returns (bytes memory) { - (bool success, bytes memory data) = DO_HTTPREQUEST.staticcall(abi.encode(request)); + function doHTTPRequest(HttpRequest memory request) internal returns (bytes memory) { + (bool success, bytes memory data) = DO_HTTPREQUEST.call(abi.encode(request)); if (!success) { revert PeekerReverted(DO_HTTPREQUEST, data); } @@ -155,8 +154,8 @@ library Suave { return abi.decode(data, (bytes)); } - function ethcall(address contractAddr, bytes memory input1) internal view returns (bytes memory) { - (bool success, bytes memory data) = ETHCALL.staticcall(abi.encode(contractAddr, input1)); + function ethcall(address contractAddr, bytes memory input1) internal returns (bytes memory) { + (bool success, bytes memory data) = ETHCALL.call(abi.encode(contractAddr, input1)); if (!success) { revert PeekerReverted(ETHCALL, data); } @@ -164,9 +163,9 @@ library Suave { return abi.decode(data, (bytes)); } - function extractHint(bytes memory bundleData) internal view returns (bytes memory) { + function extractHint(bytes memory bundleData) internal returns (bytes memory) { require(isConfidential()); - (bool success, bytes memory data) = EXTRACT_HINT.staticcall(abi.encode(bundleData)); + (bool success, bytes memory data) = EXTRACT_HINT.call(abi.encode(bundleData)); if (!success) { revert PeekerReverted(EXTRACT_HINT, data); } @@ -174,8 +173,8 @@ library Suave { return data; } - function fetchDataRecords(uint64 cond, string memory namespace) internal view returns (DataRecord[] memory) { - (bool success, bytes memory data) = FETCH_DATA_RECORDS.staticcall(abi.encode(cond, namespace)); + function fetchDataRecords(uint64 cond, string memory namespace) internal returns (DataRecord[] memory) { + (bool success, bytes memory data) = FETCH_DATA_RECORDS.call(abi.encode(cond, namespace)); if (!success) { revert PeekerReverted(FETCH_DATA_RECORDS, data); } @@ -183,9 +182,9 @@ library Suave { return abi.decode(data, (DataRecord[])); } - function fillMevShareBundle(DataId dataId) internal view returns (bytes memory) { + function fillMevShareBundle(DataId dataId) internal returns (bytes memory) { require(isConfidential()); - (bool success, bytes memory data) = FILL_MEV_SHARE_BUNDLE.staticcall(abi.encode(dataId)); + (bool success, bytes memory data) = FILL_MEV_SHARE_BUNDLE.call(abi.encode(dataId)); if (!success) { revert PeekerReverted(FILL_MEV_SHARE_BUNDLE, data); } @@ -193,8 +192,8 @@ library Suave { return data; } - function newBuilder() internal view returns (string memory) { - (bool success, bytes memory data) = NEW_BUILDER.staticcall(abi.encode()); + function newBuilder() internal returns (string memory) { + (bool success, bytes memory data) = NEW_BUILDER.call(abi.encode()); if (!success) { revert PeekerReverted(NEW_BUILDER, data); } @@ -207,9 +206,9 @@ library Suave { address[] memory allowedPeekers, address[] memory allowedStores, string memory dataType - ) internal view returns (DataRecord memory) { + ) internal returns (DataRecord memory) { (bool success, bytes memory data) = - NEW_DATA_RECORD.staticcall(abi.encode(decryptionCondition, allowedPeekers, allowedStores, dataType)); + NEW_DATA_RECORD.call(abi.encode(decryptionCondition, allowedPeekers, allowedStores, dataType)); if (!success) { revert PeekerReverted(NEW_DATA_RECORD, data); } @@ -219,10 +218,9 @@ library Suave { function signEthTransaction(bytes memory txn, string memory chainId, string memory signingKey) internal - view returns (bytes memory) { - (bool success, bytes memory data) = SIGN_ETH_TRANSACTION.staticcall(abi.encode(txn, chainId, signingKey)); + (bool success, bytes memory data) = SIGN_ETH_TRANSACTION.call(abi.encode(txn, chainId, signingKey)); if (!success) { revert PeekerReverted(SIGN_ETH_TRANSACTION, data); } @@ -230,9 +228,9 @@ library Suave { return abi.decode(data, (bytes)); } - function signMessage(bytes memory digest, string memory signingKey) internal view returns (bytes memory) { + function signMessage(bytes memory digest, string memory signingKey) internal returns (bytes memory) { require(isConfidential()); - (bool success, bytes memory data) = SIGN_MESSAGE.staticcall(abi.encode(digest, signingKey)); + (bool success, bytes memory data) = SIGN_MESSAGE.call(abi.encode(digest, signingKey)); if (!success) { revert PeekerReverted(SIGN_MESSAGE, data); } @@ -240,8 +238,8 @@ library Suave { return abi.decode(data, (bytes)); } - function simulateBundle(bytes memory bundleData) internal view returns (uint64) { - (bool success, bytes memory data) = SIMULATE_BUNDLE.staticcall(abi.encode(bundleData)); + function simulateBundle(bytes memory bundleData) internal returns (uint64) { + (bool success, bytes memory data) = SIMULATE_BUNDLE.call(abi.encode(bundleData)); if (!success) { revert PeekerReverted(SIMULATE_BUNDLE, data); } @@ -251,10 +249,9 @@ library Suave { function simulateTransaction(string memory sessionid, bytes memory txn) internal - view returns (SimulateTransactionResult memory) { - (bool success, bytes memory data) = SIMULATE_TRANSACTION.staticcall(abi.encode(sessionid, txn)); + (bool success, bytes memory data) = SIMULATE_TRANSACTION.call(abi.encode(sessionid, txn)); if (!success) { revert PeekerReverted(SIMULATE_TRANSACTION, data); } @@ -264,11 +261,10 @@ library Suave { function submitBundleJsonRPC(string memory url, string memory method, bytes memory params) internal - view returns (bytes memory) { require(isConfidential()); - (bool success, bytes memory data) = SUBMIT_BUNDLE_JSON_RPC.staticcall(abi.encode(url, method, params)); + (bool success, bytes memory data) = SUBMIT_BUNDLE_JSON_RPC.call(abi.encode(url, method, params)); if (!success) { revert PeekerReverted(SUBMIT_BUNDLE_JSON_RPC, data); } @@ -276,13 +272,9 @@ library Suave { return data; } - function submitEthBlockToRelay(string memory relayUrl, bytes memory builderBid) - internal - view - returns (bytes memory) - { + function submitEthBlockToRelay(string memory relayUrl, bytes memory builderBid) internal returns (bytes memory) { require(isConfidential()); - (bool success, bytes memory data) = SUBMIT_ETH_BLOCK_TO_RELAY.staticcall(abi.encode(relayUrl, builderBid)); + (bool success, bytes memory data) = SUBMIT_ETH_BLOCK_TO_RELAY.call(abi.encode(relayUrl, builderBid)); if (!success) { revert PeekerReverted(SUBMIT_ETH_BLOCK_TO_RELAY, data); } diff --git a/test/Forge.t.sol b/test/Forge.t.sol index 0ca3ac1..68f50f3 100644 --- a/test/Forge.t.sol +++ b/test/Forge.t.sol @@ -8,32 +8,35 @@ import "src/suavelib/Suave.sol"; contract TestForge is Test, SuaveEnabled { address[] public addressList = [0xC8df3686b4Afb2BB53e60EAe97EF043FE03Fb829]; - function testConfidentialStore() public { - Suave.DataRecord memory record = Suave.newDataRecord(0, addressList, addressList, "namespace"); + function testForgeConfidentialStoreFetch() public { + Suave.newDataRecord(0, addressList, addressList, "namespace"); - bytes memory value = abi.encode("suave works with forge!"); - Suave.confidentialStore(record.id, "key1", value); + Suave.DataRecord[] memory records = Suave.fetchDataRecords(0, "namespace"); + assertEq(records.length, 1); - bytes memory found = Suave.confidentialRetrieve(record.id, "key1"); - assertEq(keccak256(found), keccak256(value)); + Suave.newDataRecord(0, addressList, addressList, "namespace"); + Suave.newDataRecord(0, addressList, addressList, "namespace"); + + Suave.DataRecord[] memory records2 = Suave.fetchDataRecords(0, "namespace"); + assertEq(records2.length, 3); + + resetConfidentialStore(); + + Suave.DataRecord[] memory records3 = Suave.fetchDataRecords(0, "namespace"); + assertEq(records3.length, 0); } - function testConfidentialReset() public { + function testForgeConfidentialStoreRecordStore() public { Suave.DataRecord memory record = Suave.newDataRecord(0, addressList, addressList, "namespace"); bytes memory value = abi.encode("suave works with forge!"); Suave.confidentialStore(record.id, "key1", value); bytes memory found = Suave.confidentialRetrieve(record.id, "key1"); - console.logBytes(found); - - resetConfidentialStore(); - - bytes memory found2 = Suave.confidentialRetrieve(record.id, "key1"); - assertEq(found2.length, 0); + assertEq(keccak256(found), keccak256(value)); } - function testConfidentialInputs() public { + function testForgeConfidentialInputs() public { // ensure that the confidential inputs are empty bytes memory found = Suave.confidentialInputs(); assertEq(found.length, 0); diff --git a/test/forge/ConfidentialStore.t.sol b/test/forge/ConfidentialStore.t.sol new file mode 100644 index 0000000..6af080f --- /dev/null +++ b/test/forge/ConfidentialStore.t.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "src/forge/ConfidentialStore.sol"; + +contract TestMockConfidentialStore is Test { + ConfidentialStore store; + address[] public addressList = [Suave.ANYALLOWED]; + + function setUp() public { + store = new ConfidentialStore(); + } + + function testMockConfidentialStoreNewRecordAndFetch() public { + // This function tests that we can create new data records and fetch them + store.newDataRecord(0, addressList, addressList, "namespace"); + store.newDataRecord(0, addressList, addressList, "namespace1"); + store.newDataRecord(1, addressList, addressList, "namespace"); + + // fetch the records + Suave.DataRecord[] memory records = store.fetchDataRecords(0, "namespace"); + assertEq(records.length, 1); + + records = store.fetchDataRecords(0, "namespace1"); + assertEq(records.length, 1); + + records = store.fetchDataRecords(1, "namespace"); + assertEq(records.length, 1); + + // add more entries to 'namespace' + store.newDataRecord(0, addressList, addressList, "namespace"); + store.newDataRecord(0, addressList, addressList, "namespace"); + + records = store.fetchDataRecords(0, "namespace"); + assertEq(records.length, 3); + } + + function testMockConfidentialStoreStoreRetrieve() public { + // This function tests that we can store and retrieve a value from the record + Suave.DataRecord memory record = store.newDataRecord(0, addressList, addressList, "namespace"); + + bytes memory value = abi.encodePacked("value"); + store.confidentialStore(record.id, "key1", value); + + bytes memory found = store.confidentialRetrieve(record.id, "key1"); + assertEq(keccak256(found), keccak256(value)); + } + + function testMockConfidentialStoreLocalAllowedAddress() public { + // This function tests that we can store and retrieve a value from the record + address[] memory allowed = new address[](1); + allowed[0] = address(this); + + Suave.DataRecord memory record = store.newDataRecord(0, allowed, allowed, "namespace"); + + bytes memory value = abi.encodePacked("value"); + store.confidentialStore(record.id, "key1", value); + + // test that another address cannot store + vm.startPrank(0x0000000000000000000000000000000000000000); + vm.expectRevert(); + store.confidentialStore(record.id, "key1", value); + vm.stopPrank(); + } + + function testMockConfidentialStoreReset() public { + // add one record and one stored value + Suave.DataRecord memory record = store.newDataRecord(0, addressList, addressList, "namespace"); + bytes memory value = abi.encodePacked("value"); + store.confidentialStore(record.id, "key1", value); + + bytes memory found = store.confidentialRetrieve(record.id, "key1"); + assertEq(keccak256(found), keccak256(value)); + + // reset the store + store.reset(); + + // it reverts because it cannot find the metadata of 'record'. + vm.expectRevert(); + store.confidentialRetrieve(record.id, "key1"); + + Suave.DataRecord[] memory records = store.fetchDataRecords(0, "namespace"); + assertEq(records.length, 0); + + // validate that if we add new records we can reset again + store.newDataRecord(0, addressList, addressList, "namespace"); + + records = store.fetchDataRecords(0, "namespace"); + assertEq(records.length, 1); + + store.reset(); + + records = store.fetchDataRecords(0, "namespace"); + assertEq(records.length, 0); + } +}