Skip to content

Commit

Permalink
Merge pull request #6 from fxhash/test/onchfs-tests
Browse files Browse the repository at this point in the history
Test/onchfs tests
  • Loading branch information
stevennevins authored Nov 27, 2023
2 parents 3eaaa0b + 0685ed0 commit 5c78914
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 106 deletions.
28 changes: 22 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
name: test

on: workflow_dispatch
concurrency:
cancel-in-progress: true
group: ${{github.workflow}}-${{github.ref}}

env:
FOUNDRY_PROFILE: ci
on:
push:
branches:
- main
pull_request:

jobs:
check:
Expand All @@ -13,7 +18,7 @@ jobs:
name: Foundry project
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
submodules: recursive

Expand All @@ -22,13 +27,24 @@ jobs:
with:
version: nightly

- name: install dependencies
run: npm ci

- name: check formatting
run: npx prettier --check 'src/**/*.sol' 'test/**/*.sol' 'script/**/*.sol'

- name: Run Forge build
run: |
forge --version
forge build --sizes
id: build

- name: Run Forge tests
- name: Run tests
shell: bash
run: |
forge test -vvv
forge test
id: test

- name: Upload Selectors to Openchain.xyz
run: |
forge selectors upload --all
84 changes: 45 additions & 39 deletions src/FileSystem.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ contract FileSystem is IFileSystem {
/**
* @inheritdoc IFileSystem
*/
address public immutable contentStore;
address public immutable CONTENT_STORE;

/**
* @dev Mapping of checksum pointer to Inode struct
* @inheritdoc IFileSystem
*/
mapping(bytes32 checksum => Inode inode) internal inodes_;
mapping(bytes32 checksum => Inode inode) public inodes;

/*//////////////////////////////////////////////////////////////////////////
CONSTRUCTOR
Expand All @@ -36,7 +36,7 @@ contract FileSystem is IFileSystem {
* @dev Initializes the ContentStore contract
*/
constructor(address _contentStore) {
contentStore = _contentStore;
CONTENT_STORE = _contentStore;
}

/*//////////////////////////////////////////////////////////////////////////
Expand All @@ -46,52 +46,58 @@ contract FileSystem is IFileSystem {
/**
* @inheritdoc IFileSystem
*/
function createDirectory(string[] calldata _fileNames, bytes32[] calldata _filePointers) external {
if (_fileNames.length != _filePointers.length) revert LengthMismatch();
bytes32[] memory hashedNames = hashNames(_fileNames);
bytes32 checksum = keccak256(
function createDirectory(
string[] calldata _paths,
bytes32[] calldata _fileChecksums
) external returns (bytes32 directoryChecksum) {
if (_paths.length != _fileChecksums.length) revert LengthMismatch();
bytes32[] memory hashedPaths = hashPaths(_paths);
directoryChecksum = keccak256(
bytes.concat(
DIRECTORY_TYPE,
keccak256(abi.encodePacked(hashedNames)),
keccak256(abi.encodePacked(_filePointers))
keccak256(abi.encodePacked(hashedPaths)),
keccak256(abi.encodePacked(_fileChecksums))
)
);
if (inodeExists(checksum)) revert InodeAlreadyExists();
Directory memory newDirectory = Directory(_fileNames, _filePointers);
inodes_[checksum] = Inode(InodeType.Directory, File(bytes(""), new bytes32[](0)), newDirectory);
if (inodeExists(directoryChecksum)) revert InodeAlreadyExists();
Directory memory newDirectory = Directory(_paths, _fileChecksums);
inodes[directoryChecksum] = Inode(InodeType.Directory, File(bytes(""), new bytes32[](0)), newDirectory);
}

/**
* @inheritdoc IFileSystem
*/
function createFile(bytes calldata _metadata, bytes32[] calldata _chunkPointers) external {
if (_containsForbiddenChars(string(_metadata))) revert InvalidCharacter();
bytes32 checksum = keccak256(
bytes.concat(FILE_TYPE, keccak256(abi.encodePacked(_chunkPointers)), keccak256(_metadata))
function createFile(
bytes calldata _name,
bytes32[] calldata _chunkPointers
) external returns (bytes32 fileChecksum) {
if (_containsForbiddenChars(string(_name))) revert InvalidCharacter();
fileChecksum = keccak256(
bytes.concat(FILE_TYPE, keccak256(abi.encodePacked(_chunkPointers)), keccak256(_name))
);
if (inodeExists(checksum)) revert InodeAlreadyExists();
File memory newFile = File(_metadata, _chunkPointers);
inodes_[checksum] = Inode(InodeType.File, newFile, Directory(new string[](0), new bytes32[](0)));
if (inodeExists(fileChecksum)) revert InodeAlreadyExists();
File memory newFile = File(_name, _chunkPointers);
inodes[fileChecksum] = Inode(InodeType.File, newFile, Directory(new string[](0), new bytes32[](0)));
}

/**
* @inheritdoc IFileSystem
*/
function readDirectory(bytes32 _checksum) external view returns (string[] memory, bytes32[] memory) {
if (!inodeExists(_checksum)) revert InodeNotFound();
Inode memory inode = inodes_[_checksum];
Inode memory inode = inodes[_checksum];
if (inode.inodeType != InodeType.Directory) revert DirectoryNotFound();
return (inode.directory.fileNames, inode.directory.filePointers);
return (inode.directory.paths, inode.directory.fileChecksums);
}

/**
* @inheritdoc IFileSystem
*/
function readFile(bytes32 _checksum) external view returns (bytes memory) {
if (!inodeExists(_checksum)) revert InodeNotFound();
Inode memory inode = inodes_[_checksum];
Inode memory inode = inodes[_checksum];
if (inode.inodeType != InodeType.File) revert FileNotFound();
return concatenateChunks(inode.file.chunkPointers);
return concatenateChunks(inode.file.chunkChecksums);
}

/*//////////////////////////////////////////////////////////////////////////
Expand All @@ -105,7 +111,7 @@ contract FileSystem is IFileSystem {
address pointer;
bytes memory chunkContent;
for (uint256 i; i < _pointers.length; i++) {
pointer = IContentStore(contentStore).getPointer(_pointers[i]);
pointer = IContentStore(CONTENT_STORE).getPointer(_pointers[i]);
chunkContent = SSTORE2.read(pointer);
fileContent = abi.encodePacked(fileContent, chunkContent);
}
Expand All @@ -114,26 +120,26 @@ contract FileSystem is IFileSystem {
/**
* @inheritdoc IFileSystem
*/
function hashNames(string[] calldata _fileNames) public pure returns (bytes32[] memory hashedNames) {
uint256 length = _fileNames.length;
hashedNames = new bytes32[](length);
function hashPaths(string[] calldata _paths) public pure returns (bytes32[] memory hashedPaths) {
uint256 length = _paths.length;
hashedPaths = new bytes32[](length);
for (uint256 i; i < length; i++) {
if (_containsForbiddenChars(_fileNames[i])) revert InvalidCharacter();
hashedNames[i] = keccak256(bytes(_fileNames[i]));
if (_containsForbiddenChars(_paths[i])) revert InvalidCharacter();
hashedPaths[i] = keccak256(bytes(_paths[i]));
}
}

/**
* @inheritdoc IFileSystem
*/
function inodeExists(bytes32 _checksum) public view returns (bool) {
Inode memory inode = inodes_[_checksum];
File memory file = inode.file;
Directory memory directory = inode.directory;
Inode memory inode = inodes[_checksum];
if (inode.inodeType == InodeType.File) {
return file.metadata.length != 0 || file.chunkPointers.length != 0;
File memory file = inode.file;
return file.name.length != 0 || file.chunkChecksums.length != 0;
} else {
return directory.fileNames.length != 0 || directory.filePointers.length != 0;
Directory memory directory = inode.directory;
return directory.paths.length != 0 || directory.fileChecksums.length != 0;
}
}

Expand All @@ -144,11 +150,11 @@ contract FileSystem is IFileSystem {
/**
* @dev Checks if the given string contains any forbidden characters
*/
function _containsForbiddenChars(string calldata _characters) private pure returns (bool) {
uint256 length = bytes(_characters).length;
function _containsForbiddenChars(string calldata _stringToCheck) private pure returns (bool) {
uint256 length = bytes(_stringToCheck).length;
for (uint256 i; i < length; i++) {
for (uint256 j; j < CHARACTER_LEGNTH; j++) {
if (bytes(_characters)[i] == bytes(FORBIDDEN_CHARS)[j]) {
for (uint256 j; j < CHARACTER_LENGTH; j++) {
if (bytes(_stringToCheck)[i] == bytes(FORBIDDEN_CHARS)[j]) {
return true;
}
}
Expand Down
47 changes: 29 additions & 18 deletions src/interfaces/IFileSystem.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,36 +49,54 @@ interface IFileSystem {

/**
* @notice Concatenates the content of file chunks from the given pointers
* @param _pointers Pointers to the file chunks
* @param _chunkChecksums Checksums for the file chunks
* @return Concatenated content of the file chunks
*/
function concatenateChunks(bytes32[] memory _pointers) external view returns (bytes memory);
function concatenateChunks(bytes32[] memory _chunkChecksums) external view returns (bytes memory);

/**
* @notice Returns the address of the ContentStore contract
*/
function contentStore() external view returns (address);
function CONTENT_STORE() external view returns (address);

/**
* @notice Creates a new directory with the given names and file inode pointers
* @param _fileNames List of file names in the directory
* @param _filePointers Pointers to the file inodes in the directory
* @param _paths List of file paths in the directory
* @param _fileChecksums Pointers to the file inodes in the directory
*/
function createDirectory(string[] calldata _fileNames, bytes32[] calldata _filePointers) external;
function createDirectory(
string[] calldata _paths,
bytes32[] calldata _fileChecksums
) external returns (bytes32 directoryChecksum);

/**
* @notice Creates a new file with the given metadata and chunk pointers
* @param _metadata Metadata of the file
* @param _chunkPointers Pointers to the file chunks
* @param _filename Metadata of the file
* @param _chunkChecksums Checksums for chunks of the file
*/
function createFile(bytes calldata _metadata, bytes32[] calldata _chunkPointers) external;
function createFile(
bytes calldata _filename,
bytes32[] calldata _chunkChecksums
) external returns (bytes32 fileChecksum);

/**
* @notice Hashes a list of file names in the directory
* @param _names List of file names
* @param _paths List of file names
* @return Hashed names
*/
function hashNames(string[] calldata _names) external view returns (bytes32[] memory);
function hashPaths(string[] calldata _paths) external view returns (bytes32[] memory);

/**
* @notice Mapping of checksum pointer to Inode struct
*/
function inodes(bytes32 _checksum) external view returns (InodeType, File memory, Directory memory);

/**
* @notice Checks if an inode with the given checksum exists
* @param _checksum Checksum of the inode
* @return Status of inode existence
*/
function inodeExists(bytes32 _checksum) external view returns (bool);

/**
* @notice Reads the content of a directory with the given checksum
Expand All @@ -93,11 +111,4 @@ interface IFileSystem {
* @return Content of the file
*/
function readFile(bytes32 _checksum) external view returns (bytes memory);

/**
* @notice Checks if an inode with the given checksum exists
* @param _checksum Checksum of the inode
* @return Status of inode existence
*/
function inodeExists(bytes32 _checksum) external view returns (bool);
}
8 changes: 4 additions & 4 deletions src/lib/Structs.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ enum InodeType {
}

struct Directory {
string[] fileNames;
bytes32[] filePointers;
string[] paths;
bytes32[] fileChecksums;
}

struct File {
bytes metadata;
bytes32[] chunkPointers;
bytes name;
bytes32[] chunkChecksums;
}

struct Inode {
Expand Down
2 changes: 1 addition & 1 deletion src/utils/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ bytes1 constant DIRECTORY_TYPE = bytes1(0x01);
bytes1 constant FILE_TYPE = bytes1(0x00);

// Characters
uint256 constant CHARACTER_LEGNTH = 18;
uint256 constant CHARACTER_LENGTH = 18;
string constant FORBIDDEN_CHARS = ":/?#[]@!$&'()*+,;=";
18 changes: 9 additions & 9 deletions test/FileSystem/CreateDirectory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ pragma solidity 0.8.23;
import "test/FileSystem/FileSystemTest.t.sol";

contract CreateDirectory is FileSystemTest {
bytes32[] internal hashedNames;
bytes32[] internal hashedPaths;
bytes32[] internal pointers;
string[] internal names;
string[] internal paths;

function setUp() public override {
super.setUp();
Expand All @@ -20,11 +20,11 @@ contract CreateDirectory is FileSystemTest {

function test_CreateDirectory() public {
fileSystem.createDirectory(fileNames, filePointers);
hashedNames = fileSystem.hashNames(fileNames);
hashedPaths = fileSystem.hashPaths(fileNames);
checksum = keccak256(
abi.encodePacked(
bytes1(0x01),
keccak256(abi.encodePacked(hashedNames)),
DIRECTORY_TYPE,
keccak256(abi.encodePacked(hashedPaths)),
keccak256(abi.encodePacked(filePointers))
)
);
Expand All @@ -33,12 +33,12 @@ contract CreateDirectory is FileSystemTest {

function test_ReadDirectory() public {
test_CreateDirectory();
(names, pointers) = fileSystem.readDirectory(checksum);
assertEq(names.length, fileNames.length);
(paths, pointers) = fileSystem.readDirectory(checksum);
assertEq(paths.length, fileNames.length);
assertEq(pointers.length, filePointers.length);

for (uint256 i; i < names.length; i++) {
assertEq(names[i], fileNames[i]);
for (uint256 i; i < paths.length; i++) {
assertEq(paths[i], fileNames[i]);
assertEq(pointers[i], filePointers[i]);
}
}
Expand Down
Loading

0 comments on commit 5c78914

Please sign in to comment.