From c8d4c66b99154658ededcc8a7885df3695942ccb Mon Sep 17 00:00:00 2001 From: BenRey Date: Thu, 3 Oct 2024 13:35:35 +0200 Subject: [PATCH] Add metadata and fit new spec --- .../assembly/__tests__/FileBuilder.ts | 194 ---------- .../__tests__/deweb-interface.spec.ts | 267 -------------- .../__tests__/deweb-interface/FileBuilder.ts | 293 +++++++++++++++ .../deweb-interface/upload-files.spec.ts | 102 ++++++ .../__tests__/internals/chunks.spec.ts | 88 ----- .../__tests__/internals/const.spec.ts | 34 -- .../assembly/__tests__/internals/utils.ts | 63 ---- .../assembly/contracts/deweb-interface.ts | 338 ++++++++++++------ .../assembly/contracts/internals/chunks.ts | 130 +++---- .../assembly/contracts/internals/const.ts | 8 - .../assembly/contracts/internals/file-list.ts | 48 --- .../assembly/contracts/internals/fileInit.ts | 33 ++ .../assembly/contracts/internals/location.ts | 44 +++ .../assembly/contracts/internals/metadata.ts | 57 +++ .../internals/storageKeys/chunksKeys.ts | 34 ++ .../internals/storageKeys/metadataKeys.ts | 26 ++ .../contracts/internals/storageKeys/tags.ts | 10 + .../assembly/contracts/serializable/Chunk.ts | 197 ---------- .../contracts/serializable/FileChunkGet.ts | 50 +++ .../contracts/serializable/FileChunkPost.ts | 52 +++ .../contracts/serializable/FileDelete.ts | 37 ++ .../contracts/serializable/FileInit.ts | 59 +++ .../contracts/serializable/Metadata.ts | 32 ++ smart-contract/src/e2e/delete.ts | 76 ---- smart-contract/src/e2e/helpers/index.ts | 86 ++--- .../src/e2e/helpers/serializable/Chunk.ts | 101 ------ .../e2e/helpers/serializable/FileChunkGet.ts | 27 ++ .../e2e/helpers/serializable/FileChunkPost.ts | 29 ++ .../e2e/helpers/serializable/FileDelete.ts | 20 ++ .../src/e2e/helpers/serializable/FileInit.ts | 31 ++ .../src/e2e/helpers/serializable/Metadata.ts | 24 ++ smart-contract/src/e2e/index.ts | 8 - smart-contract/src/e2e/store.ts | 48 +-- 33 files changed, 1314 insertions(+), 1332 deletions(-) delete mode 100644 smart-contract/assembly/__tests__/FileBuilder.ts delete mode 100644 smart-contract/assembly/__tests__/deweb-interface.spec.ts create mode 100644 smart-contract/assembly/__tests__/deweb-interface/FileBuilder.ts create mode 100644 smart-contract/assembly/__tests__/deweb-interface/upload-files.spec.ts delete mode 100644 smart-contract/assembly/__tests__/internals/chunks.spec.ts delete mode 100644 smart-contract/assembly/__tests__/internals/const.spec.ts delete mode 100644 smart-contract/assembly/__tests__/internals/utils.ts delete mode 100644 smart-contract/assembly/contracts/internals/const.ts delete mode 100644 smart-contract/assembly/contracts/internals/file-list.ts create mode 100644 smart-contract/assembly/contracts/internals/fileInit.ts create mode 100644 smart-contract/assembly/contracts/internals/location.ts create mode 100644 smart-contract/assembly/contracts/internals/metadata.ts create mode 100644 smart-contract/assembly/contracts/internals/storageKeys/chunksKeys.ts create mode 100644 smart-contract/assembly/contracts/internals/storageKeys/metadataKeys.ts create mode 100644 smart-contract/assembly/contracts/internals/storageKeys/tags.ts delete mode 100644 smart-contract/assembly/contracts/serializable/Chunk.ts create mode 100644 smart-contract/assembly/contracts/serializable/FileChunkGet.ts create mode 100644 smart-contract/assembly/contracts/serializable/FileChunkPost.ts create mode 100644 smart-contract/assembly/contracts/serializable/FileDelete.ts create mode 100644 smart-contract/assembly/contracts/serializable/FileInit.ts create mode 100644 smart-contract/assembly/contracts/serializable/Metadata.ts delete mode 100644 smart-contract/src/e2e/delete.ts delete mode 100644 smart-contract/src/e2e/helpers/serializable/Chunk.ts create mode 100644 smart-contract/src/e2e/helpers/serializable/FileChunkGet.ts create mode 100644 smart-contract/src/e2e/helpers/serializable/FileChunkPost.ts create mode 100644 smart-contract/src/e2e/helpers/serializable/FileDelete.ts create mode 100644 smart-contract/src/e2e/helpers/serializable/FileInit.ts create mode 100644 smart-contract/src/e2e/helpers/serializable/Metadata.ts diff --git a/smart-contract/assembly/__tests__/FileBuilder.ts b/smart-contract/assembly/__tests__/FileBuilder.ts deleted file mode 100644 index d9619a8..0000000 --- a/smart-contract/assembly/__tests__/FileBuilder.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { Args, stringToBytes } from '@massalabs/as-types'; -import { sha256 } from '@massalabs/massa-as-sdk'; -import { - PreStore, - ChunkPost, - ChunkGet, - ChunkDelete, -} from '../contracts/serializable/Chunk'; -import { - preStoreFileChunks, - storeFileChunks, - getFilePathList, - getChunk, - deleteFiles, - deleteWebsite, -} from '../contracts/deweb-interface'; -const limitChunk = 10240; - -class FileInfo { - fileName: string; - fileNameHash: StaticArray; - nbChunks: u32; - data: StaticArray[]; - - constructor(fileName: string, nbChunks: u32, data: StaticArray[] = []) { - this.fileName = fileName; - this.fileNameHash = sha256(stringToBytes(fileName)); - this.nbChunks = nbChunks; - this.data = data; - } -} - -class FileBuilder { - private files: FileInfo[]; - - constructor() { - this.files = []; - } - - withFile( - fileName: string, - nbChunks: u32, - fileData: StaticArray[], - ): FileBuilder { - this.files.push(new FileInfo(fileName, nbChunks, fileData)); - return this; - } - - public preStore(): FileBuilder { - const preStoreFiles: PreStore[] = []; - for (let i = 0; i < this.files.length; i++) { - const fileInfo = this.files[i]; - preStoreFiles.push( - new PreStore( - fileInfo.fileName, - fileInfo.fileNameHash, - fileInfo.nbChunks, - ), - ); - } - - preStoreFileChunks( - new Args() - .addSerializableObjectArray(preStoreFiles) - .serialize(), - ); - - return this; - } - - store(fileName: string, index: u32): FileBuilder { - let fileInfo: FileInfo | null = null; - for (let i = 0; i < this.files.length; i++) { - if (this.files[i].fileName == fileName) { - fileInfo = this.files[i]; - break; - } - } - - if (fileInfo == null) { - throw new Error( - `File ${fileName} not initialized. Use withFile() first.`, - ); - } - - const chunk = new ChunkPost(fileName, index, fileInfo.data[index]); - - storeFileChunks( - new Args().addSerializableObjectArray([chunk]).serialize(), - ); - - return this; - } - - storeAll(limit: u32 = limitChunk): FileBuilder { - let chunks: ChunkPost[] = []; - - // prepare list of chunks - for (let i = 0; i < this.files.length; i++) { - const fileInfo = this.files[i]; - for (let j = 0; j < fileInfo.data.length; j++) { - chunks.push(new ChunkPost(fileInfo.fileName, j, fileInfo.data[j])); - } - } - - // Prepare list of list of chunks to store based on limit, one list should not exceed limit - let chunkList: ChunkPost[][] = []; - let currentChunkList: ChunkPost[] = []; - let currentChunkSize = 0; - for (let i = 0; i < chunks.length; i++) { - const chunk = chunks[i]; - if (u32(currentChunkSize + chunk.data.length) > limit) { - chunkList.push(currentChunkList); - currentChunkList = []; - currentChunkSize = 0; - } - currentChunkList.push(chunk); - currentChunkSize += chunk.data.length; - } - - if (currentChunkList.length > 0) { - chunkList.push(currentChunkList); - } - - // Store list of list of chunks - for (let i = 0; i < chunkList.length; i++) { - storeFileChunks( - new Args() - .addSerializableObjectArray(chunkList[i]) - .serialize(), - ); - } - - return this; - } - - deleteFiles(files: ChunkDelete[]): void { - deleteFiles( - new Args().addSerializableObjectArray(files).serialize(), - ); - } - - deleteWebsite(): void { - deleteWebsite(new Args().serialize()); - } - - hasFiles(): void { - const fileList = new Args(getFilePathList()).next().unwrap(); - for (let i = 0; i < this.files.length; i++) { - const fileInfo = this.files[i]; - assert( - fileList.includes(fileInfo.fileName), - `File ${fileInfo.fileName} should be in the file list`, - ); - for (let j = 0; j < fileInfo.data.length; j++) { - const storedChunk = getChunk(chunkGetArgs(fileInfo.fileNameHash, j)); - assert( - storedChunk.length == fileInfo.data[j].length, - `Chunk ${j} of ${fileInfo.fileName} should have correct length`, - ); - } - } - } - - hasNoFiles(): void { - const fileList = new Args(getFilePathList()).next().unwrap(); - assert(fileList.length === 0, 'FileList should be empty'); - } - - fileIsDeleted(filePath: string): void { - const fileList = new Args(getFilePathList()).next().unwrap(); - for (let i = 0; i < fileList.length; i++) { - assert( - !fileList.includes(filePath), - `File ${filePath} should not be in the file list`, - ); - } - } -} - -export function given(): FileBuilder { - return new FileBuilder(); -} - -export function checkThat(fileBuilder: FileBuilder): FileBuilder { - return fileBuilder; -} - -export function chunkGetArgs( - filePathHash: StaticArray, - index: u32, -): StaticArray { - return new ChunkGet(filePathHash, index).serialize(); -} diff --git a/smart-contract/assembly/__tests__/deweb-interface.spec.ts b/smart-contract/assembly/__tests__/deweb-interface.spec.ts deleted file mode 100644 index 3f2e396..0000000 --- a/smart-contract/assembly/__tests__/deweb-interface.spec.ts +++ /dev/null @@ -1,267 +0,0 @@ -import { - resetStorage, - setDeployContext, - sha256, -} from '@massalabs/massa-as-sdk'; -import {} from '../contracts/internals/chunks'; -import { - constructor, - deleteFiles, - getChunk, -} from '../contracts/deweb-interface'; -import { ChunkDelete } from '../contracts/serializable/Chunk'; -import { Args, stringToBytes } from '@massalabs/as-types'; -import { checkThat, chunkGetArgs, given } from './FileBuilder'; - -const user = 'AU12UBnqTHDQALpocVBnkPNy7y5CndUJQTLutaVDDFgMJcq5kQiKq'; -const file1Path = 'file1'; -const file2Path = 'file2'; -const file1PathHash = sha256(stringToBytes(file1Path)); -const file2PathHash = sha256(stringToBytes(file2Path)); -const fileData1 = new StaticArray(10240).fill(1); -const fileData2 = new StaticArray(10241).fill(1); - -describe('website deployer internals functions tests', () => { - describe('Store', () => { - beforeEach(() => { - resetStorage(); - setDeployContext(user); - constructor(new Args().serialize()); - }); - - test('Store 1 file with 1 chunk', () => { - const myUpload = given() - .withFile(file1Path, 1, [fileData1]) - .preStore() - .storeAll(); - - checkThat(myUpload).hasFiles(); - }); - - test('Store 2 files with 1 chunk each', () => { - const myUpload = given() - .withFile(file1Path, 1, [fileData1]) - .withFile(file2Path, 1, [fileData2]) - .preStore() - .storeAll(); - - checkThat(myUpload).hasFiles(); - }); - - test('Store a file with 2 chunks', () => { - const myUpload = given() - .withFile(file1Path, 2, [fileData1, fileData2]) - .preStore() - .storeAll(); - - checkThat(myUpload).hasFiles(); - }); - - test('Store 2 batch of chunks', () => { - const myFirstUpload = given() - .withFile(file1Path, 2, [fileData1, fileData2]) - .preStore() - .storeAll(); - - const mySecondUpload = given() - .withFile(file2Path, 2, [fileData1, fileData2]) - .preStore() - .storeAll(); - - checkThat(myFirstUpload).hasFiles(); - checkThat(mySecondUpload).hasFiles(); - }); - - test('Update a chunk with different totalChunks', () => { - const myUpload = given() - .withFile(file1Path, 2, [fileData1, fileData2]) - .preStore() - .storeAll(); - - checkThat(myUpload).hasFiles(); - - const myUpload2 = given() - .withFile(file1Path, 3, [fileData1, fileData2, fileData1]) - .preStore() - .storeAll(); - - checkThat(myUpload2).hasFiles(); - }); - - throws('Wrong totalChunk', () => { - given() - .withFile(file1Path, 3, [ - fileData1, - fileData2, - fileData2, - fileData1, - fileData2, - fileData2, - fileData1, - ]) - .preStore() - .storeAll(); - }); - }); - - describe('Get', () => { - beforeEach(() => { - resetStorage(); - setDeployContext(user); - constructor(new Args().serialize()); - }); - - throws('should throw if file does not exist', () => { - getChunk(chunkGetArgs(file1PathHash, 0)); - }); - }); - - // Testing delete files - describe('Delete File', () => { - beforeEach(() => { - resetStorage(); - setDeployContext(user); - constructor(new Args().serialize()); - }); - - test('that we can delete 1 file with 1 chunk', () => { - const myUpload = given() - .withFile(file1Path, 1, [fileData1]) - .preStore() - .storeAll(); - - const fileToDelete = new ChunkDelete( - file1Path, - sha256(stringToBytes(file1Path)), - ); - - myUpload.deleteFiles([fileToDelete]); - - throws('should throw if file does not exist', () => { - getChunk(chunkGetArgs(file1PathHash, 0)); - }); - - checkThat(myUpload).hasNoFiles(); - }); - - test('that we can delete 1 file with 2 chunks', () => { - const myUpload = given() - .withFile(file1Path, 2, [fileData1, fileData2]) - .preStore() - .storeAll(); - - const fileToDelete = new ChunkDelete(file1Path, file1PathHash); - - myUpload.deleteFiles([fileToDelete]); - - throws('should throw if file does not exist', () => { - getChunk(chunkGetArgs(file1PathHash, 0)); - }); - - checkThat(myUpload).hasNoFiles(); - }); - - test('that we can delete 1 file in batch', () => { - const myFirstUpload = given() - .withFile(file1Path, 2, [fileData1, fileData2]) - .preStore() - .storeAll(); - - const mySecondUpload = given() - .withFile(file2Path, 2, [fileData1, fileData2]) - .preStore() - .storeAll(); - - const deleteFile1 = new ChunkDelete(file1Path, file1PathHash); - - myFirstUpload.deleteFiles([deleteFile1]); - - checkThat(myFirstUpload).fileIsDeleted(deleteFile1.filePath); - checkThat(mySecondUpload).hasFiles; - - throws('should throw if file1 does not exists', () => { - getChunk(chunkGetArgs(file1PathHash, 0)); - }); - }); - - test('Should throw if there are no files to delete', () => { - throws('should throw if there is no file to delete', () => { - deleteFiles( - new Args() - .addSerializableObjectArray([ - new ChunkDelete('DeleteFile1'), - ]) - .serialize(), - ); - }); - }); - }); - - describe('Delete Files', () => { - beforeEach(() => { - resetStorage(); - setDeployContext(user); - constructor(new Args().serialize()); - }); - test('that we can delete 2 files with 1 chunk', () => { - const myUpload = given() - .withFile(file1Path, 1, [fileData1]) - .withFile(file2Path, 1, [fileData2]) - .preStore() - .storeAll(); - - const fileToDelete1 = new ChunkDelete(file1Path, file1PathHash); - const fileToDelete2 = new ChunkDelete(file2Path, file2PathHash); - - myUpload.deleteFiles([fileToDelete1, fileToDelete2]); - - throws('should throw if file does not exist', () => { - getChunk(chunkGetArgs(file1PathHash, 0)); - }); - - throws('should throw if file does not exist', () => { - getChunk(chunkGetArgs(file2PathHash, 0)); - }); - - checkThat(myUpload).hasNoFiles(); - }); - - test('that we can delete n files with multiple chunks', () => { - const myFirstUpload = given() - .withFile(file1Path, 2, [fileData1, fileData2]) - .withFile(file2Path, 4, [fileData1, fileData2, fileData2, fileData2]) - .preStore() - .storeAll(); - - const fileToDelete1 = new ChunkDelete(file1Path, file1PathHash); - const fileToDelete2 = new ChunkDelete(file2Path, file2PathHash); - - myFirstUpload.deleteFiles([fileToDelete1, fileToDelete2]); - - checkThat(myFirstUpload).hasNoFiles(); - }); - }); - describe('deleteWebsite', () => { - beforeEach(() => { - resetStorage(); - setDeployContext(user); - constructor(new Args().serialize()); - }); - - test('that we can delete a fullwebsite', () => { - const myUpload = given() - .withFile(file1Path, 1, [fileData1]) - .withFile(file2Path, 3, [fileData1, fileData1, fileData2]) - .preStore() - .storeAll(); - - myUpload.deleteWebsite(); - - throws('should throw if file does not exist', () => { - getChunk(chunkGetArgs(file1PathHash, 0)); - }); - - checkThat(myUpload).hasNoFiles(); - }); - }); -}); diff --git a/smart-contract/assembly/__tests__/deweb-interface/FileBuilder.ts b/smart-contract/assembly/__tests__/deweb-interface/FileBuilder.ts new file mode 100644 index 0000000..4de886c --- /dev/null +++ b/smart-contract/assembly/__tests__/deweb-interface/FileBuilder.ts @@ -0,0 +1,293 @@ +import { + Args, + bytesToString, + bytesToU32, + stringToBytes, +} from '@massalabs/as-types'; +import { getKeys, sha256, Storage } from '@massalabs/massa-as-sdk'; + +import { + filesInit, + uploadFileChunks, + getFileChunk, + deleteFiles, + getFileLocations, +} from '../../contracts/deweb-interface'; + +import { FileChunkGet } from '../../contracts/serializable/FileChunkGet'; +import { FileChunkPost } from '../../contracts/serializable/FileChunkPost'; +import { fileChunkCountKey } from '../../contracts/internals/storageKeys/chunksKeys'; +import { fileMetadataKey } from '../../contracts/internals/storageKeys/metadataKeys'; +import { + FILE_METADATA_LOCATION_TAG, + FILE_METADATA_TAG, + GLOBAL_METADATA_TAG, +} from '../../contracts/internals/storageKeys/tags'; +import { Metadata } from '../../contracts/serializable/Metadata'; +import { _getGlobalMetadata } from '../../contracts/internals/metadata'; +import { FileInit } from '../../contracts/serializable/FileInit'; +import { FileDelete } from '../../contracts/serializable/FileDelete'; +const limitChunk = 10240; + +class FileInfo { + locationHash: StaticArray; + + constructor( + public location: string, + public nbChunks: u32, + public data: StaticArray[] = [], + public metadata: Metadata[] = [], + ) { + this.locationHash = sha256(stringToBytes(location)); + } +} + +class FileBuilder { + private files: FileInfo[]; + private filesToDelete: FileInit[] = []; + private globalMetadata: Metadata[] = []; + + constructor() { + this.files = []; + } + + withFile( + location: string, + nbChunks: u32, + data: StaticArray[] = [], + metadata: Metadata[] = [], + ): FileBuilder { + this.files.push(new FileInfo(location, nbChunks, data, metadata)); + return this; + } + + withGlobalMetadata(key: string, value: string): FileBuilder { + this.globalMetadata.push( + new Metadata(stringToBytes(key), stringToBytes(value)), + ); + return this; + } + + withFilesToDelete(locations: string[]): FileBuilder { + for (let i = 0; i < locations.length; i++) { + const file = new FileInit( + locations[i], + sha256(stringToBytes(locations[i])), + ); + this.filesToDelete.push(file); + } + + return this; + } + + public init(): FileBuilder { + const initFiles: FileInit[] = []; + for (let i = 0; i < this.files.length; i++) { + const fileInfo = this.files[i]; + + initFiles.push( + new FileInit( + fileInfo.location, + fileInfo.locationHash, + fileInfo.nbChunks, + fileInfo.metadata, + ), + ); + } + + filesInit( + new Args() + .addSerializableObjectArray(initFiles) + .addSerializableObjectArray(this.filesToDelete) + .addSerializableObjectArray(this.globalMetadata) + .serialize(), + ); + + return this; + } + + uploadAll(limit: u32 = limitChunk): FileBuilder { + let chunks: FileChunkPost[] = []; + + // prepare list of chunks + for (let i = 0; i < this.files.length; i++) { + const fileInfo = this.files[i]; + for (let j = 0; j < fileInfo.data.length; j++) { + chunks.push(new FileChunkPost(fileInfo.location, j, fileInfo.data[j])); + } + } + + // Prepare list of list of chunks to store based on limit, one list should not exceed limit + let chunkList: FileChunkPost[][] = []; + let currentChunkList: FileChunkPost[] = []; + let currentChunkSize = 0; + for (let i = 0; i < chunks.length; i++) { + const chunk = chunks[i]; + if (u32(currentChunkSize + chunk.data.length) > limit) { + chunkList.push(currentChunkList); + currentChunkList = []; + currentChunkSize = 0; + } + currentChunkList.push(chunk); + currentChunkSize += chunk.data.length; + } + + if (currentChunkList.length > 0) { + chunkList.push(currentChunkList); + } + + // Store list of list of chunks + for (let i = 0; i < chunkList.length; i++) { + uploadFileChunks( + new Args() + .addSerializableObjectArray(chunkList[i]) + .serialize(), + ); + } + + return this; + } + + deleteFiles(files: FileDelete[]): void { + deleteFiles( + new Args().addSerializableObjectArray(files).serialize(), + ); + } + + // deleteWebsite(): void { + // deleteWebsite(new Args().serialize()); + // } + + hasUploadedFiles(): FileBuilder { + const dataStoreEntriesLocation = getKeys( + FILE_METADATA_TAG.concat(FILE_METADATA_LOCATION_TAG), + ); + // Check the list of file locations are correct + assert( + dataStoreEntriesLocation.length == this.files.length, + 'File count should be correct', + ); + + for (let i = 0; i < dataStoreEntriesLocation.length; i++) { + const location = bytesToString(Storage.get(dataStoreEntriesLocation[i])); + // TODO: Improve this check as might not be in same order ? + assert( + this.files[i].location == location, + `File ${location} should be in the file list`, + ); + } + + // Check the chunks are correct + for (let i = 0; i < this.files.length; i++) { + const fileInfo = this.files[i]; + for (let j = u32(0); j < fileInfo.nbChunks; j++) { + const storedChunk = getFileChunk( + chunkGetArgs(fileInfo.locationHash, j), + ); + assert( + storedChunk.toString() == fileInfo.data[j].toString(), + `Chunk ${j} of ${fileInfo.location} should be correct`, + ); + } + } + return this; + } + + hasGlobalMetadata(): FileBuilder { + const dataStoreEntriesMetadata = getKeys(GLOBAL_METADATA_TAG); + + for (let i = 0; i < dataStoreEntriesMetadata.length; i++) { + assert( + dataStoreEntriesMetadata[i].toString() == + GLOBAL_METADATA_TAG.concat(this.globalMetadata[i].key).toString(), + 'Metadata key should be correct', + ); + + const value = _getGlobalMetadata(this.globalMetadata[i].key); + assert( + value.toString() == this.globalMetadata[i].value.toString(), + 'Metadata value should be correct', + ); + } + return this; + } + + hasMetadata(): FileBuilder { + for (let i = 0; i < this.files.length; i++) { + const fileInfo = this.files[i]; + for (let j = 0; j < fileInfo.metadata.length; j++) { + const storageKey = fileMetadataKey( + fileInfo.locationHash, + fileInfo.metadata[j].key, + ); + assert(Storage.has(storageKey), 'Metadata should be stored'); + assert( + Storage.get(storageKey).toString() == + fileInfo.metadata[j].value.toString(), + 'Metadata value should be correct', + ); + } + } + return this; + } + + hasTheRightNumberOfFiles(): FileBuilder { + const dataStoreEntriesLocation = getKeys( + FILE_METADATA_TAG.concat(FILE_METADATA_LOCATION_TAG), + ); + + assert( + dataStoreEntriesLocation.length == this.files.length, + 'File count should be correct', + ); + + return this; + } + + hasTheRightNumberOfChunks(): FileBuilder { + for (let i = 0; i < this.files.length; i++) { + const fileInfo = this.files[i]; + const chunkCountKey = fileChunkCountKey(fileInfo.locationHash); + assert( + Storage.has(chunkCountKey), + 'Chunk count should be stored for each file', + ); + assert( + bytesToU32(Storage.get(chunkCountKey)) == fileInfo.nbChunks, + 'Chunk count should be correct', + ); + } + + return this; + } + + hasNoFiles(): void { + const fileList = new Args(getFileLocations()).next().unwrap(); + assert(fileList.length === 0, 'FileList should be empty'); + } + + fileIsDeleted(location: string): void { + const fileList = new Args(getFileLocations()).next().unwrap(); + for (let i = 0; i < fileList.length; i++) { + assert( + !fileList.includes(location), + `File ${location} should not be in the file list`, + ); + } + } +} + +export function given(): FileBuilder { + return new FileBuilder(); +} + +export function checkThat(fileBuilder: FileBuilder): FileBuilder { + return fileBuilder; +} + +export function chunkGetArgs( + hashLocation: StaticArray, + index: u32, +): StaticArray { + return new FileChunkGet(hashLocation, index).serialize(); +} diff --git a/smart-contract/assembly/__tests__/deweb-interface/upload-files.spec.ts b/smart-contract/assembly/__tests__/deweb-interface/upload-files.spec.ts new file mode 100644 index 0000000..a8f2757 --- /dev/null +++ b/smart-contract/assembly/__tests__/deweb-interface/upload-files.spec.ts @@ -0,0 +1,102 @@ +import { resetStorage, setDeployContext } from '@massalabs/massa-as-sdk'; +import { constructor } from '../../contracts/deweb-interface'; +import { Args, stringToBytes } from '@massalabs/as-types'; +import { checkThat, given } from './FileBuilder'; +import { Metadata } from '../../contracts/serializable/Metadata'; + +const user = 'AU12UBnqTHDQALpocVBnkPNy7y5CndUJQTLutaVDDFgMJcq5kQiKq'; +const file1Path = 'file1'; +const file2Path = 'file2'; +const file3Path = 'file3'; +const file4Path = 'file4'; +const fileData1: StaticArray = [1, 2, 3, 4]; +const fileData2: StaticArray = [5, 6, 7, 8]; +const fileData3: StaticArray = [9, 10, 11, 12]; +const fileData4: StaticArray = [13, 14, 15, 16]; +const metadataKey1 = stringToBytes('version'); +const metadataValue1 = stringToBytes('1.0.0'); +const metadataKey2 = stringToBytes('serve'); +const metadataValue2 = stringToBytes('0'); + +describe('Upload files', () => { + beforeEach(() => { + resetStorage(); + setDeployContext(user); + constructor(new Args().serialize()); + }); + + test('upload some files', () => { + const myUpload = given() + .withFile(file1Path, 2, [fileData1, fileData2]) + .withFile(file2Path, 1, [fileData2]) + .withFile(file3Path, 2, [fileData3, fileData4]) + .withFile(file4Path, 1, [fileData4]) + .init() + .uploadAll(); + + checkThat(myUpload).hasUploadedFiles(); + }); + + throws('if wrong total chunk', () => { + given().withFile(file1Path, 1, [fileData1, fileData2]).init().uploadAll(); + }); + + test('upload some files with global Metadata', () => { + const myUpload = given() + .withGlobalMetadata('version', '1.0.0') + .withGlobalMetadata('isReady', '0') + .withFile(file1Path, 2, [fileData1, fileData2]) + .withFile(file2Path, 1, [fileData2]) + .withFile(file3Path, 2, [fileData3, fileData4]) + .withFile(file4Path, 1, [fileData4]) + .init() + .uploadAll(); + + checkThat(myUpload).hasUploadedFiles().hasGlobalMetadata(); + }); + + test('upload some files with file Metadata', () => { + const myUpload = given() + .withFile( + file1Path, + 2, + [fileData1, fileData2], + [ + new Metadata(metadataKey1, metadataValue1), + new Metadata(metadataKey2, metadataValue2), + ], + ) + .init() + .uploadAll(); + + checkThat(myUpload).hasUploadedFiles().hasMetadata(); + }); + + test('upload some files then upload a new version', () => { + given() + .withFile( + file1Path, + 2, + [fileData1, fileData2], + [ + new Metadata(metadataKey1, metadataValue1), + new Metadata(metadataKey2, metadataValue2), + ], + ) + .withFile(file2Path, 1, [fileData2]) + .init() + .uploadAll(); + + const myUpdate = given() + .withFile(file1Path, 1, [fileData1]) + .withFilesToDelete([file2Path]) + .init() + .uploadAll(); + + checkThat(myUpdate) + .hasUploadedFiles() + .hasMetadata() + .hasTheRightNumberOfFiles() + .hasTheRightNumberOfChunks(); + }); +}); diff --git a/smart-contract/assembly/__tests__/internals/chunks.spec.ts b/smart-contract/assembly/__tests__/internals/chunks.spec.ts deleted file mode 100644 index 772a934..0000000 --- a/smart-contract/assembly/__tests__/internals/chunks.spec.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { resetStorage, sha256 } from '@massalabs/massa-as-sdk'; -import { - createRandomByteArray, - storeFileInChunks, - verifyStoredFile, -} from './utils'; -import { _setTotalChunk } from '../../contracts/internals/chunks'; -import { stringToBytes } from '@massalabs/as-types'; - -const fileName1 = 'file1'; -const fileName2 = 'file2'; -const fileName1Hash = sha256(stringToBytes(fileName1)); -const fileName2Hash = sha256(stringToBytes(fileName2)); - -describe('website deployer internals functions tests', () => { - describe('Store', () => { - beforeEach(() => { - resetStorage(); - }); - - test('Store single file with one chunk', () => { - const fileData = createRandomByteArray(1000); - const chunkSize = 1000; - _setTotalChunk(fileName1Hash, 1); - storeFileInChunks(fileName1, fileData, chunkSize); - verifyStoredFile(fileName1, fileData, chunkSize); - }); - - test('Store single file with multiple chunks', () => { - const fileData = createRandomByteArray(10240); - const chunkSize = 4096; - _setTotalChunk(fileName2Hash, 3); - storeFileInChunks(fileName2, fileData, chunkSize); - verifyStoredFile(fileName2, fileData, chunkSize); - }); - - test('Store multiple files with varying chunk sizes', () => { - const file1Data = createRandomByteArray(15000); - const file2Data = createRandomByteArray(8000); - const chunk1Size = 5000; - const chunk2Size = 3000; - _setTotalChunk(fileName1Hash, 3); - _setTotalChunk(fileName2Hash, 3); - storeFileInChunks(fileName1, file1Data, chunk1Size); - storeFileInChunks(fileName2, file2Data, chunk2Size); - verifyStoredFile(fileName1, file1Data, chunk1Size); - verifyStoredFile(fileName2, file2Data, chunk2Size); - }); - - test('Update existing file', () => { - const fileName = 'updateFile'; - const fileNameHash = sha256(stringToBytes(fileName)); - const originalData = createRandomByteArray(10000); - const updatedData = createRandomByteArray(12000); - const chunkSize = 4000; - _setTotalChunk(fileNameHash, 3); - storeFileInChunks(fileName, originalData, chunkSize); - verifyStoredFile(fileName, originalData, chunkSize); - storeFileInChunks(fileName, updatedData, chunkSize); - verifyStoredFile(fileName, updatedData, chunkSize); - }); - - test('Store file with random length and chunk size', () => { - const fileName = 'randomFile'; - const fileNameHash = sha256(stringToBytes(fileName)); - - const fileLength = u32(Math.floor(Math.random() * 50000) + 1000); // Random length between 1000 and 51000 - const chunkSize = u32(Math.floor(Math.random() * 1000) + 100); // Random chunk size between 100 and 1100 - const fileData = createRandomByteArray(fileLength); - _setTotalChunk(fileNameHash, 100); - storeFileInChunks(fileName, fileData, chunkSize); - verifyStoredFile(fileName, fileData, chunkSize); - }); - - throws('If total chunk total is 0', () => { - const fileData = createRandomByteArray(1000); - const chunkSize = 1000; - storeFileInChunks(fileName1, fileData, chunkSize); - }); - - throws('If index is out of bounds', () => { - const fileData = createRandomByteArray(2000); - const chunkSize = 1000; - _setTotalChunk(fileName1Hash, 1); - storeFileInChunks(fileName1, fileData, chunkSize); - }); - }); -}); diff --git a/smart-contract/assembly/__tests__/internals/const.spec.ts b/smart-contract/assembly/__tests__/internals/const.spec.ts deleted file mode 100644 index 7e9831f..0000000 --- a/smart-contract/assembly/__tests__/internals/const.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - FILE_TAG, - CHUNK_TAG, - CHUNK_NB_TAG, - METADATA_TAG, - MAX_STORAGE_VALUE_SIZE, - FILES_PATH_LIST, -} from '../../contracts/internals/const'; - -describe('Constants Tests', () => { - test('FILE_TAG should be 0', () => { - expect(FILE_TAG).toStrictEqual([0]); - }); - - test('CHUNK_TAG should be 1', () => { - expect(CHUNK_TAG).toStrictEqual([1]); - }); - - test('CHUNK_NB_TAG should be 2', () => { - expect(CHUNK_NB_TAG).toStrictEqual([2]); - }); - - test('METADATA_TAG should be 3', () => { - expect(METADATA_TAG).toStrictEqual([3]); - }); - - test('MAX_STORAGE_VALUE_SIZE should be 4', () => { - expect(MAX_STORAGE_VALUE_SIZE).toStrictEqual([4]); - }); - - test('FILES_PATH_LIST should use key 5', () => { - expect(FILES_PATH_LIST.key).toStrictEqual([5]); - }); -}); diff --git a/smart-contract/assembly/__tests__/internals/utils.ts b/smart-contract/assembly/__tests__/internals/utils.ts deleted file mode 100644 index a7262c0..0000000 --- a/smart-contract/assembly/__tests__/internals/utils.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { stringToBytes } from '@massalabs/as-types'; -import { sha256 } from '@massalabs/massa-as-sdk'; -import { _getFileChunk, _setFileChunk } from '../../contracts/internals/chunks'; -import { _getFilePathList } from '../../contracts/internals/file-list'; - -function calculateTotalChunks(data: StaticArray, chunkSize: u32): u32 { - return u32(Math.ceil(f64(data.length) / f64(chunkSize))); -} - -export function createRandomByteArray(length: u32): StaticArray { - const array = new StaticArray(length); - for (let i: u32 = 0; i < length; i++) { - array[i] = (Math.random() * 256) as u8; - } - - return array; -} - -export function storeFileInChunks( - fileName: string, - data: StaticArray, - chunkSize: u32, -): void { - const totalChunks = calculateTotalChunks(data, chunkSize); - - for (let i: u32 = 0; i < totalChunks; i++) { - const start = i * chunkSize; - const end = u32(Math.min(f64(start + chunkSize), f64(data.length))); - const chunk = StaticArray.fromArray(data.slice(start, end)); - _setFileChunk(fileName, i, chunk); - } -} - -export function verifyStoredFile( - fileName: string, - originalData: StaticArray, - chunkSize: u32, -): void { - const fileHash = sha256(stringToBytes(fileName)); - - const shouldTotalChunks = calculateTotalChunks(originalData, chunkSize); - - let reconstructedData: StaticArray = []; - for (let i: u32 = 0; i < shouldTotalChunks; i++) { - const chunk = _getFileChunk(fileHash, i); - - reconstructedData = reconstructedData.concat(chunk); - } - - expect(reconstructedData.length).toBe(originalData.length); - - for (let i = 0; i < originalData.length; i++) { - expect(reconstructedData[i]).toBe(originalData[i]); - } -} - -export function verifyFilesInList(expectedFileNames: string[]): void { - const fileList = _getFilePathList(); - expect(fileList.length).toBe(expectedFileNames.length); - for (let i = 0; i < expectedFileNames.length; i++) { - expect(fileList).toContain(expectedFileNames[i]); - } -} diff --git a/smart-contract/assembly/contracts/deweb-interface.ts b/smart-contract/assembly/contracts/deweb-interface.ts index d984363..697e480 100644 --- a/smart-contract/assembly/contracts/deweb-interface.ts +++ b/smart-contract/assembly/contracts/deweb-interface.ts @@ -1,187 +1,317 @@ -import { - balance, - Context, - sha256, - transferCoins, -} from '@massalabs/massa-as-sdk'; +import { balance, Context, transferCoins } from '@massalabs/massa-as-sdk'; import { _onlyOwner, _setOwner, } from '@massalabs/sc-standards/assembly/contracts/utils/ownership-internal'; -import { - ChunkPost, - ChunkGet, - PreStore, - ChunkDelete, -} from './serializable/Chunk'; import { Args, stringToBytes, u32ToBytes } from '@massalabs/as-types'; +import { FileChunkPost } from './serializable/FileChunkPost'; +import { FileDelete } from './serializable/FileDelete'; +import { FileChunkGet } from './serializable/FileChunkGet'; +import { Metadata } from './serializable/Metadata'; import { + _editFileMetadata, + _editGlobalMetadata, + _getFileMetadata, + _getGlobalMetadata, + _removeFileMetadata, + _removeGlobalMetadata, +} from './internals/metadata'; +import { + _setFileChunk, _getFileChunk, _getTotalChunk, - _setFileChunk, - _setTotalChunk, _deleteFile, - _removeChunksRange, - _removeFile, } from './internals/chunks'; - -import { _removeFilePath } from './internals/file-list'; -import { FILES_PATH_LIST } from './internals/const'; -import { _pushFilePath } from './internals/file-list'; +import { _getFileLocations } from './internals/location'; +import { _fileInit } from './internals/fileInit'; +import { FileInit } from './serializable/FileInit'; /** * Initializes the smart contract. - * Sets the contract deployer as the owner and initializes an empty file path list. - * @param _ - Unused parameter (required). + * Sets the contract deployer as the owner. */ export function constructor(_: StaticArray): void { if (!Context.isDeployingContract()) return; _setOwner(Context.caller().toString()); - FILES_PATH_LIST.set([]); } +/* -------------------------------------------------------------------------- */ +/* FILE INITIALIZATION */ +/* -------------------------------------------------------------------------- */ + +/** + * Initializes the contract storage with the given files and metadata. + * Only the contract owner can call this function. + * @param _binaryArgs - Serialized arguments containing an array of FileInit objects. + * @throws If the files are invalid or if the caller is not the owner. + */ +export function filesInit(_binaryArgs: StaticArray): void { + _onlyOwner(); + + const args = new Args(_binaryArgs); + const files = args + .nextSerializableObjectArray() + .expect('Invalid files to initialize'); + + const filesToDelete = args + .nextSerializableObjectArray() + .expect('Invalid files to delete'); + + const globalMetadata = args + .nextSerializableObjectArray() + .expect('Invalid global metadata'); + + for (let i = 0; i < filesToDelete.length; i++) { + _deleteFile(filesToDelete[i].hashLocation); + } + + for (let i = 0; i < globalMetadata.length; i++) { + _editGlobalMetadata(globalMetadata[i].key, globalMetadata[i].value); + } + + for (let i = 0; i < files.length; i++) { + _fileInit( + files[i].location, + files[i].hashLocation, + files[i].totalChunk, + files[i].metadata, + ); + } +} + +/* -------------------------------------------------------------------------- */ +/* FILES */ +/* -------------------------------------------------------------------------- */ + /** - * Stores file chunks in the contract storage. + * Uploads chunks of a file to the contract storage. * Only the contract owner can call this function. - * @param _binaryArgs - Serialized arguments containing an array of ChunkPost objects. + * @param _binaryArgs - Serialized arguments containing an array of FileChunkPost objects. * @throws If the chunks are invalid or if the caller is not the owner. */ -export function storeFileChunks(_binaryArgs: StaticArray): void { +export function uploadFileChunks(_binaryArgs: StaticArray): void { _onlyOwner(); + const args = new Args(_binaryArgs); const chunks = args - .nextSerializableObjectArray() + .nextSerializableObjectArray() .expect('Invalid chunks'); for (let i = 0; i < chunks.length; i++) { - _setFileChunk(chunks[i].filePath, chunks[i].index, chunks[i].data); + _setFileChunk(chunks[i].location, chunks[i].index, chunks[i].data); } } /** - * Prepares the storage of file chunks by setting the total number of chunks for each file. - * Additionally, it removes any chunks that exceed the new total number of chunks. - * Then, it removes the file if the new total number of chunks is 0. - * + * Retrieves a specific chunk of a file. + * @param _binaryArgs - Serialized FileChunkGet object containing hashLocation and index. + * @returns The requested chunk. + * @throws If the chunk request is invalid. + */ +export function getFileChunk(_binaryArgs: StaticArray): StaticArray { + const args = new Args(_binaryArgs); + const chunk = args.next().expect('Invalid chunk'); + return _getFileChunk(chunk.hashLocation, chunk.index); +} + +/** + * Retrieves the total number of chunks for a file. + * @param _binaryArgs - Serialized FileChunkGet object containing hashLocation. + * @returns The total number of chunks. + * @throws If the hashLocation is invalid. + */ +export function getFileChunkCount( + _binaryArgs: StaticArray, +): StaticArray { + const args = new Args(_binaryArgs); + const hashLocation = args + .next>() + .expect('Invalid hashLocation'); + + return u32ToBytes(_getTotalChunk(hashLocation)); +} + +/** + * Deletes files from the contract storage. * Only the contract owner can call this function. - * @param _binaryArgs - Serialized arguments containing an array of PreStore objects. - * @throws If the preStore data is invalid or if the caller is not the owner. + * @param _binaryArgs - Serialized arguments containing an array of FileChunkDelete objects. + * @throws If the files are invalid or if the caller is not the owner. */ -export function preStoreFileChunks(_binaryArgs: StaticArray): void { +export function deleteFiles(_binaryArgs: StaticArray): void { _onlyOwner(); + const args = new Args(_binaryArgs); const files = args - .nextSerializableObjectArray() - .expect('Invalid preStore'); + .nextSerializableObjectArray() + .expect('Invalid files'); for (let i = 0; i < files.length; i++) { - const totalChunks = _getTotalChunk(files[i].filePathHash); - - if (totalChunks === 0) { - _pushFilePath(files[i].filePath); - } - - // Remove the file if the new total chunks is 0 - if (files[i].newTotalChunks === 0) { - _removeFile(files[i].filePath, files[i].filePathHash, totalChunks - 1); - } else { - if (files[i].newTotalChunks < totalChunks) { - _removeChunksRange( - files[i].filePathHash, - files[i].newTotalChunks - 1, - totalChunks - 1, - ); - } - _setTotalChunk(files[i].filePathHash, files[i].newTotalChunks); - } + assert(_getTotalChunk(files[i].hashLocation) > 0, 'File does not exist'); + + _deleteFile(files[i].hashLocation); } } /** - * Retrieves the list of file paths stored in the contract. - * @returns Serialized array of file paths. + * Retrieves the list of file locations. + * @returns An array of file locations. */ -export function getFilePathList(): StaticArray { - return new Args().add(FILES_PATH_LIST.mustValue()).serialize(); +export function getFileLocations(): StaticArray { + return new Args().add(_getFileLocations()).serialize(); } +/* -------------------------------------------------------------------------- */ +/* GLOBAL METADATA */ +/* -------------------------------------------------------------------------- */ + /** - * Retrieves a specific chunk of a file. - * @param _binaryArgs - Serialized ChunkGet object containing filePathHash and id. - * @returns The requested file chunk. - * @throws If the chunk request is invalid. + * Sets the metadata of the contract. + * Only the contract owner can call this function. + * @param _binaryArgs - Serialized FileChunkGet object containing metadata. + * @throws If the caller is not the owner. */ -export function getChunk(_binaryArgs: StaticArray): StaticArray { +export function setMetadataGlobal(_binaryArgs: StaticArray): void { + _onlyOwner(); + const args = new Args(_binaryArgs); - const chunk = args.next().expect('Invalid chunk'); - return _getFileChunk(chunk.filePathHash, chunk.index); + const metadata = args + .nextSerializableObjectArray() + .expect('Invalid metadata'); + + for (let i = 0; i < metadata.length; i++) { + _editGlobalMetadata(metadata[i].key, metadata[i].value); + } } /** - * Retrieves the total number of chunks for a specific file. - * @param _binaryArgs - Serialized filePathHash. - * @returns Serialized number of chunks. - * @throws If the filePathHash is invalid. + * Retrieves the metadata of a specific file. + * @param _binaryArgs - Serialized FileChunkGet object containing key. + * @returns The requested global metadata. + * @throws If the metadata request is invalid. + * @throws If the metadata is not found. */ -export function getTotalChunksForFile( +export function getMetadataGlobal( _binaryArgs: StaticArray, ): StaticArray { const args = new Args(_binaryArgs); - const filePathHash = args - .next>() - .expect('Invalid filePathHash'); + const key = args.next>().expect('Invalid key'); - return u32ToBytes(_getTotalChunk(filePathHash)); + return _getGlobalMetadata(key); } /** - * Allow the owner to withdraw funds from the contract balance. + * Removes the metadata of a specific file. * Only the contract owner can call this function. - * @param _binaryArgs - Serialized amount to withdraw. + * @param _binaryArgs - Serialized FileChunkGet object containing hashLocation and key. * @throws If the caller is not the owner. + * @throws If the metadata request is invalid. + * @throws If the metadata is not found. */ -export function withdraw(binaryArgs: StaticArray): void { +export function removeMetadataGlobal(_binaryArgs: StaticArray): void { _onlyOwner(); - const args = new Args(binaryArgs); - const amount = args.next().expect('Invalid amount'); - assert(amount > 0, 'Invalid amount'); - assert(balance() >= amount, 'Insufficient balance'); - transferCoins(Context.caller(), amount); + const args = new Args(_binaryArgs); + const key = args.next().expect('Invalid key'); + + for (let i = 0; i < key.length; i++) { + _removeGlobalMetadata(stringToBytes(key[i])); + } } +/* -------------------------------------------------------------------------- */ +/* FILE METADATA */ +/* -------------------------------------------------------------------------- */ + /** - * Deletes a set of files from the contract storage. Calls deleteFile for each file. - * @param _binaryArgs - Serialized arguments containing the ChunkDelete object. - * @throws If the file does not exist or if the caller is not the owner. + * Sets the metadata of a specific file. + * Only the contract owner can call this function. + * @param _binaryArgs - Serialized FileChunkGet object containing hashLocation and metadata. + * @throws If the caller is not the owner. */ -export function deleteFiles(_binaryArgs: StaticArray): void { +export function setMetadataFile(_binaryArgs: StaticArray): void { _onlyOwner(); const args = new Args(_binaryArgs); - const files = args - .nextSerializableObjectArray() - .expect('Invalid files'); + const hashLocation = args + .next>() + .expect('Invalid hashLocation'); + const metadata = args + .nextSerializableObjectArray() + .expect('Invalid metadata'); - for (let i = 0; i < files.length; i++) { - assert(_getTotalChunk(files[i].filePathHash) > 0, 'File does not exist'); + for (let i = 0; i < metadata.length; i++) { + _editFileMetadata(metadata[i].key, metadata[i].value, hashLocation); + } +} - _deleteFile(files[i].filePathHash); +/** + * Removes the metadata of a specific file. + * Only the contract owner can call this function. + * @param _binaryArgs - Serialized FileChunkGet object containing hashLocation and key. + * @throws If the caller is not the owner. + */ +export function removeMetadataFile(_binaryArgs: StaticArray): void { + _onlyOwner(); + const args = new Args(_binaryArgs); + const hashLocation = args + .next>() + .expect('Invalid hashLocation'); + const key = args.next().expect('Invalid key'); - _removeFilePath(files[i].filePath); + for (let i = 0; i < key.length; i++) { + _removeFileMetadata(stringToBytes(key[i]), hashLocation); } } -// TODO: verify that this function throws is mustValue is empty -export function deleteWebsite(_: StaticArray): void { +/** + * Retrieves the metadata of a specific file. + * @param _binaryArgs - Serialized FileChunkGet object containing hashLocation and key. + * @returns The requested file metadata. + * @throws If the metadata request is invalid. + * @throws If the metadata is not found. + */ +export function getMetadataFile(_binaryArgs: StaticArray): StaticArray { + const args = new Args(_binaryArgs); + const hashLocation = args + .next>() + .expect('Invalid hashLocation'); + const key = args.next>().expect('Invalid key'); + + return _getFileMetadata(hashLocation, key); +} + +/* -------------------------------------------------------------------------- */ +/* COINS MANAGEMENT */ +/* -------------------------------------------------------------------------- */ + +/** + * Allow the owner to withdraw funds from the contract balance. + * Only the contract owner can call this function. + * @param _binaryArgs - Serialized amount to withdraw. + * @throws If the caller is not the owner. + */ +export function withdrawCoins(binaryArgs: StaticArray): void { _onlyOwner(); - const filePaths = FILES_PATH_LIST.mustValue(); - for (let i: i32 = 0; i < filePaths.length; i++) { - _deleteFile(sha256(stringToBytes(filePaths[i]))); - } - FILES_PATH_LIST.set([]); + const args = new Args(binaryArgs); + const amount = args.next().expect('Invalid amount'); + assert(amount > 0, 'Invalid amount'); + assert(balance() >= amount, 'Contract has insufficient balance'); + + transferCoins(Context.caller(), amount); } -// TODO: delete all metadata +/* -------------------------------------------------------------------------- */ +/* OWNERSHIP */ +/* -------------------------------------------------------------------------- */ -// TODO: delete SC +/** + * Changes the owner of the contract. + * Only the current owner can call this function. + * @param _binaryArgs - Serialized new owner address. + * @throws If the caller is not the owner. + */ +export function setOwner(_binaryArgs: StaticArray): void { + _onlyOwner(); + const args = new Args(_binaryArgs); + _setOwner(args.next().expect('Invalid owner')); +} diff --git a/smart-contract/assembly/contracts/internals/chunks.ts b/smart-contract/assembly/contracts/internals/chunks.ts index 842d0f4..a19641c 100644 --- a/smart-contract/assembly/contracts/internals/chunks.ts +++ b/smart-contract/assembly/contracts/internals/chunks.ts @@ -1,120 +1,106 @@ -import { stringToBytes, bytesToU32, u32ToBytes } from '@massalabs/as-types'; +import { stringToBytes, u32ToBytes } from '@massalabs/as-types'; import { sha256, Storage } from '@massalabs/massa-as-sdk'; -import { CHUNK_NB_TAG, FILE_TAG, CHUNK_TAG } from './const'; -import { _removeFilePath } from './file-list'; +import { fileChunkKey } from './storageKeys/chunksKeys'; +import { bytesToU32 } from '@massalabs/as-types'; +import { fileChunkCountKey } from './storageKeys/chunksKeys'; +import { _removeFileLocation } from './location'; +import { _removeAllFileMetadata } from './metadata'; +/* -------------------------------------------------------------------------- */ +/* SET */ +/* -------------------------------------------------------------------------- */ /** * Sets a chunk of a file in storage. - * @param filePath - The path of the file. + * @param location - The location of the file. * @param index - The index of the chunk. * @param chunk - The chunk data. * @throws If the chunk index is not in the expected order. */ export function _setFileChunk( - filePath: string, + location: string, index: u32, chunk: StaticArray, ): void { - const filePathHash = sha256(stringToBytes(filePath)); - const totalChunks = _getTotalChunk(filePathHash); + const hashLocation = sha256(stringToBytes(location)); + const totalChunk = _getTotalChunk(hashLocation); - assert(totalChunks > 0, "Total chunks wasn't set for this file"); - assert(index < totalChunks, 'Index out of bounds'); + assert(totalChunk > 0, "Total chunks wasn't set for this file"); + assert(index < totalChunk, 'Index out of bounds'); - Storage.set(_getChunkKey(filePathHash, index), chunk); + Storage.set(fileChunkKey(hashLocation, index), chunk); } -export function _removeChunksRange( - filePathHash: StaticArray, - start: u32 = 0, - end: u32 = 0, -): void { - for (let i = u32(start); i < end; i++) { - Storage.del(_getChunkKey(filePathHash, i)); - } -} - -export function _setTotalChunk( - filePathHash: StaticArray, - totalChunks: u32, -): void { - Storage.set(_getTotalChunkKey(filePathHash), u32ToBytes(totalChunks)); -} +/* -------------------------------------------------------------------------- */ +/* GET */ +/* -------------------------------------------------------------------------- */ /** * Retrieves a specific chunk of a file. - * @param filePathHash - The hash of the file path. + * @param hashLocation - The hash of the file location. * @param index - The index of the chunk to retrieve. * @returns The chunk data. * @throws If the chunk is not found in storage. */ export function _getFileChunk( - filePathHash: StaticArray, + hashLocation: StaticArray, index: u32, ): StaticArray { - assert(Storage.has(_getChunkKey(filePathHash, index)), 'Chunk not found'); - return Storage.get(_getChunkKey(filePathHash, index)); + assert(Storage.has(fileChunkKey(hashLocation, index)), 'Chunk not found'); + return Storage.get(fileChunkKey(hashLocation, index)); } /** * Gets the total number of chunks for a file. - * @param filePathHash - The hash of the file path. + * @param hashLocation - The hash of the file location. * @returns The total number of chunks, or 0 if not set. */ -export function _getTotalChunk(filePathHash: StaticArray): u32 { - if (!Storage.has(_getTotalChunkKey(filePathHash))) return 0; - return bytesToU32(Storage.get(_getTotalChunkKey(filePathHash))); -} - -/** - * Generates the storage key for the number of chunks of a file. - * @param filePathHash - The hash of the file path. - * @returns The storage key for the number of chunks. - */ -export function _getTotalChunkKey( - filePathHash: StaticArray, -): StaticArray { - return CHUNK_NB_TAG.concat(filePathHash); +export function _getTotalChunk(hashLocation: StaticArray): u32 { + if (!Storage.has(fileChunkCountKey(hashLocation))) return 0; + return bytesToU32(Storage.get(fileChunkCountKey(hashLocation))); } -/** - * Generates the storage key for a specific chunk of a file. - * @param filePathHash - The hash of the file path. - * @param index - The index of the chunk. - * @returns The storage key for the chunk. - */ -export function _getChunkKey( - filePathHash: StaticArray, - index: u32, -): StaticArray { - return FILE_TAG.concat(filePathHash) - .concat(CHUNK_TAG) - .concat(u32ToBytes(index)); -} +/* -------------------------------------------------------------------------- */ +/* DELETE */ +/* -------------------------------------------------------------------------- */ -export function _removeFile( - filePath: string, - filePathHash: StaticArray, - newTotalChunks: u32, +export function _removeChunksRange( + hashLocation: StaticArray, + start: u32 = 0, + end: u32 = 0, ): void { - _removeFilePath(filePath); - _removeChunksRange(filePathHash, 0, newTotalChunks - 1); - Storage.del(_getTotalChunkKey(filePathHash)); + for (let i = u32(start); i < end; i++) { + Storage.del(fileChunkKey(hashLocation, i)); + } } /** * Deletes a chunks of a given file from storage. - * @param filePathHash - The hash of the file path. + * @param hashLocation - The hash of the file location. * @throws If the chunk is not found in storage. */ -export function _deleteFile(filePathHash: StaticArray): void { - const chunkNumber = _getTotalChunk(filePathHash); +export function _deleteFile(hashLocation: StaticArray): void { + const chunkNumber = _getTotalChunk(hashLocation); for (let i: u32 = 0; i < chunkNumber; i++) { assert( - Storage.has(_getChunkKey(filePathHash, i)), + Storage.has(fileChunkKey(hashLocation, i)), 'Chunk not found while deleting', ); - Storage.del(_getChunkKey(filePathHash, i)); + Storage.del(fileChunkKey(hashLocation, i)); } - Storage.del(_getTotalChunkKey(filePathHash)); + + Storage.del(fileChunkCountKey(hashLocation)); + + _removeFileLocation(hashLocation); + _removeAllFileMetadata(hashLocation); +} + +/* -------------------------------------------------------------------------- */ +/* CHUNK COUNT */ +/* -------------------------------------------------------------------------- */ + +export function _setFileChunkCount( + hashLocation: StaticArray, + totalChunk: u32, +): void { + Storage.set(fileChunkCountKey(hashLocation), u32ToBytes(totalChunk)); } diff --git a/smart-contract/assembly/contracts/internals/const.ts b/smart-contract/assembly/contracts/internals/const.ts deleted file mode 100644 index d3be3b7..0000000 --- a/smart-contract/assembly/contracts/internals/const.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ConstantManager, KeyIncrementer } from '@massalabs/massa-as-sdk'; -const k = new KeyIncrementer(); -export const FILE_TAG = k.nextKey(); // 0 as u8 -export const CHUNK_TAG = k.nextKey(); // 1 as u8 -export const CHUNK_NB_TAG = k.nextKey(); // 2 as u8 -export const METADATA_TAG = k.nextKey(); // 3 as u8 -export const MAX_STORAGE_VALUE_SIZE = k.nextKey(); // 4 as u8 -export const FILES_PATH_LIST = new ConstantManager, u8>(k); // k will be 5 as u8 diff --git a/smart-contract/assembly/contracts/internals/file-list.ts b/smart-contract/assembly/contracts/internals/file-list.ts deleted file mode 100644 index 3f853ae..0000000 --- a/smart-contract/assembly/contracts/internals/file-list.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { FILES_PATH_LIST } from './const'; - -/** - * Adds a new file path to the list of file paths. - * @param filePath - The file path to be added. - */ -export function _pushFilePath(filePath: string): void { - const filePathList = FILES_PATH_LIST.mustValue(); - filePathList.push(filePath); - FILES_PATH_LIST.set(filePathList); -} - -/** - * Removes a file path from the list of file paths. - * If the file path is not in the list, this function does nothing. - * @param filePath - The file path to be removed. - */ -export function _removeFilePath(filePath: string): void { - const filePathList = FILES_PATH_LIST.mustValue(); - - const newFilePathList: string[] = []; - for (let i = 0; i < filePathList.length; i++) { - if (filePathList[i] !== filePath) { - newFilePathList.push(filePathList[i]); - } - } - - assert( - newFilePathList.length < filePathList.length, - 'File not found in list', - ); - - FILES_PATH_LIST.set(newFilePathList); -} - -/** - * Retrieves the list of file paths. - * If the list doesn't exist, it initializes an empty list. - * @returns An array of file paths. - */ -export function _getFilePathList(): string[] { - const result = FILES_PATH_LIST.tryValue(); - if (result.error) { - FILES_PATH_LIST.set([]); - return []; - } - return result.unwrap(); -} diff --git a/smart-contract/assembly/contracts/internals/fileInit.ts b/smart-contract/assembly/contracts/internals/fileInit.ts new file mode 100644 index 0000000..46d94e5 --- /dev/null +++ b/smart-contract/assembly/contracts/internals/fileInit.ts @@ -0,0 +1,33 @@ +import { Storage } from '@massalabs/massa-as-sdk'; +import { _pushFileLocation } from './location'; +import { fileMetadataLocationKey } from './storageKeys/metadataKeys'; +import { Metadata } from '../serializable/Metadata'; +import { _editFileMetadata } from './metadata'; +import { + _getTotalChunk, + _removeChunksRange, + _setFileChunkCount, +} from './chunks'; + +export function _fileInit( + location: string, + hashLocation: StaticArray, + totalChunk: u32, + metadata: Metadata[], +): void { + if (!Storage.has(fileMetadataLocationKey(hashLocation))) { + _pushFileLocation(location, hashLocation); + } + + for (let i = 0; i < metadata.length; i++) { + _editFileMetadata(metadata[i].key, metadata[i].value, hashLocation); + } + + const currentTotalChunk = _getTotalChunk(hashLocation); + if (totalChunk !== currentTotalChunk) { + if (totalChunk < currentTotalChunk) { + _removeChunksRange(hashLocation, totalChunk, currentTotalChunk - 1); + } + _setFileChunkCount(hashLocation, totalChunk); + } +} diff --git a/smart-contract/assembly/contracts/internals/location.ts b/smart-contract/assembly/contracts/internals/location.ts new file mode 100644 index 0000000..af30e53 --- /dev/null +++ b/smart-contract/assembly/contracts/internals/location.ts @@ -0,0 +1,44 @@ +import { bytesToString, stringToBytes } from '@massalabs/as-types'; +import { Storage } from '@massalabs/massa-as-sdk'; +import { + fileMetadataKey, + fileMetadataLocationKey, +} from './storageKeys/metadataKeys'; +import { FILE_METADATA_LOCATION_TAG } from './storageKeys/tags'; + +/** + * Adds a new file location to the list of file locations. + * @param location - The file location to be added. + */ +export function _pushFileLocation( + location: string, + hashLocation: StaticArray, +): void { + Storage.set(fileMetadataLocationKey(hashLocation), stringToBytes(location)); +} + +/** + * Removes a file location from the list of file locations. + * If the file location is not in the list, this function does nothing. + * @param location - The file location to be removed. + */ +export function _removeFileLocation(hashLocation: StaticArray): void { + const fileLocationKey = fileMetadataLocationKey(hashLocation); + assert(fileLocationKey, 'File not found'); + Storage.del(fileLocationKey); +} + +/** + * Retrieves the list of file locations. + * If the list doesn't exist, it initializes an empty list. + * @returns An array of file locations. + */ +export function _getFileLocations(): string[] { + const keys = Storage.getKeys(fileMetadataKey(FILE_METADATA_LOCATION_TAG)); + const locations: string[] = []; + for (let i = 0; i < keys.length; i++) { + locations.push(bytesToString(Storage.get(keys[i]))); + } + + return locations; +} diff --git a/smart-contract/assembly/contracts/internals/metadata.ts b/smart-contract/assembly/contracts/internals/metadata.ts new file mode 100644 index 0000000..127e69f --- /dev/null +++ b/smart-contract/assembly/contracts/internals/metadata.ts @@ -0,0 +1,57 @@ +import { Storage } from '@massalabs/massa-as-sdk'; +import { fileMetadataKey, globalMetadataKey } from './storageKeys/metadataKeys'; +import { FILE_METADATA_TAG } from './storageKeys/tags'; + +/* -------------------------------------------------------------------------- */ +/* GLOBAL METADATA */ +/* -------------------------------------------------------------------------- */ + +export function _editGlobalMetadata( + metadataKey: StaticArray, + metadataValue: StaticArray, +): void { + Storage.set(globalMetadataKey(metadataKey), metadataValue); +} + +export function _removeGlobalMetadata(metadataKey: StaticArray): void { + Storage.del(globalMetadataKey(metadataKey)); +} + +export function _getGlobalMetadata( + metadataKey: StaticArray, +): StaticArray { + return Storage.get(globalMetadataKey(metadataKey)); +} + +/* -------------------------------------------------------------------------- */ +/* FILE METADATA */ +/* -------------------------------------------------------------------------- */ + +export function _editFileMetadata( + metadataKey: StaticArray, + metadataValue: StaticArray, + hashLocation: StaticArray, +): void { + Storage.set(fileMetadataKey(hashLocation, metadataKey), metadataValue); +} + +export function _removeFileMetadata( + metadataKey: StaticArray, + hashLocation: StaticArray, +): void { + Storage.del(fileMetadataKey(hashLocation, metadataKey)); +} + +export function _removeAllFileMetadata(hashLocation: StaticArray): void { + const keys = Storage.getKeys(FILE_METADATA_TAG.concat(hashLocation)); + for (let i = 0; i < keys.length; i++) { + Storage.del(keys[i]); + } +} + +export function _getFileMetadata( + hashLocation: StaticArray, + metadataKey: StaticArray, +): StaticArray { + return Storage.get(fileMetadataKey(hashLocation, metadataKey)); +} diff --git a/smart-contract/assembly/contracts/internals/storageKeys/chunksKeys.ts b/smart-contract/assembly/contracts/internals/storageKeys/chunksKeys.ts new file mode 100644 index 0000000..95519b1 --- /dev/null +++ b/smart-contract/assembly/contracts/internals/storageKeys/chunksKeys.ts @@ -0,0 +1,34 @@ +import { u32ToBytes } from '@massalabs/as-types'; +import { CHUNK_NB_TAG, FILE_TAG, CHUNK_TAG } from './tags'; + +/** + * Generates the storage key for the number of chunks of a file. + * @param hashLocation - The hash of the file location. + * @returns The storage key for the number of chunks. + * + * @remarks + * [FILE_TAG][hash(location)][CHUNK_NB_TAG] = chunk_count_value + */ +export function fileChunkCountKey( + hashLocation: StaticArray, +): StaticArray { + return FILE_TAG.concat(hashLocation).concat(CHUNK_NB_TAG); +} + +/** + * Generates the storage key for a specific chunk of a file. + * @param hashLocation - The hash of the file location. + * @param index - The index of the chunk. + * @returns The storage key for the chunk. + * + * @remarks + * [FILE_TAG][hash(location)][CHUNK_TAG][index] = chunk_data + */ +export function fileChunkKey( + hashLocation: StaticArray, + index: u32, +): StaticArray { + return FILE_TAG.concat(hashLocation) + .concat(CHUNK_TAG) + .concat(u32ToBytes(index)); +} diff --git a/smart-contract/assembly/contracts/internals/storageKeys/metadataKeys.ts b/smart-contract/assembly/contracts/internals/storageKeys/metadataKeys.ts new file mode 100644 index 0000000..ec55486 --- /dev/null +++ b/smart-contract/assembly/contracts/internals/storageKeys/metadataKeys.ts @@ -0,0 +1,26 @@ +import { + FILE_METADATA_LOCATION_TAG, + FILE_METADATA_TAG, + GLOBAL_METADATA_TAG, +} from './tags'; + +export function globalMetadataKey( + metadataKey: StaticArray, +): StaticArray { + return GLOBAL_METADATA_TAG.concat(metadataKey); +} + +export function fileMetadataKey( + hashLocation: StaticArray, + metadataKey: StaticArray = [], +): StaticArray { + return FILE_METADATA_TAG.concat(hashLocation).concat(metadataKey); +} + +export function fileMetadataLocationKey( + hashLocation: StaticArray, +): StaticArray { + // For the file metadata location, we want to prefix the key with the FILE_METADATA_LOCATION_TAG + // so it's easier to find all the files + return fileMetadataKey(FILE_METADATA_LOCATION_TAG.concat(hashLocation)); +} diff --git a/smart-contract/assembly/contracts/internals/storageKeys/tags.ts b/smart-contract/assembly/contracts/internals/storageKeys/tags.ts new file mode 100644 index 0000000..612ce97 --- /dev/null +++ b/smart-contract/assembly/contracts/internals/storageKeys/tags.ts @@ -0,0 +1,10 @@ +import { stringToBytes } from '@massalabs/as-types'; + +export const FILE_TAG: StaticArray = [0]; +export const FILE_LOCATION_TAG: StaticArray = [1]; +export const CHUNK_TAG: StaticArray = [2]; +export const CHUNK_NB_TAG: StaticArray = [3]; +export const FILE_METADATA_TAG: StaticArray = [4]; +export const GLOBAL_METADATA_TAG: StaticArray = [5]; +export const FILE_METADATA_LOCATION_TAG: StaticArray = [6]; +export const DEWEB_VERSION_TAG = stringToBytes('\x42MASSA_DEWEB_VERSION'); diff --git a/smart-contract/assembly/contracts/serializable/Chunk.ts b/smart-contract/assembly/contracts/serializable/Chunk.ts deleted file mode 100644 index e2be085..0000000 --- a/smart-contract/assembly/contracts/serializable/Chunk.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { Serializable, Args, Result } from '@massalabs/as-types'; - -/** - * Represents a chunk of data to be posted. - * Implements the Serializable interface for encoding and decoding. - */ -export class ChunkPost implements Serializable { - /** - * Creates a new ChunkPost instance. - * @param filePath - The path of the file this chunk belongs to. - * @param index - The unique identifier of this chunk within the file. - * @param data - The actual data of the chunk. - */ - constructor( - public filePath: string = '', - public index: u32 = 0, - public data: StaticArray = [], - ) {} - - /** - * Serializes the ChunkPost instance into a byte array. - * @returns A StaticArray representing the serialized data. - */ - serialize(): StaticArray { - return new Args() - .add(this.filePath) - .add(this.index) - .add(this.data) - .serialize(); - } - - /** - * Deserializes a byte array into a ChunkPost instance. - * @param data - The byte array to deserialize. - * @param offset - The starting offset in the byte array. - * @returns A Result containing the new offset after deserialization. - */ - deserialize(data: StaticArray, offset: i32): Result { - const args = new Args(data, offset); - - const filePath = args.next(); - if (filePath.error) { - return new Result(args.offset); - } - - const index = args.next(); - if (index.error) { - return new Result(args.offset); - } - - const chunkData = args.next>(); - if (chunkData.error) { - return new Result(args.offset); - } - - this.filePath = filePath.unwrap(); - this.index = index.unwrap(); - this.data = chunkData.unwrap(); - - return new Result(args.offset); - } -} - -/** - * Represents a request to get a specific chunk of data. - * Implements the Serializable interface for encoding and decoding. - */ -export class ChunkGet implements Serializable { - /** - * Creates a new ChunkGet instance. - * @param filePathHash - The hash of the file path. - * @param id - The unique identifier of the chunk to retrieve. - */ - constructor( - public filePathHash: StaticArray = [], - public index: u32 = 0, - ) {} - - /** - * Serializes the ChunkGet instance into a byte array. - * @returns A StaticArray representing the serialized data. - */ - serialize(): StaticArray { - return new Args().add(this.filePathHash).add(this.index).serialize(); - } - - /** - * Deserializes a byte array into a ChunkGet instance. - * @param data - The byte array to deserialize. - * @param offset - The starting offset in the byte array. - * @returns A Result containing the new offset after deserialization. - */ - deserialize(data: StaticArray, offset: i32): Result { - const args = new Args(data, offset); - - const filePathHash = args.next>(); - if (filePathHash.error) { - return new Result(args.offset); - } - - const index = args.next(); - if (index.error) { - return new Result(args.offset); - } - - this.filePathHash = filePathHash.unwrap(); - this.index = index.unwrap(); - - return new Result(args.offset); - } -} - -export class PreStore implements Serializable { - constructor( - public filePath: string = '', - public filePathHash: StaticArray = [], - public newTotalChunks: u32 = 0, - ) {} - - serialize(): StaticArray { - return new Args() - .add(this.filePath) - .add(this.filePathHash) - .add(this.newTotalChunks) - .serialize(); - } - - deserialize(data: StaticArray, offset: i32): Result { - const args = new Args(data, offset); - - const filePath = args.next(); - if (filePath.error) { - return new Result(args.offset); - } - - const filePathHash = args.next>(); - if (filePathHash.error) { - return new Result(args.offset); - } - - const newTotalChunks = args.next(); - if (newTotalChunks.error) { - return new Result(args.offset); - } - - this.filePath = filePath.unwrap(); - this.filePathHash = filePathHash.unwrap(); - this.newTotalChunks = newTotalChunks.unwrap(); - - return new Result(args.offset); - } -} - -export class ChunkDelete implements Serializable { - /** - * Creates a new ChunkPost instance. - * @param filePath - The path of the file this chunk belongs to. - * @param filePathHash - The hash of the file path. - */ - constructor( - public filePath: string = '', - public filePathHash: StaticArray = [], - ) {} - - /** - * Serializes the ChunkPost instance into a byte array. - * @returns A StaticArray representing the serialized data. - */ - serialize(): StaticArray { - return new Args().add(this.filePath).add(this.filePathHash).serialize(); - } - - /** - * Deserializes a byte array into a ChunkPost instance. - * @param data - The byte array to deserialize. - * @param offset - The starting offset in the byte array. - * @returns A Result containing the new offset after deserialization. - */ - deserialize(data: StaticArray, offset: i32): Result { - const args = new Args(data, offset); - - const filePath = args.next(); - if (filePath.error) { - return new Result(args.offset); - } - - const filePathHash = args.next>(); - if (filePathHash.error) { - return new Result(args.offset); - } - - this.filePath = filePath.unwrap(); - this.filePathHash = filePathHash.unwrap(); - - return new Result(args.offset); - } -} diff --git a/smart-contract/assembly/contracts/serializable/FileChunkGet.ts b/smart-contract/assembly/contracts/serializable/FileChunkGet.ts new file mode 100644 index 0000000..84d6592 --- /dev/null +++ b/smart-contract/assembly/contracts/serializable/FileChunkGet.ts @@ -0,0 +1,50 @@ +import { Serializable, Args, Result } from '@massalabs/as-types'; + +/** + * Represents a request to get a specific chunk of data. + * Implements the Serializable interface for encoding and decoding. + */ +export class FileChunkGet implements Serializable { + /** + * Creates a new FileChunkGet instance. + * @param hashLocation - The hash of the file location. + * @param id - The unique identifier of the chunk to retrieve. + */ + constructor( + public hashLocation: StaticArray = [], + public index: u32 = 0, + ) {} + + /** + * Serializes the FileChunkGet instance into a byte array. + * @returns A StaticArray representing the serialized data. + */ + serialize(): StaticArray { + return new Args().add(this.hashLocation).add(this.index).serialize(); + } + + /** + * Deserializes a byte array into a FileChunkGet instance. + * @param data - The byte array to deserialize. + * @param offset - The starting offset in the byte array. + * @returns A Result containing the new offset after deserialization. + */ + deserialize(data: StaticArray, offset: i32): Result { + const args = new Args(data, offset); + + const hashLocation = args.next>(); + if (hashLocation.error) { + return new Result(args.offset); + } + + const index = args.next(); + if (index.error) { + return new Result(args.offset); + } + + this.hashLocation = hashLocation.unwrap(); + this.index = index.unwrap(); + + return new Result(args.offset); + } +} diff --git a/smart-contract/assembly/contracts/serializable/FileChunkPost.ts b/smart-contract/assembly/contracts/serializable/FileChunkPost.ts new file mode 100644 index 0000000..acf3b92 --- /dev/null +++ b/smart-contract/assembly/contracts/serializable/FileChunkPost.ts @@ -0,0 +1,52 @@ +import { Serializable, Args, Result } from '@massalabs/as-types'; + +/** + * Represents a chunk of data to be posted. + * Implements the Serializable interface for encoding and decoding. + */ +export class FileChunkPost implements Serializable { + /** + * Creates a new FileChunkPost instance. + * @param location - The location of the file this chunk belongs to. + * @param index - The index of the chunk. + * @param data - The data of the chunk. + */ + constructor( + public location: string = '', + public index: u32 = 0, + public data: StaticArray = [], + ) {} + + serialize(): StaticArray { + return new Args() + .add(this.location) + .add(this.index) + .add(this.data) + .serialize(); + } + + deserialize(data: StaticArray, offset: i32): Result { + const args = new Args(data, offset); + + const location = args.next(); + if (location.error) { + return new Result(args.offset); + } + + const index = args.next(); + if (index.error) { + return new Result(args.offset); + } + + const chunkData = args.next>(); + if (chunkData.error) { + return new Result(args.offset); + } + + this.location = location.unwrap(); + this.index = index.unwrap(); + this.data = chunkData.unwrap(); + + return new Result(args.offset); + } +} diff --git a/smart-contract/assembly/contracts/serializable/FileDelete.ts b/smart-contract/assembly/contracts/serializable/FileDelete.ts new file mode 100644 index 0000000..515bfcc --- /dev/null +++ b/smart-contract/assembly/contracts/serializable/FileDelete.ts @@ -0,0 +1,37 @@ +import { Serializable, Args, Result } from '@massalabs/as-types'; + +export class FileDelete implements Serializable { + /** + * Creates a new ChunkPost instance. + * @param location - The location of the file this chunk belongs to. + * @param hashLocation - The hash of the file location. + */ + constructor(public hashLocation: StaticArray = []) {} + + /** + * Serializes the ChunkPost instance into a byte array. + * @returns A StaticArray representing the serialized data. + */ + serialize(): StaticArray { + return new Args().add(this.hashLocation).serialize(); + } + + /** + * Deserializes a byte array into a ChunkPost instance. + * @param data - The byte array to deserialize. + * @param offset - The starting offset in the byte array. + * @returns A Result containing the new offset after deserialization. + */ + deserialize(data: StaticArray, offset: i32): Result { + const args = new Args(data, offset); + + const hashLocation = args.next>(); + if (hashLocation.error) { + return new Result(args.offset); + } + + this.hashLocation = hashLocation.unwrap(); + + return new Result(args.offset); + } +} diff --git a/smart-contract/assembly/contracts/serializable/FileInit.ts b/smart-contract/assembly/contracts/serializable/FileInit.ts new file mode 100644 index 0000000..2c0f50c --- /dev/null +++ b/smart-contract/assembly/contracts/serializable/FileInit.ts @@ -0,0 +1,59 @@ +import { Args, Result } from '@massalabs/as-types'; +import { Serializable } from '@massalabs/as-types/assembly/serializable'; +import { Metadata } from './Metadata'; + +export class FileInit implements Serializable { + /** + * Creates a new FileInit instance. + * @param location - The location of the file this chunk belongs to. + * @param hashLocation - The hash of the file location. + * @param totalChunk - The total number of chunks of the file. + * @param metadata - The metadata of the file. + */ + constructor( + public location: string = '', + public hashLocation: StaticArray = [], + public totalChunk: u32 = 0, + public metadata: Metadata[] = [], + ) {} + + serialize(): StaticArray { + return new Args() + .add(this.location) + .add(this.hashLocation) + .add(this.totalChunk) + .addSerializableObjectArray(this.metadata) + .serialize(); + } + + deserialize(data: StaticArray, offset: i32): Result { + const args = new Args(data, offset); + + const location = args.next(); + if (location.error) { + return new Result(args.offset); + } + + const hashLocation = args.next>(); + if (hashLocation.error) { + return new Result(args.offset); + } + + const totalChunk = args.next(); + if (totalChunk.error) { + return new Result(args.offset); + } + + const metadata = args.nextSerializableObjectArray(); + if (metadata.error) { + return new Result(args.offset); + } + + this.location = location.unwrap(); + this.hashLocation = hashLocation.unwrap(); + this.totalChunk = totalChunk.unwrap(); + this.metadata = metadata.unwrap(); + + return new Result(args.offset); + } +} diff --git a/smart-contract/assembly/contracts/serializable/Metadata.ts b/smart-contract/assembly/contracts/serializable/Metadata.ts new file mode 100644 index 0000000..0057d00 --- /dev/null +++ b/smart-contract/assembly/contracts/serializable/Metadata.ts @@ -0,0 +1,32 @@ +import { Args, Result } from '@massalabs/as-types'; +import { Serializable } from '@massalabs/as-types/assembly/serializable'; + +export class Metadata implements Serializable { + constructor( + public key: StaticArray = new StaticArray(0), + public value: StaticArray = new StaticArray(0), + ) {} + + serialize(): StaticArray { + return new Args().add(this.key).add(this.value).serialize(); + } + + deserialize(data: StaticArray, offset: i32): Result { + const args = new Args(data, offset); + + const key = args.next>(); + if (key.error) { + return new Result(args.offset); + } + + const value = args.next>(); + if (value.error) { + return new Result(args.offset); + } + + this.key = key.unwrap(); + this.value = value.unwrap(); + + return new Result(args.offset); + } +} diff --git a/smart-contract/src/e2e/delete.ts b/smart-contract/src/e2e/delete.ts deleted file mode 100644 index 4405d60..0000000 --- a/smart-contract/src/e2e/delete.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* eslint-disable no-console */ -import { Args, SmartContract } from '@massalabs/massa-web3'; -import { - assertFileIsDeleted, - assertListIsEmpty, - deleteFiles, - preStoreChunks, - storeChunk, -} from './helpers'; -import { - CHUNK_LIMIT, - CSS_FILE, - HTML_FILE, - JS_FILE, - projectPath, -} from './helpers/const'; -import { PreStore } from './helpers/serializable/Chunk'; -import { sha256 } from 'js-sha256'; -import { generateChunk } from './store'; -import { getByteCode } from '../utils'; - -export async function _deleteFile(contract: SmartContract) { - console.log('Deleting 1 file'); - await deleteFiles(contract, [HTML_FILE]); - await assertFileIsDeleted(contract, HTML_FILE); -} - -export async function _deleteFiles(contract: SmartContract) { - console.log('Deleting multiple files'); - await deleteFiles(contract, [JS_FILE, CSS_FILE]); - await assertListIsEmpty(contract); -} - -export async function testDeleteFiles(contract: SmartContract) { - await _deleteFile(contract); - await _deleteFiles(contract); -} - -export async function testDeleteWebsite(contract: SmartContract) { - console.log('Deleting website...'); - const op = await contract.call('deleteWebsite', new Args().serialize()); - await op.waitSpeculativeExecution(); - - await assertListIsEmpty(contract); -} - -export async function generateWebsite(contract: SmartContract) { - const htmlPreStore = new PreStore( - HTML_FILE, - new Uint8Array(sha256.arrayBuffer(HTML_FILE)), - 1n, - ); - - const cssPreStore = new PreStore( - CSS_FILE, - new Uint8Array(sha256.arrayBuffer(CSS_FILE)), - 1n, - ); - - await preStoreChunks(contract, [htmlPreStore, cssPreStore]); - - const chunkHtml = generateChunk( - HTML_FILE, - getByteCode(projectPath, HTML_FILE), - 0n, - ); - - const chunkCss = generateChunk( - CSS_FILE, - getByteCode(`${projectPath}/assets`, CSS_FILE), - 0n, - ); - - await storeChunk(contract, [chunkHtml]); - await storeChunk(contract, [chunkCss]); -} diff --git a/smart-contract/src/e2e/helpers/index.ts b/smart-contract/src/e2e/helpers/index.ts index 3f0b112..6da18da 100644 --- a/smart-contract/src/e2e/helpers/index.ts +++ b/smart-contract/src/e2e/helpers/index.ts @@ -1,35 +1,37 @@ /* eslint-disable no-console */ -import { - SmartContract, - Args, - ArrayTypes, - strToBytes, -} from '@massalabs/massa-web3'; -import { - ChunkPost, - ChunkGet, - PreStore, - ChunkDelete, -} from './serializable/Chunk'; - +import { SmartContract, Args, ArrayTypes } from '@massalabs/massa-web3'; import { sha256 } from 'js-sha256'; +import { FileInit } from './serializable/FileInit'; +import { FileChunkGet } from './serializable/FileChunkGet'; +import { FileChunkPost } from './serializable/FileChunkPost'; +import { FileDelete } from './serializable/FileDelete'; +import { Metadata } from './serializable/Metadata'; -export async function storeChunk(contract: SmartContract, chunks: ChunkPost[]) { +export async function uploadFileChunks( + contract: SmartContract, + chunks: FileChunkPost[], +) { const op = await contract.call( - 'storeFileChunks', + 'uploadFileChunks', new Args().addSerializableObjectArray(chunks).serialize(), ); await op.waitSpeculativeExecution(); } -export async function preStoreChunks( +export async function filesInit( contract: SmartContract, - chunks: PreStore[], + chunks: FileInit[], + fileToDelete: FileInit[] = [], + globalMetadata: Metadata[] = [], ) { const op = await contract.call( - 'preStoreFileChunks', - new Args().addSerializableObjectArray(chunks).serialize(), + 'filesInit', + new Args() + .addSerializableObjectArray(chunks) + .addSerializableObjectArray(fileToDelete) + .addSerializableObjectArray(globalMetadata) + .serialize(), ); await op.waitSpeculativeExecution(); @@ -37,13 +39,13 @@ export async function preStoreChunks( export async function deleteFiles( contract: SmartContract, - filePathArray: string[], + locationArray: string[], ) { - const deleteArray: ChunkDelete[] = []; + const deleteArray: FileDelete[] = []; - for (const filePath of filePathArray) { + for (const location of locationArray) { deleteArray.push( - new ChunkDelete(filePath, new Uint8Array(sha256.arrayBuffer(filePath))), + new FileDelete(new Uint8Array(sha256.arrayBuffer(location))), ); } @@ -55,11 +57,11 @@ export async function deleteFiles( await op.waitSpeculativeExecution(); } -export async function getFilePathList( +export async function getFileLocations( contract: SmartContract, ): Promise { const fileList = await contract.read( - 'getFilePathList', + 'getFileLocations', new Args().serialize(), ); return new Args(fileList.value).nextArray(ArrayTypes.STRING); @@ -67,57 +69,57 @@ export async function getFilePathList( export async function assertFilePathInList( contract: SmartContract, - filePaths: string[], + locations: string[], ): Promise { - const list = await getFilePathList(contract); + const list = await getFileLocations(contract); // eslint-disable-next-line guard-for-in - for (let filePath of filePaths) { - if (!list.includes(filePath)) { - throw new Error(`File not found in list: ${filePath}`); + for (let location of locations) { + if (!list.includes(location)) { + throw new Error(`File not found in list: ${location}`); } - console.log(`File found in list: ${filePath}`); + console.log(`File found in list: ${location}`); } } export async function assertChunkExists( contract: SmartContract, - chunk: ChunkPost, + chunk: FileChunkPost, ): Promise { - const chunkGet = new ChunkGet( - new Uint8Array(sha256.arrayBuffer(chunk.filePath)), + const chunkGet = new FileChunkGet( + new Uint8Array(sha256.arrayBuffer(chunk.location)), chunk.index, ); const result = await contract.read( - 'getChunk', + 'getFileChunk', new Args().addSerializable(chunkGet).serialize(), ); if (result.value.length !== chunk.data.length) throw new Error('Invalid chunk'); - console.log(`Chunk found: ${chunk.filePath} ${chunk.index}`); + console.log(`Chunk found: ${chunk.location} ${chunk.index}`); } export async function assertFileIsDeleted( contract: SmartContract, - filePath: string, + location: string, ) { // assert that the file is deleted from the list - const list = await getFilePathList(contract); + const list = await getFileLocations(contract); for (let file of list) { - if (file === filePath) { - throw new Error(`File still exists in list: ${filePath}`); + if (file === location) { + throw new Error(`File still exists in list: ${location}`); } // IMPROVE: assert that the file is deleted from the chunks } - console.log(`File successfully deleted ${filePath} from list`); + console.log(`File successfully deleted ${location} from list`); } export async function assertListIsEmpty(contract: SmartContract) { - const list = await getFilePathList(contract); + const list = await getFileLocations(contract); if (list.length !== 0) { throw new Error('List is not empty'); diff --git a/smart-contract/src/e2e/helpers/serializable/Chunk.ts b/smart-contract/src/e2e/helpers/serializable/Chunk.ts deleted file mode 100644 index e4d81d5..0000000 --- a/smart-contract/src/e2e/helpers/serializable/Chunk.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Args, DeserializedResult, Serializable } from '@massalabs/massa-web3'; - -export class ChunkPost implements Serializable { - constructor( - public filePath: string = '', - public index: bigint = 0n, - public data: Uint8Array = new Uint8Array(0), - ) {} - - serialize(): Uint8Array { - return new Args() - .addString(this.filePath) - .addU32(this.index) - .addUint8Array(this.data) - .serialize(); - } - - deserialize(data: Uint8Array, offset: number): DeserializedResult { - const args = new Args(data, offset); - this.filePath = args.nextString(); - this.index = args.nextU32(); - this.data = args.nextUint8Array(); - - return { instance: this, offset: args.getOffset() }; - } -} - -export class ChunkGet implements Serializable { - constructor( - public filePathHash: Uint8Array = new Uint8Array(0), - public index: bigint = 0n, - ) {} - - serialize(): Uint8Array { - return new Args() - .addUint8Array(this.filePathHash) - .addU32(this.index) - .serialize(); - } - - deserialize(data: Uint8Array, offset: number): DeserializedResult { - const args = new Args(data, offset); - - this.filePathHash = args.nextUint8Array(); - this.index = args.nextU32(); - - return { instance: this, offset: args.getOffset() }; - } -} - -export class PreStore implements Serializable { - constructor( - public filePath: string = '', - public filePathHash: Uint8Array = new Uint8Array(0), - public newTotalChunks: bigint = 0n, - ) {} - - serialize(): Uint8Array { - return new Args() - .addString(this.filePath) - .addUint8Array(this.filePathHash) - .addU32(this.newTotalChunks) - .serialize(); - } - - deserialize(data: Uint8Array, offset: number): DeserializedResult { - const args = new Args(data, offset); - - this.filePath = args.nextString(); - this.filePathHash = args.nextUint8Array(); - this.newTotalChunks = args.nextU32(); - - return { instance: this, offset: args.getOffset() }; - } -} - -export class ChunkDelete implements Serializable { - constructor( - public filePathName: string = '', - public filePathHash: Uint8Array = new Uint8Array(0), - ) {} - - serialize(): Uint8Array { - return new Args() - .addString(this.filePathName) - .addUint8Array(this.filePathHash) - .serialize(); - } - - deserialize( - data: Uint8Array, - offset: number, - ): DeserializedResult { - const args = new Args(data, offset); - - this.filePathName = args.nextString(); - this.filePathHash = args.nextUint8Array(); - - return { instance: this, offset: args.getOffset() }; - } -} diff --git a/smart-contract/src/e2e/helpers/serializable/FileChunkGet.ts b/smart-contract/src/e2e/helpers/serializable/FileChunkGet.ts new file mode 100644 index 0000000..e52a4fb --- /dev/null +++ b/smart-contract/src/e2e/helpers/serializable/FileChunkGet.ts @@ -0,0 +1,27 @@ +import { Args, DeserializedResult, Serializable } from '@massalabs/massa-web3'; + +export class FileChunkGet implements Serializable { + constructor( + public hashLocation: Uint8Array = new Uint8Array(0), + public index: bigint = 0n, + ) {} + + serialize(): Uint8Array { + return new Args() + .addUint8Array(this.hashLocation) + .addU32(this.index) + .serialize(); + } + + deserialize( + data: Uint8Array, + offset: number, + ): DeserializedResult { + const args = new Args(data, offset); + + this.hashLocation = args.nextUint8Array(); + this.index = args.nextU32(); + + return { instance: this, offset: args.getOffset() }; + } +} diff --git a/smart-contract/src/e2e/helpers/serializable/FileChunkPost.ts b/smart-contract/src/e2e/helpers/serializable/FileChunkPost.ts new file mode 100644 index 0000000..4654937 --- /dev/null +++ b/smart-contract/src/e2e/helpers/serializable/FileChunkPost.ts @@ -0,0 +1,29 @@ +import { Args, DeserializedResult, Serializable } from '@massalabs/massa-web3'; + +export class FileChunkPost implements Serializable { + constructor( + public location: string = '', + public index: bigint = 0n, + public data: Uint8Array = new Uint8Array(0), + ) {} + + serialize(): Uint8Array { + return new Args() + .addString(this.location) + .addU32(this.index) + .addUint8Array(this.data) + .serialize(); + } + + deserialize( + data: Uint8Array, + offset: number, + ): DeserializedResult { + const args = new Args(data, offset); + this.location = args.nextString(); + this.index = args.nextU32(); + this.data = args.nextUint8Array(); + + return { instance: this, offset: args.getOffset() }; + } +} diff --git a/smart-contract/src/e2e/helpers/serializable/FileDelete.ts b/smart-contract/src/e2e/helpers/serializable/FileDelete.ts new file mode 100644 index 0000000..8ea4e96 --- /dev/null +++ b/smart-contract/src/e2e/helpers/serializable/FileDelete.ts @@ -0,0 +1,20 @@ +import { Args, DeserializedResult, Serializable } from '@massalabs/massa-web3'; + +export class FileDelete implements Serializable { + constructor(public hashLocation: Uint8Array = new Uint8Array(0)) {} + + serialize(): Uint8Array { + return new Args().addUint8Array(this.hashLocation).serialize(); + } + + deserialize( + data: Uint8Array, + offset: number, + ): DeserializedResult { + const args = new Args(data, offset); + + this.hashLocation = args.nextUint8Array(); + + return { instance: this, offset: args.getOffset() }; + } +} diff --git a/smart-contract/src/e2e/helpers/serializable/FileInit.ts b/smart-contract/src/e2e/helpers/serializable/FileInit.ts new file mode 100644 index 0000000..f7e9987 --- /dev/null +++ b/smart-contract/src/e2e/helpers/serializable/FileInit.ts @@ -0,0 +1,31 @@ +import { Args, DeserializedResult, Serializable } from '@massalabs/massa-web3'; +import { Metadata } from './Metadata'; + +export class FileInit implements Serializable { + constructor( + public location: string = '', + public hashLocation: Uint8Array = new Uint8Array(0), + public totalChunk: bigint = 0n, + public metadata: Metadata[] = [], + ) {} + + serialize(): Uint8Array { + return new Args() + .addString(this.location) + .addUint8Array(this.hashLocation) + .addU32(this.totalChunk) + .addSerializableObjectArray(this.metadata) + .serialize(); + } + + deserialize(data: Uint8Array, offset: number): DeserializedResult { + const args = new Args(data, offset); + + this.location = args.nextString(); + this.hashLocation = args.nextUint8Array(); + this.totalChunk = args.nextU32(); + this.metadata = args.nextSerializableObjectArray(Metadata); + + return { instance: this, offset: args.getOffset() }; + } +} diff --git a/smart-contract/src/e2e/helpers/serializable/Metadata.ts b/smart-contract/src/e2e/helpers/serializable/Metadata.ts new file mode 100644 index 0000000..fd2e67b --- /dev/null +++ b/smart-contract/src/e2e/helpers/serializable/Metadata.ts @@ -0,0 +1,24 @@ +import { Args, DeserializedResult, Serializable } from '@massalabs/massa-web3'; + +export class Metadata implements Serializable { + constructor( + public key: Uint8Array = new Uint8Array(0), + public value: Uint8Array = new Uint8Array(0), + ) {} + + serialize(): Uint8Array { + return new Args() + .addUint8Array(this.key) + .addUint8Array(this.value) + .serialize(); + } + + deserialize(data: Uint8Array, offset: number): DeserializedResult { + const args = new Args(data, offset); + + this.key = args.nextUint8Array(); + this.value = args.nextUint8Array(); + + return { instance: this, offset: args.getOffset() }; + } +} diff --git a/smart-contract/src/e2e/index.ts b/smart-contract/src/e2e/index.ts index ca5dde8..8eab198 100644 --- a/smart-contract/src/e2e/index.ts +++ b/smart-contract/src/e2e/index.ts @@ -5,7 +5,6 @@ import { SmartContract, Web3Provider, } from '@massalabs/massa-web3'; -import { generateWebsite, testDeleteFiles, testDeleteWebsite } from './delete'; import { testStoreFiles } from './store'; import { getByteCode } from '../utils'; import { CONTRACT_FILE } from './helpers/const'; @@ -34,13 +33,6 @@ async function main() { console.log('Testing store files...'); await testStoreFiles(contract); console.log('Finished test\n'); - console.log('Testing file deletion...'); - await testDeleteFiles(contract); - console.log('Finished test\n'); - console.log('Testing website deletion...'); - await generateWebsite(contract); - await testDeleteWebsite(contract); - console.log('Finished test\n'); } main(); diff --git a/smart-contract/src/e2e/store.ts b/smart-contract/src/e2e/store.ts index ade0b14..b08aaf4 100644 --- a/smart-contract/src/e2e/store.ts +++ b/smart-contract/src/e2e/store.ts @@ -1,70 +1,58 @@ /* eslint-disable no-console */ -import { - Account, - Mas, - SmartContract, - Web3Provider, -} from '@massalabs/massa-web3'; +import { SmartContract } from '@massalabs/massa-web3'; import { getByteCode } from '../utils'; -import { ChunkPost, PreStore } from './helpers/serializable/Chunk'; import { - storeChunk, + uploadFileChunks, assertFilePathInList, assertChunkExists, - preStoreChunks, + filesInit, } from './helpers'; import { CSS_FILE, HTML_FILE, JS_FILE } from './helpers/const'; import { sha256 } from 'js-sha256'; +import { FileInit } from './helpers/serializable/FileInit'; +import { FileChunkPost } from './helpers/serializable/FileChunkPost'; const CHUNK_LIMIT = 4 * 1024; const projectPath = 'src/e2e/test-project/dist'; -export function generateChunk( - filePath: string, - data: Uint8Array, - id: bigint, -): ChunkPost { - return new ChunkPost(filePath, id, data); -} - export async function testStoreFiles(contract: SmartContract) { - const htmlPreStore = new PreStore( + const htmlFileInit = new FileInit( HTML_FILE, new Uint8Array(sha256.arrayBuffer(HTML_FILE)), 1n, ); - const cssPreStore = new PreStore( + const cssFileInit = new FileInit( CSS_FILE, new Uint8Array(sha256.arrayBuffer(CSS_FILE)), 1n, ); - const jsPreStore = new PreStore( + const jsFileInit = new FileInit( JS_FILE, new Uint8Array(sha256.arrayBuffer(JS_FILE)), 2n, ); - await preStoreChunks(contract, [htmlPreStore, cssPreStore, jsPreStore]); + await filesInit(contract, [htmlFileInit, cssFileInit, jsFileInit]); - const chunkHtml = generateChunk( + const chunkHtml = new FileChunkPost( HTML_FILE, - getByteCode(projectPath, HTML_FILE), 0n, + getByteCode(projectPath, HTML_FILE), ); console.log('Uploading Html file...'); - await storeChunk(contract, [chunkHtml]); + await uploadFileChunks(contract, [chunkHtml]); await assertFilePathInList(contract, [HTML_FILE]); await assertChunkExists(contract, chunkHtml); // Add multiple files - const chunkCss = generateChunk( + const chunkCss = new FileChunkPost( CSS_FILE, - getByteCode(`${projectPath}/assets`, CSS_FILE), 0n, + getByteCode(`${projectPath}/assets`, CSS_FILE), ); // Calculate remaining bytes to fit css and part of js file @@ -74,15 +62,15 @@ export async function testStoreFiles(contract: SmartContract) { const jsPart2 = jsFile.subarray(remainingBytes); // TODO: Put file name in const - const chunkJsPart1 = generateChunk(JS_FILE, jsPart1, 0n); - const chunkJsPart2 = generateChunk(JS_FILE, jsPart2, 1n); + const chunkJsPart1 = new FileChunkPost(JS_FILE, 0n, jsPart1); + const chunkJsPart2 = new FileChunkPost(JS_FILE, 1n, jsPart2); // Css file is small enough to be uploaded in one chunk so we will add a part of the js file // Js file is too big so we will split it in multiple chunks console.log('Uploading Css and part of Js file...'); - await storeChunk(contract, [chunkCss, chunkJsPart1]); + await uploadFileChunks(contract, [chunkCss, chunkJsPart1]); console.log('Uploading remaining part of Js file...'); - await storeChunk(contract, [chunkJsPart2]); + await uploadFileChunks(contract, [chunkJsPart2]); await assertFilePathInList(contract, [HTML_FILE, CSS_FILE, JS_FILE]);