diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ff9bfb0527..db44f85f57 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -89,7 +89,6 @@ # "examples/complex_types", # "examples/func_types", # "examples/generics", -# "examples/guard_functions", # "examples/inline_types", # "examples/motoko_examples/superheroes", # "examples/motoko_examples/threshold_ecdsa", @@ -150,8 +149,9 @@ jobs: "examples/composite_queries", "examples/cross_canister_calls", "examples/cycles", - "examples/ethereum_json_rpc", "examples/date", + "examples/ethereum_json_rpc", + "examples/guard_functions", "examples/heartbeat", "examples/ic_api", "examples/imports", diff --git a/examples/guard_functions/package-lock.json b/examples/guard_functions/package-lock.json index 0dc7c2c410..56d21a9b7a 100644 --- a/examples/guard_functions/package-lock.json +++ b/examples/guard_functions/package-lock.json @@ -8,9 +8,9 @@ "azle": "0.17.1" }, "devDependencies": { - "@dfinity/agent": "0.18.1", - "ts-node": "10.7.0", - "typescript": "4.6.3" + "@dfinity/agent": "^0.19.2", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" } }, "node_modules/@cspotcode/source-map-consumer": { @@ -33,36 +33,76 @@ } }, "node_modules/@dfinity/agent": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@dfinity/agent/-/agent-0.18.1.tgz", - "integrity": "sha512-nMFK/y0ZkPfQYECdojmltXsBIdGvXa1Sxa4rDV2cibEq9lsjWMEIUqPsiBaNHuwuz+gzsGVq4N2b9umKQIQaRQ==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@dfinity/agent/-/agent-0.19.2.tgz", + "integrity": "sha512-KLRWEjeU9SyyaS7IBVJ9ZUcufxufr55e/kRIyClK157+0pkTG9a8xKjUIMx3QzKvLsqqzXL238nWwdoP6jAD8g==", "dev": true, "dependencies": { + "@noble/hashes": "^1.3.1", "base64-arraybuffer": "^0.2.0", "borc": "^2.1.1", - "js-sha256": "0.9.0", "simple-cbor": "^0.4.1" }, "peerDependencies": { - "@dfinity/candid": "^0.18.1", - "@dfinity/principal": "^0.18.1" + "@dfinity/candid": "^0.19.2", + "@dfinity/principal": "^0.19.2" } }, "node_modules/@dfinity/candid": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-0.18.1.tgz", - "integrity": "sha512-/PC3wDnrGcWhaF/veYKevcAAn5A5jK0mRkVKcz0YxK/a78Ai9wMJg0fUk7aoyZryCOz8JPVgR4nK1/zTmvEBHg==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-0.19.2.tgz", + "integrity": "sha512-X2hCqNMhnnmwtnOc0WnymOZYx3qphjEMuSYbBr7tMIkV7Hwt9BmXXlLnQTxUytTPxf+3he0GcS3KzsSQ9CK8ew==", "dev": true, - "peer": true + "peer": true, + "peerDependencies": { + "@dfinity/principal": "^0.19.2" + } }, "node_modules/@dfinity/principal": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-0.18.1.tgz", - "integrity": "sha512-xeV+5zV7VQRT2xJTbXW4IDra1urEcchuwuKFTU/ERcJWBWEk8chsZcd6DBc8SPq83xRgXW2ZTVVSOZR8NKaVoQ==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-0.19.2.tgz", + "integrity": "sha512-vsKN6BKya70bQUsjgKRDlR2lOpv/XpUkCMIiji6rjMtKHIuWEB5Eu3JqZsOuBmWo3A3TT/K/osT9VPm0k4qdYQ==", "dev": true, "peer": true, "dependencies": { - "js-sha256": "^0.9.0" + "@noble/hashes": "^1.3.1" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "dev": true, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/@swc/core": { @@ -450,9 +490,9 @@ ] }, "node_modules/bignumber.js": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", - "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", "dev": true, "engines": { "node": "*" @@ -992,12 +1032,12 @@ } }, "node_modules/ts-node": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", - "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dev": true, "dependencies": { - "@cspotcode/source-map-support": "0.7.0", + "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", @@ -1008,7 +1048,7 @@ "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.0", + "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "bin": { @@ -1034,17 +1074,29 @@ } } }, + "node_modules/ts-node/node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/typescript": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", - "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/universalify": { @@ -1091,34 +1143,63 @@ } }, "@dfinity/agent": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@dfinity/agent/-/agent-0.18.1.tgz", - "integrity": "sha512-nMFK/y0ZkPfQYECdojmltXsBIdGvXa1Sxa4rDV2cibEq9lsjWMEIUqPsiBaNHuwuz+gzsGVq4N2b9umKQIQaRQ==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@dfinity/agent/-/agent-0.19.2.tgz", + "integrity": "sha512-KLRWEjeU9SyyaS7IBVJ9ZUcufxufr55e/kRIyClK157+0pkTG9a8xKjUIMx3QzKvLsqqzXL238nWwdoP6jAD8g==", "dev": true, "requires": { + "@noble/hashes": "^1.3.1", "base64-arraybuffer": "^0.2.0", "borc": "^2.1.1", - "js-sha256": "0.9.0", "simple-cbor": "^0.4.1" } }, "@dfinity/candid": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-0.18.1.tgz", - "integrity": "sha512-/PC3wDnrGcWhaF/veYKevcAAn5A5jK0mRkVKcz0YxK/a78Ai9wMJg0fUk7aoyZryCOz8JPVgR4nK1/zTmvEBHg==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-0.19.2.tgz", + "integrity": "sha512-X2hCqNMhnnmwtnOc0WnymOZYx3qphjEMuSYbBr7tMIkV7Hwt9BmXXlLnQTxUytTPxf+3he0GcS3KzsSQ9CK8ew==", "dev": true, - "peer": true + "peer": true, + "requires": {} }, "@dfinity/principal": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-0.18.1.tgz", - "integrity": "sha512-xeV+5zV7VQRT2xJTbXW4IDra1urEcchuwuKFTU/ERcJWBWEk8chsZcd6DBc8SPq83xRgXW2ZTVVSOZR8NKaVoQ==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-0.19.2.tgz", + "integrity": "sha512-vsKN6BKya70bQUsjgKRDlR2lOpv/XpUkCMIiji6rjMtKHIuWEB5Eu3JqZsOuBmWo3A3TT/K/osT9VPm0k4qdYQ==", "dev": true, "peer": true, "requires": { - "js-sha256": "^0.9.0" + "@noble/hashes": "^1.3.1" } }, + "@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "dev": true + }, "@swc/core": { "version": "1.2.151", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.2.151.tgz", @@ -1322,9 +1403,9 @@ "dev": true }, "bignumber.js": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", - "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", "dev": true }, "borc": { @@ -1615,12 +1696,12 @@ } }, "ts-node": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", - "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dev": true, "requires": { - "@cspotcode/source-map-support": "0.7.0", + "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", @@ -1631,14 +1712,25 @@ "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.0", + "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" + }, + "dependencies": { + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + } + } } }, "typescript": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", - "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true }, "universalify": { diff --git a/examples/guard_functions/package.json b/examples/guard_functions/package.json index 39aeba9888..6bc660e4c9 100644 --- a/examples/guard_functions/package.json +++ b/examples/guard_functions/package.json @@ -7,8 +7,8 @@ "azle": "0.17.1" }, "devDependencies": { - "@dfinity/agent": "0.18.1", - "ts-node": "10.7.0", - "typescript": "4.6.3" + "@dfinity/agent": "^0.19.2", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" } } diff --git a/examples/guard_functions/src/guards.ts b/examples/guard_functions/src/guards.ts index ef6b079c33..773747dd91 100644 --- a/examples/guard_functions/src/guards.ts +++ b/examples/guard_functions/src/guards.ts @@ -1,48 +1,29 @@ -import { ic, GuardResult } from 'azle'; -import { state } from './state'; +import { state } from './index'; -export function allowModifyStateGuarded(): GuardResult { - console.log('allowModifyStateGuarded called'); - if (ic.methodName() === 'modifyStateGuarded') { - console.log( - `Method ${ic.methodName()} allowed by inspectMessage's guard function: allowModifyStateGuarded` - ); - } else { - console.log( - `Method ${ic.methodName()} would be rejected by inspectMessage's guard function... but we are in inspect message mode so doing so would be a contract violation. Therefore, proceeding.` - ); - } - - return { Ok: null }; -} - -export function allowAll(): GuardResult { +export function allowAll() { console.log('allowAll called'); - return { Ok: null }; } -export function acceptAllThenRejectAll(): GuardResult { +export function acceptAllThenRejectAll() { console.log('acceptAllThenRejectAll called'); if (++state.heartbeatTick > 20) { console.log(`Heartbeat suppressed`); - return { Err: 'This error message will never be seen' }; + throw 'This error message will never be seen'; } console.log(`Accepted heartbeat tick #${state.heartbeatTick}`); - return { Ok: null }; } -export function incrementCounterAndAllowAll(): GuardResult { +export function incrementCounterAndAllowAll() { console.log('incrementCounterAndAllowAll called'); state.counter++; - return { Ok: null }; } -export function unpassable(): GuardResult { +export function unpassable() { console.log('unpassable called'); - return { Err: 'Execution halted by "unpassable" guard function' }; + throw 'Execution halted by "unpassable" guard function'; } -export function throwString(): GuardResult { +export function throwString() { console.log('throw string called'); throw 'Execution halted by "throw string" guard function'; } @@ -54,38 +35,19 @@ class CustomError extends Error { } } -export function throwCustomError(): GuardResult { +export function throwCustomError() { console.log('throwCustomError called'); throw new CustomError( 'Execution halted by "throw custom error" guard function' ); } -export function preventUpgrades(): GuardResult { +export function preventUpgrades() { console.log('preventUpgrades called'); - return { Err: 'Upgrades to this canister are disabled' }; -} - -export function returnInvalidType(): GuardResult { - console.log('returnInvalidType called'); - // @ts-ignore - return 'Something other than a guard result'; -} - -export function returnNonGuardResultObject(): GuardResult { - console.log('returnNonGuardResultObject called'); - // @ts-ignore - return { badProp: 'Something other than a guard result' }; -} - -export function returnNonNullOkValue(): GuardResult { - console.log('nonNullOkValue called'); - // @ts-ignore - return { Ok: 'Something other than null' }; + return 'Upgrades to this canister are disabled'; } -export function returnNonStringErrValue(): GuardResult { +export function returnNonStringErrValue() { console.log('nonStringErrValue called'); - // @ts-ignore - return { Err: { badProp: 'Something other than a string' } }; + throw { badProp: 'Something other than a string' }; } diff --git a/examples/guard_functions/src/index.did b/examples/guard_functions/src/index.did index 8ff236f245..7a14f6a0a9 100644 --- a/examples/guard_functions/src/index.did +++ b/examples/guard_functions/src/index.did @@ -1,19 +1,14 @@ -type State = record { counter : int32; heartbeatTick : int32 }; -service : () -> { - badObjectGuarded : () -> (bool) query; - callExpressionWithEmptyOptionsObject : () -> (bool) query; - callExpressionWithoutOptionsObject : () -> (bool) query; - customErrorGuarded : () -> (bool) query; - errorStringGuarded : () -> (bool) query; - getState : () -> (State) query; - identifierAnnotation : () -> (bool) query; - invalidReturnTypeGuarded : () -> (bool) query; - looselyGuarded : () -> (bool) query; - looselyGuardedManual : () -> (bool) query; - looselyGuardedWithGuardOptionKeyAsString : () -> (bool) query; - modifyStateGuarded : () -> (bool); - nonNullOkValueGuarded : () -> (bool) query; - nonStringErrValueGuarded : () -> (bool) query; - tightlyGuarded : () -> (bool) query; - unallowedMethod : () -> (bool); -} \ No newline at end of file +service: () -> { + getCounter: () -> (nat32) query; + identifierAnnotation: () -> (bool) query; + callExpressionWithoutOptionsObject: () -> (bool) query; + callExpressionWithEmptyOptionsObject: () -> (bool) query; + looselyGuarded: () -> (bool) query; + looselyGuardedManual: () -> (bool) query; + looselyGuardedWithGuardOptionKeyAsString: () -> (bool) query; + modifyStateGuarded: () -> (bool); + tightlyGuarded: () -> (bool) query; + errorStringGuarded: () -> (bool) query; + customErrorGuarded: () -> (bool) query; + nonStringErrValueGuarded: () -> (bool) query; +} diff --git a/examples/guard_functions/src/index.ts b/examples/guard_functions/src/index.ts index 9b281e1bf5..4a40dd658c 100644 --- a/examples/guard_functions/src/index.ts +++ b/examples/guard_functions/src/index.ts @@ -1,148 +1,88 @@ +import { bool, ic, Manual, nat32, query, Service, update } from 'azle'; import { - $heartbeat, - $inspectMessage, - $preUpgrade, - $query, - $update, - ic, - Manual -} from 'azle'; -import { - acceptAllThenRejectAll, allowAll, - allowModifyStateGuarded, - unpassable, incrementCounterAndAllowAll, - preventUpgrades, - returnInvalidType, - returnNonGuardResultObject, - returnNonNullOkValue, returnNonStringErrValue, throwCustomError, - throwString + throwString, + unpassable } from './guards'; -import { State, state } from './state'; - -$query; -export function getState(): State { - return state; -} -// #region Guarded functions are called -$inspectMessage({ guard: allowModifyStateGuarded }); -export function inspectMessage(): void { - console.log('inspectMessage called'); +export let state = { + counter: 0, + heartbeatTick: 0 +}; - if (ic.methodName() === 'modifyStateGuarded') { - console.log(`Method ${ic.methodName()} allowed by inspectMessage`); - ic.acceptMessage(); - } else { - console.log(`Method ${ic.methodName()} rejected by inspectMessage`); +export default class extends Service { + @query([], nat32) + getCounter(): nat32 { + return state.counter; } -} - -$heartbeat({ guard: acceptAllThenRejectAll }); -export function heartbeat(): void { - console.log('heartbeat called'); -} - -$preUpgrade({ guard: preventUpgrades }); -export function preUpgrade(): void { - console.log('preUpgrade called'); -} - -$query; -export function identifierAnnotation(): boolean { - console.log('identifierAnnotation called'); - return true; -} -$query(); -export function callExpressionWithoutOptionsObject(): boolean { - console.log('callExpressionWithoutOptionsObject called'); - return true; -} - -$query({}); -export function callExpressionWithEmptyOptionsObject(): boolean { - console.log('callExpressionWithEmptyOptionsObject called'); - return true; -} - -$query({ guard: allowAll }); -export function looselyGuarded(): boolean { - console.log('looselyGuarded called'); - return true; -} - -$query({ guard: allowAll }); -export function looselyGuardedManual(): Manual { - console.log('looselyGuardedManual called'); - ic.reply(true); -} + @query([], bool) + identifierAnnotation(): bool { + console.log('identifierAnnotation called'); + return true; + } -// prettier-ignore -$query({ "guard": allowAll }); -export function looselyGuardedWithGuardOptionKeyAsString(): boolean { - console.log('looselyGuardedWithGuardOptionKeyAsString called'); - return true; -} + @query([], bool) + callExpressionWithoutOptionsObject(): bool { + console.log('callExpressionWithoutOptionsObject called'); + return true; + } -$update({ guard: incrementCounterAndAllowAll }); -export function modifyStateGuarded(): boolean { - console.log('modifyStateGuarded called'); - return true; -} + @query([], bool, {}) + callExpressionWithEmptyOptionsObject(): bool { + console.log('callExpressionWithEmptyOptionsObject called'); + return true; + } -$update({ guard: incrementCounterAndAllowAll }); -export function unallowedMethod(): boolean { - console.log('modifyStateGuarded called'); - return true; -} -// #endregion Guarded functions are called + @query([], bool, { guard: allowAll }) + looselyGuarded(): bool { + console.log('looselyGuarded called'); + return true; + } -// #region Execution halted by guard function -$query({ guard: unpassable }); -export function tightlyGuarded(): boolean { - console.log('tightlyGuarded called'); - return true; -} + @query([], bool, { manual: true, guard: allowAll }) + looselyGuardedManual(): Manual { + console.log('looselyGuardedManual called'); + ic.reply(true, bool); + } -$query({ guard: throwString }); -export function errorStringGuarded(): boolean { - console.log('errorStringGuarded called'); - return true; -} + // prettier-ignore + @query([], bool, { "guard": allowAll }) + looselyGuardedWithGuardOptionKeyAsString(): bool { + console.log('looselyGuardedWithGuardOptionKeyAsString called'); + return true; + } -$query({ guard: throwCustomError }); -export function customErrorGuarded(): boolean { - console.log('customErrorGuarded called'); - return true; -} -// #endregion Execution halted by guard functions + @update([], bool, { guard: incrementCounterAndAllowAll }) + modifyStateGuarded(): bool { + console.log('modifyStateGuarded called'); + return true; + } -// #region Execution halted by runtime error -$query({ guard: returnInvalidType }); -export function invalidReturnTypeGuarded(): boolean { - console.log('invalidReturnTypeGuarded called'); - return true; -} + @query([], bool, { guard: unpassable }) + tightlyGuarded(): bool { + console.log('tightlyGuarded called'); + return true; + } -$query({ guard: returnNonGuardResultObject }); -export function badObjectGuarded(): boolean { - console.log('badObjectGuarded called'); - return true; -} + @query([], bool, { guard: throwString }) + errorStringGuarded(): bool { + console.log('errorStringGuarded called'); + return true; + } -$query({ guard: returnNonNullOkValue }); -export function nonNullOkValueGuarded(): boolean { - console.log('nonNullOkValueGuarded called'); - return true; -} + @query([], bool, { guard: throwCustomError }) + customErrorGuarded(): bool { + console.log('customErrorGuarded called'); + return true; + } -$query({ guard: returnNonStringErrValue }); -export function nonStringErrValueGuarded(): boolean { - console.log('nonStringErrValueGuarded called'); - return true; + @query([], bool, { guard: returnNonStringErrValue }) + nonStringErrValueGuarded(): bool { + console.log('nonStringErrValueGuarded called'); + return true; + } } -// #endregion Execution halted by runtime error diff --git a/examples/guard_functions/src/invalid.ts b/examples/guard_functions/src/invalid.ts deleted file mode 100644 index 3054e2c791..0000000000 --- a/examples/guard_functions/src/invalid.ts +++ /dev/null @@ -1,51 +0,0 @@ -// The following file gives examples of invalid guard function usage that will -// result in compilation time errors. - -import { $query, GuardResult } from 'azle'; - -const array_containing_options_object = [ - { guard: 'this is not a function declaration' } -]; -const options_subset = { guard: 'this is not a function declaration' }; - -function guard_function(): GuardResult { - return { Ok: null }; -} - -// 1. Custom annotation not directly followed by a function declaration -$query; -console.log('Something between annotation and function declaration'); -export function fn1(): void {} - -// 2. Spreading the options object -$query(...array_containing_options_object); -export function fn3(): void {} - -// 3. Spreading inside the options object -$query({ ...options_subset }); -export function fn4(): void {} - -// 4. Too many arguments -$query({}, true); -export function fn5(): void {} - -// 5. Too many options -$query({ guard: guard_function, anything_else: true, 4: true }); -export function fn6(): void {} - -// 6. Invalid option -$query({ anything_else: false }); -export function fn7(): void {} - -let computed_prop_name = 'guard'; - -// 7. Computed guard key name -$query({ [computed_prop_name]: guard_function }); -export function fn8(): void {} - -// 8. Non exported function -$query; -function fn9(): void {} - -// 9. Dangling guard without accompanying exported function -$query; diff --git a/examples/guard_functions/src/state.ts b/examples/guard_functions/src/state.ts deleted file mode 100644 index 74d1a055a6..0000000000 --- a/examples/guard_functions/src/state.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Record, int32 } from 'azle'; - -export type State = Record<{ - counter: int32; - heartbeatTick: int32; -}>; - -export let state: State = { - counter: 0, - heartbeatTick: 0 -}; diff --git a/examples/guard_functions/test/tests.ts b/examples/guard_functions/test/tests.ts index 9afc001924..53b803445a 100644 --- a/examples/guard_functions/test/tests.ts +++ b/examples/guard_functions/test/tests.ts @@ -8,26 +8,6 @@ export function getTests( guardFunctionsCanister: ActorSubclass<_SERVICE> ): Test[] { return [ - { - name: 'heartbeat guard', - test: async () => { - const initialState = await guardFunctionsCanister.getState(); - console.log( - `Value at initial check was: ${initialState.heartbeatTick}` - ); - await sleep(15_000); - const stateAfterRest = await guardFunctionsCanister.getState(); - console.log( - `Value after 15s delay was: ${stateAfterRest.heartbeatTick}` - ); - - return { - Ok: - initialState.heartbeatTick < 20 && - stateAfterRest.heartbeatTick === 20 - }; - } - }, { name: 'identifierAnnotation', test: async () => { @@ -84,35 +64,19 @@ export function getTests( { name: 'modifyStateGuarded', test: async () => { - const stateBefore = await guardFunctionsCanister.getState(); + const counterBefore = await guardFunctionsCanister.getCounter(); const methodExecuted = await guardFunctionsCanister.modifyStateGuarded(); - const stateAfter = await guardFunctionsCanister.getState(); + const counterAfter = await guardFunctionsCanister.getCounter(); return { Ok: - stateBefore.counter === 0 && + counterBefore === 0 && methodExecuted && - stateAfter.counter === 1 + counterAfter === 1 }; } }, - { - name: 'unallowedMethod', - test: async () => { - try { - const result = - await guardFunctionsCanister.unallowedMethod(); - return { - Err: 'Expected unallowedMethod function to throw' - }; - } catch (error) { - return { - Ok: (error as AgentError).message.includes('IC0516') - }; - } - } - }, { name: 'tightlyGuarded', test: async () => { @@ -125,7 +89,7 @@ export function getTests( } catch (error) { return { Ok: (error as AgentError).message.includes( - `"Message": "Execution halted by \\"unpassable\\" guard function"` + `"Message": "Uncaught Execution halted by \\"unpassable\\" guard function"` ) }; } @@ -143,7 +107,7 @@ export function getTests( } catch (error) { return { Ok: (error as AgentError).message.includes( - `Execution halted by \\"throw string\\" guard function` + `Uncaught Execution halted by \\"throw string\\" guard function` ) }; } @@ -159,76 +123,14 @@ export function getTests( Err: 'Expected customErrorGuarded function to throw' }; } catch (error) { - // TODO: I actually expect this to say "Uncaught CustomError: Execution..." - // Why it only says "Error" not "CustomError" I don't understand. return { Ok: (error as AgentError).message.includes( - `Execution halted by \\"throw custom error\\" guard function` + `Uncaught CustomError: Execution halted by \\"throw custom error\\" guard function` ) }; } } }, - { - name: 'invalidReturnTypeGuarded', - test: async () => { - try { - const result = - await guardFunctionsCanister.invalidReturnTypeGuarded(); - return { - Err: 'Expected invalidReturnTypeGuarded function to throw' - }; - } catch (error) { - return { - Ok: (error as AgentError).message.includes( - `TypeError: Value is not of type 'GuardResult'` - ) - }; - } - } - }, - { - name: 'badObjectGuarded', - test: async () => { - try { - const result = - await guardFunctionsCanister.badObjectGuarded(); - return { - Err: 'Expected badObjectGuarded function to throw' - }; - } catch (error: any) { - return { - Ok: - 'result' in error && - 'reject_message' in error.result && - error.result.reject_message.includes( - `Uncaught TypeError: Value is not of type 'GuardResult'\n [cause]: TypeError: Value must contain exactly one of the following properties: ['Ok', 'Err']` - ) - }; - } - } - }, - { - name: 'nonNullOkValueGuarded', - test: async () => { - try { - const result = - await guardFunctionsCanister.nonNullOkValueGuarded(); - return { - Err: 'Expected nonNullOkValueGuarded function to throw' - }; - } catch (error: any) { - return { - Ok: - 'result' in error && - 'reject_message' in error.result && - error.result.reject_message.includes( - `TypeError: Value is not of type 'GuardResult'\n [cause]: TypeError: Value is not of type 'null'` - ) - }; - } - } - }, { name: 'nonStringErrValueGuarded', test: async () => { @@ -241,26 +143,11 @@ export function getTests( } catch (error) { return { Ok: (error as AgentError).message.includes( - `TypeError: Value is not of type 'string'` + `Uncaught [object Object]` ) }; } } - }, - { - name: 'preventUpgrades', - test: async () => { - try { - execSync('dfx deploy'); - return { Err: "Guard function didn't prevent upgrades" }; - } catch (error) { - return { Ok: true }; - } - } } ]; } - -function sleep(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} diff --git a/src/compiler/typescript_to_rust/azle_generate_rearchitecture/src/main.rs b/src/compiler/typescript_to_rust/azle_generate_rearchitecture/src/main.rs index 093bdfd8d2..76afa1872f 100644 --- a/src/compiler/typescript_to_rust/azle_generate_rearchitecture/src/main.rs +++ b/src/compiler/typescript_to_rust/azle_generate_rearchitecture/src/main.rs @@ -32,6 +32,7 @@ struct CanisterMethods { struct CanisterMethod { name: String, composite: Option, + guard_name: Option, } fn main() -> Result<(), String> { @@ -104,11 +105,20 @@ fn main() -> Result<(), String> { let js_function_name = &canister_method.name; let is_composite = canister_method.composite.unwrap_or(false); + let (guard_attribute, guard_function) = if let Some(guard_name) = &canister_method.guard_name + { + get_guard_token_stream(guard_name) + } else { + (quote!(), quote!()) + }; + quote! { - #[ic_cdk_macros::query(manual_reply = true, composite = #is_composite)] + #[ic_cdk_macros::query(manual_reply = true, composite = #is_composite #guard_attribute)] fn #rust_function_name() { execute_js(#js_function_name, true); } + + #guard_function } }); @@ -120,11 +130,20 @@ fn main() -> Result<(), String> { let rust_function_name = canister_method.name.to_ident(); let js_function_name = &canister_method.name; + let (guard_attribute, guard_function) = + if let Some(guard_name) = &canister_method.guard_name { + get_guard_token_stream(guard_name) + } else { + (quote!(), quote!()) + }; + quote! { - #[ic_cdk_macros::update(manual_reply = true)] + #[ic_cdk_macros::update(manual_reply = true #guard_attribute)] fn #rust_function_name() { execute_js(#js_function_name, true); } + + #guard_function } }); @@ -294,3 +313,39 @@ fn get_compiler_info(compiler_info_path: &str) -> Result { Ok(compiler_info) } + +fn get_guard_token_stream( + guard_name: &str, +) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { + let guard_name_ident = guard_name.to_string().to_ident(); + + ( + quote!(, guard = #guard_name), + quote! { + // TODO should the guard function have access to the raw args? + fn #guard_name_ident() -> Result<(), String> { + CONTEXT.with(|context| { + let mut context = context.borrow_mut(); + let context = context.as_mut().unwrap(); + + let global = context.global_object().unwrap(); + let guard_function = global.get_property(#guard_name).unwrap(); + + // TODO I am not sure what the first parameter to call is supposed to be + let result = guard_function.call(&guard_function, &[]); + + match result { + Ok(_) => { + Ok(()) + }, + Err(err_js_value_ref) => { + let err: String = err_js_value_ref.to_string(); + + Err(err) + } + } + }) + } + }, + ) +} diff --git a/src/compiler/utils/types.ts b/src/compiler/utils/types.ts index caaacbc2ea..66a8416590 100644 --- a/src/compiler/utils/types.ts +++ b/src/compiler/utils/types.ts @@ -55,10 +55,10 @@ export type CanisterMethods = { inspect_message?: CanisterMethod; }; -// TODO things like guard might also go in here export type CanisterMethod = { name: string; composite?: boolean; + guard?: () => void; }; export type Plugin = { diff --git a/src/lib_new/method_decorators.ts b/src/lib_new/method_decorators.ts index 6c7075088a..326a08b2cb 100644 --- a/src/lib_new/method_decorators.ts +++ b/src/lib_new/method_decorators.ts @@ -1,5 +1,5 @@ import { ic } from './ic'; -import { IDL } from './index'; +import { GuardResult, IDL } from './index'; import { CandidClass, @@ -29,7 +29,8 @@ const modeToCandid = { update: '' }; -type MethodArgs = { manual: boolean }; +// TODO add the GuardResult return type +type MethodArgs = { manual?: boolean; guard?: () => void }; // Until we can figure how how to type check Funcs, Variants, and Records we are just going to have to use any here // export function query(paramsIdls: CandidClass[], returnIdl: ReturnCandidClass) { @@ -41,6 +42,7 @@ export function init(paramsIdls: any[]): any { [], 'init', false, + undefined, key, descriptor ); @@ -57,6 +59,7 @@ export function postUpgrade(paramsIdls: any[]): any { [], 'postUpgrade', false, + undefined, key, descriptor ); @@ -74,6 +77,7 @@ export function preUpgrade( [], 'preUpgrade', false, + undefined, key, descriptor ); @@ -90,6 +94,7 @@ export function heartbeat( [], 'heartbeat', false, + undefined, key, descriptor ); @@ -106,6 +111,7 @@ export function inspectMessage( [], 'inspectMessage', false, + undefined, key, descriptor ); @@ -128,6 +134,7 @@ export function query( returnIdl, 'query', args.manual, + args.guard, key, descriptor ); @@ -153,6 +160,7 @@ export function update( returnIdl, 'update', args.manual, + args.guard, key, descriptor ); @@ -189,6 +197,7 @@ function setupCanisterMethod( returnIdl: ReturnCandidClass, mode: Mode, manual: boolean, + guard: (() => GuardResult) | undefined, key: string, descriptor: PropertyDescriptor ) { @@ -236,13 +245,15 @@ function setupCanisterMethod( if (mode === 'query') { target.constructor._azleCanisterMethods.queries.push({ name: key, - composite: isAsync(originalMethod, key) + composite: isAsync(originalMethod, key), + guard_name: createGlobalGuard(guard, key) }); } if (mode === 'update') { target.constructor._azleCanisterMethods.updates.push({ - name: key + name: key, + guard_name: createGlobalGuard(guard, key) }); } @@ -349,3 +360,18 @@ function isAsync(originalFunction: any, key: string) { return false; } } + +function createGlobalGuard( + guard: (() => GuardResult) | undefined, + functionName: string +): string | undefined { + if (guard === undefined) { + return undefined; + } + + const guardName = `_azleGuard_${functionName}`; + + (globalThis as any)[guardName] = guard; + + return guardName; +} diff --git a/src/lib_new/system_types.ts b/src/lib_new/system_types.ts index 284b5efbe0..28d6fb9e30 100644 --- a/src/lib_new/system_types.ts +++ b/src/lib_new/system_types.ts @@ -1,6 +1,5 @@ import { candid } from './index'; import { Null } from './primitives'; -import { Result } from './result'; import { Variant } from './variant'; /**