From 930c5d060d4ca9b13f7447def62ce889a8ded40e Mon Sep 17 00:00:00 2001 From: Phoebe Lartisant Date: Tue, 24 Sep 2024 10:00:07 +0200 Subject: [PATCH 01/15] add ChunkDelete Class --- .../assembly/contracts/serializable/Chunk.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/smart-contract/assembly/contracts/serializable/Chunk.ts b/smart-contract/assembly/contracts/serializable/Chunk.ts index 20b643a..60869e3 100644 --- a/smart-contract/assembly/contracts/serializable/Chunk.ts +++ b/smart-contract/assembly/contracts/serializable/Chunk.ts @@ -150,3 +150,52 @@ export class PreStore implements Serializable { 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); + } +} From 0ca2bb7dc363a4be1db761117248d733fdff0bcf Mon Sep 17 00:00:00 2001 From: Phoebe Lartisant Date: Tue, 24 Sep 2024 10:01:32 +0200 Subject: [PATCH 02/15] add main and internal functions --- .../assembly/contracts/deweb-interface.ts | 63 ++++++++++++++++++- .../assembly/contracts/internals/chunks.ts | 19 +++++- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/smart-contract/assembly/contracts/deweb-interface.ts b/smart-contract/assembly/contracts/deweb-interface.ts index e7b7540..97093af 100644 --- a/smart-contract/assembly/contracts/deweb-interface.ts +++ b/smart-contract/assembly/contracts/deweb-interface.ts @@ -3,16 +3,25 @@ import { _onlyOwner, _setOwner, } from '@massalabs/sc-standards/assembly/contracts/utils/ownership-internal'; -import { ChunkPost, ChunkGet, PreStore } from './serializable/Chunk'; +import { + ChunkPost, + ChunkGet, + PreStore, + ChunkDelete, +} from './serializable/Chunk'; + import { Args, u32ToBytes } from '@massalabs/as-types'; import { _getFileChunk, _getTotalChunk, - _removeChunksRange, - _removeFile, _setFileChunk, _setTotalChunk, + _deleteFile, + _removeChunksRange, + _removeFile, } from './internals/chunks'; + +import { _isPathFileInList, _removeFilePath } from './internals/file-list'; import { FILES_PATH_LIST } from './internals/const'; import { _pushFilePath } from './internals/file-list'; @@ -136,3 +145,51 @@ export function withdraw(binaryArgs: StaticArray): void { transferCoins(Context.caller(), amount); } + +/** + * Deletes a file from the contract storage. + * @param _binaryArgs - Serialized arguments containing the ChunkDelete object. + * @param index - (optional) The index of the file to delete of deleting multiple files. + * @throws If the file does not exist or if the caller is not the owner. + */ +export function deleteFile(_binaryArgs: StaticArray): void { + _onlyOwner(); + const args = new Args(_binaryArgs); + + const file = args + .nextSerializableObjectArray() + .expect('Invalid files'); + + assert(_isPathFileInList(file[0].filePath), 'File does not exist'); + + _deleteFile(file[0].filePathHash); + + _removeFilePath(file[0].filePath); +} + +/** + * 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. + */ +export function deleteFiles(_binaryArgs: StaticArray): void { + _onlyOwner(); + const args = new Args(_binaryArgs); + const files = args + .nextSerializableObjectArray() + .expect('Invalid files'); + + for (let i = 0; i < files.length; i++) { + assert(_isPathFileInList(files[i].filePath), 'File does not exist'); + + _deleteFile(files[i].filePathHash); + + _removeFilePath(files[i].filePath); + } +} + +//TODO: delete all files in project + +//TODO: delete all metadata + +//TODO: delete SC diff --git a/smart-contract/assembly/contracts/internals/chunks.ts b/smart-contract/assembly/contracts/internals/chunks.ts index 2ceb5a2..635f28d 100644 --- a/smart-contract/assembly/contracts/internals/chunks.ts +++ b/smart-contract/assembly/contracts/internals/chunks.ts @@ -1,5 +1,5 @@ import { stringToBytes, bytesToU32, u32ToBytes } from '@massalabs/as-types'; -import { sha256, Storage } from '@massalabs/massa-as-sdk'; +import { set, sha256, Storage } from '@massalabs/massa-as-sdk'; import { CHUNK_NB_TAG, FILE_TAG, CHUNK_TAG } from './const'; import { _removeFilePath } from './file-list'; @@ -101,3 +101,20 @@ export function _removeFile( _removeChunksRange(filePathHash, 0, newTotalChunks - 1); Storage.del(_getTotalChunkKey(filePathHash)); } + +/** + * Deletes a chunks of a given file from storage. + * @param filePathHash - The hash of the file path. + * @throws If the chunk is not found in storage. + */ +export function _deleteFile(filePathHash: StaticArray): void { + const chunkNumber = _getTotalChunk(filePathHash); + for (let i: u32 = 0; i < chunkNumber; i++) { + assert( + Storage.has(_getChunkKey(filePathHash, i)), + 'Chunk not found while deleting', + ); + Storage.del(_getChunkKey(filePathHash, i)); + } + _setTotalChunk(filePathHash, 0); +} From 9a86153412c8de6159a51d1862c253a1e252da60 Mon Sep 17 00:00:00 2001 From: Phoebe Lartisant Date: Tue, 24 Sep 2024 10:02:16 +0200 Subject: [PATCH 03/15] add unit tests --- .../assembly/__tests__/FileBuilder.ts | 47 ++++- .../__tests__/deweb-interface.spec.ts | 172 +++++++++++++++--- 2 files changed, 192 insertions(+), 27 deletions(-) diff --git a/smart-contract/assembly/__tests__/FileBuilder.ts b/smart-contract/assembly/__tests__/FileBuilder.ts index fb31710..4f29b06 100644 --- a/smart-contract/assembly/__tests__/FileBuilder.ts +++ b/smart-contract/assembly/__tests__/FileBuilder.ts @@ -1,11 +1,18 @@ import { Args, stringToBytes } from '@massalabs/as-types'; import { sha256 } from '@massalabs/massa-as-sdk'; -import { PreStore, ChunkPost, ChunkGet } from '../contracts/serializable/Chunk'; +import { + PreStore, + ChunkPost, + ChunkGet, + ChunkDelete, +} from '../contracts/serializable/Chunk'; import { preStoreFileChunks, storeFileChunks, getFilePathList, getChunk, + deleteFile, + deleteFiles, } from '../contracts/deweb-interface'; const limitChunk = 10240; @@ -127,6 +134,18 @@ class FileBuilder { return this; } + deleteFile(file: ChunkDelete): void { + deleteFile( + new Args().addSerializableObjectArray([file]).serialize(), + ); + } + + deleteFiles(files: ChunkDelete[]): void { + deleteFiles( + new Args().addSerializableObjectArray(files).serialize(), + ); + } + hasFiles(): void { const fileList = new Args(getFilePathList()).next().unwrap(); for (let i = 0; i < this.files.length; i++) { @@ -136,9 +155,7 @@ class FileBuilder { `File ${fileInfo.fileName} should be in the file list`, ); for (let j = 0; j < fileInfo.data.length; j++) { - const storedChunk = getChunk( - new ChunkGet(fileInfo.fileNameHash, j).serialize(), - ); + const storedChunk = getChunk(chunkGetArgs(fileInfo.fileNameHash, j)); assert( storedChunk.length == fileInfo.data[j].length, `Chunk ${j} of ${fileInfo.fileName} should have correct length`, @@ -146,6 +163,21 @@ class FileBuilder { } } } + + 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 { @@ -155,3 +187,10 @@ export function given(): 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 index f619155..d28faf5 100644 --- a/smart-contract/assembly/__tests__/deweb-interface.spec.ts +++ b/smart-contract/assembly/__tests__/deweb-interface.spec.ts @@ -4,15 +4,29 @@ import { sha256, } from '@massalabs/massa-as-sdk'; import {} from '../contracts/internals/chunks'; -import { constructor, getChunk } from '../contracts/deweb-interface'; -import { ChunkGet } from '../contracts/serializable/Chunk'; +import { + constructor, + getChunk, + storeFileChunks, + preStoreFileChunks, + deleteFile, + deleteFiles, + getFilePathList, +} from '../contracts/deweb-interface'; +import { + ChunkDelete, + ChunkGet, + ChunkPost, + PreStore, +} from '../contracts/serializable/Chunk'; import { Args, stringToBytes } from '@massalabs/as-types'; -import { given, checkThat } from './FileBuilder'; +import { checkThat, chunkGetArgs, given } from './FileBuilder'; const user = 'AU12UBnqTHDQALpocVBnkPNy7y5CndUJQTLutaVDDFgMJcq5kQiKq'; -const file1Name = 'file1'; -const file2Name = 'file2'; -const file1NameHash = sha256(stringToBytes(file1Name)); +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); @@ -26,7 +40,7 @@ describe('website deployer internals functions tests', () => { test('Store 1 file with 1 chunk', () => { const myUpload = given() - .withFile(file1Name, 1, [fileData1]) + .withFile(file1Path, 1, [fileData1]) .preStore() .storeAll(); @@ -35,8 +49,8 @@ describe('website deployer internals functions tests', () => { test('Store 2 files with 1 chunk each', () => { const myUpload = given() - .withFile(file1Name, 1, [fileData1]) - .withFile(file2Name, 1, [fileData2]) + .withFile(file1Path, 1, [fileData1]) + .withFile(file2Path, 1, [fileData2]) .preStore() .storeAll(); @@ -45,7 +59,7 @@ describe('website deployer internals functions tests', () => { test('Store a file with 2 chunks', () => { const myUpload = given() - .withFile(file1Name, 2, [fileData1, fileData2]) + .withFile(file1Path, 2, [fileData1, fileData2]) .preStore() .storeAll(); @@ -54,12 +68,12 @@ describe('website deployer internals functions tests', () => { test('Store 2 batch of chunks', () => { const myFirstUpload = given() - .withFile(file1Name, 2, [fileData1, fileData2]) + .withFile(file1Path, 2, [fileData1, fileData2]) .preStore() .storeAll(); const mySecondUpload = given() - .withFile(file2Name, 2, [fileData1, fileData2]) + .withFile(file2Path, 2, [fileData1, fileData2]) .preStore() .storeAll(); @@ -69,14 +83,14 @@ describe('website deployer internals functions tests', () => { test('Update a chunk with different totalChunks', () => { const myUpload = given() - .withFile(file1Name, 2, [fileData1, fileData2]) + .withFile(file1Path, 2, [fileData1, fileData2]) .preStore() .storeAll(); checkThat(myUpload).hasFiles(); const myUpload2 = given() - .withFile(file1Name, 3, [fileData1, fileData2, fileData1]) + .withFile(file1Path, 3, [fileData1, fileData2, fileData1]) .preStore() .storeAll(); @@ -85,7 +99,7 @@ describe('website deployer internals functions tests', () => { throws('Wrong totalChunk', () => { given() - .withFile(file1Name, 3, [ + .withFile(file1Path, 3, [ fileData1, fileData2, fileData2, @@ -107,14 +121,126 @@ describe('website deployer internals functions tests', () => { }); throws('should throw if file does not exist', () => { - getChunk(chunkGetArgs(file1NameHash, 0)); + getChunk(chunkGetArgs(file1PathHash, 0)); }); }); -}); -function chunkGetArgs( - filePathHash: StaticArray, - index: u32, -): StaticArray { - return new ChunkGet(filePathHash, index).serialize(); -} + // Testing delete files + describe('Delete', () => { + beforeEach(() => { + resetStorage(); + setDeployContext(user); + constructor(new Args().serialize()); + }); + + test('Should delete 1 file with 1 chunk', () => { + const myUpload = given() + .withFile(file1Path, 1, [fileData1]) + .preStore() + .storeAll(); + + const fileToDelete = new ChunkDelete( + file1Path, + sha256(stringToBytes(file1Path)), + ); + + myUpload.deleteFile(fileToDelete); + + throws('should throw if file does not exist', () => { + getChunk(chunkGetArgs(file1PathHash, 0)); + }); + + checkThat(myUpload).hasNoFiles(); + }); + + test('Should delete 1 file with 2 chunks', () => { + const myUpload = given() + .withFile(file1Path, 2, [fileData1, fileData2]) + .preStore() + .storeAll(); + + const fileToDelete = new ChunkDelete(file1Path, file1PathHash); + + myUpload.deleteFile(fileToDelete); + + throws('should throw if file does not exist', () => { + getChunk(chunkGetArgs(file1PathHash, 0)); + }); + + checkThat(myUpload).hasNoFiles(); + }); + + test('should 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.deleteFile(deleteFile1); + + checkThat(myFirstUpload).fileIsDeleted(deleteFile1.filePath); + checkThat(mySecondUpload).hasFiles; + + throws('should throw if file1 does not exists', () => { + getChunk(chunkGetArgs(file1PathHash, 0)); + }); + }); + + test('Should 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('Should delete all given files', () => { + 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(); + }); + + test('Should throw if there are no files to delete', () => { + throws('should throw if there is no file to delete', () => { + deleteFile( + new Args() + .addSerializableObjectArray([ + new ChunkDelete('DeleteFile1'), + ]) + .serialize(), + ); + }); + }); + }); +}); From daecab070606ebbbc5ee5c719a433bd8dd1037ea Mon Sep 17 00:00:00 2001 From: Phoebe Lartisant Date: Tue, 24 Sep 2024 10:02:36 +0200 Subject: [PATCH 04/15] add e2e tests --- smart-contract/src/e2e/delete.ts | 25 ++++++ smart-contract/src/e2e/helpers/const.ts | 2 + smart-contract/src/e2e/helpers/index.ts | 84 ++++++++++++++++++- .../src/e2e/helpers/serializable/Chunk.ts | 26 ++++++ smart-contract/src/e2e/index.ts | 5 +- smart-contract/src/e2e/store.ts | 3 +- 6 files changed, 140 insertions(+), 5 deletions(-) create mode 100644 smart-contract/src/e2e/delete.ts diff --git a/smart-contract/src/e2e/delete.ts b/smart-contract/src/e2e/delete.ts new file mode 100644 index 0000000..ba90382 --- /dev/null +++ b/smart-contract/src/e2e/delete.ts @@ -0,0 +1,25 @@ +import { SmartContract } from '@massalabs/massa-web3'; +import { + assertFileIsDeleted, + assertListIsEmpty, + deleteFile, + deleteFiles, +} from './helpers'; +import { CSS_FILE, HTML_FILE, JS_FILE } from './helpers/const'; + +export async function _deleteFile(contract: SmartContract) { + console.log('Deleting 1 file'); + await deleteFile(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); +} diff --git a/smart-contract/src/e2e/helpers/const.ts b/smart-contract/src/e2e/helpers/const.ts index 947399c..0b7d0c8 100644 --- a/smart-contract/src/e2e/helpers/const.ts +++ b/smart-contract/src/e2e/helpers/const.ts @@ -2,3 +2,5 @@ export const JS_FILE = 'index.js'; export const CSS_FILE = 'index.css'; export const HTML_FILE = 'index.html'; export const CONTRACT_FILE = 'deweb-interface.wasm'; +export const CHUNK_LIMIT = 4 * 1024; +export const projectPath = 'src/e2e/test-project/dist'; diff --git a/smart-contract/src/e2e/helpers/index.ts b/smart-contract/src/e2e/helpers/index.ts index ff993b6..750d2ad 100644 --- a/smart-contract/src/e2e/helpers/index.ts +++ b/smart-contract/src/e2e/helpers/index.ts @@ -1,6 +1,19 @@ /* eslint-disable no-console */ -import { SmartContract, Args, ArrayTypes } from '@massalabs/massa-web3'; -import { ChunkPost, ChunkGet, PreStore } from './serializable/Chunk'; +import { + SmartContract, + Args, + ArrayTypes, + strToBytes, +} from '@massalabs/massa-web3'; +import { + ChunkPost, + ChunkGet, + PreStore, + ChunkDelete, +} from './serializable/Chunk'; + +import assert from 'assert'; + import { sha256 } from 'js-sha256'; export async function storeChunk(contract: SmartContract, chunks: ChunkPost[]) { @@ -24,7 +37,42 @@ export async function preStoreChunks( await op.waitSpeculativeExecution(); } -async function getFilePathList(contract: SmartContract): Promise { +export async function deleteFile(contract: SmartContract, filePath: string) { + const deleteChunk = new ChunkDelete( + filePath, + new Uint8Array(sha256.arrayBuffer(filePath)), + ); + const op = await contract.call( + 'deleteFile', + new Args().addSerializableObjectArray([deleteChunk]).serialize(), + ); + + await op.waitSpeculativeExecution(); +} + +export async function deleteFiles( + contract: SmartContract, + filePathArray: string[], +) { + const deleteArray: ChunkDelete[] = []; + + for (const filePath of filePathArray) { + deleteArray.push( + new ChunkDelete(filePath, new Uint8Array(sha256.arrayBuffer(filePath))), + ); + } + + const op = await contract.call( + 'deleteFiles', + new Args().addSerializableObjectArray(deleteArray).serialize(), + ); + + await op.waitSpeculativeExecution(); +} + +export async function getFilePathList( + contract: SmartContract, +): Promise { const fileList = await contract.read( 'getFilePathList', new Args().serialize(), @@ -65,3 +113,33 @@ export async function assertChunkExists( console.log(`Chunk found: ${chunk.filePath} ${chunk.index}`); } + +export async function assertFileIsDeleted( + contract: SmartContract, + filePath: string, +) { + // assert that the file is deleted from the list + const list = await getFilePathList(contract); + for (let file of list) { + if (file === filePath) { + throw new Error(`File still exists in list: ${filePath}`); + } + + //TODO: assert that the file is deleted from the chunks + } + + console.log(`File successfully deleted ${filePath} from list`); +} + +export async function assertListIsEmpty(contract: SmartContract) { + const list = await getFilePathList(contract); + + if (list.length !== 0) { + throw new Error('List is not empty'); + } + + console.log('All files have been deleted'); + // await assert.rejects(async () => { + // await getFilePathList(contract); + // }, 'should throw if there is no file to delete'); +} diff --git a/smart-contract/src/e2e/helpers/serializable/Chunk.ts b/smart-contract/src/e2e/helpers/serializable/Chunk.ts index 9355dc1..e4d81d5 100644 --- a/smart-contract/src/e2e/helpers/serializable/Chunk.ts +++ b/smart-contract/src/e2e/helpers/serializable/Chunk.ts @@ -73,3 +73,29 @@ export class PreStore implements Serializable { 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/index.ts b/smart-contract/src/e2e/index.ts index 6c47cd3..bd1d036 100644 --- a/smart-contract/src/e2e/index.ts +++ b/smart-contract/src/e2e/index.ts @@ -1,10 +1,13 @@ /* eslint-disable no-console */ +import { testDeleteFiles } from './delete'; import { testStoreFiles } from './store'; async function main() { console.log('Starting tests...'); console.log('Testing store files...'); - await testStoreFiles(); + const contract = await testStoreFiles(); + console.log('Testing deletion...'); + await testDeleteFiles(contract); console.log('Tests finished'); } diff --git a/smart-contract/src/e2e/store.ts b/smart-contract/src/e2e/store.ts index 773cbef..524d02a 100644 --- a/smart-contract/src/e2e/store.ts +++ b/smart-contract/src/e2e/store.ts @@ -30,7 +30,7 @@ async function deploy(provider: Web3Provider): Promise { return contract; } -function generateChunk( +export function generateChunk( filePath: string, data: Uint8Array, id: bigint, @@ -108,4 +108,5 @@ export async function testStoreFiles() { const provider = Web3Provider.buildnet(account); const contract = await deploy(provider); await testStoreChunks(contract); + return contract; } From 7f7375cf9ac62a6676aa768ff17c41550538626c Mon Sep 17 00:00:00 2001 From: Phoebe Lartisant Date: Tue, 24 Sep 2024 10:08:52 +0200 Subject: [PATCH 05/15] update SC test structure --- .../__tests__/deweb-interface.spec.ts | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/smart-contract/assembly/__tests__/deweb-interface.spec.ts b/smart-contract/assembly/__tests__/deweb-interface.spec.ts index d28faf5..75bda53 100644 --- a/smart-contract/assembly/__tests__/deweb-interface.spec.ts +++ b/smart-contract/assembly/__tests__/deweb-interface.spec.ts @@ -126,7 +126,7 @@ describe('website deployer internals functions tests', () => { }); // Testing delete files - describe('Delete', () => { + describe('Delete File', () => { beforeEach(() => { resetStorage(); setDeployContext(user); @@ -193,6 +193,25 @@ describe('website deployer internals functions tests', () => { }); }); + test('Should throw if there are no files to delete', () => { + throws('should throw if there is no file to delete', () => { + deleteFile( + new Args() + .addSerializableObjectArray([ + new ChunkDelete('DeleteFile1'), + ]) + .serialize(), + ); + }); + }); + }); + + describe('Delete Files', () => { + beforeEach(() => { + resetStorage(); + setDeployContext(user); + constructor(new Args().serialize()); + }); test('Should delete 2 files with 1 chunk', () => { const myUpload = given() .withFile(file1Path, 1, [fileData1]) @@ -230,17 +249,5 @@ describe('website deployer internals functions tests', () => { checkThat(myFirstUpload).hasNoFiles(); }); - - test('Should throw if there are no files to delete', () => { - throws('should throw if there is no file to delete', () => { - deleteFile( - new Args() - .addSerializableObjectArray([ - new ChunkDelete('DeleteFile1'), - ]) - .serialize(), - ); - }); - }); }); }); From 309125599073c4aea1c84dad187951e225814f25 Mon Sep 17 00:00:00 2001 From: Phoebe Lartisant Date: Tue, 24 Sep 2024 10:17:14 +0200 Subject: [PATCH 06/15] clean helpers/index.ts --- smart-contract/src/e2e/helpers/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/smart-contract/src/e2e/helpers/index.ts b/smart-contract/src/e2e/helpers/index.ts index 750d2ad..a95f20a 100644 --- a/smart-contract/src/e2e/helpers/index.ts +++ b/smart-contract/src/e2e/helpers/index.ts @@ -125,7 +125,7 @@ export async function assertFileIsDeleted( throw new Error(`File still exists in list: ${filePath}`); } - //TODO: assert that the file is deleted from the chunks + //IMPROVE: assert that the file is deleted from the chunks } console.log(`File successfully deleted ${filePath} from list`); @@ -139,7 +139,4 @@ export async function assertListIsEmpty(contract: SmartContract) { } console.log('All files have been deleted'); - // await assert.rejects(async () => { - // await getFilePathList(contract); - // }, 'should throw if there is no file to delete'); } From 1606aa7b841384d4fa5f499cefd105e8ed4a930a Mon Sep 17 00:00:00 2001 From: Phoebe Lartisant Date: Tue, 24 Sep 2024 14:51:29 +0200 Subject: [PATCH 07/15] edit remove file internal function --- .../assembly/contracts/internals/file-list.ts | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/smart-contract/assembly/contracts/internals/file-list.ts b/smart-contract/assembly/contracts/internals/file-list.ts index ee71978..3f853ae 100644 --- a/smart-contract/assembly/contracts/internals/file-list.ts +++ b/smart-contract/assembly/contracts/internals/file-list.ts @@ -17,7 +17,6 @@ export function _pushFilePath(filePath: string): void { */ export function _removeFilePath(filePath: string): void { const filePathList = FILES_PATH_LIST.mustValue(); - assert(_isPathFileInList(filePath), 'File does not exists'); const newFilePathList: string[] = []; for (let i = 0; i < filePathList.length; i++) { @@ -26,6 +25,11 @@ export function _removeFilePath(filePath: string): void { } } + assert( + newFilePathList.length < filePathList.length, + 'File not found in list', + ); + FILES_PATH_LIST.set(newFilePathList); } @@ -42,16 +46,3 @@ export function _getFilePathList(): string[] { } return result.unwrap(); } - -/** - * Checks if a given file path exists in the list of file paths. - * @param filePath - The file path to check. - * @returns True if the file path is in the list, false otherwise. - */ -export function _isPathFileInList(filePath: string): bool { - const filePathList = _getFilePathList(); - for (let i = 0; i < filePathList.length; i++) { - if (filePathList[i] === filePath) return true; - } - return false; -} From 6df2e8f34683135465b1b2a531b0368856009383 Mon Sep 17 00:00:00 2001 From: BenRey Date: Tue, 24 Sep 2024 14:59:30 +0200 Subject: [PATCH 08/15] clean test e2e --- smart-contract/src/e2e/delete.ts | 1 + smart-contract/src/e2e/helpers/index.ts | 2 -- smart-contract/src/e2e/index.ts | 25 ++++++++++++++++++++++++- smart-contract/src/e2e/store.ts | 23 ++--------------------- 4 files changed, 27 insertions(+), 24 deletions(-) diff --git a/smart-contract/src/e2e/delete.ts b/smart-contract/src/e2e/delete.ts index ba90382..063fd38 100644 --- a/smart-contract/src/e2e/delete.ts +++ b/smart-contract/src/e2e/delete.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import { SmartContract } from '@massalabs/massa-web3'; import { assertFileIsDeleted, diff --git a/smart-contract/src/e2e/helpers/index.ts b/smart-contract/src/e2e/helpers/index.ts index a95f20a..ec49e42 100644 --- a/smart-contract/src/e2e/helpers/index.ts +++ b/smart-contract/src/e2e/helpers/index.ts @@ -12,8 +12,6 @@ import { ChunkDelete, } from './serializable/Chunk'; -import assert from 'assert'; - import { sha256 } from 'js-sha256'; export async function storeChunk(contract: SmartContract, chunks: ChunkPost[]) { diff --git a/smart-contract/src/e2e/index.ts b/smart-contract/src/e2e/index.ts index bd1d036..8cb7489 100644 --- a/smart-contract/src/e2e/index.ts +++ b/smart-contract/src/e2e/index.ts @@ -1,11 +1,34 @@ /* eslint-disable no-console */ +import { + Account, + Mas, + SmartContract, + Web3Provider, +} from '@massalabs/massa-web3'; import { testDeleteFiles } from './delete'; import { testStoreFiles } from './store'; +import { getByteCode } from '../utils'; +import { CONTRACT_FILE } from './helpers/const'; + +async function deploy(provider: Web3Provider): Promise { + const byteCode = getByteCode('build', CONTRACT_FILE); + const contract = await SmartContract.deploy(provider, byteCode, undefined, { + coins: Mas.fromString('50'), + }); + + console.log('Contract deployed at:', contract.address); + + return contract; +} async function main() { console.log('Starting tests...'); + const account = await Account.fromEnv(); + const provider = Web3Provider.buildnet(account); + const contract = await deploy(provider); + console.log('Testing store files...'); - const contract = await testStoreFiles(); + await testStoreFiles(contract); console.log('Testing deletion...'); await testDeleteFiles(contract); console.log('Tests finished'); diff --git a/smart-contract/src/e2e/store.ts b/smart-contract/src/e2e/store.ts index 524d02a..ade0b14 100644 --- a/smart-contract/src/e2e/store.ts +++ b/smart-contract/src/e2e/store.ts @@ -13,23 +13,12 @@ import { assertChunkExists, preStoreChunks, } from './helpers'; -import { CONTRACT_FILE, CSS_FILE, HTML_FILE, JS_FILE } from './helpers/const'; +import { CSS_FILE, HTML_FILE, JS_FILE } from './helpers/const'; import { sha256 } from 'js-sha256'; const CHUNK_LIMIT = 4 * 1024; const projectPath = 'src/e2e/test-project/dist'; -async function deploy(provider: Web3Provider): Promise { - const byteCode = getByteCode('build', CONTRACT_FILE); - const contract = await SmartContract.deploy(provider, byteCode, undefined, { - coins: Mas.fromString('50'), - }); - - console.log('Contract deployed at:', contract.address); - - return contract; -} - export function generateChunk( filePath: string, data: Uint8Array, @@ -38,7 +27,7 @@ export function generateChunk( return new ChunkPost(filePath, id, data); } -async function testStoreChunks(contract: SmartContract) { +export async function testStoreFiles(contract: SmartContract) { const htmlPreStore = new PreStore( HTML_FILE, new Uint8Array(sha256.arrayBuffer(HTML_FILE)), @@ -102,11 +91,3 @@ async function testStoreChunks(contract: SmartContract) { await assertChunkExists(contract, chunkJsPart1); await assertChunkExists(contract, chunkJsPart2); } - -export async function testStoreFiles() { - const account = await Account.fromEnv(); - const provider = Web3Provider.buildnet(account); - const contract = await deploy(provider); - await testStoreChunks(contract); - return contract; -} From 2a3ed7de7066dabb14c0c52beadef4bfc83a20eb Mon Sep 17 00:00:00 2001 From: Phoebe Lartisant Date: Wed, 25 Sep 2024 11:08:23 +0200 Subject: [PATCH 09/15] rework delete functions --- .../assembly/__tests__/FileBuilder.ts | 7 ------ .../__tests__/deweb-interface.spec.ts | 21 +++++----------- .../assembly/contracts/deweb-interface.ts | 25 ++----------------- .../assembly/contracts/internals/chunks.ts | 4 +-- .../assembly/contracts/serializable/Chunk.ts | 6 +---- 5 files changed, 11 insertions(+), 52 deletions(-) diff --git a/smart-contract/assembly/__tests__/FileBuilder.ts b/smart-contract/assembly/__tests__/FileBuilder.ts index 4f29b06..f5aaae4 100644 --- a/smart-contract/assembly/__tests__/FileBuilder.ts +++ b/smart-contract/assembly/__tests__/FileBuilder.ts @@ -11,7 +11,6 @@ import { storeFileChunks, getFilePathList, getChunk, - deleteFile, deleteFiles, } from '../contracts/deweb-interface'; const limitChunk = 10240; @@ -134,12 +133,6 @@ class FileBuilder { return this; } - deleteFile(file: ChunkDelete): void { - deleteFile( - new Args().addSerializableObjectArray([file]).serialize(), - ); - } - deleteFiles(files: ChunkDelete[]): void { deleteFiles( new Args().addSerializableObjectArray(files).serialize(), diff --git a/smart-contract/assembly/__tests__/deweb-interface.spec.ts b/smart-contract/assembly/__tests__/deweb-interface.spec.ts index 75bda53..7188587 100644 --- a/smart-contract/assembly/__tests__/deweb-interface.spec.ts +++ b/smart-contract/assembly/__tests__/deweb-interface.spec.ts @@ -6,19 +6,10 @@ import { import {} from '../contracts/internals/chunks'; import { constructor, - getChunk, - storeFileChunks, - preStoreFileChunks, - deleteFile, deleteFiles, - getFilePathList, + getChunk, } from '../contracts/deweb-interface'; -import { - ChunkDelete, - ChunkGet, - ChunkPost, - PreStore, -} from '../contracts/serializable/Chunk'; +import { ChunkDelete } from '../contracts/serializable/Chunk'; import { Args, stringToBytes } from '@massalabs/as-types'; import { checkThat, chunkGetArgs, given } from './FileBuilder'; @@ -144,7 +135,7 @@ describe('website deployer internals functions tests', () => { sha256(stringToBytes(file1Path)), ); - myUpload.deleteFile(fileToDelete); + myUpload.deleteFiles([fileToDelete]); throws('should throw if file does not exist', () => { getChunk(chunkGetArgs(file1PathHash, 0)); @@ -161,7 +152,7 @@ describe('website deployer internals functions tests', () => { const fileToDelete = new ChunkDelete(file1Path, file1PathHash); - myUpload.deleteFile(fileToDelete); + myUpload.deleteFiles([fileToDelete]); throws('should throw if file does not exist', () => { getChunk(chunkGetArgs(file1PathHash, 0)); @@ -183,7 +174,7 @@ describe('website deployer internals functions tests', () => { const deleteFile1 = new ChunkDelete(file1Path, file1PathHash); - myFirstUpload.deleteFile(deleteFile1); + myFirstUpload.deleteFiles([deleteFile1]); checkThat(myFirstUpload).fileIsDeleted(deleteFile1.filePath); checkThat(mySecondUpload).hasFiles; @@ -195,7 +186,7 @@ describe('website deployer internals functions tests', () => { test('Should throw if there are no files to delete', () => { throws('should throw if there is no file to delete', () => { - deleteFile( + deleteFiles( new Args() .addSerializableObjectArray([ new ChunkDelete('DeleteFile1'), diff --git a/smart-contract/assembly/contracts/deweb-interface.ts b/smart-contract/assembly/contracts/deweb-interface.ts index 97093af..9baf80f 100644 --- a/smart-contract/assembly/contracts/deweb-interface.ts +++ b/smart-contract/assembly/contracts/deweb-interface.ts @@ -21,7 +21,7 @@ import { _removeFile, } from './internals/chunks'; -import { _isPathFileInList, _removeFilePath } from './internals/file-list'; +import { _removeFilePath } from './internals/file-list'; import { FILES_PATH_LIST } from './internals/const'; import { _pushFilePath } from './internals/file-list'; @@ -146,27 +146,6 @@ export function withdraw(binaryArgs: StaticArray): void { transferCoins(Context.caller(), amount); } -/** - * Deletes a file from the contract storage. - * @param _binaryArgs - Serialized arguments containing the ChunkDelete object. - * @param index - (optional) The index of the file to delete of deleting multiple files. - * @throws If the file does not exist or if the caller is not the owner. - */ -export function deleteFile(_binaryArgs: StaticArray): void { - _onlyOwner(); - const args = new Args(_binaryArgs); - - const file = args - .nextSerializableObjectArray() - .expect('Invalid files'); - - assert(_isPathFileInList(file[0].filePath), 'File does not exist'); - - _deleteFile(file[0].filePathHash); - - _removeFilePath(file[0].filePath); -} - /** * Deletes a set of files from the contract storage. Calls deleteFile for each file. * @param _binaryArgs - Serialized arguments containing the ChunkDelete object. @@ -180,7 +159,7 @@ export function deleteFiles(_binaryArgs: StaticArray): void { .expect('Invalid files'); for (let i = 0; i < files.length; i++) { - assert(_isPathFileInList(files[i].filePath), 'File does not exist'); + assert(_getTotalChunk(files[i].filePathHash) > 0, 'File does not exist'); _deleteFile(files[i].filePathHash); diff --git a/smart-contract/assembly/contracts/internals/chunks.ts b/smart-contract/assembly/contracts/internals/chunks.ts index 635f28d..842d0f4 100644 --- a/smart-contract/assembly/contracts/internals/chunks.ts +++ b/smart-contract/assembly/contracts/internals/chunks.ts @@ -1,5 +1,5 @@ import { stringToBytes, bytesToU32, u32ToBytes } from '@massalabs/as-types'; -import { set, sha256, Storage } from '@massalabs/massa-as-sdk'; +import { sha256, Storage } from '@massalabs/massa-as-sdk'; import { CHUNK_NB_TAG, FILE_TAG, CHUNK_TAG } from './const'; import { _removeFilePath } from './file-list'; @@ -116,5 +116,5 @@ export function _deleteFile(filePathHash: StaticArray): void { ); Storage.del(_getChunkKey(filePathHash, i)); } - _setTotalChunk(filePathHash, 0); + Storage.del(_getTotalChunkKey(filePathHash)); } diff --git a/smart-contract/assembly/contracts/serializable/Chunk.ts b/smart-contract/assembly/contracts/serializable/Chunk.ts index 60869e3..e2be085 100644 --- a/smart-contract/assembly/contracts/serializable/Chunk.ts +++ b/smart-contract/assembly/contracts/serializable/Chunk.ts @@ -167,11 +167,7 @@ export class ChunkDelete implements Serializable { * @returns A StaticArray representing the serialized data. */ serialize(): StaticArray { - return new Args() - .add(this.filePath) - .add(this.filePathHash) - - .serialize(); + return new Args().add(this.filePath).add(this.filePathHash).serialize(); } /** From 34ac28e2aedfd720b2645bf41224dad04b6b4e4a Mon Sep 17 00:00:00 2001 From: Phoebe Lartisant Date: Wed, 25 Sep 2024 11:15:04 +0200 Subject: [PATCH 10/15] update e2e --- smart-contract/src/e2e/delete.ts | 9 ++------- smart-contract/src/e2e/helpers/index.ts | 13 ------------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/smart-contract/src/e2e/delete.ts b/smart-contract/src/e2e/delete.ts index 063fd38..a9cd289 100644 --- a/smart-contract/src/e2e/delete.ts +++ b/smart-contract/src/e2e/delete.ts @@ -1,16 +1,11 @@ /* eslint-disable no-console */ import { SmartContract } from '@massalabs/massa-web3'; -import { - assertFileIsDeleted, - assertListIsEmpty, - deleteFile, - deleteFiles, -} from './helpers'; +import { assertFileIsDeleted, assertListIsEmpty, deleteFiles } from './helpers'; import { CSS_FILE, HTML_FILE, JS_FILE } from './helpers/const'; export async function _deleteFile(contract: SmartContract) { console.log('Deleting 1 file'); - await deleteFile(contract, HTML_FILE); + await deleteFiles(contract, [HTML_FILE]); await assertFileIsDeleted(contract, HTML_FILE); } diff --git a/smart-contract/src/e2e/helpers/index.ts b/smart-contract/src/e2e/helpers/index.ts index ec49e42..339efce 100644 --- a/smart-contract/src/e2e/helpers/index.ts +++ b/smart-contract/src/e2e/helpers/index.ts @@ -35,19 +35,6 @@ export async function preStoreChunks( await op.waitSpeculativeExecution(); } -export async function deleteFile(contract: SmartContract, filePath: string) { - const deleteChunk = new ChunkDelete( - filePath, - new Uint8Array(sha256.arrayBuffer(filePath)), - ); - const op = await contract.call( - 'deleteFile', - new Args().addSerializableObjectArray([deleteChunk]).serialize(), - ); - - await op.waitSpeculativeExecution(); -} - export async function deleteFiles( contract: SmartContract, filePathArray: string[], From 92a2e9d0fc27c092c40dd9a09e52c86e296c19f7 Mon Sep 17 00:00:00 2001 From: Phoebe Lartisant Date: Wed, 25 Sep 2024 11:21:13 +0200 Subject: [PATCH 11/15] improve unit test description --- .../assembly/__tests__/deweb-interface.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/smart-contract/assembly/__tests__/deweb-interface.spec.ts b/smart-contract/assembly/__tests__/deweb-interface.spec.ts index 7188587..e67ba5a 100644 --- a/smart-contract/assembly/__tests__/deweb-interface.spec.ts +++ b/smart-contract/assembly/__tests__/deweb-interface.spec.ts @@ -124,7 +124,7 @@ describe('website deployer internals functions tests', () => { constructor(new Args().serialize()); }); - test('Should delete 1 file with 1 chunk', () => { + test('that we can delete 1 file with 1 chunk', () => { const myUpload = given() .withFile(file1Path, 1, [fileData1]) .preStore() @@ -144,7 +144,7 @@ describe('website deployer internals functions tests', () => { checkThat(myUpload).hasNoFiles(); }); - test('Should delete 1 file with 2 chunks', () => { + test('that we can delete 1 file with 2 chunks', () => { const myUpload = given() .withFile(file1Path, 2, [fileData1, fileData2]) .preStore() @@ -161,7 +161,7 @@ describe('website deployer internals functions tests', () => { checkThat(myUpload).hasNoFiles(); }); - test('should delete 1 file in batch', () => { + test('that we can delete 1 file in batch', () => { const myFirstUpload = given() .withFile(file1Path, 2, [fileData1, fileData2]) .preStore() @@ -203,7 +203,7 @@ describe('website deployer internals functions tests', () => { setDeployContext(user); constructor(new Args().serialize()); }); - test('Should delete 2 files with 1 chunk', () => { + test('that we can delete 2 files with 1 chunk', () => { const myUpload = given() .withFile(file1Path, 1, [fileData1]) .withFile(file2Path, 1, [fileData2]) @@ -226,7 +226,7 @@ describe('website deployer internals functions tests', () => { checkThat(myUpload).hasNoFiles(); }); - test('Should delete all given files', () => { + 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]) From 3cf0fbd5efcf8cc41f132f472a82f9bd1e506344 Mon Sep 17 00:00:00 2001 From: Phoebe Lartisant Date: Thu, 26 Sep 2024 09:39:09 +0200 Subject: [PATCH 12/15] add deleteWebsite fn --- .../assembly/__tests__/FileBuilder.ts | 5 ++++ .../__tests__/deweb-interface.spec.ts | 23 +++++++++++++++++++ .../assembly/contracts/deweb-interface.ts | 19 +++++++++++++-- 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/smart-contract/assembly/__tests__/FileBuilder.ts b/smart-contract/assembly/__tests__/FileBuilder.ts index f5aaae4..d9619a8 100644 --- a/smart-contract/assembly/__tests__/FileBuilder.ts +++ b/smart-contract/assembly/__tests__/FileBuilder.ts @@ -12,6 +12,7 @@ import { getFilePathList, getChunk, deleteFiles, + deleteWebsite, } from '../contracts/deweb-interface'; const limitChunk = 10240; @@ -139,6 +140,10 @@ class FileBuilder { ); } + deleteWebsite(): void { + deleteWebsite(new Args().serialize()); + } + hasFiles(): void { const fileList = new Args(getFilePathList()).next().unwrap(); for (let i = 0; i < this.files.length; i++) { diff --git a/smart-contract/assembly/__tests__/deweb-interface.spec.ts b/smart-contract/assembly/__tests__/deweb-interface.spec.ts index e67ba5a..3f2e396 100644 --- a/smart-contract/assembly/__tests__/deweb-interface.spec.ts +++ b/smart-contract/assembly/__tests__/deweb-interface.spec.ts @@ -241,4 +241,27 @@ describe('website deployer internals functions tests', () => { 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/contracts/deweb-interface.ts b/smart-contract/assembly/contracts/deweb-interface.ts index 9baf80f..472067d 100644 --- a/smart-contract/assembly/contracts/deweb-interface.ts +++ b/smart-contract/assembly/contracts/deweb-interface.ts @@ -1,4 +1,10 @@ -import { balance, Context, transferCoins } from '@massalabs/massa-as-sdk'; +import { + balance, + Context, + generateEvent, + sha256, + transferCoins, +} from '@massalabs/massa-as-sdk'; import { _onlyOwner, _setOwner, @@ -10,7 +16,7 @@ import { ChunkDelete, } from './serializable/Chunk'; -import { Args, u32ToBytes } from '@massalabs/as-types'; +import { Args, stringToBytes, u32ToBytes } from '@massalabs/as-types'; import { _getFileChunk, _getTotalChunk, @@ -167,6 +173,15 @@ export function deleteFiles(_binaryArgs: StaticArray): void { } } +export function deleteWebsite(_: 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([]); +} + //TODO: delete all files in project //TODO: delete all metadata From 739d10dcee13297233e9cbfa862b94c7f1d9b1c7 Mon Sep 17 00:00:00 2001 From: Phoebe Lartisant Date: Thu, 26 Sep 2024 11:19:55 +0200 Subject: [PATCH 13/15] add e2e test --- smart-contract/src/e2e/delete.ts | 61 ++++++++++++++++++++++++++++++-- smart-contract/src/e2e/index.ts | 19 +++++++--- 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/smart-contract/src/e2e/delete.ts b/smart-contract/src/e2e/delete.ts index a9cd289..4405d60 100644 --- a/smart-contract/src/e2e/delete.ts +++ b/smart-contract/src/e2e/delete.ts @@ -1,7 +1,23 @@ /* eslint-disable no-console */ -import { SmartContract } from '@massalabs/massa-web3'; -import { assertFileIsDeleted, assertListIsEmpty, deleteFiles } from './helpers'; -import { CSS_FILE, HTML_FILE, JS_FILE } from './helpers/const'; +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'); @@ -19,3 +35,42 @@ 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/index.ts b/smart-contract/src/e2e/index.ts index 8cb7489..ca5dde8 100644 --- a/smart-contract/src/e2e/index.ts +++ b/smart-contract/src/e2e/index.ts @@ -5,7 +5,7 @@ import { SmartContract, Web3Provider, } from '@massalabs/massa-web3'; -import { testDeleteFiles } from './delete'; +import { generateWebsite, testDeleteFiles, testDeleteWebsite } from './delete'; import { testStoreFiles } from './store'; import { getByteCode } from '../utils'; import { CONTRACT_FILE } from './helpers/const'; @@ -21,17 +21,26 @@ async function deploy(provider: Web3Provider): Promise { return contract; } -async function main() { - console.log('Starting tests...'); +async function generateContract(): Promise { + console.log('Starting first tests...'); const account = await Account.fromEnv(); const provider = Web3Provider.buildnet(account); const contract = await deploy(provider); + return contract; +} +async function main() { + const contract = await generateContract(); console.log('Testing store files...'); await testStoreFiles(contract); - console.log('Testing deletion...'); + console.log('Finished test\n'); + console.log('Testing file deletion...'); await testDeleteFiles(contract); - console.log('Tests finished'); + console.log('Finished test\n'); + console.log('Testing website deletion...'); + await generateWebsite(contract); + await testDeleteWebsite(contract); + console.log('Finished test\n'); } main(); From 59a928a74c9439cdab71f8fa6fa0e8101b183118 Mon Sep 17 00:00:00 2001 From: Phoebe Lartisant Date: Thu, 26 Sep 2024 17:28:44 +0200 Subject: [PATCH 14/15] add deleteWebsite cli function --- cli/src/commands/delete.ts | 54 +++++++++++++++---- cli/src/commands/list.ts | 2 +- cli/src/tasks/tasks.ts | 6 +++ .../assembly/contracts/deweb-interface.ts | 3 +- 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/cli/src/commands/delete.ts b/cli/src/commands/delete.ts index c5e7a57..11a548c 100644 --- a/cli/src/commands/delete.ts +++ b/cli/src/commands/delete.ts @@ -2,6 +2,8 @@ import { Command } from '@commander-js/extra-typings' import { SmartContract } from '@massalabs/massa-web3' import { makeProviderFromNodeURLAndSecret } from './utils' +import { Listr, ListrTask } from 'listr2' +import { DeleteCtx } from '../tasks/tasks' export const deleteCommand = new Command('delete') .alias('d') @@ -21,15 +23,49 @@ export const deleteCommand = new Command('delete') // eslint-disable-next-line @typescript-eslint/no-unused-vars const sc = new SmartContract(provider, address) - console.error('deleteWebsite not implemented yet in the SC') + const ctx: DeleteCtx = { + provider: provider, + sc: sc, + address: address, + } - // No deleteWebsite in the SC yet + const tasks = new Listr(deleteWebsiteTask(), { + concurrent: false, + }) - // sc.call('deleteWebsite', new Uint8Array()) - // .then((result) => { - // console.log(result) - // }) - // .catch((error) => { - // console.error(error) - // }) + try { + await tasks.run(ctx) + } catch (error) { + console.error('Error during the process:', error) + process.exit(1) + } }) + +export function deleteWebsiteTask(): ListrTask { + return { + title: 'Deleting website', + task: async (ctx, task) => { + // No deleteWebsite in the SC yet + await deleteWebsite(ctx) + }, + rendererOptions: { + outputBar: Infinity, + persistentOutput: true, + collapseSubtasks: false, + }, + } +} + +async function deleteWebsite(ctx: DeleteCtx) { + if (!ctx.sc) { + throw new Error('Smart contract is not deployed yet') + } + ctx.sc + .call('deleteWebsite', new Uint8Array()) + .then(() => { + console.log(`Successfully deleted the website at ${ctx.address}`) + }) + .catch((error: any) => { + console.error(error) + }) +} diff --git a/cli/src/commands/list.ts b/cli/src/commands/list.ts index 7989632..f8073f9 100644 --- a/cli/src/commands/list.ts +++ b/cli/src/commands/list.ts @@ -6,7 +6,7 @@ import { listFiles } from '../lib/website/read' export const listFilesCommand = new Command('list') .alias('ls') .description('Lists files from the given website on Massa blockchain') - .option('-a, --address
', 'Address of the website to edit') + .option('-a, --address
', 'Address of the website to list') .action(async (options, command) => { const globalOptions = command.parent?.opts() diff --git a/cli/src/tasks/tasks.ts b/cli/src/tasks/tasks.ts index 33da09d..265f227 100644 --- a/cli/src/tasks/tasks.ts +++ b/cli/src/tasks/tasks.ts @@ -18,3 +18,9 @@ export interface UploadCtx { chunkSize: number minimalFees: bigint } + +export interface DeleteCtx { + provider: Web3Provider + sc?: SmartContract + address: string +} diff --git a/smart-contract/assembly/contracts/deweb-interface.ts b/smart-contract/assembly/contracts/deweb-interface.ts index 472067d..2f3e38e 100644 --- a/smart-contract/assembly/contracts/deweb-interface.ts +++ b/smart-contract/assembly/contracts/deweb-interface.ts @@ -173,6 +173,7 @@ export function deleteFiles(_binaryArgs: StaticArray): void { } } +//TODO: verify that this function throws is mustValue is empty export function deleteWebsite(_: StaticArray): void { _onlyOwner(); const filePaths = FILES_PATH_LIST.mustValue(); @@ -182,8 +183,6 @@ export function deleteWebsite(_: StaticArray): void { FILES_PATH_LIST.set([]); } -//TODO: delete all files in project - //TODO: delete all metadata //TODO: delete SC From 82b26379356caf19c452d8f2797a6f12ddad59e3 Mon Sep 17 00:00:00 2001 From: Phoebe Lartisant Date: Thu, 26 Sep 2024 17:31:26 +0200 Subject: [PATCH 15/15] fmt --- cli/README.md | 5 +++++ cli/src/commands/delete.ts | 6 +++--- smart-contract/assembly/contracts/deweb-interface.ts | 7 +++---- smart-contract/src/e2e/helpers/index.ts | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 cli/README.md diff --git a/cli/README.md b/cli/README.md new file mode 100644 index 0000000..a51578b --- /dev/null +++ b/cli/README.md @@ -0,0 +1,5 @@ +Upload commands +`npm run build` +`SECRET_KEY=S...y node bin/index.js upload ./dist` + +`npm run dev -- upload -w ./wallet_faker1.yaml -p faker1 ./dist` diff --git a/cli/src/commands/delete.ts b/cli/src/commands/delete.ts index 11a548c..920e074 100644 --- a/cli/src/commands/delete.ts +++ b/cli/src/commands/delete.ts @@ -5,6 +5,7 @@ import { makeProviderFromNodeURLAndSecret } from './utils' import { Listr, ListrTask } from 'listr2' import { DeleteCtx } from '../tasks/tasks' +// Minimal implementation of delete command export const deleteCommand = new Command('delete') .alias('d') .description('Delete the given website from Massa blockchain') @@ -20,7 +21,6 @@ export const deleteCommand = new Command('delete') const provider = await makeProviderFromNodeURLAndSecret(globalOptions) - // eslint-disable-next-line @typescript-eslint/no-unused-vars const sc = new SmartContract(provider, address) const ctx: DeleteCtx = { @@ -44,7 +44,7 @@ export const deleteCommand = new Command('delete') export function deleteWebsiteTask(): ListrTask { return { title: 'Deleting website', - task: async (ctx, task) => { + task: async (ctx) => { // No deleteWebsite in the SC yet await deleteWebsite(ctx) }, @@ -65,7 +65,7 @@ async function deleteWebsite(ctx: DeleteCtx) { .then(() => { console.log(`Successfully deleted the website at ${ctx.address}`) }) - .catch((error: any) => { + .catch((error) => { console.error(error) }) } diff --git a/smart-contract/assembly/contracts/deweb-interface.ts b/smart-contract/assembly/contracts/deweb-interface.ts index 2f3e38e..d984363 100644 --- a/smart-contract/assembly/contracts/deweb-interface.ts +++ b/smart-contract/assembly/contracts/deweb-interface.ts @@ -1,7 +1,6 @@ import { balance, Context, - generateEvent, sha256, transferCoins, } from '@massalabs/massa-as-sdk'; @@ -173,7 +172,7 @@ export function deleteFiles(_binaryArgs: StaticArray): void { } } -//TODO: verify that this function throws is mustValue is empty +// TODO: verify that this function throws is mustValue is empty export function deleteWebsite(_: StaticArray): void { _onlyOwner(); const filePaths = FILES_PATH_LIST.mustValue(); @@ -183,6 +182,6 @@ export function deleteWebsite(_: StaticArray): void { FILES_PATH_LIST.set([]); } -//TODO: delete all metadata +// TODO: delete all metadata -//TODO: delete SC +// TODO: delete SC diff --git a/smart-contract/src/e2e/helpers/index.ts b/smart-contract/src/e2e/helpers/index.ts index 339efce..3f0b112 100644 --- a/smart-contract/src/e2e/helpers/index.ts +++ b/smart-contract/src/e2e/helpers/index.ts @@ -110,7 +110,7 @@ export async function assertFileIsDeleted( throw new Error(`File still exists in list: ${filePath}`); } - //IMPROVE: assert that the file is deleted from the chunks + // IMPROVE: assert that the file is deleted from the chunks } console.log(`File successfully deleted ${filePath} from list`);