diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..25d68b5 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,8 @@ +node_modules +.github +.vscode +dist +package-lock.json +package.json +README.md +tsconfig.json diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..458596c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "semi": true, + "trailingComma": "all", + "singleQuote": false, + "printWidth": 80, + "tabWidth": 2, + "bracketSpacing": true +} diff --git a/bin/index.ts b/bin/index.ts index bd9b5f8..bfd6406 100755 --- a/bin/index.ts +++ b/bin/index.ts @@ -35,7 +35,7 @@ function copyFiles(args: Args) { copyBlockchainFeatures( args.directory, TEMPLATE_DIR_NAME, - blockchainFeatures + blockchainFeatures, ); } copyAppletHandlers(args.directory, TEMPLATE_DIR_NAME, appletHandlers); @@ -73,7 +73,7 @@ function logWelcomeMessage() { \\ /\\___ \\ / __ \\| |_> > |_> > \\/\\_//____ > (____ / __/| __/ \\/ \\/|__| |__| -`) +`); } function logFinalMessage(args: Args) { diff --git a/bin/meow-cli.ts b/bin/meow-cli.ts index 68635ab..3000d51 100644 --- a/bin/meow-cli.ts +++ b/bin/meow-cli.ts @@ -13,6 +13,8 @@ const cli: Result = meow( --binding, -b Include an onchain device registration and binding template --erc20, -e Include an ERC20 token template --erc721, -n Include an ERC721 token template + --sbt, -t Include a soul bound token template + --erc1155, -f Include an ERC1155 token template --help, -h Display this message --minimal, -m Create a minimal applet without any tests and utils -y Skip prompts and use yes as the answer for all prompts @@ -43,6 +45,14 @@ const cli: Result = meow( type: "boolean", shortFlag: "n", }, + sbt: { + type: "boolean", + shortFlag: "t", + }, + erc1155: { + type: "boolean", + shortFlag: "f", + }, minimal: { type: "boolean", shortFlag: "m", @@ -56,7 +66,7 @@ const cli: Result = meow( shortFlag: "y", }, }, - } + }, ); export default cli; diff --git a/bin/messages.ts b/bin/messages.ts index 3737a5c..caefa6b 100644 --- a/bin/messages.ts +++ b/bin/messages.ts @@ -18,7 +18,7 @@ ${c.bold("npm start")} export const blockchainInstructions = (args: Args) => ` ${c.cyan( - "🔗 To get started with onchain functionallity, type the following commands:" + "🔗 To get started with onchain functionallity, type the following commands:", )} cd ${args.directory}/blockchain touch .env ${c.dim("// and add your IoTeX Private Key to .env file")} diff --git a/bin/types.ts b/bin/types.ts index fc36275..b773c61 100644 --- a/bin/types.ts +++ b/bin/types.ts @@ -15,6 +15,14 @@ export type Flags = { type: "boolean"; shortFlag: "n"; }; + sbt: { + type: "boolean"; + shortFlag: "t"; + }; + erc1155: { + type: "boolean"; + shortFlag: "f"; + }; minimal: { type: "boolean"; shortFlag: "m"; @@ -34,6 +42,8 @@ export type Args = { binding: boolean | undefined; erc20: boolean | undefined; erc721: boolean | undefined; + erc1155: boolean | undefined; + sbt: boolean | undefined; simulator: boolean | undefined; minimal: boolean | undefined; yes: boolean | undefined; diff --git a/bin/utils/copy-files.ts b/bin/utils/copy-files.ts index 93bce55..380132e 100644 --- a/bin/utils/copy-files.ts +++ b/bin/utils/copy-files.ts @@ -7,7 +7,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url)); export function copyTemplates( projectPath: string, templatePath: string, - subdirs: { [key: string]: string } + subdirs: { [key: string]: string }, ) { for (const [subdir, template] of Object.entries(subdirs)) { const templateSubdirPath = path.join(__dirname, templatePath, template); @@ -19,14 +19,14 @@ export function copyTemplates( export function copyBlockchainFeatures( projectPath: string, templatePath: string, - blockchainFeatures: string[] + blockchainFeatures: string[], ) { blockchainFeatures.forEach((feature) => { const templateSubdirPath = path.join( __dirname, templatePath, "extensions", - feature + feature, ); const projectSubdirPath = path.join(projectPath, "blockchain"); copyBlockchainFeature(templateSubdirPath, projectSubdirPath); @@ -38,7 +38,7 @@ export function copyBlockchainFeatures( export function copyAppletHandlers( projectPath: string, templatePath: string, - appletHandlers: string[] + appletHandlers: string[], ) { appletHandlers.forEach((handler) => { const templateSubdirPath = path.join( @@ -46,7 +46,7 @@ export function copyAppletHandlers( templatePath, "extensions", "applet", - handler + handler, ); const projectSubdirPath = path.join(projectPath, "applet", "assembly"); @@ -59,7 +59,7 @@ export function copyAppletHandlers( function copyBlockchainFeature( templateSubdirPath: string, - projectSubdirPath: string + projectSubdirPath: string, ) { const subdirs = ["contracts", "test", "tasks", "deploy"]; @@ -73,7 +73,7 @@ function copyBlockchainFeature( function copyAppletHandler( templateSubdirPath: string, - projectSubdirPath: string + projectSubdirPath: string, ) { fs.copySync(templateSubdirPath, projectSubdirPath); } @@ -92,14 +92,20 @@ function addHandlerToIndex(projectSubdirPath: string, handler: string) { } function addFeatureTaskToIndex(projectSubdirPath: string, feature: string) { - const taskIndex = path.join(projectSubdirPath, "tasks", "index.js"); + const taskIndex = path.join(projectSubdirPath, "tasks", "index.ts"); + if (feature === "binding") { + fs.appendFileSync(taskIndex, `\nrimport "./binding";`); + } + if (feature === "sbt") { + fs.appendFileSync(taskIndex, `\nimport "./sbt";`); + } if (feature === "erc20") { - fs.appendFileSync(taskIndex, `\nrequire("./erc20");`); + fs.appendFileSync(taskIndex, `\nimport "./erc20";`); } if (feature === "erc721") { - fs.appendFileSync(taskIndex, `\nrequire("./nft");`); + fs.appendFileSync(taskIndex, `\nimport "./nft";`); } - if (feature === "binding") { - fs.appendFileSync(taskIndex, `\nrequire("./binding");`); + if (feature === "erc1155") { + fs.appendFileSync(taskIndex, `\nimport "./rewards";`); } } diff --git a/bin/utils/helpers.ts b/bin/utils/helpers.ts index 3e66416..96e312f 100644 --- a/bin/utils/helpers.ts +++ b/bin/utils/helpers.ts @@ -14,7 +14,7 @@ export function extractSubdirsAndFeatures(args: Args): { const appletPath = args.minimal ? path.join("light", "applet") : "applet"; subdirs.applet = appletPath; - if (args.binding || args.erc20 || args.erc721) { + if (args.binding || args.erc20 || args.erc721 || args.erc1155 || args.sbt) { subdirs.blockchain = "blockchain"; addBlockchainFeatures(args, blockchainFeatures); } @@ -44,6 +44,12 @@ function addBlockchainFeatures(args: Args, blockchainFeatures: string[]) { if (args.erc721) { pushStringToArray(blockchainFeatures, "erc721"); } + if (args.erc1155) { + pushStringToArray(blockchainFeatures, "erc1155"); + } + if (args.sbt) { + pushStringToArray(blockchainFeatures, "sbt"); + } } function pushStringToArray(subdirs: string[], template: string) { diff --git a/bin/utils/normalize-args.ts b/bin/utils/normalize-args.ts index 5f3a1c6..5b7dc2b 100644 --- a/bin/utils/normalize-args.ts +++ b/bin/utils/normalize-args.ts @@ -58,6 +58,8 @@ function setFlagsValue(cli: Result, value: boolean) { cli.flags.binding = value; cli.flags.erc20 = value; cli.flags.erc721 = value; + cli.flags.erc1155 = value; + cli.flags.sbt = value; cli.flags.simulator = value; } @@ -66,7 +68,9 @@ function isAnyOfTheFlagsTrue(cli: Result) { cli.flags.binding || cli.flags.erc20 || cli.flags.erc721 || - cli.flags.simulator + cli.flags.simulator || + cli.flags.sbt || + cli.flags.erc1155 ); } @@ -75,6 +79,8 @@ async function promtAllFlags(cli: Result) { cli.flags.erc721 = await promtConfirmation("erc721"); cli.flags.binding = await promtConfirmation("binding"); cli.flags.simulator = await promtConfirmation("simulator"); + cli.flags.sbt = await promtConfirmation("sbt"); + cli.flags.erc1155 = await promtConfirmation("erc1155"); } async function promtConfirmation(tmpName: string) { @@ -96,10 +102,14 @@ function buildMessage(tmpName: string) { return `${emoji} include a device ${tmpName} template?`; case "binding": return `${emoji} include an onchain device registration and binding template?`; + case "sbt": + return `${emoji} include an ${tmpName} template?`; case "erc20": return `${emoji} include an ${tmpName} token template?`; case "erc721": return `${emoji} include an ${tmpName} token template?`; + case "erc1155": + return `${emoji} include an ${tmpName} token template?`; default: return ""; } @@ -115,6 +125,10 @@ function selectEmoji(tmpName: string) { return "🖼️ "; case "simulator": return "🤖"; + case "sbt": + return "👤"; + case "erc1155": + return "🎟️"; default: return ""; } diff --git a/package-lock.json b/package-lock.json index 4bbe9ff..e8c88ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "devDependencies": { "@types/fs-extra": "^11.0.1", "@types/node": "^20.1.0", + "prettier": "^3.0.3", "typescript": "^5.0.4" } }, @@ -983,6 +984,21 @@ "node": ">=8" } }, + "node_modules/prettier": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/quick-lru": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.1.tgz", diff --git a/package.json b/package.json index 90d8e79..b4b21f5 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "devDependencies": { "@types/fs-extra": "^11.0.1", "@types/node": "^20.1.0", + "prettier": "^3.0.3", "typescript": "^5.0.4" } } diff --git a/templates/applet/as-pect.asconfig.json b/templates/applet/as-pect.asconfig.json index b94f27b..dd2dbec 100644 --- a/templates/applet/as-pect.asconfig.json +++ b/templates/applet/as-pect.asconfig.json @@ -21,4 +21,4 @@ }, "extends": "./asconfig.json", "entries": ["./node_modules/@as-pect/assembly/assembly/index.ts"] -} \ No newline at end of file +} diff --git a/templates/applet/asconfig.json b/templates/applet/asconfig.json index 8776597..e50a76c 100644 --- a/templates/applet/asconfig.json +++ b/templates/applet/asconfig.json @@ -19,4 +19,4 @@ "options": { "bindings": "esm" } -} \ No newline at end of file +} diff --git a/templates/applet/assembly/utils/__tests__/build-tx.spec.ts b/templates/applet/assembly/utils/__tests__/build-tx.spec.ts index d9cafd7..d920556 100644 --- a/templates/applet/assembly/utils/__tests__/build-tx.spec.ts +++ b/templates/applet/assembly/utils/__tests__/build-tx.spec.ts @@ -3,9 +3,9 @@ import { buildTxData } from "../build-tx"; const TX_DATA = "0x40c10f1900000000000000000000000036f075ef0437b5fe95a7d0293823f1e085416ddf0000000000000000000000000000000000000000000000003782dace9d900000"; const TX_DATA_2 = - "0x40c10f1900000000000000000000000036f075ef0437b5fe95a7d0293823f1e085416ddf000000000000000000000000000000000000000000000001dd6559bdb1700000" + "0x40c10f1900000000000000000000000036f075ef0437b5fe95a7d0293823f1e085416ddf000000000000000000000000000000000000000000000001dd6559bdb1700000"; const TX_DATA_3 = - "0x40c10f1900000000000000000000000036f075ef0437b5fe95a7d0293823f1e085416ddf0000000000000000000000000000000000000000000422e5a5eed78714500000" + "0x40c10f1900000000000000000000000036f075ef0437b5fe95a7d0293823f1e085416ddf0000000000000000000000000000000000000000000422e5a5eed78714500000"; const RECIPIENT_ADDR = "0x36f075ef0437b5fe95a7d0293823f1e085416ddf"; const FUNCTION_ADDR = "40c10f19"; @@ -13,25 +13,23 @@ const FUNCTION_ADDR = "40c10f19"; test("tx data builder", () => { it("should build a tx data string with amount as a string", () => { expect(buildTxData(FUNCTION_ADDR, RECIPIENT_ADDR, "4")).toBe( - TX_DATA + TX_DATA, ); expect(buildTxData(FUNCTION_ADDR, RECIPIENT_ADDR, "34.4")).toBe( - TX_DATA_2 + TX_DATA_2, ); expect(buildTxData(FUNCTION_ADDR, RECIPIENT_ADDR, "5000500")).toBe( - TX_DATA_3 - ) + TX_DATA_3, + ); }); it("should build a tx data string with amount as a number", () => { - expect(buildTxData(FUNCTION_ADDR, RECIPIENT_ADDR, 4)).toBe( - TX_DATA - ); + expect(buildTxData(FUNCTION_ADDR, RECIPIENT_ADDR, 4)).toBe(TX_DATA); expect(buildTxData(FUNCTION_ADDR, RECIPIENT_ADDR, 34.4)).toBe( - TX_DATA_2 + TX_DATA_2, ); expect(buildTxData(FUNCTION_ADDR, RECIPIENT_ADDR, 5000500)).toBe( - TX_DATA_3 - ) + TX_DATA_3, + ); }); itThrows("should throw an error if amount is not a number", () => { buildTxData(FUNCTION_ADDR, RECIPIENT_ADDR, "a"); diff --git a/templates/applet/assembly/utils/__tests__/eth-to-wei.spec.ts b/templates/applet/assembly/utils/__tests__/eth-to-wei.spec.ts deleted file mode 100644 index 5b81497..0000000 --- a/templates/applet/assembly/utils/__tests__/eth-to-wei.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { ethToWei } from "../eth-to-wei"; - -test("Test utils", () => { - describe("parseEth", () => { - it("should parse eth with whole numbers", () => { - expect(ethToWei("1")).toBe("1000000000000000000"); - expect(ethToWei("10")).toBe("10000000000000000000"); - expect(ethToWei("18")).toBe("18000000000000000000"); - expect(ethToWei("100")).toBe("100000000000000000000"); - expect(ethToWei("200")).toBe("200000000000000000000"); - expect(ethToWei("999")).toBe("999000000000000000000"); - expect(ethToWei("5000")).toBe("5000000000000000000000"); - }); - it("should parse eth with decimals", () => { - expect(ethToWei("0.1")).toBe("100000000000000000"); - expect(ethToWei("0.04")).toBe("40000000000000000"); - expect(ethToWei("0.01")).toBe("10000000000000000"); - expect(ethToWei("0.001")).toBe("1000000000000000"); - expect(ethToWei("0.0001")).toBe("100000000000000"); - expect(ethToWei("0.00001")).toBe("10000000000000"); - }); - it("should parse eth with decimals and whole numbers", () => { - expect(ethToWei("1.1")).toBe("1100000000000000000"); - expect(ethToWei("1.01")).toBe("1010000000000000000"); - expect(ethToWei("1.001")).toBe("1001000000000000000"); - expect(ethToWei("1.0001")).toBe("1000100000000000000"); - expect(ethToWei("1.00001")).toBe("1000010000000000000"); - expect(ethToWei("34.4")).toBe("34400000000000000000"); - expect(ethToWei("340.444")).toBe("340444000000000000000"); - }); - itThrows( - "should throw if value is not a number", - () => { - ethToWei("a"); - }, - "Value is not a number" - ); - itThrows( - "should throw if value is empty", - () => { - ethToWei(""); - }, - "Value is not a number" - ); - }); -}); diff --git a/templates/applet/assembly/utils/__tests__/wei-to-hex.spec.ts b/templates/applet/assembly/utils/__tests__/wei-to-hex.spec.ts deleted file mode 100644 index 2887614..0000000 --- a/templates/applet/assembly/utils/__tests__/wei-to-hex.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { weiToTxSlot, tokenNumberToHex } from "../wei-to-hex"; - -test("Test utils", () => { - describe("weiToHex", () => { - it("should convert wei to hex", () => { - expect(weiToTxSlot("1")).toBe( - "0000000000000000000000000000000000000000000000000000000000000001" - ); - expect(weiToTxSlot("10000000000000000000")).toBe( - "0000000000000000000000000000000000000000000000008ac7230489e80000" - ); - expect(weiToTxSlot("4000000000000000000")).toBe( - "0000000000000000000000000000000000000000000000003782dace9d900000" - ); - }); - }); - describe("tokenNumberToHex", () => { - it("should convert token number to hex", () => { - expect(tokenNumberToHex("10")).toBe( - "0000000000000000000000000000000000000000000000008ac7230489e80000" - ); - expect(tokenNumberToHex("4")).toBe( - "0000000000000000000000000000000000000000000000003782dace9d900000" - ); - }); - }); -}); diff --git a/templates/applet/assembly/utils/build-tx.ts b/templates/applet/assembly/utils/build-tx.ts index 57653fe..f801a87 100644 --- a/templates/applet/assembly/utils/build-tx.ts +++ b/templates/applet/assembly/utils/build-tx.ts @@ -1,24 +1,21 @@ -import { tokenNumberToHex } from "./wei-to-hex"; +import { + buildTxSlot, + buildTxString, + ethToHex, +} from "@w3bstream/wasm-sdk/assembly/utility"; export function buildTxData( functionAddr: string, recipient: string, - tokenAmount: T + tokenAmount: T, ): string { - const slotForRecipient = buildRecepientSlot(recipient); - const slotForAmount = tokenNumberToHex(tokenAmount); + const slotForRecipient = buildTxSlot(recipient.replace("0x", "")); + const slotForAmount = tokenNumberToTxSlot(tokenAmount); return buildTxString([functionAddr, slotForRecipient, slotForAmount]); } -function buildRecepientSlot(recipient: string): string { - return `000000000000000000000000${formatAddress(recipient)}`; -} - -function formatAddress(address: string): string { - return address.replace("0x", ""); -} - -function buildTxString(args: string[]): string { - return "0x" + args.join(""); +function tokenNumberToTxSlot(value: T): string { + const ethHex = ethToHex(value); + return buildTxSlot(ethHex); } diff --git a/templates/applet/assembly/utils/eth-to-wei.ts b/templates/applet/assembly/utils/eth-to-wei.ts deleted file mode 100644 index 0374181..0000000 --- a/templates/applet/assembly/utils/eth-to-wei.ts +++ /dev/null @@ -1,9 +0,0 @@ -import Big from "as-big"; - -export function ethToWei(eth: T): string { - if (eth instanceof String && eth === "") { - throw new Error("Value is not a number"); - } - const v = Big.of(eth).times(Big.of(10).pow(18)); - return v.__stringify(false, true); -} diff --git a/templates/applet/assembly/utils/message-validation.ts b/templates/applet/assembly/utils/message-validation.ts index 0691a44..ff5a76c 100644 --- a/templates/applet/assembly/utils/message-validation.ts +++ b/templates/applet/assembly/utils/message-validation.ts @@ -11,7 +11,7 @@ class MessageValidationError extends Error { export function validateField( data: JSON.Obj, - field: string + field: string, ): void { const value = getField(data, field); diff --git a/templates/applet/assembly/utils/payload-parser.ts b/templates/applet/assembly/utils/payload-parser.ts index 93baadb..da6ec00 100644 --- a/templates/applet/assembly/utils/payload-parser.ts +++ b/templates/applet/assembly/utils/payload-parser.ts @@ -6,7 +6,7 @@ export function getPayloadValue(message: string): JSON.Obj { export function getField( data: JSON.Obj, - field: string + field: string, ): T | null { return data.get(field) as T; } diff --git a/templates/applet/assembly/utils/wei-to-hex.ts b/templates/applet/assembly/utils/wei-to-hex.ts deleted file mode 100644 index aad2e8b..0000000 --- a/templates/applet/assembly/utils/wei-to-hex.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { u128 } from "as-bignum/assembly"; - -import { ethToWei } from "./eth-to-wei"; - -export function tokenNumberToHex(value: T): string { - const weiStr = ethToWei(value); - return weiToTxSlot(weiStr); -} - -export function weiToTxSlot(value: string): string { - const hexStr = u128.from(value.toString()).toString(16); - return composeAmountStr(hexStr); -} - -export function composeAmountStr(amountHexStr: string): string { - return "0".repeat(64 - amountHexStr.length) + amountHexStr; -} - -export function intToHexStr(value: u64): string { - return value.toString(16); -} diff --git a/templates/applet/db_schema.json b/templates/applet/db_schema.json deleted file mode 100644 index a7b6c5b..0000000 --- a/templates/applet/db_schema.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "tables": [ - { - "name": "measurements", - "cols": [ - { - "name": "device_id", - "constrains": { - "datatype": "TEXT", - "length": 130, - "desc": "device id" - } - }, - { - "name": "recipient_address", - "constrains": { - "datatype": "TEXT", - "length": 40, - "desc": "user wallet address" - } - }, - { - "name": "data_timestamp", - "constrains": { - "datatype": "UINT64", - "desc": "sample time" - } - }, - { - "name": "sensor_reading", - "constrains": { - "datatype": "FLOAT32", - "desc": "energy sample" - } - } - ], - "keys":[ - { - "name": "recipient_address", - "isUnique": true, - "columnNames": [ - "recipient_address" - ] - } - ], - "withSoftDeletion": true, - "withPrimaryKey": true - } - ] - } - \ No newline at end of file diff --git a/templates/applet/package.json b/templates/applet/package.json index 12f78a2..b56d3d8 100644 --- a/templates/applet/package.json +++ b/templates/applet/package.json @@ -13,7 +13,7 @@ "license": "ISC", "devDependencies": { "@as-pect/cli": "^8.1.0", - "@w3bstream/wasm-sdk": "^0.6.1", + "@w3bstream/wasm-sdk": "^0.12.0", "as-big": "^0.2.2", "as-bignum": "^0.2.40", "assemblyscript": "^0.27.0" diff --git a/templates/applet/sql-commands.txt b/templates/applet/sql-commands.txt deleted file mode 100644 index e823d48..0000000 --- a/templates/applet/sql-commands.txt +++ /dev/null @@ -1,3 +0,0 @@ -CREATE TABLE `device_registry`. (`device_id` TEXT NOT NULL ,`is_active` TEXT NOT NULL ); -INSERT INTO "device_registry" (device_id, is_registered, is_active) VALUES (?,?,?); -INSERT INTO "device_bindings" (device_id, owner_address) VALUES (?,?); \ No newline at end of file diff --git a/templates/applet/wsproject.json b/templates/applet/wsproject.json new file mode 100644 index 0000000..7ed5fe3 --- /dev/null +++ b/templates/applet/wsproject.json @@ -0,0 +1,213 @@ +{ + "name": "drive_to_earn", + "description": "Mobility", + "database": { + "schemas": [ + { + "schemaName": "public", + "tables": [ + { + "tableName": "device_binding", + "tableSchema": "public", + "comment": "", + "columns": [ + { + "name": "id", + "type": "int8", + "defaultValue": null, + "isIdentity": true, + "isNullable": false, + "isUnique": false, + "isPrimaryKey": true, + "comment": null + }, + { + "name": "created_at", + "type": "timestamp", + "defaultValue": "now()", + "isIdentity": false, + "isNullable": false, + "isUnique": false, + "isPrimaryKey": false, + "comment": null + }, + { + "name": "device_id", + "type": "text", + "defaultValue": null, + "isIdentity": false, + "isNullable": false, + "isUnique": true, + "isPrimaryKey": false, + "comment": null + }, + { + "name": "owner_address", + "type": "text", + "defaultValue": null, + "isIdentity": false, + "isNullable": false, + "isUnique": false, + "isPrimaryKey": false, + "comment": null + } + ], + "relationships": [] + }, + { + "tableName": "device_registry", + "tableSchema": "public", + "comment": "", + "columns": [ + { + "name": "id", + "type": "int8", + "defaultValue": null, + "isIdentity": true, + "isNullable": false, + "isUnique": false, + "isPrimaryKey": true, + "comment": null + }, + { + "name": "created_at", + "type": "timestamp", + "defaultValue": "now()", + "isIdentity": false, + "isNullable": false, + "isUnique": false, + "isPrimaryKey": false, + "comment": null + }, + { + "name": "device_id", + "type": "text", + "defaultValue": null, + "isIdentity": false, + "isNullable": false, + "isUnique": true, + "isPrimaryKey": false, + "comment": null + }, + { + "name": "is_registered", + "type": "bool", + "defaultValue": null, + "isIdentity": false, + "isNullable": true, + "isUnique": false, + "isPrimaryKey": false, + "comment": null + }, + { + "name": "is_active", + "type": "bool", + "defaultValue": null, + "isIdentity": false, + "isNullable": true, + "isUnique": false, + "isPrimaryKey": false, + "comment": null + } + ], + "relationships": [] + }, + { + "tableName": "distances", + "tableSchema": "public", + "comment": "", + "columns": [ + { + "name": "id", + "type": "int8", + "defaultValue": null, + "isIdentity": true, + "isNullable": false, + "isUnique": false, + "isPrimaryKey": true, + "comment": null + }, + { + "name": "created_at", + "type": "timestamp", + "defaultValue": "now()", + "isIdentity": false, + "isNullable": false, + "isUnique": false, + "isPrimaryKey": false, + "comment": null + }, + { + "name": "device_id", + "type": "text", + "defaultValue": null, + "isIdentity": false, + "isNullable": false, + "isUnique": false, + "isPrimaryKey": false, + "comment": null + }, + { + "name": "date", + "type": "text", + "defaultValue": null, + "isIdentity": false, + "isNullable": false, + "isUnique": false, + "isPrimaryKey": false, + "comment": null + }, + { + "name": "distance", + "type": "float8", + "defaultValue": null, + "isIdentity": false, + "isNullable": false, + "isUnique": false, + "isPrimaryKey": false, + "comment": null + } + ], + "relationships": [] + } + ] + } + ] + }, + "envs": { + "env": [ + ["LOG_LEVEL", "debug"], + ["DEVICE_BINDING_TABLE", "device_binding"], + ["DEVICE_REGISTRY_TABLE", "device_registry"], + ["DISTANCE_TABLE", "distances"], + ["EVALUATION_PERIOD_DAYS", "7"], + ["EVALUATION_DISTANCE", "50"], + ["CHAIN_ID", "4690"] + ] + }, + "cronJob": [], + "contractLog": [], + "chainHeight": [], + "eventRounting": [ + { + "eventType": "DEFAULT", + "handler": "start" + }, + { + "eventType": "ON_DEVICE_REGISTERED", + "handler": "handle_device_registration" + }, + { + "eventType": "ON_DEVICE_BOUND", + "handler": "handle_device_binding" + }, + { + "eventType": "DISTANCE_DATA", + "handler": "handle_receive_data" + }, + { + "eventType": "ANALYZE_DISTANCE_DATA", + "handler": "handle_analyze_data" + } + ] +} diff --git a/templates/blockchain/.env.template b/templates/blockchain/.env.template new file mode 100644 index 0000000..519c94c --- /dev/null +++ b/templates/blockchain/.env.template @@ -0,0 +1 @@ +PRIVATE_KEY=your_private_key \ No newline at end of file diff --git a/templates/blockchain/README.md b/templates/blockchain/README.md index 6b986fd..70d852b 100644 --- a/templates/blockchain/README.md +++ b/templates/blockchain/README.md @@ -5,8 +5,8 @@ ### 1. Set up environment ```bash -echo IOTEX_PRIVATE_KEY= > .env -// e.g. echo IOTEX_PRIVATE_KEY=111111111111111111111111111111111111 > .env +echo PRIVATE_KEY= > .env +// e.g. echo PRIVATE_KEY=111111111111111111111111111111111111 > .env ``` ### 2. Run tests diff --git a/templates/blockchain/hardhat.config.js b/templates/blockchain/hardhat.config.js deleted file mode 100644 index f1f4327..0000000 --- a/templates/blockchain/hardhat.config.js +++ /dev/null @@ -1,26 +0,0 @@ -/** @type import('hardhat/config').HardhatUserConfig */ -require("@nomicfoundation/hardhat-toolbox"); -require("@nomiclabs/hardhat-ethers"); -require('hardhat-deploy'); -require("dotenv").config(); - -require("./tasks"); - -module.exports = { - solidity: "0.8.17", - networks: { - testnet: { - url: "https://babel-api.testnet.iotex.io", - accounts: [process.env.IOTEX_PRIVATE_KEY], - }, - mainnet: { - url: "https://babel-api.mainnet.iotex.io", - accounts: [process.env.IOTEX_PRIVATE_KEY], - }, - }, - namedAccounts: { - deployer: { - default: 0, - }, - }, -}; diff --git a/templates/blockchain/hardhat.config.ts b/templates/blockchain/hardhat.config.ts new file mode 100644 index 0000000..30780db --- /dev/null +++ b/templates/blockchain/hardhat.config.ts @@ -0,0 +1,28 @@ +import { HardhatUserConfig } from "hardhat/config"; +import "@nomicfoundation/hardhat-toolbox"; +import "@nomiclabs/hardhat-ethers"; +import "hardhat-deploy"; +import "dotenv/config"; + +import "./tasks"; + +const config: HardhatUserConfig = { + solidity: "0.8.17", + networks: { + testnet: { + url: "https://babel-api.testnet.iotex.io", + accounts: [process.env.PRIVATE_KEY || ""], + }, + mainnet: { + url: "https://babel-api.mainnet.iotex.io", + accounts: [process.env.PRIVATE_KEY || ""], + }, + }, + namedAccounts: { + deployer: { + default: 0, + }, + }, +}; + +export default config; diff --git a/templates/blockchain/package.json b/templates/blockchain/package.json index 59d1ddf..347e80b 100644 --- a/templates/blockchain/package.json +++ b/templates/blockchain/package.json @@ -2,16 +2,22 @@ "name": "hardhat-project", "scripts": { "compile": "hardhat compile", - "test": "hardhat test", + "test": "hardhat test --typecheck", "deploy": "hardhat deploy", "deploy:testnet": "hardhat deploy --network testnet --export-all deployments.json", "deploy:mainnet": "hardhat deploy --network mainnet --export-all deployments.json" }, "devDependencies": { - "@nomicfoundation/hardhat-toolbox": "^2.0.1", + "@nomicfoundation/hardhat-toolbox": "^3.0.0", "@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers", "@openzeppelin/contracts": "^4.8.1", "dotenv": "^16.0.3", - "hardhat-deploy": "^0.11.28" + "hardhat-deploy": "^0.11.28", + "@types/chai": "^4.3.5", + "@types/mocha": "^10.0.1", + "@types/node": "^20.2.3", + "chai": "^4.3.7", + "ts-node": "^10.9.1", + "typescript": "^5.0.4" } } \ No newline at end of file diff --git a/templates/blockchain/tasks/index.js b/templates/blockchain/tasks/index.js deleted file mode 100644 index 1d305fa..0000000 --- a/templates/blockchain/tasks/index.js +++ /dev/null @@ -1 +0,0 @@ -require("./welcome"); diff --git a/templates/blockchain/tasks/index.ts b/templates/blockchain/tasks/index.ts new file mode 100644 index 0000000..704df14 --- /dev/null +++ b/templates/blockchain/tasks/index.ts @@ -0,0 +1 @@ +import "./welcome"; diff --git a/templates/blockchain/tasks/welcome.js b/templates/blockchain/tasks/welcome.ts similarity index 74% rename from templates/blockchain/tasks/welcome.js rename to templates/blockchain/tasks/welcome.ts index 2b9ec93..b898670 100644 --- a/templates/blockchain/tasks/welcome.js +++ b/templates/blockchain/tasks/welcome.ts @@ -1,3 +1,5 @@ +import { task } from "hardhat/config"; + task("hello-world", "Prints hello world").setAction(async (taskArgs, hre) => { console.log("Hello World!"); }); diff --git a/templates/blockchain/test/fixtures.ts b/templates/blockchain/test/fixtures.ts new file mode 100644 index 0000000..c8ee59a --- /dev/null +++ b/templates/blockchain/test/fixtures.ts @@ -0,0 +1,13 @@ +export const DEVICE_ID_1 = + "0x1234567890123456789012345678901234567890123456789012345678901234"; +export const DEVICE_ID_2 = + "0x1234567890123456789012345678901234567890123456789012345678901235"; +export const DEVICE_ID_3 = + "0x1234567890123456789012345678901234567890123456789012345678901236"; +export const URI_EXAMPLE = + "ipfs://QmbfSCb68jn1kfEQpMeSCQqXMqoptUiQDkmfHxZMBFXgib"; + +export const SBT_CONTRACT_NAME = "Device SBT"; +export const SBT_CONTRACT_SYMBOL = "DSBT"; + +export const REWARDS_CONTRACT_NAME = "Device Rewards"; diff --git a/templates/blockchain/tsconfig.json b/templates/blockchain/tsconfig.json new file mode 100644 index 0000000..b0211aa --- /dev/null +++ b/templates/blockchain/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true + }, + "include": ["./hardhat.config.ts", "./tasks", "./deploy", "./test", "./*.ts"] +} \ No newline at end of file diff --git a/templates/blockchain/utils/read-write.ts b/templates/blockchain/utils/read-write.ts new file mode 100644 index 0000000..23c8204 --- /dev/null +++ b/templates/blockchain/utils/read-write.ts @@ -0,0 +1,16 @@ +import path from "path"; +import fs from "fs"; + +const PATH_TO_CONFIG = "../../applet/wsproject.json"; + +export const getWSProjectConfig = () => { + const file = fs.readFileSync(path.resolve(__dirname, PATH_TO_CONFIG)); + return JSON.parse(file.toString()); +}; + +export const writeToConfig = (config: any) => { + fs.writeFileSync( + path.resolve(__dirname, PATH_TO_CONFIG), + JSON.stringify(config, null, 2), + ); +}; diff --git a/templates/blockchain/utils/update-envs.ts b/templates/blockchain/utils/update-envs.ts new file mode 100644 index 0000000..5f50d49 --- /dev/null +++ b/templates/blockchain/utils/update-envs.ts @@ -0,0 +1,40 @@ +import { getWSProjectConfig, writeToConfig } from "./read-write"; + +interface EnvVar { + envName: string; + envValue: string | number; +} + +export const addEnvVarToWSProjectConfig = (env: EnvVar) => { + const projectConfig = getWSProjectConfig(); + addEnvVarToConfig(projectConfig, env); + writeToConfig(projectConfig); +}; + +const addEnvVarToConfig = (config: any, env: EnvVar) => { + if (isEnvExistsInConfig(config, env)) { + replaceEnvInConfig(config, env); + } else { + appendEnvToConfig(config, env); + } +}; + +const isEnvExistsInConfig = (config: any, env: EnvVar): boolean => { + const { envs } = config; + const { envName } = env; + const res = envs.env.find(([name]: [string, string]) => name === envName); + return !!res; +}; + +const replaceEnvInConfig = (config: any, env: EnvVar) => { + config.envs.env = config.envs.env.map(([name, value]: [string, string]) => { + if (name === env.envName) { + return [name, env.envValue]; + } + return [name, value]; + }); +}; + +const appendEnvToConfig = (config: any, env: EnvVar) => { + config.envs.env.push([env.envName, env.envValue]); +}; diff --git a/templates/blockchain/utils/update-monitor.ts b/templates/blockchain/utils/update-monitor.ts new file mode 100644 index 0000000..bdb1736 --- /dev/null +++ b/templates/blockchain/utils/update-monitor.ts @@ -0,0 +1,49 @@ +import { getWSProjectConfig, writeToConfig } from "./read-write"; + +interface ContractMonitor { + eventType: string; + chainID: number; + contractAddress: string; + blockStart: number; + blockEnd: number; + topic0: string; +} + +export const updateContractMonitor = (monitor: ContractMonitor) => { + const projectConfig = getWSProjectConfig(); + addMonitorToConfig(projectConfig, monitor); + writeToConfig(projectConfig); +}; + +const addMonitorToConfig = (config: any, monitor: ContractMonitor) => { + if (isEventExistsInConfig(config, monitor)) { + replaceEventInConfig(config, monitor); + } else { + appendEventToConfig(config, monitor); + } +}; + +const isEventExistsInConfig = ( + config: any, + monitor: ContractMonitor, +): boolean => { + const { contractLog } = config; + const { eventType } = monitor; + const res = contractLog.find( + (event: ContractMonitor) => event.eventType === eventType, + ); + return !!res; +}; + +const replaceEventInConfig = (config: any, monitor: ContractMonitor) => { + config.contractLog = config.contractLog.map((event: ContractMonitor) => { + if (event.eventType === monitor.eventType) { + return monitor; + } + return event; + }); +}; + +const appendEventToConfig = (config: any, monitor: ContractMonitor) => { + config.contractLog.push(monitor); +}; diff --git a/templates/extensions/binding/contracts/DeviceBinding.sol b/templates/extensions/binding/contracts/DeviceBinding.sol index 59b11a0..be7d33c 100644 --- a/templates/extensions/binding/contracts/DeviceBinding.sol +++ b/templates/extensions/binding/contracts/DeviceBinding.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; -import "./DevicesRegistry.sol"; +import "./DeviceRegistry.sol"; contract DeviceBinding is Ownable { using Counters for Counters.Counter; - DevicesRegistry public devicesRegistry; + DeviceRegistry public deviceRegistry; Counters.Counter private totalDevices; mapping(bytes32 => address) public deviceToOwner; @@ -23,7 +23,7 @@ contract DeviceBinding is Ownable { modifier onlyAuthorizedDevice(bytes32 _deviceId) { require( - devicesRegistry.isAuthorizedDevice(_deviceId), + deviceRegistry.isAuthorizedDevice(_deviceId), "device is not authorized" ); _; @@ -51,8 +51,17 @@ contract DeviceBinding is Ownable { _; } - constructor(address _devicesRegistryAddress) { - devicesRegistry = DevicesRegistry(_devicesRegistryAddress); + constructor(address _deviceRegistryAddress) { + deviceRegistry = DeviceRegistry(_deviceRegistryAddress); + } + + function bindDevices( + bytes32[] memory _deviceIds, + address _ownerAddress + ) public onlyOwner { + for (uint256 i = 0; i < _deviceIds.length; i++) { + bindDevice(_deviceIds[i], _ownerAddress); + } } function bindDevice( diff --git a/templates/extensions/binding/contracts/DevicesRegistry.sol b/templates/extensions/binding/contracts/DeviceRegistry.sol similarity index 62% rename from templates/extensions/binding/contracts/DevicesRegistry.sol rename to templates/extensions/binding/contracts/DeviceRegistry.sol index 4e244a2..7873b83 100644 --- a/templates/extensions/binding/contracts/DevicesRegistry.sol +++ b/templates/extensions/binding/contracts/DeviceRegistry.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/access/Ownable.sol"; -contract DevicesRegistry is Ownable { +contract DeviceRegistry is Ownable { event DeviceRegistered(bytes32 indexed _deviceId); event DeviceDeleted(bytes32 indexed _deviceId); event DeviceSuspended(bytes32 indexed _deviceId); @@ -19,31 +19,31 @@ contract DevicesRegistry is Ownable { constructor() {} modifier onlyRegisteredDevice(bytes32 _deviceId) { - require( - devices[_deviceId].isRegistered, - "Data Source is not registered" - ); + require(devices[_deviceId].isRegistered, "Device is not registered"); _; } modifier onlyUnregisteredDevice(bytes32 _deviceId) { - require( - !devices[_deviceId].isRegistered, - "Data Source already registered" - ); + require(!devices[_deviceId].isRegistered, "Device already registered"); _; } modifier onlyActiveDevice(bytes32 _deviceId) { - require(devices[_deviceId].isActive, "Data Source is suspended"); + require(devices[_deviceId].isActive, "Device is suspended"); _; } modifier onlySuspendedDevice(bytes32 _deviceId) { - require(!devices[_deviceId].isActive, "Data Source is active"); + require(!devices[_deviceId].isActive, "Device is active"); _; } + function registerDevices(bytes32[] memory _deviceIds) public onlyOwner { + for (uint256 i = 0; i < _deviceIds.length; i++) { + registerDevice(_deviceIds[i]); + } + } + function registerDevice( bytes32 _deviceId ) public onlyOwner onlyUnregisteredDevice(_deviceId) { @@ -82,15 +82,18 @@ contract DevicesRegistry is Ownable { emit DeviceActivated(_deviceId); } - function isAuthorizedDevice( - bytes32 _deviceId - ) - public - view - onlyRegisteredDevice(_deviceId) - onlyActiveDevice(_deviceId) - returns (bool) - { - return true; + function isAuthorizedDevices( + bytes32[] memory _deviceIds + ) public view returns (bool[] memory isAuthorized) { + isAuthorized = new bool[](_deviceIds.length); + for (uint256 i = 0; i < _deviceIds.length; i++) { + isAuthorized[i] = isAuthorizedDevice(_deviceIds[i]); + } + } + + function isAuthorizedDevice(bytes32 _deviceId) public view returns (bool) { + if (devices[_deviceId].isRegistered && devices[_deviceId].isActive) { + return true; + } } } diff --git a/templates/extensions/binding/deploy/00-deploy-device-registry.js b/templates/extensions/binding/deploy/00-deploy-device-registry.js deleted file mode 100644 index 8dd8f70..0000000 --- a/templates/extensions/binding/deploy/00-deploy-device-registry.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = async ({ getNamedAccounts, deployments }) => { - const { deploy } = deployments; - const { deployer } = await getNamedAccounts(); - - const tx = await deploy("DevicesRegistry", { - from: deployer, - args: [], - log: true, - }); - console.log("DevicesRegistry deployed at block: ", tx.receipt.blockNumber); -}; -module.exports.tags = ["DevicesRegistry"]; diff --git a/templates/extensions/binding/deploy/00-deploy-device-registry.ts b/templates/extensions/binding/deploy/00-deploy-device-registry.ts new file mode 100644 index 0000000..9d26f12 --- /dev/null +++ b/templates/extensions/binding/deploy/00-deploy-device-registry.ts @@ -0,0 +1,38 @@ +import { addEnvVarToWSProjectConfig } from "../utils/update-envs"; +import { updateContractMonitor } from "../utils/update-monitor"; +import { DeployFunction } from "hardhat-deploy/types"; + +const func: DeployFunction = async ({ + getNamedAccounts, + deployments, + getChainId, +}) => { + const { deploy } = deployments; + const chainId = await getChainId(); + const { deployer } = await getNamedAccounts(); + const tx = await deploy("DeviceRegistry", { + from: deployer, + args: [], + log: true, + }); + console.log("DeviceRegistry deployed at block: ", tx.receipt?.blockNumber); + + if (chainId !== "31337") { + updateContractMonitor({ + eventType: "ON_DEVICE_REGISTERED", + chainID: Number(chainId), + contractAddress: tx.address, + blockStart: tx.receipt?.blockNumber || 20400000, + blockEnd: 0, + topic0: + "0x543b01d8fc03bd0f400fb055a7c379dc964b3c478f922bb2e198fa9bccb8e714", + }); + addEnvVarToWSProjectConfig({ + envName: "CHAIN_ID", + envValue: chainId, + }); + } +}; + +export default func; +func.tags = ["DeviceRegistry"]; diff --git a/templates/extensions/binding/deploy/01-deploy-device-binding.js b/templates/extensions/binding/deploy/01-deploy-device-binding.js deleted file mode 100644 index 6fa6b39..0000000 --- a/templates/extensions/binding/deploy/01-deploy-device-binding.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = async ({ getNamedAccounts, deployments }) => { - const { deploy } = deployments; - const { deployer } = await getNamedAccounts(); - const DevicesRegistry = await deployments.get("DevicesRegistry"); - - const tx = await deploy("DeviceBinding", { - from: deployer, - args: [DevicesRegistry.address], - log: true, - }); - console.log("DeviceBinding deployed at block: ", tx.receipt.blockNumber); -}; -module.exports.tags = ["DeviceBinding"]; diff --git a/templates/extensions/binding/deploy/01-deploy-device-binding.ts b/templates/extensions/binding/deploy/01-deploy-device-binding.ts new file mode 100644 index 0000000..bf51f3b --- /dev/null +++ b/templates/extensions/binding/deploy/01-deploy-device-binding.ts @@ -0,0 +1,41 @@ +import { DeployFunction } from "hardhat-deploy/types"; +import { updateContractMonitor } from "../utils/update-monitor"; +import { addEnvVarToWSProjectConfig } from "../utils/update-envs"; + +const func: DeployFunction = async ({ + getNamedAccounts, + deployments, + getChainId, +}) => { + const { deploy } = deployments; + const chainId = await getChainId(); + const { deployer } = await getNamedAccounts(); + const DeviceRegistry = await deployments.get("DeviceRegistry"); + + const tx = await deploy("DeviceBinding", { + from: deployer, + args: [DeviceRegistry.address], + log: true, + }); + + console.log("DeviceBinding deployed at block: ", tx.receipt?.blockNumber); + + if (chainId !== "31337") { + updateContractMonitor({ + eventType: "ON_DEVICE_BOUND", + chainID: Number(chainId), + contractAddress: tx.address, + blockStart: tx.receipt?.blockNumber || 20400000, + blockEnd: 0, + topic0: + "0x79e9049c280370b9eda34d20f57456b7dcc94e83ac839777f71209901f780f48", + }); + addEnvVarToWSProjectConfig({ + envName: "CHAIN_ID", + envValue: chainId, + }); + } +}; + +func.tags = ["DeviceBinding"]; +export default func; diff --git a/templates/extensions/binding/tasks/binding.js b/templates/extensions/binding/tasks/binding.ts similarity index 67% rename from templates/extensions/binding/tasks/binding.js rename to templates/extensions/binding/tasks/binding.ts index 651bea2..0faf6bd 100644 --- a/templates/extensions/binding/tasks/binding.js +++ b/templates/extensions/binding/tasks/binding.ts @@ -1,15 +1,17 @@ +import { task } from "hardhat/config"; + task("register-device", "Register a device") .addParam("deviceid", "Id of the device") .setAction(async (taskArgs, hre) => { const { deviceid } = taskArgs; const { deployments } = hre; - const [deployer] = await ethers.getSigners(); + const [deployer] = await hre.ethers.getSigners(); - const DevicesRegistry = await deployments.get("DevicesRegistry"); - const deviceRegistry = await ethers.getContractAt( - "DevicesRegistry", - DevicesRegistry.address, - deployer + const DeviceRegistry = await deployments.get("DeviceRegistry"); + const deviceRegistry = await hre.ethers.getContractAt( + "DeviceRegistry", + DeviceRegistry.address, + deployer, ); const tx = await deviceRegistry.registerDevice(deviceid); @@ -24,13 +26,13 @@ task("bind-device", "Bind a device to a user") .setAction(async (taskArgs, hre) => { const { deviceid, userid } = taskArgs; const { deployments } = hre; - const [deployer] = await ethers.getSigners(); + const [deployer] = await hre.ethers.getSigners(); const DeviceBinding = await deployments.get("DeviceBinding"); - const deviceBinding = await ethers.getContractAt( + const deviceBinding = await hre.ethers.getContractAt( "DeviceBinding", DeviceBinding.address, - deployer + deployer, ); const tx = await deviceBinding.bindDevice(deviceid, userid); diff --git a/templates/extensions/binding/test/deviceBinding.test.js b/templates/extensions/binding/test/deviceBinding.test.js deleted file mode 100644 index c8a5786..0000000 --- a/templates/extensions/binding/test/deviceBinding.test.js +++ /dev/null @@ -1,189 +0,0 @@ -const { expect } = require("chai"); -const hre = require("hardhat"); - -let devicesRegistry; -let deviceBinding; -let user, badGuy, user_2; - -const DEVICE_ID_1 = - "0x1234567890123456789012345678901234567890123456789012345678901234"; -const DEVICE_ID_2 = - "0x1234567890123456789012345678901234567890123456789012345678901235"; -const DEVICE_ID_3 = - "0x1234567890123456789012345678901234567890123456789012345678901236"; -const ZERO_ADDR = hre.ethers.constants.AddressZero; - -describe("Device Binding", function () { - before(async function () { - [_, user, badGuy, user_2] = await hre.ethers.getSigners(); - }); - describe("Initialization", function () { - it("Should initialize the contract with Device Registry", async function () { - const DevicesRegistry = await hre.ethers.getContractFactory( - "DevicesRegistry" - ); - const devicesRegistry = await DevicesRegistry.deploy(); - await devicesRegistry.deployed(); - - const DeviceBinding = await hre.ethers.getContractFactory( - "DeviceBinding" - ); - const deviceBinding = await DeviceBinding.deploy(devicesRegistry.address); - await deviceBinding.deployed(); - expect(await deviceBinding.devicesRegistry()).to.equal( - devicesRegistry.address - ); - }); - }); - describe("Binding", function () { - beforeEach(async function () { - const DevicesRegistry = await hre.ethers.getContractFactory( - "DevicesRegistry" - ); - devicesRegistry = await DevicesRegistry.deploy(); - await devicesRegistry.deployed(); - - const DeviceBinding = await hre.ethers.getContractFactory( - "DeviceBinding" - ); - deviceBinding = await DeviceBinding.deploy(devicesRegistry.address); - await deviceBinding.deployed(); - }); - it("Should bind a device", async function () { - await devicesRegistry.registerDevice(DEVICE_ID_1); - await deviceBinding.bindDevice(DEVICE_ID_1, user.address); - expect(await deviceBinding.getDeviceOwner(DEVICE_ID_1)).to.equal( - user.address - ); - expect(await deviceBinding.getDevicesCount()).to.equal(1); - expect(await deviceBinding.getOwnedDevices(user.address)).to.eql([ - DEVICE_ID_1, - ]); - }); - it("Should bind multiple devices", async function () { - await devicesRegistry.registerDevice(DEVICE_ID_1); - await devicesRegistry.registerDevice(DEVICE_ID_2); - await deviceBinding.bindDevice(DEVICE_ID_1, user.address); - await deviceBinding.bindDevice(DEVICE_ID_2, user.address); - expect(await deviceBinding.getDeviceOwner(DEVICE_ID_1)).to.equal( - user.address - ); - expect(await deviceBinding.getDeviceOwner(DEVICE_ID_2)).to.equal( - user.address - ); - expect(await deviceBinding.getDevicesCount()).to.equal(2); - expect(await deviceBinding.getOwnedDevices(user.address)).to.eql([ - DEVICE_ID_1, - DEVICE_ID_2, - ]); - }); - it("Should bind multiple devices to multiple users", async function () { - await devicesRegistry.registerDevice(DEVICE_ID_1); - await devicesRegistry.registerDevice(DEVICE_ID_2); - await devicesRegistry.registerDevice(DEVICE_ID_3); - - await deviceBinding.bindDevice(DEVICE_ID_1, user.address); - await deviceBinding.bindDevice(DEVICE_ID_2, user_2.address); - await deviceBinding.bindDevice(DEVICE_ID_3, user.address); - - expect(await deviceBinding.getDeviceOwner(DEVICE_ID_1)).to.equal( - user.address - ); - expect(await deviceBinding.getDeviceOwner(DEVICE_ID_2)).to.equal( - user_2.address - ); - expect(await deviceBinding.getDeviceOwner(DEVICE_ID_3)).to.equal( - user.address - ); - expect(await deviceBinding.getDevicesCount()).to.equal(3); - expect(await deviceBinding.getOwnedDevices(user.address)).to.eql([ - DEVICE_ID_1, - DEVICE_ID_3, - ]); - expect(await deviceBinding.getOwnedDevices(user_2.address)).to.eql([ - DEVICE_ID_2, - ]); - }); - it("Should emit an event when binding a device", async function () { - await devicesRegistry.registerDevice(DEVICE_ID_1); - await expect(deviceBinding.bindDevice(DEVICE_ID_1, user.address)) - .to.emit(deviceBinding, "OwnershipAssigned") - .withArgs(DEVICE_ID_1, user.address); - }); - it("Should not bind a device if it is already bound", async function () { - await devicesRegistry.registerDevice(DEVICE_ID_1); - await deviceBinding.bindDevice(DEVICE_ID_1, user.address); - await expect( - deviceBinding.bindDevice(DEVICE_ID_1, user.address) - ).to.be.revertedWith("device has already been bound"); - }); - it("Should not bind a device if it is not authorized", async function () { - await expect( - deviceBinding.bindDevice(DEVICE_ID_1, user.address) - ).to.be.revertedWith("Data Source is not registered"); - }); - it("Should not bind a device if it was suspended", async function () { - await devicesRegistry.registerDevice(DEVICE_ID_1); - await devicesRegistry.suspendDevice(DEVICE_ID_1); - await expect( - deviceBinding.bindDevice(DEVICE_ID_1, user.address) - ).to.be.revertedWith("Data Source is suspended"); - }); - }); - describe("Unbinding", function () { - beforeEach(async function () { - const DevicesRegistry = await hre.ethers.getContractFactory( - "DevicesRegistry" - ); - devicesRegistry = await DevicesRegistry.deploy(); - await devicesRegistry.deployed(); - - const DeviceBinding = await hre.ethers.getContractFactory( - "DeviceBinding" - ); - deviceBinding = await DeviceBinding.deploy(devicesRegistry.address); - await deviceBinding.deployed(); - - await devicesRegistry.registerDevice(DEVICE_ID_1); - await deviceBinding.bindDevice(DEVICE_ID_1, user.address); - }); - it("Should unbind a device", async function () { - await deviceBinding.unbindDevice(DEVICE_ID_1); - expect(await deviceBinding.getDeviceOwner(DEVICE_ID_1)).to.equal( - ZERO_ADDR - ); - expect(await deviceBinding.getDevicesCount()).to.equal(0); - expect(await deviceBinding.getOwnedDevices(user.address)).to.eql([]); - }); - it("Should unbind multiple devices", async function () { - await devicesRegistry.registerDevice(DEVICE_ID_2); - await deviceBinding.bindDevice(DEVICE_ID_2, user.address); - await deviceBinding.unbindDevice(DEVICE_ID_1); - await deviceBinding.unbindDevice(DEVICE_ID_2); - expect(await deviceBinding.getDeviceOwner(DEVICE_ID_1)).to.equal( - ZERO_ADDR - ); - }); - it("Should emit an event when unbinding a device", async function () { - await expect(deviceBinding.unbindDevice(DEVICE_ID_1)) - .to.emit(deviceBinding, "OwnershipRenounced") - .withArgs(DEVICE_ID_1); - }); - it("Should not unbind a device if it is not bound", async function () { - await expect(deviceBinding.unbindDevice(DEVICE_ID_2)).to.be.revertedWith( - "device is not bound" - ); - }); - it("Should not unbind a device if it is already unbound", async function () { - await deviceBinding.unbindDevice(DEVICE_ID_1); - await expect(deviceBinding.unbindDevice(DEVICE_ID_1)).to.be.revertedWith( - "device is not bound" - ); - }); - it("Should not unbind a device if it is not owned by the sender", async function () { - await expect( - deviceBinding.connect(badGuy).unbindDevice(DEVICE_ID_1) - ).to.be.revertedWith("not the device owner or admin"); - }); - }); -}); diff --git a/templates/extensions/binding/test/deviceBinding.test.ts b/templates/extensions/binding/test/deviceBinding.test.ts new file mode 100644 index 0000000..2b29bc2 --- /dev/null +++ b/templates/extensions/binding/test/deviceBinding.test.ts @@ -0,0 +1,239 @@ +import { expect } from "chai"; +import { ethers } from "hardhat"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; + +import { DeviceBinding, DeviceRegistry } from "../typechain-types"; +import { DEVICE_ID_1, DEVICE_ID_2, DEVICE_ID_3 } from "./fixtures"; + +const ZERO_ADDR = ethers.constants.AddressZero; + +async function setup() { + const deviceRegistry = await ethers.getContractFactory("DeviceRegistry"); + const deviceRegistryInstance = await deviceRegistry.deploy(); + await deviceRegistryInstance.deployed(); + + const DeviceBinding = await ethers.getContractFactory("DeviceBinding"); + const deviceBindingInstance = await DeviceBinding.deploy( + deviceRegistryInstance.address, + ); + await deviceBindingInstance.deployed(); + + const contracts = { + DeviceRegistry: deviceRegistryInstance as DeviceRegistry, + DeviceBinding: deviceBindingInstance as DeviceBinding, + }; + + return { + ...contracts, + }; +} + +describe("Device Binding", function () { + let [_, user, badGuy, user_2]: SignerWithAddress[] = []; + + before(async function () { + [_, user, badGuy, user_2] = await ethers.getSigners(); + }); + + describe("Initialization", function () { + it("Should initialize the contract with Device Registry", async function () { + const { DeviceBinding, DeviceRegistry } = await setup(); + expect(await DeviceBinding.deviceRegistry()).to.equal( + DeviceRegistry.address, + ); + }); + }); + describe("Binding", function () { + it("Should bind a device", async function () { + const { DeviceBinding, DeviceRegistry } = await setup(); + await DeviceRegistry.registerDevice(DEVICE_ID_1); + await DeviceBinding.bindDevice(DEVICE_ID_1, user.address); + expect(await DeviceBinding.getDeviceOwner(DEVICE_ID_1)).to.equal( + user.address, + ); + expect(await DeviceBinding.getDevicesCount()).to.equal(1); + expect(await DeviceBinding.getOwnedDevices(user.address)).to.eql([ + DEVICE_ID_1, + ]); + }); + it("Should bind multiple devices", async function () { + const { DeviceBinding, DeviceRegistry } = await setup(); + await DeviceRegistry.registerDevice(DEVICE_ID_1); + await DeviceRegistry.registerDevice(DEVICE_ID_2); + await DeviceBinding.bindDevice(DEVICE_ID_1, user.address); + await DeviceBinding.bindDevice(DEVICE_ID_2, user.address); + expect(await DeviceBinding.getDeviceOwner(DEVICE_ID_1)).to.equal( + user.address, + ); + expect(await DeviceBinding.getDeviceOwner(DEVICE_ID_2)).to.equal( + user.address, + ); + expect(await DeviceBinding.getDevicesCount()).to.equal(2); + expect(await DeviceBinding.getOwnedDevices(user.address)).to.eql([ + DEVICE_ID_1, + DEVICE_ID_2, + ]); + }); + it("Should bind multiple devices to multiple users", async function () { + const { DeviceBinding, DeviceRegistry } = await setup(); + await DeviceRegistry.registerDevice(DEVICE_ID_1); + await DeviceRegistry.registerDevice(DEVICE_ID_2); + await DeviceRegistry.registerDevice(DEVICE_ID_3); + + await DeviceBinding.bindDevice(DEVICE_ID_1, user.address); + await DeviceBinding.bindDevice(DEVICE_ID_2, user_2.address); + await DeviceBinding.bindDevice(DEVICE_ID_3, user.address); + + expect(await DeviceBinding.getDeviceOwner(DEVICE_ID_1)).to.equal( + user.address, + ); + expect(await DeviceBinding.getDeviceOwner(DEVICE_ID_2)).to.equal( + user_2.address, + ); + expect(await DeviceBinding.getDeviceOwner(DEVICE_ID_3)).to.equal( + user.address, + ); + expect(await DeviceBinding.getDevicesCount()).to.equal(3); + expect(await DeviceBinding.getOwnedDevices(user.address)).to.eql([ + DEVICE_ID_1, + DEVICE_ID_3, + ]); + expect(await DeviceBinding.getOwnedDevices(user_2.address)).to.eql([ + DEVICE_ID_2, + ]); + }); + it("Should emit an event when binding a device", async function () { + const { DeviceBinding, DeviceRegistry } = await setup(); + await DeviceRegistry.registerDevice(DEVICE_ID_1); + await expect(DeviceBinding.bindDevice(DEVICE_ID_1, user.address)) + .to.emit(DeviceBinding, "OwnershipAssigned") + .withArgs(DEVICE_ID_1, user.address); + }); + it("Should not bind a device if it is already bound", async function () { + const { DeviceBinding, DeviceRegistry } = await setup(); + await DeviceRegistry.registerDevice(DEVICE_ID_1); + await DeviceBinding.bindDevice(DEVICE_ID_1, user.address); + await expect( + DeviceBinding.bindDevice(DEVICE_ID_1, user.address), + ).to.be.revertedWith("device has already been bound"); + }); + it("Should not bind a device if it is not authorized", async function () { + const { DeviceBinding } = await setup(); + await expect( + DeviceBinding.bindDevice(DEVICE_ID_1, user.address), + ).to.be.revertedWith("device is not authorized"); + }); + it("Should not bind a device if it was suspended", async function () { + const { DeviceBinding, DeviceRegistry } = await setup(); + await DeviceRegistry.registerDevice(DEVICE_ID_1); + await DeviceRegistry.suspendDevice(DEVICE_ID_1); + await expect( + DeviceBinding.bindDevice(DEVICE_ID_1, user.address), + ).to.be.revertedWith("device is not authorized"); + }); + it("Should bind devices in batches", async function () { + const { DeviceBinding, DeviceRegistry } = await setup(); + await DeviceRegistry.registerDevices([DEVICE_ID_1, DEVICE_ID_2]); + + await DeviceBinding.bindDevices([DEVICE_ID_1, DEVICE_ID_2], user.address); + + expect(await DeviceBinding.getDeviceOwner(DEVICE_ID_1)).to.equal( + user.address, + ); + expect(await DeviceBinding.getDeviceOwner(DEVICE_ID_2)).to.equal( + user.address, + ); + }); + it("Should emit events when binding devices in batches", async function () { + const { DeviceBinding, DeviceRegistry } = await setup(); + await DeviceRegistry.registerDevices([DEVICE_ID_1, DEVICE_ID_2]); + + await expect( + DeviceBinding.bindDevices([DEVICE_ID_1, DEVICE_ID_2], user.address), + ) + .to.emit(DeviceBinding, "OwnershipAssigned") + .withArgs(DEVICE_ID_1, user.address) + .to.emit(DeviceBinding, "OwnershipAssigned") + .withArgs(DEVICE_ID_2, user.address); + }); + it("Should not bind devices in batches if they are already bound", async function () { + const { DeviceBinding, DeviceRegistry } = await setup(); + await DeviceRegistry.registerDevices([DEVICE_ID_1, DEVICE_ID_2]); + await DeviceBinding.bindDevice(DEVICE_ID_2, user.address); + await expect( + DeviceBinding.bindDevices([DEVICE_ID_1, DEVICE_ID_2], user.address), + ).to.be.revertedWith("device has already been bound"); + }); + it("Should not bind devices in batches if they are not authorized", async function () { + const { DeviceBinding, DeviceRegistry } = await setup(); + await DeviceRegistry.registerDevices([DEVICE_ID_1]); + await expect( + DeviceBinding.bindDevices([DEVICE_ID_1, DEVICE_ID_2], user.address), + ).to.be.revertedWith("device is not authorized"); + }); + }); + describe("Unbinding", function () { + it("Should unbind a device", async function () { + const { DeviceBinding, DeviceRegistry } = await setup(); + await DeviceRegistry.registerDevice(DEVICE_ID_1); + await DeviceBinding.bindDevice(DEVICE_ID_1, user.address); + + await DeviceBinding.unbindDevice(DEVICE_ID_1); + expect(await DeviceBinding.getDeviceOwner(DEVICE_ID_1)).to.equal( + ZERO_ADDR, + ); + expect(await DeviceBinding.getDevicesCount()).to.equal(0); + expect(await DeviceBinding.getOwnedDevices(user.address)).to.eql([]); + }); + it("Should unbind multiple devices", async function () { + const { DeviceBinding, DeviceRegistry } = await setup(); + await DeviceRegistry.registerDevice(DEVICE_ID_1); + await DeviceBinding.bindDevice(DEVICE_ID_1, user.address); + + await DeviceRegistry.registerDevice(DEVICE_ID_2); + await DeviceBinding.bindDevice(DEVICE_ID_2, user.address); + await DeviceBinding.unbindDevice(DEVICE_ID_1); + await DeviceBinding.unbindDevice(DEVICE_ID_2); + expect(await DeviceBinding.getDeviceOwner(DEVICE_ID_1)).to.equal( + ZERO_ADDR, + ); + }); + it("Should emit an event when unbinding a device", async function () { + const { DeviceBinding, DeviceRegistry } = await setup(); + await DeviceRegistry.registerDevice(DEVICE_ID_1); + await DeviceBinding.bindDevice(DEVICE_ID_1, user.address); + + await expect(DeviceBinding.unbindDevice(DEVICE_ID_1)) + .to.emit(DeviceBinding, "OwnershipRenounced") + .withArgs(DEVICE_ID_1); + }); + it("Should not unbind a device if it is not bound", async function () { + const { DeviceBinding, DeviceRegistry } = await setup(); + await DeviceRegistry.registerDevice(DEVICE_ID_1); + await DeviceBinding.bindDevice(DEVICE_ID_1, user.address); + + await expect(DeviceBinding.unbindDevice(DEVICE_ID_2)).to.be.revertedWith( + "device is not bound", + ); + }); + it("Should not unbind a device if it is already unbound", async function () { + const { DeviceBinding, DeviceRegistry } = await setup(); + await DeviceRegistry.registerDevice(DEVICE_ID_1); + await DeviceBinding.bindDevice(DEVICE_ID_1, user.address); + + await DeviceBinding.unbindDevice(DEVICE_ID_1); + await expect(DeviceBinding.unbindDevice(DEVICE_ID_1)).to.be.revertedWith( + "device is not bound", + ); + }); + it("Should not unbind a device if it is not owned by the sender", async function () { + const { DeviceBinding, DeviceRegistry } = await setup(); + await DeviceRegistry.registerDevice(DEVICE_ID_1); + await DeviceBinding.bindDevice(DEVICE_ID_1, user.address); + + await expect( + DeviceBinding.connect(badGuy).unbindDevice(DEVICE_ID_1), + ).to.be.revertedWith("not the device owner or admin"); + }); + }); +}); diff --git a/templates/extensions/binding/test/deviceRegistry.test.js b/templates/extensions/binding/test/deviceRegistry.test.js deleted file mode 100644 index d709d73..0000000 --- a/templates/extensions/binding/test/deviceRegistry.test.js +++ /dev/null @@ -1,11 +0,0 @@ -const { expect } = require("chai"); - -describe("Device Registry", function () { - it("Should deploy Device Registry", async function () { - const DevicesRegistry = await ethers.getContractFactory("DevicesRegistry"); - const registry = await DevicesRegistry.deploy(); - await registry.deployed(); - - expect(registry.address).to.not.equal(0); - }); -}); diff --git a/templates/extensions/binding/test/deviceRegistry.test.ts b/templates/extensions/binding/test/deviceRegistry.test.ts new file mode 100644 index 0000000..c0d5a1f --- /dev/null +++ b/templates/extensions/binding/test/deviceRegistry.test.ts @@ -0,0 +1,131 @@ +import { ethers } from "hardhat"; +import { expect } from "chai"; +import { DeviceRegistry } from "../typechain-types"; + +const DEVICE1 = + "0x9fa25c908f0955a0174b396f8f2dc4f7ec88316e37ee06bdc0115f3e5b6df6c1"; +const DEVICE2 = + "0xbca809a5cc11eb022d14ddfe49691325d7176d79973a2cf7f81795e18ec722ed"; +const DEVICE3 = + "0xffe87b6de33486c8bbfd575b45cf72be800d387bb803f9187528b22125045360"; +const DEVICE4 = + "0x6ac641c08cddf37c7f5018f418b6c10d9fcfe1498423f4a2cb35cb2fb3d0c796"; + +async function setup() { + const deviceRegistry = await ethers.getContractFactory("DeviceRegistry"); + const deviceRegistryInstance = await deviceRegistry.deploy(); + await deviceRegistryInstance.deployed(); + + const contracts = { + DeviceRegistry: deviceRegistryInstance as DeviceRegistry, + }; + + return { + ...contracts, + }; +} + +describe("Device Registry", function () { + it("Should deploy Device Registry", async function () { + const { DeviceRegistry } = await setup(); + + expect(DeviceRegistry.address).to.not.equal(0); + }); + describe("Device registration", function () { + it("Should register one device", async function () { + const { DeviceRegistry } = await setup(); + + await DeviceRegistry.registerDevice(DEVICE1); + + const isAuthorized = await DeviceRegistry.isAuthorizedDevice(DEVICE1); + expect(isAuthorized).to.equal(true); + }); + it("Should revert if one of the devices in batch is already registered", async function () { + const { DeviceRegistry } = await setup(); + + await DeviceRegistry.registerDevice(DEVICE1); + + await expect( + DeviceRegistry.registerDevices([DEVICE1, DEVICE2]), + ).to.be.revertedWith("Device already registered"); + }); + it("Should register devices in a batch", async function () { + const { DeviceRegistry } = await setup(); + + await DeviceRegistry.registerDevices([ + DEVICE1, + DEVICE2, + DEVICE3, + DEVICE4, + ]); + + const isAuthorized1 = await DeviceRegistry.isAuthorizedDevice(DEVICE1); + const isAuthorized2 = await DeviceRegistry.isAuthorizedDevice(DEVICE2); + const isAuthorized3 = await DeviceRegistry.isAuthorizedDevice(DEVICE3); + const isAuthorized4 = await DeviceRegistry.isAuthorizedDevice(DEVICE4); + + expect(isAuthorized1).to.equal(true); + expect(isAuthorized2).to.equal(true); + expect(isAuthorized3).to.equal(true); + expect(isAuthorized4).to.equal(true); + }); + it("Should emit events when registering devices in a batch", async function () { + const { DeviceRegistry } = await setup(); + + const tx = await DeviceRegistry.registerDevices([DEVICE1, DEVICE2]); + + await expect(tx) + .to.emit(DeviceRegistry, "DeviceRegistered") + .withArgs(DEVICE1); + await expect(tx) + .to.emit(DeviceRegistry, "DeviceRegistered") + .withArgs(DEVICE2); + }); + it("Should show registered devices and register unregistered devices", async function () { + const { DeviceRegistry } = await setup(); + const devicesIds = [DEVICE1, DEVICE2, DEVICE3, DEVICE4]; + + await DeviceRegistry.registerDevice(DEVICE3); + + const isAuthorizedBatch = + await DeviceRegistry.isAuthorizedDevices(devicesIds); + + expect(isAuthorizedBatch).to.deep.equal([false, false, true, false]); + + await DeviceRegistry.registerDevices( + devicesIds.filter((_, id) => !isAuthorizedBatch[id]), + ); + + const isAuthorizedBatch2 = + await DeviceRegistry.isAuthorizedDevices(devicesIds); + + expect(isAuthorizedBatch2).to.deep.equal([true, true, true, true]); + }); + }); + describe("Device status", function () { + it("Should return false if device is not registered", async function () { + const { DeviceRegistry } = await setup(); + + const isAuthorized = await DeviceRegistry.isAuthorizedDevice(DEVICE1); + expect(isAuthorized).to.equal(false); + }); + it("Should return false if device is suspended", async function () { + const { DeviceRegistry } = await setup(); + + await DeviceRegistry.registerDevice(DEVICE1); + await DeviceRegistry.suspendDevice(DEVICE1); + + const isAuthorized = await DeviceRegistry.isAuthorizedDevice(DEVICE1); + expect(isAuthorized).to.equal(false); + }); + it("Should return false if device has been removed", async function () { + const { DeviceRegistry } = await setup(); + + await DeviceRegistry.registerDevice(DEVICE1); + await DeviceRegistry.removeDevice(DEVICE1); + + const isAuthorized = await DeviceRegistry.isAuthorizedDevice(DEVICE1); + expect(isAuthorized).to.equal(false); + }); + }); +}); diff --git a/templates/extensions/erc1155/contracts/DeviceRewards.sol b/templates/extensions/erc1155/contracts/DeviceRewards.sol new file mode 100644 index 0000000..0f588c1 --- /dev/null +++ b/templates/extensions/erc1155/contracts/DeviceRewards.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; +import "@openzeppelin/contracts/access/AccessControl.sol"; +import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol"; + +contract DeviceRewards is ERC1155, AccessControl, ERC1155Supply { + bytes32 public constant URI_SETTER_ROLE = keccak256("URI_SETTER_ROLE"); + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + + mapping(uint256 => mapping(address => uint256)) public allowance; + + string public name; + + constructor(string memory newuri, string memory _name) ERC1155(newuri) { + name = _name; + _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); + _grantRole(URI_SETTER_ROLE, msg.sender); + _grantRole(MINTER_ROLE, msg.sender); + } + + function setURI(string memory newuri) public onlyRole(URI_SETTER_ROLE) { + _setURI(newuri); + } + + function mint( + address account, + uint256 id, + uint256 amount, + bytes memory data + ) public onlyRole(MINTER_ROLE) { + _mint(account, id, amount, data); + } + + function mintFromAllowance(uint256 id, bytes memory data) public { + uint256 amount = allowance[id][msg.sender]; + require(amount > 0, "Insufficient allowance"); + allowance[id][msg.sender] -= amount; + _mint(msg.sender, id, amount, data); + } + + function mintBatch( + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) public onlyRole(MINTER_ROLE) { + _mintBatch(to, ids, amounts, data); + } + + function approve( + address to, + uint256 id, + uint256 amount + ) public onlyRole(MINTER_ROLE) { + allowance[id][to] += amount; + } + + // The following functions are overrides required by Solidity. + + function _beforeTokenTransfer( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal override(ERC1155, ERC1155Supply) { + super._beforeTokenTransfer(operator, from, to, ids, amounts, data); + } + + function supportsInterface( + bytes4 interfaceId + ) public view override(ERC1155, AccessControl) returns (bool) { + return super.supportsInterface(interfaceId); + } +} diff --git a/templates/extensions/erc1155/deploy/04-deploy-device-rewards.ts b/templates/extensions/erc1155/deploy/04-deploy-device-rewards.ts new file mode 100644 index 0000000..d5cb3c4 --- /dev/null +++ b/templates/extensions/erc1155/deploy/04-deploy-device-rewards.ts @@ -0,0 +1,38 @@ +import { DeployFunction } from "hardhat-deploy/types"; +import { addEnvVarToWSProjectConfig } from "../utils/update-envs"; + +const func: DeployFunction = async ({ + getNamedAccounts, + deployments, + getChainId, +}) => { + const { deploy } = deployments; + const chainId = await getChainId(); + const { deployer } = await getNamedAccounts(); + + const REWARDS_CONTRACT_NAME = + process.env.REWARDS_CONTRACT_NAME || "Device Rewards"; + const REWARDS_URI = process.env.REWARDS_URI || ""; + + const tx = await deploy("DeviceRewards", { + from: deployer, + args: [REWARDS_URI, REWARDS_CONTRACT_NAME], + log: true, + }); + + console.log("DeviceRewards deployed at block: ", tx.receipt?.blockNumber); + + if (chainId !== "31337") { + addEnvVarToWSProjectConfig({ + envName: "REWARDS_CONTRACT_ADDRESS", + envValue: tx.address, + }); + addEnvVarToWSProjectConfig({ + envName: "CHAIN_ID", + envValue: chainId, + }); + } +}; + +export default func; +func.tags = ["DeviceRewards"]; diff --git a/templates/extensions/erc1155/tasks/rewards.ts b/templates/extensions/erc1155/tasks/rewards.ts new file mode 100644 index 0000000..079e504 --- /dev/null +++ b/templates/extensions/erc1155/tasks/rewards.ts @@ -0,0 +1,39 @@ +import { task } from "hardhat/config"; + +task( + "grant-rewards-minter", + "Grant rewards minter role to an address", +).setAction(async (_, hre) => { + const { deployments } = hre; + const [deployer] = await hre.ethers.getSigners(); + + const DeviceRewards = await deployments.get("DeviceRewards"); + const rewards = await hre.ethers.getContractAt( + "DeviceRewards", + DeviceRewards.address, + deployer, + ); + + const minterRole = await rewards.MINTER_ROLE(); + const tx = await rewards.grantRole(minterRole, process.env.OPERATOR_ADDRESS); + await tx.wait(); + + console.log(`Minter role granted to ${process.env.OPERATOR_ADDRESS}`); +}); + +task("update-rewards-uri", "Update rewards uri").setAction(async (_, hre) => { + const { deployments } = hre; + const [deployer] = await hre.ethers.getSigners(); + + const DeviceRewards = await deployments.get("DeviceRewards"); + const rewards = await hre.ethers.getContractAt( + "DeviceRewards", + DeviceRewards.address, + deployer, + ); + + const tx = await rewards.setURI(process.env.REWARDS_URI || ""); + await tx.wait(); + + console.log(`DeviceRewards uri updated to ${process.env.REWARDS_URI}`); +}); diff --git a/templates/extensions/erc1155/test/rewards.test.ts b/templates/extensions/erc1155/test/rewards.test.ts new file mode 100644 index 0000000..8d0bc16 --- /dev/null +++ b/templates/extensions/erc1155/test/rewards.test.ts @@ -0,0 +1,95 @@ +import { ethers } from "hardhat"; +import { expect } from "chai"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; + +import { DeviceRewards } from "../typechain-types"; +import { REWARDS_CONTRACT_NAME } from "./fixtures"; + +const TIER_1_URI = + "ipfs://QmQr1X5o4Jhdeb6BMviYN5anHMCjeMHxQPcu45YpN8SfQD/{id}.json"; + +const TIER_1_ID = 0; + +async function setup() { + const rewards = await ethers.getContractFactory("DeviceRewards"); + const rewardsInstance = await rewards.deploy( + TIER_1_URI, + REWARDS_CONTRACT_NAME, + ); + await rewardsInstance.deployed(); + + const contracts = { + DeviceRewards: rewardsInstance as DeviceRewards, + }; + + return { + ...contracts, + }; +} + +async function grantMinterRole( + rewards: DeviceRewards, + minter: SignerWithAddress, +) { + const minterRole = await rewards.MINTER_ROLE(); + await rewards.grantRole(minterRole, minter.address); +} + +describe("DeviceRewards", function () { + let [admin, user, badGuy, minter, uriSetter]: SignerWithAddress[] = []; + let rewards: DeviceRewards; + before(async function () { + [admin, user, badGuy, minter, uriSetter] = await ethers.getSigners(); + }); + beforeEach(async function () { + const { DeviceRewards } = await setup(); + rewards = DeviceRewards; + await grantMinterRole(rewards, minter); + }); + it("Should deploy DeviceRewards", async function () { + expect(rewards.address).to.not.equal(0); + expect(await rewards.name()).to.equal(REWARDS_CONTRACT_NAME); + }); + it("Should initialize with an uri", async function () { + expect(await rewards.uri(TIER_1_ID)).to.equal(TIER_1_URI); + }); + it("Uri setter should set token uri", async function () { + const uriSetterRole = await rewards.URI_SETTER_ROLE(); + await rewards.grantRole(uriSetterRole, uriSetter.address); + + await rewards.connect(uriSetter).setURI(TIER_1_URI + "2"); + + expect(await rewards.uri(TIER_1_ID)).to.equal(TIER_1_URI + "2"); + }); + it("Minter should be able to mint one token to a user", async function () { + await rewards.connect(minter).mint(user.address, TIER_1_ID, 1, []); + expect(await rewards.balanceOf(user.address, TIER_1_ID)).to.equal(1); + }); + it("Minter should be able to mint multiple tokens to a user", async function () { + await rewards.connect(minter).mint(user.address, TIER_1_ID, 2, []); + expect(await rewards.balanceOf(user.address, TIER_1_ID)).to.equal(2); + }); + it("Minter should be able to approve a user to mint token of a tier", async function () { + await rewards.connect(minter).approve(user.address, TIER_1_ID, 1); + expect(await rewards.allowance(TIER_1_ID, user.address)).to.equal(1); + }); + it("Minter should be able to approve a user to mint multiple tokens of a tier", async function () { + await rewards.connect(minter).approve(user.address, TIER_1_ID, 2); + expect(await rewards.allowance(TIER_1_ID, user.address)).to.equal(2); + }); + it("Minter should be able to approve a user to mint token of a tier multiple times", async function () { + await rewards.connect(minter).approve(user.address, TIER_1_ID, 1); + await rewards.connect(minter).approve(user.address, TIER_1_ID, 2); + expect(await rewards.allowance(TIER_1_ID, user.address)).to.equal(3); + }); + it("User cannot set allowance", async function () { + await expect(rewards.connect(user).approve(user.address, TIER_1_ID, 1)).to + .be.reverted; + }); + it("User should be able to mint a token of a tier", async function () { + await rewards.connect(minter).approve(user.address, TIER_1_ID, 1); + await rewards.connect(user).mintFromAllowance(TIER_1_ID, []); + expect(await rewards.balanceOf(user.address, TIER_1_ID)).to.equal(1); + expect(await rewards.allowance(TIER_1_ID, user.address)).to.equal(0); + }); +}); diff --git a/templates/extensions/erc20/deploy/02-deploy-erc20-token.js b/templates/extensions/erc20/deploy/02-deploy-erc20-token.ts similarity index 58% rename from templates/extensions/erc20/deploy/02-deploy-erc20-token.js rename to templates/extensions/erc20/deploy/02-deploy-erc20-token.ts index 26f65c1..151db2d 100644 --- a/templates/extensions/erc20/deploy/02-deploy-erc20-token.js +++ b/templates/extensions/erc20/deploy/02-deploy-erc20-token.ts @@ -1,4 +1,6 @@ -module.exports = async ({ getNamedAccounts, deployments }) => { +import { DeployFunction } from "hardhat-deploy/types"; + +const func: DeployFunction = async ({ getNamedAccounts, deployments }) => { const { deploy } = deployments; const { deployer } = await getNamedAccounts(); @@ -10,4 +12,6 @@ module.exports = async ({ getNamedAccounts, deployments }) => { console.log("Token deployed at block: ", tx.receipt.blockNumber); }; -module.exports.tags = ["Token"]; + +func.tags = ["Token"]; +export default func; diff --git a/templates/extensions/erc20/tasks/erc20.js b/templates/extensions/erc20/tasks/erc20.ts similarity index 78% rename from templates/extensions/erc20/tasks/erc20.js rename to templates/extensions/erc20/tasks/erc20.ts index 8e81e11..1f85380 100644 --- a/templates/extensions/erc20/tasks/erc20.js +++ b/templates/extensions/erc20/tasks/erc20.ts @@ -1,3 +1,5 @@ +import { task } from "hardhat/config"; + task("add-erc20-minter", "Grant erc20 token minter role to an address") .addParam("address", "Address to grant minter role to") .setAction(async (taskArgs, hre) => { @@ -6,7 +8,11 @@ task("add-erc20-minter", "Grant erc20 token minter role to an address") const [deployer] = await ethers.getSigners(); const Token = await deployments.get("Token"); - const token = await ethers.getContractAt("Token", Token.address, deployer); + const token = await hre.ethers.getContractAt( + "Token", + Token.address, + deployer, + ); const minterRole = await token.MINTER_ROLE(); const tx = await token.grantRole(minterRole, address); diff --git a/templates/extensions/erc20/test/token.test.js b/templates/extensions/erc20/test/token.test.ts similarity index 83% rename from templates/extensions/erc20/test/token.test.js rename to templates/extensions/erc20/test/token.test.ts index e9830c6..fe416d8 100644 --- a/templates/extensions/erc20/test/token.test.js +++ b/templates/extensions/erc20/test/token.test.ts @@ -1,4 +1,5 @@ -const { expect } = require("chai"); +import { expect } from "chai"; +import { ethers } from "hardhat"; describe("Token", function () { it("Should deploy Token", async function () { diff --git a/templates/extensions/erc721/deploy/03-deploy-nft.js b/templates/extensions/erc721/deploy/03-deploy-nft.ts similarity index 59% rename from templates/extensions/erc721/deploy/03-deploy-nft.js rename to templates/extensions/erc721/deploy/03-deploy-nft.ts index 25817b3..1e33202 100644 --- a/templates/extensions/erc721/deploy/03-deploy-nft.js +++ b/templates/extensions/erc721/deploy/03-deploy-nft.ts @@ -1,4 +1,6 @@ -module.exports = async ({ getNamedAccounts, deployments }) => { +import { DeployFunction } from "hardhat-deploy/types"; + +const func: DeployFunction = async ({ getNamedAccounts, deployments }) => { const { deploy } = deployments; const { deployer } = await getNamedAccounts(); @@ -10,4 +12,6 @@ module.exports = async ({ getNamedAccounts, deployments }) => { console.log("ExampleTokenERC721 deployed at block: ", tx.receipt.blockNumber); }; -module.exports.tags = ["ExampleTokenERC721"]; + +func.tags = ["ExampleTokenERC721"]; +export default func; diff --git a/templates/extensions/erc721/tasks/nft.js b/templates/extensions/erc721/tasks/nft.ts similarity index 74% rename from templates/extensions/erc721/tasks/nft.js rename to templates/extensions/erc721/tasks/nft.ts index 2b9ec93..b898670 100644 --- a/templates/extensions/erc721/tasks/nft.js +++ b/templates/extensions/erc721/tasks/nft.ts @@ -1,3 +1,5 @@ +import { task } from "hardhat/config"; + task("hello-world", "Prints hello world").setAction(async (taskArgs, hre) => { console.log("Hello World!"); }); diff --git a/templates/extensions/erc721/test/nft.test.js b/templates/extensions/erc721/test/nft.test.ts similarity index 83% rename from templates/extensions/erc721/test/nft.test.js rename to templates/extensions/erc721/test/nft.test.ts index a344640..9b0436a 100644 --- a/templates/extensions/erc721/test/nft.test.js +++ b/templates/extensions/erc721/test/nft.test.ts @@ -1,4 +1,5 @@ -const { expect } = require("chai"); +import { expect } from "chai"; +import { ethers } from "hardhat"; describe("NFT", function () { it("Should deploy NFT", async function () { diff --git a/templates/extensions/sbt/contracts/DeviceSBT.sol b/templates/extensions/sbt/contracts/DeviceSBT.sol new file mode 100644 index 0000000..a4e40e4 --- /dev/null +++ b/templates/extensions/sbt/contracts/DeviceSBT.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts/access/AccessControl.sol"; + +contract DeviceSBT is ERC721, AccessControl { + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + string private _deviceUri = ""; + + mapping(uint256 => address) public sbtApprovals; + + constructor( + string memory uri_, + string memory _name, + string memory _symbol + ) ERC721(_name, _symbol) { + _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); + _grantRole(MINTER_ROLE, msg.sender); + _deviceUri = uri_; + } + + function setURI(string memory uri) public onlyRole(DEFAULT_ADMIN_ROLE) { + _deviceUri = uri; + } + + function tokenURI( + uint256 tokenId + ) public view override returns (string memory) { + _requireMinted(tokenId); + return _deviceUri; + } + + function approveSBT( + address to, + uint256 tokenId + ) public onlyRole(MINTER_ROLE) { + sbtApprovals[tokenId] = to; + } + + function mintSBT(uint256 tokenId) public { + address to = sbtApprovals[tokenId]; + _safeMint(to, tokenId); + } + + function safeMint( + address to, + bytes32 deviceId + ) public onlyRole(MINTER_ROLE) { + _safeMint(to, uint256(deviceId)); + } + + function _beforeTokenTransfer( + address from, + address to, + uint256 tokenId, + uint256 batchSize + ) internal override(ERC721) { + require(from == address(0), "DeviceSBT: Only minting allowed"); + super._beforeTokenTransfer(from, to, tokenId, batchSize); + } + + // The following functions are overrides required by Solidity. + + function supportsInterface( + bytes4 interfaceId + ) public view override(ERC721, AccessControl) returns (bool) { + return super.supportsInterface(interfaceId); + } +} diff --git a/templates/extensions/sbt/deploy/05-deploy-device-sbt.ts b/templates/extensions/sbt/deploy/05-deploy-device-sbt.ts new file mode 100644 index 0000000..10b6b7c --- /dev/null +++ b/templates/extensions/sbt/deploy/05-deploy-device-sbt.ts @@ -0,0 +1,38 @@ +import { addEnvVarToWSProjectConfig } from "../utils/update-envs"; +import { DeployFunction } from "hardhat-deploy/types"; + +const func: DeployFunction = async ({ + getNamedAccounts, + deployments, + getChainId, +}) => { + const { deploy } = deployments; + const chainId = await getChainId(); + const { deployer } = await getNamedAccounts(); + + const SBT_CONTRACT_NAME = process.env.SBT_CONTRACT_NAME || "Device SBT"; + const SBT_CONTRACT_SYMBOL = process.env.SBT_CONTRACT_SYMBOL || "DSBT"; + const SBT_URI = process.env.SBT_URI || ""; + + const tx = await deploy("DeviceSBT", { + from: deployer, + args: [SBT_URI, SBT_CONTRACT_NAME, SBT_CONTRACT_SYMBOL], + log: true, + }); + + console.log("DeviceSBT deployed at block: ", tx.receipt?.blockNumber); + + if (chainId !== "31337") { + addEnvVarToWSProjectConfig({ + envName: "SBT_CONTRACT_ADDRESS", + envValue: tx.address, + }); + addEnvVarToWSProjectConfig({ + envName: "CHAIN_ID", + envValue: chainId, + }); + } +}; + +export default func; +func.tags = ["DeviceSBT"]; diff --git a/templates/extensions/sbt/tasks/sbt.ts b/templates/extensions/sbt/tasks/sbt.ts new file mode 100644 index 0000000..bc68e0e --- /dev/null +++ b/templates/extensions/sbt/tasks/sbt.ts @@ -0,0 +1,74 @@ +import { task } from "hardhat/config"; + +task("grant-sbt-minter", "Grant sbt minter role to an address").setAction( + async (_, hre) => { + const { deployments } = hre; + const [deployer] = await hre.ethers.getSigners(); + + const DeviceSBT = await deployments.get("DeviceSBT"); + const sbt = await hre.ethers.getContractAt( + "DeviceSBT", + DeviceSBT.address, + deployer, + ); + + const minterRole = await sbt.MINTER_ROLE(); + const tx = await sbt.grantRole(minterRole, process.env.OPERATOR_ADDRESS); + await tx.wait(); + + console.log(`Minter role granted to ${process.env.OPERATOR_ADDRESS}`); + }, +); + +task("update-sbt-uri", "Update sbt URI").setAction(async (_, hre) => { + const { deployments } = hre; + const [deployer] = await hre.ethers.getSigners(); + + const DeviceSBT = await deployments.get("DeviceSBT"); + const sbt = await hre.ethers.getContractAt( + "DeviceSBT", + DeviceSBT.address, + deployer, + ); + + const tx = await sbt.setURI(process.env.SBT_URI); + await tx.wait(); + + console.log(`SBT URI updated to ${process.env.SBT_URI}`); +}); + +task("check-sbt-balance", "Check sbt balance of an address") + .addParam("address", "Address to check") + .setAction(async (taskArgs, hre) => { + const { address } = taskArgs; + const { deployments } = hre; + const [deployer] = await hre.ethers.getSigners(); + + const DeviceSBT = await deployments.get("DeviceSBT"); + const sbt = await hre.ethers.getContractAt( + "DeviceSBT", + DeviceSBT.address, + deployer, + ); + + const balance = await sbt.balanceOf(address); + console.log(`Balance of ${address}: ${balance}`); + }); + +task("get-sbt-uri", "Get sbt URI") + .addParam("id", "Token ID") + .setAction(async (taskArgs, hre) => { + const { id } = taskArgs; + const { deployments } = hre; + const [deployer] = await hre.ethers.getSigners(); + + const DeviceSBT = await deployments.get("DeviceSBT"); + const sbt = await hre.ethers.getContractAt( + "DeviceSBT", + DeviceSBT.address, + deployer, + ); + + const uri = await sbt.tokenURI(id); + console.log(`URI of token ${id}: ${uri}`); + }); diff --git a/templates/extensions/sbt/test/sbt.test.ts b/templates/extensions/sbt/test/sbt.test.ts new file mode 100644 index 0000000..43e41cc --- /dev/null +++ b/templates/extensions/sbt/test/sbt.test.ts @@ -0,0 +1,121 @@ +import { ethers } from "hardhat"; +import { expect } from "chai"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; + +import { + DEVICE_ID_1, + DEVICE_ID_2, + URI_EXAMPLE, + SBT_CONTRACT_NAME, + SBT_CONTRACT_SYMBOL, +} from "./fixtures"; +import { DeviceSBT } from "../typechain-types"; + +async function setup() { + const sbt = await ethers.getContractFactory("DeviceSBT"); + const sbtInstance = await sbt.deploy( + URI_EXAMPLE, + SBT_CONTRACT_NAME, + SBT_CONTRACT_SYMBOL, + ); + await sbtInstance.deployed(); + + const contracts = { + DeviceSBT: sbtInstance as DeviceSBT, + }; + + return { + ...contracts, + }; +} + +async function grantMinterRole(sbt: DeviceSBT, minter: SignerWithAddress) { + const minterRole = await sbt.MINTER_ROLE(); + await sbt.grantRole(minterRole, minter.address); +} + +describe("DeviceSBT", function () { + let [admin, user, badGuy, minter]: SignerWithAddress[] = []; + let sbt: DeviceSBT; + before(async function () { + [admin, user, badGuy, minter] = await ethers.getSigners(); + }); + beforeEach(async function () { + const { DeviceSBT } = await setup(); + sbt = DeviceSBT; + await grantMinterRole(sbt, minter); + }); + it("Should deploy DeviceSBT", async function () { + expect(await sbt.name()).to.equal(SBT_CONTRACT_NAME); + expect(await sbt.symbol()).to.equal(SBT_CONTRACT_SYMBOL); + }); + it("Minter should be able to mint one token to a user", async function () { + await sbt.connect(minter).safeMint(user.address, DEVICE_ID_1); + + expect(await sbt.balanceOf(user.address)).to.equal(1); + expect(await sbt.ownerOf(DEVICE_ID_1)).to.equal(user.address); + expect(await sbt.tokenURI(DEVICE_ID_1)).to.equal(URI_EXAMPLE); + }); + it("Minter should be able to mint multiple tokens to a user", async function () { + await sbt.connect(minter).safeMint(user.address, DEVICE_ID_1); + await sbt.connect(minter).safeMint(user.address, DEVICE_ID_2); + + expect(await sbt.balanceOf(user.address)).to.equal(2); + }); + it("User should not be able to transfer SBT to another user", async function () { + await sbt.connect(minter).safeMint(user.address, DEVICE_ID_1); + + await expect( + sbt.connect(user).transferFrom(user.address, badGuy.address, DEVICE_ID_1), + ).to.be.revertedWith("DeviceSBT: Only minting allowed"); + }); + it("Minter cannot mint a token with an already used sbt id", async function () { + await sbt.connect(minter).safeMint(user.address, DEVICE_ID_1); + + await expect( + sbt.connect(minter).safeMint(user.address, DEVICE_ID_1), + ).to.be.revertedWith("ERC721: token already minted"); + }); + it("Minter can approve a user to mint a sbt", async function () { + await sbt.connect(minter).approveSBT(user.address, DEVICE_ID_1); + expect(await sbt.sbtApprovals(DEVICE_ID_1)).to.equal(user.address); + }); + it("Should throw if user tries to approve a sbt", async function () { + await expect(sbt.connect(badGuy).approveSBT(badGuy.address, DEVICE_ID_1)).to + .be.reverted; + }); + it("User can mint a sbt if approved", async function () { + await sbt.connect(minter).approveSBT(user.address, DEVICE_ID_1); + await sbt.connect(user).mintSBT(DEVICE_ID_1); + + expect(await sbt.balanceOf(user.address)).to.equal(1); + expect(await sbt.ownerOf(DEVICE_ID_1)).to.equal(user.address); + expect(await sbt.tokenURI(DEVICE_ID_1)).to.equal(URI_EXAMPLE); + }); + it("User cannot mint a sbt if not approved", async function () { + await expect(sbt.connect(user).mintSBT(DEVICE_ID_1)).to.be.revertedWith( + "ERC721: mint to the zero address", + ); + }); + it("User cannot mint a sbt if already minted", async function () { + await sbt.connect(minter).approveSBT(user.address, DEVICE_ID_1); + await sbt.connect(user).mintSBT(DEVICE_ID_1); + + await expect(sbt.connect(user).mintSBT(DEVICE_ID_1)).to.be.revertedWith( + "ERC721: token already minted", + ); + }); + it("Minter cannot mint a sbt if already minted by user", async function () { + await sbt.connect(minter).approveSBT(user.address, DEVICE_ID_1); + await sbt.connect(user).mintSBT(DEVICE_ID_1); + + await expect( + sbt.connect(minter).safeMint(user.address, DEVICE_ID_1), + ).to.be.revertedWith("ERC721: token already minted"); + }); + it("admin can update uri", async function () { + await sbt.connect(admin).setURI(URI_EXAMPLE + "2"); + await sbt.connect(minter).safeMint(user.address, DEVICE_ID_1); + expect(await sbt.tokenURI(DEVICE_ID_1)).to.equal(URI_EXAMPLE + "2"); + }); +}); diff --git a/templates/light/applet/asconfig.json b/templates/light/applet/asconfig.json index 8776597..e50a76c 100644 --- a/templates/light/applet/asconfig.json +++ b/templates/light/applet/asconfig.json @@ -19,4 +19,4 @@ "options": { "bindings": "esm" } -} \ No newline at end of file +} diff --git a/templates/simulator/.env.template b/templates/simulator/.env.template index d638bbb..154f0a0 100644 --- a/templates/simulator/.env.template +++ b/templates/simulator/.env.template @@ -1,3 +1,3 @@ -PUB_TOKEN=token -PROJECT_NAME=project_name +API_TOKEN=token +HTTP_ROUTE=http_route EVENT_TYPE=type \ No newline at end of file diff --git a/templates/simulator/config.ts b/templates/simulator/config.ts deleted file mode 100644 index 9feebe2..0000000 --- a/templates/simulator/config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import dotenv from "dotenv"; -dotenv.config(); - -export default { - PUB_TOKEN: process.env.PUB_TOKEN || "", - W3BSTREAM_ENDPOINT: `https://devnet-prod.w3bstream.com/api/w3bapp/event/${ - process.env.PROJECT_NAME - }?eventType=${process.env.EVENT_TYPE || "DEFAULT"}`, -}; diff --git a/templates/simulator/index.ts b/templates/simulator/index.ts index 70b0a7d..a3857da 100644 --- a/templates/simulator/index.ts +++ b/templates/simulator/index.ts @@ -1,13 +1,14 @@ import { Simulator } from "@w3bstream/w3bstream-http-client-simulator"; +import "dotenv/config"; import dataGenerator from "./generator"; -import config from "./config"; - -const { PUB_TOKEN, W3BSTREAM_ENDPOINT} = config; +const API_TOKEN = process.env.API_TOKEN || ""; +const W3BSTREAM_ENDPOINT = process.env.HTTP_ROUTE || ""; +const EVENT_TYPE = process.env.EVENT_TYPE || ""; const MSG_INTERVAL_SEC = 10; -const simulator = new Simulator(PUB_TOKEN, W3BSTREAM_ENDPOINT); +const simulator = new Simulator(API_TOKEN, W3BSTREAM_ENDPOINT); simulator.init(); @@ -16,7 +17,7 @@ simulator.dataPointGenerator = dataGenerator; async function start() { try { console.log("Starting simulator"); - simulator.powerOn(MSG_INTERVAL_SEC) + simulator.powerOn(MSG_INTERVAL_SEC, EVENT_TYPE); } catch (error) { console.log(error); } diff --git a/templates/simulator/package.json b/templates/simulator/package.json index 6819bdc..256dd6e 100644 --- a/templates/simulator/package.json +++ b/templates/simulator/package.json @@ -10,12 +10,11 @@ }, "author": "", "license": "ISC", - "dependencies": { - "@w3bstream/w3bstream-http-client-simulator": "^2.0.3", - "dotenv": "^16.0.3", - "typescript": "^5.0.4" - }, "devDependencies": { - "@types/node": "^18.15.11" + "@w3bstream/w3bstream-http-client-simulator": "^3.1.0", + "dotenv": "^16.0.3", + "typescript": "^5.0.4", + "@types/node": "^18.15.11", + "ts-node": "^10.9.1" } } \ No newline at end of file