diff --git a/src/commands/contract/compile.ts b/src/commands/contract/compile.ts index e56f1638..56129c01 100644 --- a/src/commands/contract/compile.ts +++ b/src/commands/contract/compile.ts @@ -1,9 +1,9 @@ import { Args, Flags } from "@oclif/core"; import path from "node:path"; +import { ensureCargoContractVersionCompatibility, extractCargoContractVersion, generateTypes, Spinner, storeArtifacts } from "../../lib/index.js"; import { spawn } from "node:child_process"; import { pathExists } from "fs-extra/esm"; import { SwankyCommand } from "../../lib/swankyCommand.js"; -import { ensureCargoContractVersionCompatibility, extractCargoContractVersion, Spinner, storeArtifacts } from "../../lib/index.js"; import { ConfigError, InputError, ProcessError } from "../../lib/errors.js"; export class CompileContract extends SwankyCommand { @@ -126,6 +126,12 @@ export class CompileContract extends SwankyCommand { return storeArtifacts(artifactsPath, contractInfo.name, contractInfo.moduleName); }, "Moving artifacts"); + await spinner.runCommand( + async () => await generateTypes(contractInfo.name), + `Generating ${contractName} contract ts types`, + `${contractName} contract's TS types Generated successfully`, + ); + this.swankyConfig.contracts[contractName].build = { timestamp: Date.now(), artifactsPath, diff --git a/src/commands/contract/new.ts b/src/commands/contract/new.ts index c671886f..0936663f 100644 --- a/src/commands/contract/new.ts +++ b/src/commands/contract/new.ts @@ -1,12 +1,11 @@ import { Args, Flags } from "@oclif/core"; import path from "node:path"; -import { ensureDir, pathExists, pathExistsSync, writeJSON } from "fs-extra/esm"; +import { ensureDir, pathExists, writeJSON } from "fs-extra/esm"; import { checkCliDependencies, copyContractTemplateFiles, processTemplates, getTemplates, - prepareTestFiles, } from "../../lib/index.js"; import { email, name, pickTemplate } from "../../lib/prompts.js"; import { paramCase, pascalCase, snakeCase } from "change-case"; @@ -79,18 +78,6 @@ export class NewContract extends SwankyCommand { "Copying contract template files" ); - if (contractTemplate === "psp22") { - const e2eTestHelpersPath = path.resolve(projectPath, "tests", "test_helpers"); - if (!pathExistsSync(e2eTestHelpersPath)) { - await this.spinner.runCommand( - () => prepareTestFiles("e2e", path.resolve(templates.templatesPath), projectPath), - "Copying e2e test helpers" - ); - } else { - console.log("e2e test helpers already exist. No files were copied."); - } - } - await this.spinner.runCommand( () => processTemplates(projectPath, { @@ -106,6 +93,7 @@ export class NewContract extends SwankyCommand { ); await ensureDir(path.resolve(projectPath, "artifacts", args.contractName)); + await ensureDir(path.resolve(projectPath, "tests", args.contractName)); await this.spinner.runCommand(async () => { this.swankyConfig.contracts[args.contractName] = { diff --git a/src/commands/contract/test.ts b/src/commands/contract/test.ts index 3ef72bfe..216eb6b0 100644 --- a/src/commands/contract/test.ts +++ b/src/commands/contract/test.ts @@ -3,13 +3,11 @@ import { Flags, Args } from "@oclif/core"; import path from "node:path"; import { globby } from "globby"; import Mocha from "mocha"; -import { emptyDir, pathExistsSync } from "fs-extra/esm"; +import { emptyDir } from "fs-extra/esm"; import shell from "shelljs"; import { Contract } from "../../lib/contract.js"; import { SwankyCommand } from "../../lib/swankyCommand.js"; -import { ConfigError, FileError, InputError, ProcessError, TestError } from "../../lib/errors.js"; -import { spawn } from "node:child_process"; -import { Spinner } from "../../lib/index.js"; +import { ConfigError, FileError, InputError, TestError } from "../../lib/errors.js"; declare global { var contractTypesPath: string; // eslint-disable-line no-var @@ -22,11 +20,7 @@ export class TestContract extends SwankyCommand { all: Flags.boolean({ default: false, char: "a", - description: "Run tests for all contracts", - }), - mocha: Flags.boolean({ - default: false, - description: "Run tests with mocha", + description: "Set all to true to compile all contracts", }), }; @@ -49,7 +43,7 @@ export class TestContract extends SwankyCommand { ? Object.keys(this.swankyConfig.contracts) : [args.contractName]; - const spinner = new Spinner(); + const testDir = path.resolve("tests"); for (const contractName of contractNames) { const contractRecord = this.swankyConfig.contracts[contractName]; @@ -67,119 +61,54 @@ export class TestContract extends SwankyCommand { ); } - console.log(`Testing contract: ${contractName}`); + const artifactsCheck = await contract.artifactsExist(); - if (!flags.mocha) { - await spinner.runCommand( - async () => { - return new Promise((resolve, reject) => { - const compileArgs = [ - "test", - "--features", - "e2e-tests", - "--manifest-path", - `contracts/${contractName}/Cargo.toml`, - "--release" - ]; - - const compile = spawn("cargo", compileArgs); - this.logger.info(`Running e2e-tests command: [${JSON.stringify(compile.spawnargs)}]`); - let outputBuffer = ""; - let errorBuffer = ""; - - compile.stdout.on("data", (data) => { - outputBuffer += data.toString(); - spinner.ora.clear(); - }); - compile.stdout.pipe(process.stdout); - - compile.stderr.on("data", (data) => { - errorBuffer += data; - }); - - compile.on("exit", (code) => { - if (code === 0) { - const regex = /test result: (.*)/; - const match = outputBuffer.match(regex); - if (match) { - this.logger.info(`Contract ${contractName} e2e-testing done.`); - resolve(match[1]); - } - } else { - reject(new ProcessError(errorBuffer)); - } - }); - }); - }, - `Testing ${contractName} contract`, - `${contractName} testing finished successfully` + if (!artifactsCheck.result) { + throw new FileError( + `No artifact file found at path: ${artifactsCheck.missingPaths.toString()}` ); - } else { - - const testDir = path.resolve("tests"); - - if (!pathExistsSync(testDir)) { - throw new FileError(`Tests folder does not exist: ${testDir}`); - } - - const artifactsCheck = await contract.artifactsExist(); - - if (!artifactsCheck.result) { - throw new FileError( - `No artifact file found at path: ${artifactsCheck.missingPaths.toString()}` - ); - } - - const artifactPath = path.resolve("typedContracts", `${contractName}`); - const typedContractCheck = await contract.typedContractExists(contractName); - - this.log(`artifactPath: ${artifactPath}`); - - if (!typedContractCheck.result) { - throw new FileError( - `No typed contract found at path: ${typedContractCheck.missingPaths.toString()}` - ); - } - - const reportDir = path.resolve(testDir, contract.name, "testReports"); - - await emptyDir(reportDir); - - const mocha = new Mocha({ - timeout: 200000, - reporter: "mochawesome", - reporterOptions: { - reportDir, - charts: true, - reportTitle: `${contractName} test report`, - quiet: true, - json: false, - }, - }); - - const tests = await globby(`${path.resolve(testDir, contractName)}/*.test.ts`); + } - tests.forEach((test) => { - mocha.addFile(test); - }); + console.log(`Testing contract: ${contractName}`); - global.contractTypesPath = path.resolve(testDir, contractName, "typedContract"); - - shell.cd(`${testDir}/${contractName}`); - try { - await new Promise((resolve, reject) => { - mocha.run((failures) => { - if (failures) { - reject(`At least one of the tests failed. Check report for details: ${reportDir}`); - } else { - this.log(`All tests passing. Check the report for details: ${reportDir}`); - resolve(); - } - }); + const reportDir = path.resolve(testDir, contract.name, "testReports"); + + await emptyDir(reportDir); + + const mocha = new Mocha({ + timeout: 200000, + reporter: "mochawesome", + reporterOptions: { + reportDir, + charts: true, + reportTitle: `${contractName} test report`, + quiet: true, + json: false, + }, + }); + + const tests = await globby(`${path.resolve(testDir, contractName)}/*.test.ts`); + + tests.forEach((test) => { + mocha.addFile(test); + }); + + global.contractTypesPath = path.resolve(testDir, contractName, "typedContract"); + + shell.cd(`${testDir}/${contractName}`); + try { + await new Promise((resolve, reject) => { + mocha.run((failures) => { + if (failures) { + reject(`At least one of the tests failed. Check report for details: ${reportDir}`); + } else { + this.log(`All tests passing. Check the report for details: ${reportDir}`); + resolve(); + } }); - } catch (cause) { - throw new TestError("Error in test", { cause }); - } + }); + } catch (cause) { + throw new TestError("Error in test", { cause }); } } } diff --git a/src/commands/generate/types.ts b/src/commands/contract/typegen.ts similarity index 91% rename from src/commands/generate/types.ts rename to src/commands/contract/typegen.ts index 79cf6134..391e0a3c 100644 --- a/src/commands/generate/types.ts +++ b/src/commands/contract/typegen.ts @@ -4,7 +4,7 @@ import { Contract } from "../../lib/contract.js"; import { SwankyCommand } from "../../lib/swankyCommand.js"; import { ConfigError, FileError } from "../../lib/errors.js"; -export class GenerateTypes extends SwankyCommand { +export class TypegenCommand extends SwankyCommand { static description = "Generate types from compiled contract metadata"; static args = { @@ -16,7 +16,7 @@ export class GenerateTypes extends SwankyCommand { }; async run(): Promise { - const { args } = await this.parse(GenerateTypes); + const { args } = await this.parse(TypegenCommand); const contractRecord = this.swankyConfig.contracts[args.contractName]; if (!contractRecord) { diff --git a/src/commands/generate/tests.ts b/src/commands/generate/tests.ts deleted file mode 100644 index 09af4ead..00000000 --- a/src/commands/generate/tests.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { Args, Flags } from "@oclif/core"; -import { getTemplates, prepareTestFiles, processTemplates } from "../../lib/index.js"; -import { Contract } from "../../lib/contract.js"; -import { SwankyCommand } from "../../lib/swankyCommand.js"; -import { ConfigError, FileError, InputError } from "../../lib/errors.js"; -import path from "node:path"; -import { existsSync } from "node:fs"; -import inquirer from "inquirer"; -import { paramCase, pascalCase } from "change-case"; -import { TestType } from "../../index.js"; - -export class GenerateTests extends SwankyCommand { - static description = "Generate test files for the specified contract"; - - static args = { - contractName: Args.string({ - name: "contractName", - required: false, - description: "Name of the contract", - }), - }; - - static flags = { - template: Flags.string({ - options: getTemplates().contractTemplatesList, - }), - mocha: Flags.boolean({ - default: false, - description: "Generate mocha test files", - }), - }; - - async run(): Promise { - const { args, flags } = await this.parse(GenerateTests); - - if (flags.mocha) { - if (!args.contractName) { - throw new InputError("The 'contractName' argument is required to generate mocha tests."); - } - - await this.checkContract(args.contractName) - } - - const testType: TestType = flags.mocha ? "mocha" : "e2e"; - const testsFolderPath = path.resolve("tests"); - const testPath = this.getTestPath(testType, testsFolderPath, args.contractName); - - const templates = getTemplates(); - const templateName = await this.resolveTemplateName(flags, templates.contractTemplatesList); - - const overwrite = await this.checkOverwrite(testPath, testType, args.contractName); - if (!overwrite) return; - - await this.generateTests( - testType, - templates.templatesPath, - process.cwd(), - args.contractName, - templateName - ); - } - - async checkContract(name: string) { - const contractRecord = this.swankyConfig.contracts[name]; - if (!contractRecord) { - throw new ConfigError( - `Cannot find a contract named ${name} in swanky.config.json` - ); - } - - const contract = new Contract(contractRecord); - if (!(await contract.pathExists())) { - throw new FileError( - `Path to contract ${name} does not exist: ${contract.contractPath}` - ); - } - - const artifactsCheck = await contract.artifactsExist(); - if (!artifactsCheck.result) { - throw new FileError( - `No artifact file found at path: ${artifactsCheck.missingPaths.toString()}` - ); - } - } - - async checkOverwrite( - testPath: string, - testType: TestType, - contractName?: string - ): Promise { - if (!existsSync(testPath)) return true; // No need to overwrite - const message = - testType === "e2e" - ? "Test helpers already exist. Overwrite?" - : `Mocha tests for ${contractName} already exist. Overwrite?`; - - const { overwrite } = await inquirer.prompt({ - type: "confirm", - name: "overwrite", - message, - default: false, - }); - - return overwrite; - } - - getTestPath(testType: TestType, testsPath: string, contractName?: string): string { - if (testType === "e2e") { - return path.resolve(testsPath, "test_helpers"); - } else if (testType === "mocha" && contractName) { - return path.resolve(testsPath, contractName, "index.test.ts"); - } else { - throw new InputError("The 'contractName' argument is required to generate mocha tests."); - } - } - - async resolveTemplateName(flags: any, templates: any): Promise { - if (flags.mocha && !flags.template) { - if (!templates?.length) throw new ConfigError("Template list is empty!"); - const response = await inquirer.prompt([ - { - type: "list", - name: "template", - message: "Choose a contract template:", - choices: templates, - }, - ]); - return response.template; - } - return flags.template; - } - - async generateTests( - testType: TestType, - templatesPath: string, - projectPath: string, - contractName?: string, - templateName?: string - ): Promise { - if (testType === "e2e") { - await this.spinner.runCommand( - () => prepareTestFiles("e2e", templatesPath, projectPath), - "Generating e2e test helpers" - ); - } else { - await this.spinner.runCommand( - () => prepareTestFiles("mocha", templatesPath, projectPath, templateName, contractName), - `Generating tests for ${contractName} with mocha` - ); - } - await this.spinner.runCommand( - () => - processTemplates(projectPath, { - project_name: paramCase(this.config.pjson.name), - swanky_version: this.config.pjson.version, - contract_name: contractName ?? "", - contract_name_pascal: contractName ? pascalCase(contractName) : "", - }), - "Processing templates" - ); - } -} diff --git a/src/commands/init/index.ts b/src/commands/init/index.ts index 226cdacf..21712eba 100644 --- a/src/commands/init/index.ts +++ b/src/commands/init/index.ts @@ -1,23 +1,21 @@ import { Args, Flags } from "@oclif/core"; import path from "node:path"; -import { copy, ensureDir, outputFile, pathExists, readJSON, remove, writeJSON } from "fs-extra/esm"; -import { readdir, readFile, stat } from "fs/promises"; +import { ensureDir, writeJSON, pathExists, copy, outputFile, readJSON, remove } from "fs-extra/esm"; +import { stat, readdir, readFile } from "fs/promises"; import { execaCommand, execaCommandSync } from "execa"; import { paramCase, pascalCase, snakeCase } from "change-case"; import inquirer from "inquirer"; import TOML from "@iarna/toml"; import { choice, email, name, pickNodeVersion, pickTemplate } from "../../lib/prompts.js"; import { - ChainAccount, checkCliDependencies, copyCommonTemplateFiles, copyContractTemplateFiles, downloadNode, - getTemplates, installDeps, - prepareTestFiles, + ChainAccount, processTemplates, - swankyNodeVersions + getTemplates, swankyNodeVersions, } from "../../lib/index.js"; import { ALICE_URI, BOB_URI, @@ -28,7 +26,7 @@ import { } from "../../lib/consts.js"; import { SwankyCommand } from "../../lib/swankyCommand.js"; import { InputError, UnknownError } from "../../lib/errors.js"; -import { globby, GlobEntry } from "globby"; +import { GlobEntry, globby } from "globby"; import { merge } from "lodash-es"; import inquirerFuzzyPath from "inquirer-fuzzy-path"; import { SwankyConfig } from "../../types/index.js"; @@ -93,7 +91,6 @@ export class Init extends SwankyCommand { super(argv, config); (this.constructor as typeof SwankyCommand).ENSURE_SWANKY_CONFIG = false; } - projectPath = ""; @@ -209,6 +206,7 @@ export class Init extends SwankyCommand { Object.keys(this.configBuilder.contracts!).forEach(async (contractName) => { await ensureDir(path.resolve(this.projectPath, "artifacts", contractName)); + await ensureDir(path.resolve(this.projectPath, "tests", contractName)); }); this.taskQueue.push({ @@ -234,7 +232,7 @@ export class Init extends SwankyCommand { runningMessage, successMessage, failMessage, - shouldExitOnError, + shouldExitOnError ); if (result && callback) { callback(result as string); @@ -289,14 +287,6 @@ export class Init extends SwankyCommand { runningMessage: "Copying contract template files", }); - if (contractTemplate === "psp22") { - this.taskQueue.push({ - task: prepareTestFiles, - args: ["e2e", path.resolve(templates.templatesPath), this.projectPath], - runningMessage: "Copying test helpers", - }); - } - this.taskQueue.push({ task: processTemplates, args: [ @@ -334,7 +324,7 @@ export class Init extends SwankyCommand { } catch (cause) { throw new InputError( `Error reading target directory [${chalk.yellowBright(pathToExistingProject)}]`, - { cause }, + { cause } ); } @@ -354,7 +344,7 @@ export class Init extends SwankyCommand { const candidatesList: CopyCandidates = await getCopyCandidatesList( pathToExistingProject, - copyGlobsList, + copyGlobsList ); const testDir = await detectTests(pathToExistingProject); @@ -522,10 +512,10 @@ async function confirmCopyList(candidatesList: CopyCandidates) { ( item: PathEntry & { group: "contracts" | "crates" | "tests"; - }, + } ) => { resultingList[item.group]?.push(item); - }, + } ); return resultingList; } @@ -552,7 +542,7 @@ async function detectTests(pathToExistingProject: string): Promise { const { selectedDirectory } = await inquirer.prompt([ { @@ -623,7 +613,7 @@ async function getCopyCandidatesList( pathsToCopy: { contractsDirectories: string[]; cratesDirectories: string[]; - }, + } ) { const detectedPaths = { contracts: await getDirsAndFiles(projectPath, pathsToCopy.contractsDirectories), @@ -644,7 +634,7 @@ async function getGlobPaths(projectPath: string, globList: string[], isDirOnly: onlyDirectories: isDirOnly, deep: 1, objectMode: true, - }, + } ); } diff --git a/src/lib/contract.ts b/src/lib/contract.ts index fb102450..7d9d85ab 100644 --- a/src/lib/contract.ts +++ b/src/lib/contract.ts @@ -41,19 +41,6 @@ export class Contract { return result; } - async typedContractExists(contractName: string) { - const result: { result: boolean; missingPaths: string[] } = { - result: true, - missingPaths: [], - }; - const artifactPath = path.resolve("typedContracts", `${contractName}`); - if(!(await pathExists(artifactPath))) { - result.result = false; - result.missingPaths.push(artifactPath); - } - return result; - } - async getABI(): Promise { const check = await this.artifactsExist(); if (!check.result && check.missingTypes.includes(".json")) { diff --git a/src/lib/tasks.ts b/src/lib/tasks.ts index b3eb3a1f..2705ac42 100644 --- a/src/lib/tasks.ts +++ b/src/lib/tasks.ts @@ -6,12 +6,12 @@ import { globby } from "globby"; import handlebars from "handlebars"; import { DownloadEndedStats, DownloaderHelper } from "node-downloader-helper"; import process from "node:process"; -import semver from "semver"; import { nodeInfo } from "./nodeInfo.js"; import decompress from "decompress"; import { Spinner } from "./spinner.js"; -import { SupportedPlatforms, SupportedArch, TestType } from "../types/index.js"; +import { SupportedPlatforms, SupportedArch } from "../types/index.js"; import { ConfigError, NetworkError, ProcessError } from "./errors.js"; +import semver from "semver"; import { commandStdoutOrNull } from "./command-utils.js"; export async function checkCliDependencies(spinner: Spinner) { @@ -55,41 +55,10 @@ export async function copyContractTemplateFiles( path.resolve(contractTemplatePath, "contract"), path.resolve(projectPath, "contracts", contractName) ); -} - -export async function prepareTestFiles( - testType: TestType, - templatePath: string, - projectPath: string, - templateName?: string, - contractName?: string -) { - switch (testType) { - case "e2e": { - await copy( - path.resolve(templatePath, "test_helpers"), - path.resolve(projectPath, "tests", "test_helpers") - ); - break; - } - case "mocha": { - if (!templateName) { - throw new ProcessError("'templateName' argument is required for mocha tests"); - } - if (!contractName) { - throw new ProcessError("'contractName' argument is required for mocha tests"); - } - await copy( - path.resolve(templatePath, "contracts", templateName, "test"), - path.resolve(projectPath, "tests", contractName) - ); - break; - } - default: { - // This case will make the switch exhaustive - throw new ProcessError("Unhandled test type"); - } - } + await copy( + path.resolve(contractTemplatePath, "test"), + path.resolve(projectPath, "tests", contractName) + ); } export async function processTemplates(projectPath: string, templateData: Record) { diff --git a/src/templates/contracts/flipper/contract/Cargo.toml.hbs b/src/templates/contracts/flipper/contract/Cargo.toml.hbs index 2de5449d..de90dba7 100644 --- a/src/templates/contracts/flipper/contract/Cargo.toml.hbs +++ b/src/templates/contracts/flipper/contract/Cargo.toml.hbs @@ -10,9 +10,6 @@ ink = { version = "4.2.1", default-features = false } scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true } -[dev-dependencies] -ink_e2e = "4.2.1" - [lib] name = "{{contract_name_snake}}" path = "src/lib.rs" @@ -25,4 +22,3 @@ std = [ "scale-info/std", ] ink-as-dependency = [] -e2e-tests = [] diff --git a/src/templates/contracts/flipper/contract/src/lib.rs.hbs b/src/templates/contracts/flipper/contract/src/lib.rs.hbs index 0681c362..a5137e64 100644 --- a/src/templates/contracts/flipper/contract/src/lib.rs.hbs +++ b/src/templates/contracts/flipper/contract/src/lib.rs.hbs @@ -57,90 +57,17 @@ mod {{contract_name_snake}} { /// We test if the default constructor does its job. #[ink::test] fn default_works() { - let flipper = {{contract_name_pascal}}::default(); - assert_eq!(flipper.get(), false); + let {{contract_name_snake}} = {{contract_name_pascal}}::default(); + assert_eq!({{contract_name_snake}}.get(), false); } /// We test a simple use case of our contract. #[ink::test] fn it_works() { - let mut flipper = {{contract_name_pascal}}::new(false); - assert_eq!(flipper.get(), false); - flipper.flip(); - assert_eq!(flipper.get(), true); - } - } - - - /// This is how you'd write end-to-end (E2E) or integration tests for ink! contracts. - /// - /// When running these you need to make sure that you: - /// - Compile the tests with the `e2e-tests` feature flag enabled (`--features e2e-tests`) - /// - Are running a Substrate node which contains `pallet-contracts` in the background - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - /// Imports all the definitions from the outer scope so we can use them here. - use super::*; - - /// A helper function used for calling contract messages. - use ink_e2e::build_message; - - /// The End-to-End test `Result` type. - type E2EResult = std::result::Result>; - - /// We test that we can upload and instantiate the contract using its default constructor. - #[ink_e2e::test] - async fn default_works(mut client: ink_e2e::Client) -> E2EResult<()> { - // Given - let constructor = {{contract_name_pascal}}Ref::default(); - - // When - let contract_account_id = client - .instantiate("{{contract_name_snake}}", &ink_e2e::alice(), constructor, 0, None) - .await - .expect("instantiate failed") - .account_id; - - // Then - let get = build_message::<{{contract_name_pascal}}Ref>(contract_account_id.clone()) - .call(|flipper| flipper.get()); - let get_result = client.call_dry_run(&ink_e2e::alice(), &get, 0, None).await; - assert!(matches!(get_result.return_value(), false)); - - Ok(()) - } - - /// We test that we can read and write a value from the on-chain contract contract. - #[ink_e2e::test] - async fn it_works(mut client: ink_e2e::Client) -> E2EResult<()> { - // Given - let constructor = {{contract_name_pascal}}Ref::new(false); - let contract_account_id = client - .instantiate("{{contract_name_snake}}", &ink_e2e::bob(), constructor, 0, None) - .await - .expect("instantiate failed") - .account_id; - - let get = build_message::<{{contract_name_pascal}}Ref>(contract_account_id.clone()) - .call(|flipper| flipper.get()); - let get_result = client.call_dry_run(&ink_e2e::bob(), &get, 0, None).await; - assert!(matches!(get_result.return_value(), false)); - - // When - let flip = build_message::<{{contract_name_pascal}}Ref>(contract_account_id.clone()) - .call(|flipper| flipper.flip()); - let _flip_result = client - .call(&ink_e2e::bob(), flip, 0, None) - .await - .expect("flip failed"); - - // Then - let get = build_message::<{{contract_name_pascal}}Ref>(contract_account_id.clone()) - .call(|flipper| flipper.get()); - let get_result = client.call_dry_run(&ink_e2e::bob(), &get, 0, None).await; - assert!(matches!(get_result.return_value(), true)); - - Ok(()) + let mut {{contract_name_snake}} = {{contract_name_pascal}}::new(false); + assert_eq!({{contract_name_snake}}.get(), false); + {{contract_name_snake}}.flip(); + assert_eq!({{contract_name_snake}}.get(), true); } } } diff --git a/src/templates/contracts/psp22/contract/Cargo.toml.hbs b/src/templates/contracts/psp22/contract/Cargo.toml.hbs index 0cf1e1d6..a2c880f3 100644 --- a/src/templates/contracts/psp22/contract/Cargo.toml.hbs +++ b/src/templates/contracts/psp22/contract/Cargo.toml.hbs @@ -10,11 +10,7 @@ ink = { version = "4.2.1", default-features = false} scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true } -openbrush = { git = "https://github.com/Brushfam/openbrush-contracts", tag = "4.0.0", default-features = false, features = ["psp22"] } - -[dev-dependencies] -ink_e2e = "4.2.1" -test_helpers = { path = "../../tests/test_helpers", default-features = false } +openbrush = { git = "https://github.com/Brushfam/openbrush-contracts", tag = "4.0.0-beta", default-features = false, features = ["psp22"] } [lib] name = "{{contract_name_snake}}" @@ -29,7 +25,6 @@ std = [ "openbrush/std", ] ink-as-dependency = [] -e2e-tests = [] [profile.dev] codegen-units = 16 diff --git a/src/templates/contracts/psp22/contract/src/lib.rs.hbs b/src/templates/contracts/psp22/contract/src/lib.rs.hbs index 88b50918..4b85fb96 100644 --- a/src/templates/contracts/psp22/contract/src/lib.rs.hbs +++ b/src/templates/contracts/psp22/contract/src/lib.rs.hbs @@ -69,89 +69,4 @@ pub mod {{contract_name_snake}} { PSP22::total_supply(self) } } - - #[cfg(all(test, feature = "e2e-tests"))] - pub mod tests { - use super::*; - use ink_e2e::{ - build_message, - }; - use openbrush::contracts::psp22::psp22_external::PSP22; - use test_helpers::{ - address_of, - balance_of, - }; - - type E2EResult = Result>; - - #[ink_e2e::test] - async fn assigns_initial_balance(mut client: ink_e2e::Client) -> E2EResult<()> { - let constructor = {{contract_name_pascal}}Ref::new(100); - let address = client - .instantiate("{{contract_name_snake}}", &ink_e2e::alice(), constructor, 0, None) - .await - .expect("instantiate failed") - .account_id; - - let result = { - let _msg = build_message::<{{contract_name_pascal}}Ref>(address.clone()) - .call(|contract| contract.balance_of(address_of!(Alice))); - client.call_dry_run(&ink_e2e::alice(), &_msg, 0, None).await - }; - - assert!(matches!(result.return_value(), 100)); - - Ok(()) - } - - #[ink_e2e::test] - async fn transfer_adds_amount_to_destination_account(mut client: ink_e2e::Client) -> E2EResult<()> { - let constructor = {{contract_name_pascal}}Ref::new(100); - let address = client - .instantiate("{{contract_name_snake}}", &ink_e2e::alice(), constructor, 0, None) - .await - .expect("instantiate failed") - .account_id; - - let result = { - let _msg = build_message::<{{contract_name_pascal}}Ref>(address.clone()) - .call(|contract| contract.transfer(address_of!(Bob), 50, vec![])); - client - .call(&ink_e2e::alice(), _msg, 0, None) - .await - .expect("transfer failed") - }; - - assert!(matches!(result.return_value(), Ok(()))); - - let balance_of_alice = balance_of!({{contract_name_pascal}}Ref, client, address, Alice); - - let balance_of_bob = balance_of!({{contract_name_pascal}}Ref, client, address, Bob); - - assert_eq!(balance_of_bob, 50, "Bob should have 50 tokens"); - assert_eq!(balance_of_alice, 50, "Alice should have 50 tokens"); - - Ok(()) - } - - #[ink_e2e::test] - async fn cannot_transfer_above_the_amount(mut client: ink_e2e::Client) -> E2EResult<()> { - let constructor = {{contract_name_pascal}}Ref::new(100); - let address = client - .instantiate("{{contract_name_snake}}", &ink_e2e::alice(), constructor, 0, None) - .await - .expect("instantiate failed") - .account_id; - - let result = { - let _msg = build_message::<{{contract_name_pascal}}Ref>(address.clone()) - .call(|contract| contract.transfer(address_of!(Bob), 101, vec![])); - client.call_dry_run(&ink_e2e::alice(), &_msg, 0, None).await - }; - - assert!(matches!(result.return_value(), Err(PSP22Error::InsufficientBalance))); - - Ok(()) - } - } } diff --git a/src/templates/contracts/psp22/test/index.test.ts.hbs b/src/templates/contracts/psp22/test/index.test.ts.hbs index c17776a3..5e018a64 100644 --- a/src/templates/contracts/psp22/test/index.test.ts.hbs +++ b/src/templates/contracts/psp22/test/index.test.ts.hbs @@ -79,4 +79,47 @@ describe("{{contract_name}} test", () => { }) ).to.eventually.be.rejected; }); -}); \ No newline at end of file + + it("Can not transfer to hated account", async () => { + const hated_account = wallet2; + const transferredAmount = 10; + const { gasRequired } = await contract + .withSigner(deployer) + .query.transfer(wallet1.address, transferredAmount, []); + // Check that we can transfer money while account is not hated + await expect( + contract.tx.transfer(hated_account.address, 10, [], { + gasLimit: gasRequired, + }) + ).to.eventually.be.fulfilled; + + const result = await contract.query.balanceOf(hated_account.address); + expect(result.value.ok?.toNumber()).to.equal(transferredAmount); + + expect((await contract.query.getHatedAccount()).value.ok).to.equal( + EMPTY_ADDRESS + ); + + // Hate account + await expect( + contract.tx.setHatedAccount(hated_account.address, { + gasLimit: gasRequired, + }) + ).to.eventually.be.ok; + expect((await contract.query.getHatedAccount()).value.ok).to.equal( + hated_account.address + ); + + // Transfer must fail + expect( + contract.tx.transfer(hated_account.address, 10, [], { + gasLimit: gasRequired, + }) + ).to.eventually.be.rejected; + + // Amount of tokens must be the same + expect( + (await contract.query.balanceOf(hated_account.address)).value.ok?.toNumber() + ).to.equal(10); + }); +}); diff --git a/src/templates/test_helpers/Cargo.toml.hbs b/src/templates/test_helpers/Cargo.toml.hbs deleted file mode 100644 index e0724a21..00000000 --- a/src/templates/test_helpers/Cargo.toml.hbs +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "test_helpers" -version= "0.1.0" -authors = ["{{author_name}}"] -edition = "2021" - -[lib] -name = "test_helpers" -path = "lib.rs" - -[profile.dev] -codegen-units = 16 \ No newline at end of file diff --git a/src/templates/test_helpers/lib.rs.hbs b/src/templates/test_helpers/lib.rs.hbs deleted file mode 100644 index b45d2688..00000000 --- a/src/templates/test_helpers/lib.rs.hbs +++ /dev/null @@ -1,18 +0,0 @@ -#[macro_export] -macro_rules! address_of { - ($account:ident) => { - ::ink_e2e::account_id(::ink_e2e::AccountKeyring::$account) - }; -} - -#[macro_export] -macro_rules! balance_of { - ($contract_ref:ident, $client:ident, $address:ident, $account:ident) => \{{ - let _msg = - build_message::<$contract_ref>($address.clone()).call(|contract| contract.balance_of(address_of!($account))); - $client - .call_dry_run(&::ink_e2e::alice(), &_msg, 0, None) - .await - .return_value() - }}; -} \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index aa574dda..542a1d7f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -53,5 +53,3 @@ export interface SwankyConfig { export type SupportedPlatforms = "darwin" | "linux"; export type SupportedArch = "arm64" | "x64"; - -export type TestType = "e2e" | "mocha";