diff --git a/canister_templates/experimental.wasm b/canister_templates/experimental.wasm index 39e330f059..04e2d7370d 100644 Binary files a/canister_templates/experimental.wasm and b/canister_templates/experimental.wasm differ diff --git a/canister_templates/stable.wasm b/canister_templates/stable.wasm index 84ac448b53..337bd195aa 100644 Binary files a/canister_templates/stable.wasm and b/canister_templates/stable.wasm differ diff --git a/examples/experimental/test/end_to_end/candid_rpc/timers/src/timers.ts b/examples/experimental/test/end_to_end/candid_rpc/timers/src/timers.ts index 3adde97da9..a8dee76293 100644 --- a/examples/experimental/test/end_to_end/candid_rpc/timers/src/timers.ts +++ b/examples/experimental/test/end_to_end/candid_rpc/timers/src/timers.ts @@ -86,7 +86,7 @@ export default Canister({ repeatCrossCanister: repeatCrossCanisterId }; }), - statusReport: query([], StatusReport, () => { + getStatusReport: query([], StatusReport, () => { return statusReport; }) }); diff --git a/examples/experimental/test/end_to_end/candid_rpc/timers/test/tests.ts b/examples/experimental/test/end_to_end/candid_rpc/timers/test/tests.ts index 7164c61558..e7954c6b3f 100644 --- a/examples/experimental/test/end_to_end/candid_rpc/timers/test/tests.ts +++ b/examples/experimental/test/end_to_end/candid_rpc/timers/test/tests.ts @@ -16,7 +16,7 @@ let timerIds = { export function getTests(timersCanister: ActorSubclass<_SERVICE>): Test { return () => { it('gets initial timer values', async () => { - const result = await timersCanister.statusReport(); + const result = await timersCanister.getStatusReport(); const expectedResult = { single: false, @@ -39,7 +39,7 @@ export function getTests(timersCanister: ActorSubclass<_SERVICE>): Test { wait('for repeated timer to be called once', 7_000); it('checks that only the repeated timers were called', async () => { - const result = await timersCanister.statusReport(); + const result = await timersCanister.getStatusReport(); const expectedResult = { single: false, @@ -60,7 +60,7 @@ export function getTests(timersCanister: ActorSubclass<_SERVICE>): Test { wait('for the single timer to finish', 5_000); it('checks that everything got called (and the repeated timers ran a second time)', async () => { - const result = await timersCanister.statusReport(); + const result = await timersCanister.getStatusReport(); const expectedResult = { single: true, @@ -93,7 +93,7 @@ export function getTests(timersCanister: ActorSubclass<_SERVICE>): Test { wait('for the repeating call interval', 7_000); it('checks that the repeating timers stopped', async () => { - const result = await timersCanister.statusReport(); + const result = await timersCanister.getStatusReport(); const expectedResult = { single: true, diff --git a/examples/stable/test/end_to_end/candid_rpc/complex_types/src/index.ts b/examples/stable/test/end_to_end/candid_rpc/complex_types/src/index.ts index 520b7d7ce6..6113efb85e 100644 --- a/examples/stable/test/end_to_end/candid_rpc/complex_types/src/index.ts +++ b/examples/stable/test/end_to_end/candid_rpc/complex_types/src/index.ts @@ -3,10 +3,18 @@ import { IDL, query, update } from 'azle'; import { Post, Reaction, ReactionType, Thread, User } from './candid_types'; import * as posts from './posts'; import * as reactions from './reactions'; +import { State } from './state'; import * as threads from './threads'; import * as users from './users'; -export default class { +export default class Canister { + state: State = { + posts: {}, + reactions: {}, + threads: {}, + users: {} + }; + @update([IDL.Text, IDL.Text, IDL.Text, IDL.Nat32], Post) createPost( authorId: string, @@ -14,12 +22,12 @@ export default class { threadId: string, joinDepth: number ): Post { - return posts.createPost(authorId, text, threadId, joinDepth); + return posts.createPost(this, authorId, text, threadId, joinDepth); } @query([IDL.Nat32], IDL.Vec(Post)) getAllPosts(joinDepth: number): Post[] { - return posts.getAllPosts(joinDepth); + return posts.getAllPosts(this, joinDepth); } @update([IDL.Text, IDL.Text, ReactionType, IDL.Nat32], Reaction) @@ -30,6 +38,7 @@ export default class { joinDepth: number ): Reaction { return reactions.createReaction( + this, authorId, postId, reactionType, @@ -39,26 +48,26 @@ export default class { @query([IDL.Nat32], IDL.Vec(Reaction)) getAllReactions(joinDepth: number): Reaction[] { - return reactions.getAllReactions(joinDepth); + return reactions.getAllReactions(this, joinDepth); } @update([IDL.Text, IDL.Text, IDL.Nat32], Thread) createThread(title: string, authorId: string, joinDepth: number): Thread { - return threads.createThread(title, authorId, joinDepth); + return threads.createThread(this, title, authorId, joinDepth); } @query([IDL.Nat32], IDL.Vec(Thread)) getAllThreads(joinDepth: number): Thread[] { - return threads.getAllThreads(joinDepth); + return threads.getAllThreads(this, joinDepth); } @update([IDL.Text, IDL.Nat32], User) createUser(username: string, joinDepth: number): User { - return users.createUser(username, joinDepth); + return users.createUser(this, username, joinDepth); } @query([IDL.Nat32], IDL.Vec(User)) getAllUsers(joinDepth: number): User[] { - return users.getAllUsers(joinDepth); + return users.getAllUsers(this, joinDepth); } } diff --git a/examples/stable/test/end_to_end/candid_rpc/complex_types/src/posts.ts b/examples/stable/test/end_to_end/candid_rpc/complex_types/src/posts.ts index 4eb612f067..853bb979c9 100644 --- a/examples/stable/test/end_to_end/candid_rpc/complex_types/src/posts.ts +++ b/examples/stable/test/end_to_end/candid_rpc/complex_types/src/posts.ts @@ -1,16 +1,18 @@ +import Canister from '.'; import { Post } from './candid_types'; import { getReactionFromStateReaction } from './reactions'; -import { state, StatePost, StateThread, StateUser } from './state'; +import { StatePost, StateThread, StateUser } from './state'; import { getThreadFromStateThread } from './threads'; import { getUserFromStateUser } from './users'; export function createPost( + canister: Canister, authorId: string, text: string, threadId: string, joinDepth: number ): Post { - const id = Object.keys(state.posts).length.toString(); + const id = Object.keys(canister.state.posts).length.toString(); const statePost: StatePost = { id, @@ -19,43 +21,52 @@ export function createPost( text, threadId }; - const updatedStateAuthor = getUpdatedStateAuthor(authorId, statePost.id); - const updatedStateThread = getUpdatedStateThread(threadId, statePost.id); + const updatedStateAuthor = getUpdatedStateAuthor( + canister, + authorId, + statePost.id + ); + const updatedStateThread = getUpdatedStateThread( + canister, + threadId, + statePost.id + ); - state.posts[id] = statePost; - state.users[authorId] = updatedStateAuthor; - state.threads[threadId] = updatedStateThread; + canister.state.posts[id] = statePost; + canister.state.users[authorId] = updatedStateAuthor; + canister.state.threads[threadId] = updatedStateThread; - const post = getPostFromStatePost(statePost, joinDepth); + const post = getPostFromStatePost(canister, statePost, joinDepth); return post; } -export function getAllPosts(joinDepth: number): Post[] { - return Object.values(state.posts).map((statePost) => - getPostFromStatePost(statePost!, joinDepth) +export function getAllPosts(canister: Canister, joinDepth: number): Post[] { + return Object.values(canister.state.posts).map((statePost) => + getPostFromStatePost(canister, statePost!, joinDepth) ); } export function getPostFromStatePost( + canister: Canister, statePost: StatePost, joinDepth: number ): Post { - const stateAuthor = state.users[statePost.authorId]; + const stateAuthor = canister.state.users[statePost.authorId]; if (stateAuthor === undefined) { throw new Error('Author not found'); } - const author = getUserFromStateUser(stateAuthor, joinDepth); + const author = getUserFromStateUser(canister, stateAuthor, joinDepth); - const stateThread = state.threads[statePost.threadId]; + const stateThread = canister.state.threads[statePost.threadId]; if (stateThread === undefined) { throw new Error('Thread not found'); } - const thread = getThreadFromStateThread(stateThread, joinDepth); + const thread = getThreadFromStateThread(canister, stateThread, joinDepth); if (joinDepth === 0) { return { @@ -67,9 +78,13 @@ export function getPostFromStatePost( }; } else { const reactions = statePost.reactionIds - .map((reactionId) => state.reactions[reactionId]) + .map((reactionId) => canister.state.reactions[reactionId]) .map((stateReaction) => - getReactionFromStateReaction(stateReaction!, joinDepth - 1) + getReactionFromStateReaction( + canister, + stateReaction!, + joinDepth - 1 + ) ); return { @@ -82,8 +97,12 @@ export function getPostFromStatePost( } } -function getUpdatedStateAuthor(authorId: string, postId: string): StateUser { - const stateAuthor = state.users[authorId]; +function getUpdatedStateAuthor( + canister: Canister, + authorId: string, + postId: string +): StateUser { + const stateAuthor = canister.state.users[authorId]; if (stateAuthor === undefined) { throw new Error('Author not found'); @@ -97,8 +116,12 @@ function getUpdatedStateAuthor(authorId: string, postId: string): StateUser { return updatedStateAuthor; } -function getUpdatedStateThread(threadId: string, postId: string): StateThread { - const stateThread = state.threads[threadId]; +function getUpdatedStateThread( + canister: Canister, + threadId: string, + postId: string +): StateThread { + const stateThread = canister.state.threads[threadId]; if (stateThread === undefined) { throw new Error('Thread not found'); diff --git a/examples/stable/test/end_to_end/candid_rpc/complex_types/src/reactions.ts b/examples/stable/test/end_to_end/candid_rpc/complex_types/src/reactions.ts index f2b89b8863..ef2744ae1f 100644 --- a/examples/stable/test/end_to_end/candid_rpc/complex_types/src/reactions.ts +++ b/examples/stable/test/end_to_end/candid_rpc/complex_types/src/reactions.ts @@ -1,15 +1,17 @@ +import Canister from '.'; import { Reaction, ReactionType } from './candid_types'; import { getPostFromStatePost } from './posts'; -import { state, StatePost, StateReaction, StateUser } from './state'; +import { StatePost, StateReaction, StateUser } from './state'; import { getUserFromStateUser } from './users'; export function createReaction( + canister: Canister, authorId: string, postId: string, reactionType: ReactionType, joinDepth: number ): Reaction { - const id = Object.keys(state.reactions).length.toString(); + const id = Object.keys(canister.state.reactions).length.toString(); const stateReaction: StateReaction = { id, @@ -18,45 +20,58 @@ export function createReaction( reactionType }; const updatedStateAuthor = getUpdatedStateAuthor( + canister, authorId, stateReaction.id ); - const updatedStatePost = getUpdatedStatePost(postId, stateReaction.id); + const updatedStatePost = getUpdatedStatePost( + canister, + postId, + stateReaction.id + ); - state.reactions[id] = stateReaction; - state.users[authorId] = updatedStateAuthor; - state.posts[postId] = updatedStatePost; + canister.state.reactions[id] = stateReaction; + canister.state.users[authorId] = updatedStateAuthor; + canister.state.posts[postId] = updatedStatePost; - const reaction = getReactionFromStateReaction(stateReaction, joinDepth); + const reaction = getReactionFromStateReaction( + canister, + stateReaction, + joinDepth + ); return reaction; } -export function getAllReactions(joinDepth: number): Reaction[] { - return Object.values(state.reactions).map((stateReaction) => - getReactionFromStateReaction(stateReaction!, joinDepth) +export function getAllReactions( + canister: Canister, + joinDepth: number +): Reaction[] { + return Object.values(canister.state.reactions).map((stateReaction) => + getReactionFromStateReaction(canister, stateReaction!, joinDepth) ); } export function getReactionFromStateReaction( + canister: Canister, stateReaction: StateReaction, joinDepth: number ): Reaction { - const stateAuthor = state.users[stateReaction.authorId]; + const stateAuthor = canister.state.users[stateReaction.authorId]; if (stateAuthor === undefined) { throw new Error('Author not found'); } - const author = getUserFromStateUser(stateAuthor, joinDepth); + const author = getUserFromStateUser(canister, stateAuthor, joinDepth); - const statePost = state.posts[stateReaction.postId]; + const statePost = canister.state.posts[stateReaction.postId]; if (statePost === undefined) { throw new Error('Post not found'); } - const post = getPostFromStatePost(statePost, joinDepth); + const post = getPostFromStatePost(canister, statePost, joinDepth); return { id: stateReaction.id, @@ -67,10 +82,11 @@ export function getReactionFromStateReaction( } function getUpdatedStateAuthor( + canister: Canister, authorId: string, reactionId: string ): StateUser { - const stateAuthor = state.users[authorId]; + const stateAuthor = canister.state.users[authorId]; if (stateAuthor === undefined) { throw new Error('Author not found'); @@ -84,8 +100,12 @@ function getUpdatedStateAuthor( return updatedStateAuthor; } -function getUpdatedStatePost(postId: string, reactionId: string): StatePost { - const statePost = state.posts[postId]; +function getUpdatedStatePost( + canister: Canister, + postId: string, + reactionId: string +): StatePost { + const statePost = canister.state.posts[postId]; if (statePost === undefined) { throw new Error('Post not found'); diff --git a/examples/stable/test/end_to_end/candid_rpc/complex_types/src/state.ts b/examples/stable/test/end_to_end/candid_rpc/complex_types/src/state.ts index 17eca531e9..fdfb0dc0b2 100644 --- a/examples/stable/test/end_to_end/candid_rpc/complex_types/src/state.ts +++ b/examples/stable/test/end_to_end/candid_rpc/complex_types/src/state.ts @@ -1,13 +1,5 @@ import { ReactionType } from './candid_types'; -// TODO this state should go on the class -export let state: State = { - posts: {}, - reactions: {}, - threads: {}, - users: {} -}; - export type State = { posts: { [id: string]: StatePost | undefined; diff --git a/examples/stable/test/end_to_end/candid_rpc/complex_types/src/threads.ts b/examples/stable/test/end_to_end/candid_rpc/complex_types/src/threads.ts index 753f023594..cc491a5f9d 100644 --- a/examples/stable/test/end_to_end/candid_rpc/complex_types/src/threads.ts +++ b/examples/stable/test/end_to_end/candid_rpc/complex_types/src/threads.ts @@ -1,14 +1,16 @@ +import Canister from '.'; import { Thread } from './candid_types'; import { getPostFromStatePost } from './posts'; -import { state, StateThread, StateUser } from './state'; +import { StateThread, StateUser } from './state'; import { getUserFromStateUser } from './users'; export function createThread( + canister: Canister, title: string, authorId: string, joinDepth: number ): Thread { - const id = Object.keys(state.threads).length.toString(); + const id = Object.keys(canister.state.threads).length.toString(); const stateThread: StateThread = { id, @@ -16,33 +18,38 @@ export function createThread( postIds: [], title }; - const updatedStateAuthor = getUpdatedStateAuthor(authorId, stateThread.id); + const updatedStateAuthor = getUpdatedStateAuthor( + canister, + authorId, + stateThread.id + ); - state.threads[id] = stateThread; - state.users[authorId] = updatedStateAuthor; + canister.state.threads[id] = stateThread; + canister.state.users[authorId] = updatedStateAuthor; - const thread = getThreadFromStateThread(stateThread, joinDepth); + const thread = getThreadFromStateThread(canister, stateThread, joinDepth); return thread; } -export function getAllThreads(joinDepth: number): Thread[] { - return Object.values(state.threads).map((stateThread) => - getThreadFromStateThread(stateThread!, joinDepth) +export function getAllThreads(canister: Canister, joinDepth: number): Thread[] { + return Object.values(canister.state.threads).map((stateThread) => + getThreadFromStateThread(canister, stateThread!, joinDepth) ); } export function getThreadFromStateThread( + canister: Canister, stateThread: StateThread, joinDepth: number ): Thread { - const stateAuthor = state.users[stateThread.authorId]; + const stateAuthor = canister.state.users[stateThread.authorId]; if (stateAuthor === undefined) { throw new Error('Author not found'); } - const author = getUserFromStateUser(stateAuthor, joinDepth); + const author = getUserFromStateUser(canister, stateAuthor, joinDepth); if (joinDepth === 0) { return { @@ -53,9 +60,9 @@ export function getThreadFromStateThread( }; } else { const posts = stateThread.postIds - .map((postId) => state.posts[postId]) + .map((postId) => canister.state.posts[postId]) .map((statePost) => - getPostFromStatePost(statePost!, joinDepth - 1) + getPostFromStatePost(canister, statePost!, joinDepth - 1) ); return { @@ -67,8 +74,12 @@ export function getThreadFromStateThread( } } -function getUpdatedStateAuthor(authorId: string, threadId: string): StateUser { - const stateAuthor = state.users[authorId]; +function getUpdatedStateAuthor( + canister: Canister, + authorId: string, + threadId: string +): StateUser { + const stateAuthor = canister.state.users[authorId]; if (stateAuthor === undefined) { throw new Error('Author not found'); diff --git a/examples/stable/test/end_to_end/candid_rpc/complex_types/src/users.ts b/examples/stable/test/end_to_end/candid_rpc/complex_types/src/users.ts index 1167b7bbfb..ea1dce7105 100644 --- a/examples/stable/test/end_to_end/candid_rpc/complex_types/src/users.ts +++ b/examples/stable/test/end_to_end/candid_rpc/complex_types/src/users.ts @@ -1,11 +1,16 @@ +import Canister from '.'; import { User } from './candid_types'; import { getPostFromStatePost } from './posts'; import { getReactionFromStateReaction } from './reactions'; -import { state, StateUser } from './state'; +import { StateUser } from './state'; import { getThreadFromStateThread } from './threads'; -export function createUser(username: string, joinDepth: number): User { - const id = Object.keys(state.users).length.toString(); +export function createUser( + canister: Canister, + username: string, + joinDepth: number +): User { + const id = Object.keys(canister.state.users).length.toString(); const stateUser: StateUser = { id, @@ -15,20 +20,21 @@ export function createUser(username: string, joinDepth: number): User { username }; - state.users[id] = stateUser; + canister.state.users[id] = stateUser; - const user = getUserFromStateUser(stateUser, joinDepth); + const user = getUserFromStateUser(canister, stateUser, joinDepth); return user; } -export function getAllUsers(joinDepth: number): User[] { - return Object.values(state.users).map((stateUser) => - getUserFromStateUser(stateUser!, joinDepth) +export function getAllUsers(canister: Canister, joinDepth: number): User[] { + return Object.values(canister.state.users).map((stateUser) => + getUserFromStateUser(canister, stateUser!, joinDepth) ); } export function getUserFromStateUser( + canister: Canister, stateUser: StateUser, joinDepth: number ): User { @@ -42,21 +48,25 @@ export function getUserFromStateUser( }; } else { const posts = stateUser.postIds - .map((postId) => state.posts[postId]) + .map((postId) => canister.state.posts[postId]) .map((statePost) => - getPostFromStatePost(statePost!, joinDepth - 1) + getPostFromStatePost(canister, statePost!, joinDepth - 1) ); const reactions = stateUser.reactionIds - .map((reactionId) => state.reactions[reactionId]) + .map((reactionId) => canister.state.reactions[reactionId]) .map((stateReaction) => - getReactionFromStateReaction(stateReaction!, joinDepth - 1) + getReactionFromStateReaction( + canister, + stateReaction!, + joinDepth - 1 + ) ); const threads = stateUser.threadIds - .map((threadId) => state.threads[threadId]) + .map((threadId) => canister.state.threads[threadId]) .map((stateThread) => - getThreadFromStateThread(stateThread!, joinDepth - 1) + getThreadFromStateThread(canister, stateThread!, joinDepth - 1) ); return { diff --git a/examples/stable/test/end_to_end/candid_rpc/timers/src/timers.ts b/examples/stable/test/end_to_end/candid_rpc/timers/src/timers.ts index bbd09ca05d..1be40772f7 100644 --- a/examples/stable/test/end_to_end/candid_rpc/timers/src/timers.ts +++ b/examples/stable/test/end_to_end/candid_rpc/timers/src/timers.ts @@ -42,16 +42,16 @@ type TimerIds = { repeatCrossCanister: bigint; }; -let statusReport: StatusReport = { - single: false, - inline: 0, - capture: '', - repeat: 0, - singleCrossCanister: Uint8Array.from([]), - repeatCrossCanister: Uint8Array.from([]) -}; +export default class Canister { + statusReport: StatusReport = { + single: false, + inline: 0, + capture: '', + repeat: 0, + singleCrossCanister: Uint8Array.from([]), + repeatCrossCanister: Uint8Array.from([]) + }; -export default class { @update([IDL.Nat64]) clearTimer(timerId: bigint): void { clearTimer(timerId); @@ -62,31 +62,29 @@ export default class { setTimers(delay: bigint, interval: bigint): TimerIds { const capturedValue = '🚩'; - const singleId = setTimer(delay, oneTimeTimerCallback); + const singleId = setTimer(delay, () => oneTimeTimerCallback(this)); const inlineId = setTimer(delay, () => { - statusReport.inline = 1; + this.statusReport.inline = 1; console.info('Inline timer called'); }); const captureId = setTimer(delay, () => { - statusReport.capture = capturedValue; + this.statusReport.capture = capturedValue; console.info(`Timer captured value ${capturedValue}`); }); const repeatId = setTimerInterval(interval, () => { - statusReport.repeat++; - console.info(`Repeating timer. Call ${statusReport.repeat}`); + this.statusReport.repeat++; + console.info(`Repeating timer. Call ${this.statusReport.repeat}`); }); - const singleCrossCanisterId = setTimer( - delay, - singleCrossCanisterTimerCallback + const singleCrossCanisterId = setTimer(delay, () => + singleCrossCanisterTimerCallback(this) ); - const repeatCrossCanisterId = setTimerInterval( - interval, - repeatCrossCanisterTimerCallback + const repeatCrossCanisterId = setTimerInterval(interval, () => + repeatCrossCanisterTimerCallback(this) ); return { @@ -100,27 +98,31 @@ export default class { } @query([], StatusReport) - statusReport(): StatusReport { - return statusReport; + getStatusReport(): StatusReport { + return this.statusReport; } } -function oneTimeTimerCallback(): void { - statusReport.single = true; +function oneTimeTimerCallback(canister: Canister): void { + canister.statusReport.single = true; console.info('oneTimeTimerCallback called'); } -async function singleCrossCanisterTimerCallback(): Promise { +async function singleCrossCanisterTimerCallback( + canister: Canister +): Promise { console.info('singleCrossCanisterTimerCallback'); - statusReport.singleCrossCanister = await getRandomness(); + canister.statusReport.singleCrossCanister = await getRandomness(); } -async function repeatCrossCanisterTimerCallback(): Promise { +async function repeatCrossCanisterTimerCallback( + canister: Canister +): Promise { console.info('repeatCrossCanisterTimerCallback'); - statusReport.repeatCrossCanister = Uint8Array.from([ - ...statusReport.repeatCrossCanister, + canister.statusReport.repeatCrossCanister = Uint8Array.from([ + ...canister.statusReport.repeatCrossCanister, ...(await getRandomness()) ]); } diff --git a/examples/stable/test/property/ic_api/reject_code/src/caller/index.ts b/examples/stable/test/property/ic_api/reject_code/src/caller/index.ts index 690f137d86..98fd374eef 100644 --- a/examples/stable/test/property/ic_api/reject_code/src/caller/index.ts +++ b/examples/stable/test/property/ic_api/reject_code/src/caller/index.ts @@ -1,4 +1,4 @@ -import { call, IDL, rejectCode, RejectionCode, trap, update } from 'azle'; +import { call, IDL, rejectCode, RejectionCode, update } from 'azle'; import { AssertType, NotAnyAndExact } from 'azle/type_tests/assert_type'; export default class { @@ -111,8 +111,9 @@ function isRejectionCode(code: RejectionCode): boolean { } function getRejectorPrincipal(): string { - return ( - process.env.REJECTOR_PRINCIPAL ?? - trap('process.env.REJECTOR_PRINCIPAL is undefined') - ); + if (process.env.REJECTOR_PRINCIPAL !== undefined) { + return process.env.REJECTOR_PRINCIPAL; + } + + throw new Error(`process.env.REJECTOR_PRINCIPAL is undefined`); } diff --git a/examples/stable/test/property/ic_api/reject_message/src/caller/index.ts b/examples/stable/test/property/ic_api/reject_message/src/caller/index.ts index b093d3b950..99ea6e1f4b 100644 --- a/examples/stable/test/property/ic_api/reject_message/src/caller/index.ts +++ b/examples/stable/test/property/ic_api/reject_message/src/caller/index.ts @@ -1,4 +1,4 @@ -import { call, IDL, rejectMessage, trap, update } from 'azle'; +import { call, IDL, rejectMessage, update } from 'azle'; import { AssertType, NotAnyAndExact } from 'azle/type_tests/assert_type'; export default class { @@ -38,8 +38,9 @@ async function getRejectMessage( } function getRejectorPrincipal(): string { - return ( - process.env.REJECTOR_PRINCIPAL ?? - trap('process.env.REJECTOR_PRINCIPAL is undefined') - ); + if (process.env.REJECTOR_PRINCIPAL !== undefined) { + return process.env.REJECTOR_PRINCIPAL; + } + + throw new Error(`process.env.REJECTOR_PRINCIPAL is undefined`); } diff --git a/src/build/experimental/commands/compile/wasm_binary/rust/experimental_canister_template/src/execute_method_js.rs b/src/build/experimental/commands/compile/wasm_binary/rust/experimental_canister_template/src/execute_method_js.rs index 2dcf6cc81a..1ec46bbc39 100644 --- a/src/build/experimental/commands/compile/wasm_binary/rust/experimental_canister_template/src/execute_method_js.rs +++ b/src/build/experimental/commands/compile/wasm_binary/rust/experimental_canister_template/src/execute_method_js.rs @@ -1,5 +1,5 @@ use crate::{benchmarking::record_benchmark, run_event_loop, RUNTIME, WASM_DATA_REF_CELL}; -use wasmedge_quickjs::AsObject; +use wasmedge_quickjs::{AsObject, JsValue}; #[no_mangle] #[allow(unused)] @@ -13,7 +13,16 @@ pub extern "C" fn execute_method_js(function_index: i32, pass_arg_data: i32) { runtime.run_with_context(|context| { let global = context.get_global(); - let callbacks = global.get("_azleCallbacks"); + + let exported_canister_class_instance = global.get("_azleExportedCanisterClassInstance"); + + let callbacks = if matches!(exported_canister_class_instance, JsValue::UnDefined) { + global.get("_azleCallbacks") + } else { + exported_canister_class_instance + .get("_azleCallbacks") + .unwrap() + }; let method_callback = callbacks.get(&function_name).unwrap(); diff --git a/src/build/stable/commands/compile/javascript.ts b/src/build/stable/commands/compile/javascript.ts index 632c4e6a5c..13e0a6590c 100644 --- a/src/build/stable/commands/compile/javascript.ts +++ b/src/build/stable/commands/compile/javascript.ts @@ -25,24 +25,60 @@ function getPrelude(main: string): string { export function handleClassApiCanister(): string { return /*TS*/ ` - if (globalThis._azleNodeWasmEnvironment === false) { - const canisterClassInstance = new Canister.default(); - globalThis._azleCanisterClassInstance = canisterClassInstance; - } + const exportedCanisterClassInstance = getExportedCanisterClassInstance(); - const canisterIdlType = IDL.Service(globalThis._azleCanisterMethodIdlTypes); + const canisterIdlType = IDL.Service(exportedCanisterClassInstance._azleCanisterMethodIdlTypes); const candid = canisterIdlType.accept(new DidVisitor(), { ...getDefaultVisitorData(), isFirstService: true, - systemFuncs: globalThis._azleInitAndPostUpgradeIdlTypes + systemFuncs: exportedCanisterClassInstance._azleInitAndPostUpgradeIdlTypes }); globalThis._azleGetCandidAndMethodMeta = () => { return JSON.stringify({ candid: toDidString(candid), - methodMeta: globalThis._azleMethodMeta + methodMeta: exportedCanisterClassInstance._azleMethodMeta }); }; + + /** + * @internal + * + * This function is designed with a very specific purpose. + * We need to get the _azle properties off of this class instance to use in generating the candid + * and method meta information. But we can't just set the result of instantiating the class to a local variable. + * This is because exceptions might be thrown during the class's instantiation, in the constructor or + * property initializers. If this happens, we would not be able to proceed to get those _azle properties out of the local variable. + * The likelihood of this happening is very high, since to get the candid and method meta information we must + * execute the Wasm module outside of the replica in our Node.js Wasm environment. Though the likelihood of this happening + * is high, it is not an issue unless our decorators cannot complete the setting of the _azle properties. + * We believe there is a very high likelihood of always being able to set the _azle properties, even if errors + * are thrown after this process completes. + * This environment is not the true canister environment and may not have certain globals or other APIs. + * So we use a try/catch. If there is an exception we check if we're in the Node.js Wasm environment. + * If we are, we do not throw an error unless the global _azleExportedCanisterClassInstance is undefined. + * If it is not undefined, we assume that the decorators have executed during the context.addInitializer callback. + * This callback occurs before the class's constructor or properties are initialized. + * There may be some rare conditions where this scheme will not work, but we believe the likelihood is extremely low. + */ + function getExportedCanisterClassInstance() { + try { + Canister.default.prototype._azleShouldRegisterCanisterMethods = true; + new Canister.default(); + Canister.default.prototype._azleShouldRegisterCanisterMethods = false; + } catch (error) { + if (globalThis._azleNodeWasmEnvironment === true) { + if (globalThis._azleExportedCanisterClassInstance === undefined) { + throw error; + } + } + else { + throw error; + } + } + + return globalThis._azleExportedCanisterClassInstance; + } `; } @@ -113,7 +149,7 @@ function experimentalMessage(importName: string): string { function handleBenchmarking(): string { return /*TS*/ ` if (globalThis._azleRecordBenchmarks === true) { - const methodMeta = globalThis._azleMethodMeta; + const methodMeta = exportedCanisterClassInstance._azleMethodMeta; globalThis._azleCanisterMethodNames = Object.entries(methodMeta).reduce((acc, [key, value]) => { if (value === undefined) { diff --git a/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/execute_method_js.rs b/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/execute_method_js.rs index 13fbd41782..18d1585135 100644 --- a/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/execute_method_js.rs +++ b/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/execute_method_js.rs @@ -29,14 +29,24 @@ fn execute_method_js_with_result( pass_arg_data: bool, ) -> Result<(), Box> { quickjs_with_ctx(|ctx| { - let callbacks: Object = ctx + let exported_canister_class_instance: Object = ctx .clone() .globals() + .get("_azleExportedCanisterClassInstance") + .map_err(|e| { + format!("Failed to get globalThis._azleExportedCanisterClassInstance: {e}") + })?; + + let callbacks: Object = exported_canister_class_instance .get("_azleCallbacks") - .map_err(|e| format!("Failed to get globalThis._azleCallbacks: {e}"))?; + .map_err(|e| { + format!("Failed to get exportedCanisterClassInstance._azleCallbacks: {e}") + })?; let method_callback: Function = callbacks.get(&function_name).map_err(|e| { - format!("Failed to get globalThis._azleCallbacks[{function_name}]: {e}") + format!( + "Failed to get exportedCanisterClassInstance._azleCallbacks[{function_name}]: {e}" + ) })?; let candid_args = if pass_arg_data { diff --git a/src/lib/experimental/globals.ts b/src/lib/experimental/globals.ts index 7f8d102767..e493d1ed96 100644 --- a/src/lib/experimental/globals.ts +++ b/src/lib/experimental/globals.ts @@ -8,9 +8,20 @@ import * as process from 'process'; import { URL } from 'url'; import { v4 } from 'uuid'; +import { MethodMeta } from '../../build/stable/utils/types'; import { azleFetch } from './fetch'; +type Callbacks = { + [key: string]: (...args: any) => any; +}; + declare global { + // eslint-disable-next-line no-var + var _azleCallbacks: Callbacks; + // eslint-disable-next-line no-var + var _azleCanisterMethodsIndex: number; + // eslint-disable-next-line no-var + var _azleMethodMeta: MethodMeta; // eslint-disable-next-line no-var var _azleOutgoingHttpOptionsSubnetSize: number | undefined; // eslint-disable-next-line no-var @@ -32,6 +43,15 @@ globalThis._azleInsideCanister = : true; if (globalThis._azleInsideCanister === true) { + globalThis._azleCallbacks = {}; + + globalThis._azleCanisterMethodsIndex = 0; + + globalThis._azleMethodMeta = { + queries: [], + updates: [] + }; + // Even though these are set in stable/globals // we must set them again here because importing the url module above // seemingly resets globalThis.TextDecoder and globalThis.TextEncoder diff --git a/src/lib/stable/canister_methods/heartbeat.ts b/src/lib/stable/canister_methods/heartbeat.ts index c639d3f5dd..44aa0aedb4 100644 --- a/src/lib/stable/canister_methods/heartbeat.ts +++ b/src/lib/stable/canister_methods/heartbeat.ts @@ -1,8 +1,27 @@ -import { decoratorArgumentsHandler, MethodType } from '.'; +import { Context, decoratorArgumentsHandler, OriginalMethod } from '.'; -export function heartbeat( - originalMethod: MethodType, - context: ClassMethodDecoratorContext +/** + * Decorator to mark a method as the `heartbeat` entry point. + * + * @remarks + * + * It is advised to use `setTimer` and `setTimerInterval` instead of the `heartbeat` method. + * + * The `heartbeat` entry point will be called periodically by the IC (~every second or so). + * + * Only one `heartbeat` method is allowed per canister. + * + * - **State**: read-write + * + * - **Replication**: Yes + * + * - **Async**: Yes + * + * - **Instruction limit**: [40_000_000_000](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/maintain/resource-limits) + */ +export function heartbeat( + originalMethod: OriginalMethod, + context: Context ): void { decoratorArgumentsHandler('heartbeat', originalMethod, context); } diff --git a/src/lib/stable/canister_methods/index.ts b/src/lib/stable/canister_methods/index.ts index bdd50e1610..7ea1421d38 100644 --- a/src/lib/stable/canister_methods/index.ts +++ b/src/lib/stable/canister_methods/index.ts @@ -1,55 +1,81 @@ import { IDL } from '@dfinity/candid'; +import { MethodMeta } from '../../../build/stable/utils/types'; import { handleUncaughtError } from '../error'; import { CanisterMethodMode, executeAndReplyWithCandidSerde } from '../execute_with_candid_serde'; +import { QueryOptions } from './query'; +import { UpdateOptions } from './update'; -export type MethodType = ( +export interface ExportedCanisterClass { + _azleCallbacks?: { + [key: string]: (args?: Uint8Array) => Promise; + }; + _azleCanisterMethodIdlTypes?: { [key: string]: IDL.FuncClass }; + _azleCanisterMethodsIndex?: number; + _azleInitAndPostUpgradeIdlTypes?: IDL.FuncClass[]; + _azleMethodMeta?: MethodMeta; + _azleShouldRegisterCanisterMethods?: boolean; +} + +export type MethodType = ( this: This, ...args: Args ) => Return; -export type DecoratorFunction = ( - originalMethod: MethodType, - context: ClassMethodDecoratorContext -) => MethodType; +export type DecoratorFunction = ( + originalMethod: OriginalMethod, + context: Context +) => void; + +export type OriginalMethod = MethodType< + This, + Args, + Return +>; + +export type Context< + This, + Args extends unknown[], + Return +> = ClassMethodDecoratorContext>; -export function decoratorArgumentsHandler( +/** + * @internal + * + * This is the entry point for our overloaded decorator functions before calling the common implementation. + * It handles determining which overload we are using and then either calling the common implementation or + * returning a decorator function which calls the common implementation. + */ +export function decoratorArgumentsHandler( canisterMethodMode: CanisterMethodMode, - param1?: MethodType | IDL.Type[], - param2?: ClassMethodDecoratorContext | IDL.Type, - param3?: { composite?: boolean; manual?: boolean } -): MethodType | DecoratorFunction { - // First overload - decorator without params - if ( - typeof param1 === 'function' && - param2 !== undefined && - 'kind' in param2 && - param2.kind === 'method' && - param2.metadata !== undefined && - param2.name !== undefined - ) { - const originalMethod = param1; - const context = param2 as ClassMethodDecoratorContext; + param1?: OriginalMethod | IDL.Type[], + param2?: Context | IDL.Type, + param3?: QueryOptions | UpdateOptions +): void | DecoratorFunction { + const decoratorIsOverloadedWithoutParams = + isDecoratorOverloadedWithoutParams(param1, param2); + + if (decoratorIsOverloadedWithoutParams === true) { + const originalMethod = param1 as OriginalMethod; + const context = param2 as Context; return decoratorImplementation( canisterMethodMode, originalMethod, context ); - } - // Second overload - decorator with params - else { + } else { const paramIdlTypes = param1 as IDL.Type[] | undefined; const returnIdlType = param2 as IDL.Type | undefined; const options = param3; return ( - originalMethod: MethodType, - context: ClassMethodDecoratorContext - ): MethodType => { + originalMethod: OriginalMethod, + context: Context + ): void => { return decoratorImplementation( canisterMethodMode, originalMethod, @@ -62,104 +88,179 @@ export function decoratorArgumentsHandler( } } -function decoratorImplementation( +/** + * @internal + * + * The common implementation for all of our exposed canister method decorators. + */ +function decoratorImplementation( canisterMethodMode: CanisterMethodMode, - originalMethod: MethodType, - context: ClassMethodDecoratorContext, + originalMethod: OriginalMethod, + context: Context, paramIdlTypes?: IDL.Type[], returnIdlType?: IDL.Type, - options?: { composite?: boolean; manual?: boolean } -): MethodType { - const name = context.name as string; - - const index = globalThis._azleCanisterMethodsIndex++; - const indexString = index.toString(); - - if (canisterMethodMode === 'query') { - globalThis._azleMethodMeta.queries?.push({ - name, - index, - composite: options?.composite ?? false - }); - - globalThis._azleCanisterMethodIdlTypes[name] = IDL.Func( - paramIdlTypes ?? [], - returnIdlType === undefined ? [] : [returnIdlType], - ['query'] - ); - } + options?: QueryOptions | UpdateOptions +): void { + context.addInitializer(function () { + let exportedCanisterClassInstance = this as ExportedCanisterClass; - if (canisterMethodMode === 'update') { - globalThis._azleMethodMeta.updates?.push({ - name, - index - }); + if ( + exportedCanisterClassInstance._azleCanisterMethodsIndex === + undefined + ) { + exportedCanisterClassInstance._azleCanisterMethodsIndex = 0; + } - globalThis._azleCanisterMethodIdlTypes[name] = IDL.Func( - paramIdlTypes ?? [], - returnIdlType === undefined ? [] : [returnIdlType] - ); - } + if ( + exportedCanisterClassInstance._azleCanisterMethodIdlTypes === + undefined + ) { + exportedCanisterClassInstance._azleCanisterMethodIdlTypes = {}; + } - if (canisterMethodMode === 'init') { - globalThis._azleMethodMeta.init = { - name, - index - }; + if ( + exportedCanisterClassInstance._azleInitAndPostUpgradeIdlTypes === + undefined + ) { + exportedCanisterClassInstance._azleInitAndPostUpgradeIdlTypes = []; + } - globalThis._azleInitAndPostUpgradeIdlTypes.push( - IDL.Func(paramIdlTypes ?? [], [], ['init']) - ); - } + if (exportedCanisterClassInstance._azleMethodMeta === undefined) { + exportedCanisterClassInstance._azleMethodMeta = { + queries: [], + updates: [] + }; + } - if (canisterMethodMode === 'postUpgrade') { - globalThis._azleMethodMeta.post_upgrade = { - name, - index - }; + if (exportedCanisterClassInstance._azleCallbacks === undefined) { + exportedCanisterClassInstance._azleCallbacks = {}; + } - globalThis._azleInitAndPostUpgradeIdlTypes.push( - IDL.Func(paramIdlTypes ?? [], [], ['post_upgrade']) - ); - } + const name = context.name as string; - if (canisterMethodMode === 'preUpgrade') { - globalThis._azleMethodMeta.pre_upgrade = { - name, - index - }; - } + const index = exportedCanisterClassInstance._azleCanisterMethodsIndex++; + const indexString = index.toString(); - if (canisterMethodMode === 'heartbeat') { - globalThis._azleMethodMeta.heartbeat = { - name, - index - }; - } + if (canisterMethodMode === 'query') { + exportedCanisterClassInstance._azleMethodMeta.queries?.push({ + name, + index, + composite: (options as QueryOptions)?.composite ?? false + }); - if (canisterMethodMode === 'inspectMessage') { - globalThis._azleMethodMeta.inspect_message = { - name, - index - }; - } + exportedCanisterClassInstance._azleCanisterMethodIdlTypes[name] = + IDL.Func( + paramIdlTypes ?? [], + returnIdlType === undefined ? [] : [returnIdlType], + ['query'] + ); + } - globalThis._azleCallbacks[indexString] = async ( - args?: Uint8Array - ): Promise => { - try { - await executeAndReplyWithCandidSerde( - canisterMethodMode, - args ?? new Uint8Array(), - originalMethod.bind(globalThis._azleCanisterClassInstance), - paramIdlTypes ?? [], - returnIdlType, - options?.manual ?? false + if (canisterMethodMode === 'update') { + exportedCanisterClassInstance._azleMethodMeta.updates?.push({ + name, + index + }); + + exportedCanisterClassInstance._azleCanisterMethodIdlTypes[name] = + IDL.Func( + paramIdlTypes ?? [], + returnIdlType === undefined ? [] : [returnIdlType] + ); + } + + if (canisterMethodMode === 'init') { + exportedCanisterClassInstance._azleMethodMeta.init = { + name, + index + }; + + exportedCanisterClassInstance._azleInitAndPostUpgradeIdlTypes.push( + IDL.Func(paramIdlTypes ?? [], [], ['init']) ); - } catch (error: any) { - handleUncaughtError(error); } - }; - return originalMethod; + if (canisterMethodMode === 'postUpgrade') { + exportedCanisterClassInstance._azleMethodMeta.post_upgrade = { + name, + index + }; + + exportedCanisterClassInstance._azleInitAndPostUpgradeIdlTypes.push( + IDL.Func(paramIdlTypes ?? [], [], ['post_upgrade']) + ); + } + + if (canisterMethodMode === 'preUpgrade') { + exportedCanisterClassInstance._azleMethodMeta.pre_upgrade = { + name, + index + }; + } + + if (canisterMethodMode === 'heartbeat') { + exportedCanisterClassInstance._azleMethodMeta.heartbeat = { + name, + index + }; + } + + if (canisterMethodMode === 'inspectMessage') { + exportedCanisterClassInstance._azleMethodMeta.inspect_message = { + name, + index + }; + } + + exportedCanisterClassInstance._azleCallbacks[indexString] = async ( + args?: Uint8Array + ): Promise => { + try { + await executeAndReplyWithCandidSerde( + canisterMethodMode, + args ?? new Uint8Array(), + originalMethod.bind(exportedCanisterClassInstance as This), + paramIdlTypes ?? [], + returnIdlType, + options?.manual ?? false + ); + } catch (error: unknown) { + handleUncaughtError(error); + } + }; + + if ( + exportedCanisterClassInstance._azleShouldRegisterCanisterMethods === + true && + globalThis._azleExportedCanisterClassInstance === undefined + ) { + globalThis._azleExportedCanisterClassInstance = + exportedCanisterClassInstance; + } + }); +} + +/** + * @internal + * + * Determines if the params are from a pure decorator function without our own parameter currying. + */ +function isDecoratorOverloadedWithoutParams< + This, + Args extends unknown[], + Return +>( + param1?: MethodType | IDL.Type[], + param2?: + | ClassMethodDecoratorContext> + | IDL.Type +): boolean { + return ( + typeof param1 === 'function' && + param2 !== undefined && + 'kind' in param2 && + param2.kind === 'method' && + param2.metadata !== undefined && + param2.name !== undefined + ); } diff --git a/src/lib/stable/canister_methods/init.ts b/src/lib/stable/canister_methods/init.ts index d734ec0eb3..8a1cc6dd26 100644 --- a/src/lib/stable/canister_methods/init.ts +++ b/src/lib/stable/canister_methods/init.ts @@ -1,22 +1,60 @@ import { IDL } from '@dfinity/candid'; -import { decoratorArgumentsHandler, DecoratorFunction, MethodType } from '.'; +import { + Context, + decoratorArgumentsHandler, + DecoratorFunction, + OriginalMethod +} from '.'; -export function init( - originalMethod: MethodType, - context: ClassMethodDecoratorContext -): MethodType; +/** + * Decorator to mark a method as the initialization entry point. + * + * @remarks + * + * Canister initialization generally happens once per canister lifecycle. + * + * Only one `init` method is allowed per canister. + * + * - **State**: read-write + * + * - **Replication**: yes + * + * - **Async**: no + * + * - **Instruction limit**: [300_000_000_000](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/maintain/resource-limits) + */ +export function init( + originalMethod: OriginalMethod, + context: Context +): void; -export function init( +/** + * Decorator to mark a method as the initialization entry point. + * + * @param paramIdlTypes - Optional array of Candid IDL types for the method parameters. The runtime arguments will be decoded using these types. + * + * @remarks + * + * Canister initialization generally happens once per canister lifecycle. + * + * Only one initialization method is allowed per canister. + * + * - **State**: read-write + * + * - **Replication**: yes + * + * - **Async**: no + * + * - **Instruction limit**: [300_000_000_000](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/maintain/resource-limits) + */ +export function init( paramIdlTypes?: IDL.Type[] -): ( - originalMethod: MethodType, - context: ClassMethodDecoratorContext -) => MethodType; +): DecoratorFunction; -export function init( - param1?: MethodType | IDL.Type[], - param2?: ClassMethodDecoratorContext -): MethodType | DecoratorFunction { +export function init( + param1?: OriginalMethod | IDL.Type[], + param2?: Context +): void | DecoratorFunction { return decoratorArgumentsHandler('init', param1, param2); } diff --git a/src/lib/stable/canister_methods/inspect_message.ts b/src/lib/stable/canister_methods/inspect_message.ts index dbcf9a72de..38e9f1584c 100644 --- a/src/lib/stable/canister_methods/inspect_message.ts +++ b/src/lib/stable/canister_methods/inspect_message.ts @@ -1,9 +1,29 @@ -import { decoratorArgumentsHandler, MethodType } from '.'; +import { Context, decoratorArgumentsHandler, OriginalMethod } from '.'; -// TODO explain here in a jsdoc that the dev can get the raw args using argDataRaw -export function inspectMessage( - originalMethod: MethodType, - context: ClassMethodDecoratorContext +/** + * Decorator to mark a method as the `inspectMessage` entry point. + * + * @remarks + * + * The `inspectMessage` entry point will be called just before a call to an `update` entry point. + * + * Arguments to the `update` entry point can be accessed using `argDataRaw`. + * + * Only one `inspectMessage` method is allowed per canister. + * + * - **State**: read-only + * + * - **Replication**: None + * + * - **Async**: No + * + * - **Instruction limit**: [200_000_000](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/maintain/resource-limits) + * + * See [more documentation](https://internetcomputer.org/docs/current/references/ic-interface-spec#system-api-inspect-message). + */ +export function inspectMessage( + originalMethod: OriginalMethod, + context: Context ): void { decoratorArgumentsHandler('inspectMessage', originalMethod, context); } diff --git a/src/lib/stable/canister_methods/post_upgrade.ts b/src/lib/stable/canister_methods/post_upgrade.ts index 68e2ca3fb8..e9d6d52da1 100644 --- a/src/lib/stable/canister_methods/post_upgrade.ts +++ b/src/lib/stable/canister_methods/post_upgrade.ts @@ -1,22 +1,64 @@ import { IDL } from '@dfinity/candid'; -import { decoratorArgumentsHandler, DecoratorFunction, MethodType } from '.'; +import { + Context, + decoratorArgumentsHandler, + DecoratorFunction, + OriginalMethod +} from '.'; -export function postUpgrade( - originalMethod: MethodType, - context: ClassMethodDecoratorContext -): MethodType; +/** + * Decorator to mark a method as the `postUpgrade` entry point. + * + * @remarks + * + * Canister upgrades can be performed multiple times per canister lifecycle. + * + * By default canister upgrades erase the canister's heap memory. + * + * Only one `postUpgrade` method is allowed per canister. + * + * - **State**: read-write + * + * - **Replication**: yes + * + * - **Async**: no + * + * - **Instruction limit**: [300_000_000_000](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/maintain/resource-limits) (shared with `preUpgrade`) + */ +export function postUpgrade( + originalMethod: OriginalMethod, + context: Context +): void; -export function postUpgrade( +/** + * Decorator to mark a method as the `postUpgrade` entry point. + * + * @param paramIdlTypes - Optional array of Candid IDL types for the method parameters. The runtime arguments will be decoded using these types. + * + * @remarks + * + * Canister upgrades can be performed multiple times per canister lifecycle. + * + * By default canister upgrades erase the canister's heap memory. + * + * Only one `postUpgrade` method is allowed per canister. + * + * - **State**: read-write + * + * - **Replication**: yes + * + * - **Async**: no + * + * - **Instruction limit**: [300_000_000_000](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/maintain/resource-limits) (shared with `preUpgrade`) + */ +export function postUpgrade( paramIdlTypes?: IDL.Type[] -): ( - originalMethod: MethodType, - context: ClassMethodDecoratorContext -) => MethodType; +): DecoratorFunction; -export function postUpgrade( - param1?: MethodType | IDL.Type[], - param2?: ClassMethodDecoratorContext -): MethodType | DecoratorFunction { +export function postUpgrade( + param1?: OriginalMethod | IDL.Type[], + param2?: Context +): void | DecoratorFunction { return decoratorArgumentsHandler('postUpgrade', param1, param2); } diff --git a/src/lib/stable/canister_methods/pre_upgrade.ts b/src/lib/stable/canister_methods/pre_upgrade.ts index 23db4e8c85..dc4952f300 100644 --- a/src/lib/stable/canister_methods/pre_upgrade.ts +++ b/src/lib/stable/canister_methods/pre_upgrade.ts @@ -1,8 +1,25 @@ -import { decoratorArgumentsHandler, MethodType } from '.'; +import { Context, decoratorArgumentsHandler, OriginalMethod } from '.'; -export function preUpgrade( - originalMethod: MethodType, - context: ClassMethodDecoratorContext +/** + * Decorator to mark a method as the `preUpgrade` entry point. + * + * @remarks + * + * The `preUpgrade` method will be called just before the canister is upgraded. + * + * Only one `preUpgrade` method is allowed per canister. + * + * - **State**: read-write + * + * - **Replication**: yes + * + * - **Async**: no + * + * - **Instruction limit**: [300_000_000_000](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/maintain/resource-limits) (shared with `postUpgrade`) + */ +export function preUpgrade( + originalMethod: OriginalMethod, + context: Context ): void { decoratorArgumentsHandler('preUpgrade', originalMethod, context); } diff --git a/src/lib/stable/canister_methods/query.ts b/src/lib/stable/canister_methods/query.ts index 57427827c3..2e853eb69b 100644 --- a/src/lib/stable/canister_methods/query.ts +++ b/src/lib/stable/canister_methods/query.ts @@ -1,28 +1,65 @@ import { IDL } from '@dfinity/candid'; -import { decoratorArgumentsHandler, DecoratorFunction, MethodType } from '.'; +import { + Context, + decoratorArgumentsHandler, + DecoratorFunction, + OriginalMethod +} from '.'; -export function query( - originalMethod: MethodType, - context: ClassMethodDecoratorContext -): MethodType; +export type QueryOptions = { + composite?: boolean; + manual?: boolean; +}; -export function query( +/** + * Decorator to mark a method as a `query` call entry point. + * + * @remarks + * + * - **State**: read-only + * + * - **Replication**: possible + * + * - **Async**: yes with `composite` set to true + * + * - **Instruction limit**: [5_000_000_000](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/maintain/resource-limits) + * + */ +export function query( + originalMethod: OriginalMethod, + context: Context +): void; + +/** + * Decorator to mark a method as a `query` call entry point. + * + * @remarks + * + * - **State**: read-only + * + * - **Replication**: possible + * + * - **Async**: yes with `composite` set to true + * + * - **Instruction limit**: [5_000_000_000](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/maintain/resource-limits) + * + * @param paramIdlTypes - Optional array of Candid IDL types for the method parameters. The runtime arguments will be decoded using these types. + * @param returnIdlType - Optional Candid IDL type for the method return value. The runtime return value will be encoded using this type. + * @param options - Optional configuration object + * @param options.composite - Optional flag to indicate that the method should be treated as a composite query method capable of some cross-canister query calls. + * @param options.manual - Optional flag to indicate manual handling of the method's runtime return value. This is meant to be used with `reply`, skipping automatic Candid encoding of the runtime return value. + */ +export function query( paramIdlTypes?: IDL.Type[], returnIdlType?: IDL.Type, - options?: { - composite?: boolean; - manual?: boolean; - } -): ( - originalMethod: MethodType, - context: ClassMethodDecoratorContext -) => MethodType; + options?: QueryOptions +): DecoratorFunction; -export function query( - param1?: MethodType | IDL.Type[], - param2?: ClassMethodDecoratorContext | IDL.Type, - param3?: { composite?: boolean; manual?: boolean } -): MethodType | DecoratorFunction { +export function query( + param1?: OriginalMethod | IDL.Type[], + param2?: Context | IDL.Type, + param3?: QueryOptions +): void | DecoratorFunction { return decoratorArgumentsHandler('query', param1, param2, param3); } diff --git a/src/lib/stable/canister_methods/update.ts b/src/lib/stable/canister_methods/update.ts index 818426bbb6..d8b6262bf3 100644 --- a/src/lib/stable/canister_methods/update.ts +++ b/src/lib/stable/canister_methods/update.ts @@ -1,27 +1,63 @@ import { IDL } from '@dfinity/candid'; -import { decoratorArgumentsHandler, DecoratorFunction, MethodType } from '.'; +import { + Context, + decoratorArgumentsHandler, + DecoratorFunction, + OriginalMethod +} from '.'; -export function update( - originalMethod: MethodType, - context: ClassMethodDecoratorContext -): MethodType; +export type UpdateOptions = { + manual?: boolean; +}; -export function update( +/** + * Decorator to mark a method as an `update` call entry point. + * + * @remarks + * + * - **State**: read-write + * + * - **Replication**: yes + * + * - **Async**: yes + * + * - **Instruction limit**: [40_000_000_000](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/maintain/resource-limits) + * + */ +export function update( + originalMethod: OriginalMethod, + context: Context +): void; + +/** + * Decorator to mark a method as an `update` call entry point. + * + * @param paramIdlTypes - Optional array of Candid IDL types for the method parameters. The runtime arguments will be decoded using these types. + * @param returnIdlType - Optional Candid IDL type for the method return value. The runtime return value will be encoded using this type. + * @param options - Optional configuration object + * @param options.manual - Optional flag to indicate manual handling of the method's runtime return value. This is meant to be used with `reply` or `reject`, skipping automatic Candid encoding of the runtime return value. + * + * @remarks + * + * - **State**: read-write + * + * - **Replication**: yes + * + * - **Async**: yes + * + * - **Instruction limit**: [40_000_000_000](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/maintain/resource-limits) + */ +export function update( paramIdlTypes?: IDL.Type[], returnIdlType?: IDL.Type, - options?: { - manual?: boolean; - } -): ( - originalMethod: MethodType, - context: ClassMethodDecoratorContext -) => MethodType; + options?: UpdateOptions +): DecoratorFunction; -export function update( - param1?: MethodType | IDL.Type[], - param2?: ClassMethodDecoratorContext | IDL.Type, - param3?: { manual?: boolean } -): MethodType | DecoratorFunction { +export function update( + param1?: OriginalMethod | IDL.Type[], + param2?: Context | IDL.Type, + param3?: UpdateOptions +): void | DecoratorFunction { return decoratorArgumentsHandler('update', param1, param2, param3); } diff --git a/src/lib/stable/globals.ts b/src/lib/stable/globals.ts index 094d66b9be..376fb9597a 100644 --- a/src/lib/stable/globals.ts +++ b/src/lib/stable/globals.ts @@ -1,45 +1,30 @@ -import { IDL } from '@dfinity/candid'; // @ts-expect-error import { TextDecoder, TextEncoder } from '@sinonjs/text-encoding'; -import { MethodMeta } from '../../build/stable/utils/types'; import { AzleIcExperimental } from '../experimental/ic/azle_ic_experimental'; import { jsonReplacer } from '../stable/stable_structures/stable_json'; +import { ExportedCanisterClass } from './canister_methods'; import { print } from './ic_apis'; import { AzleIcStable } from './ic_apis/azle_ic_stable'; -type Callbacks = { - [key: string]: (...args: any) => any; -}; - declare global { - // eslint-disable-next-line no-var - var _azleCallbacks: Callbacks; - // eslint-disable-next-line no-var - var _azleCanisterClassInstance: any; - // eslint-disable-next-line no-var - var _azleCanisterMethodIdlTypes: { [key: string]: IDL.FuncClass }; - // eslint-disable-next-line no-var - var _azleCanisterMethodsIndex: number; // eslint-disable-next-line no-var var _azleCanisterMethodNames: { [key: string]: string }; // eslint-disable-next-line no-var var _azleExperimental: boolean; // eslint-disable-next-line no-var + var _azleExportedCanisterClassInstance: ExportedCanisterClass; + // eslint-disable-next-line no-var var _azleIcExperimental: AzleIcExperimental; // eslint-disable-next-line no-var var _azleIcStable: AzleIcStable; // eslint-disable-next-line no-var var _azleIcTimers: { [key: string]: string }; // eslint-disable-next-line no-var - var _azleInitAndPostUpgradeIdlTypes: IDL.FuncClass[]; - // eslint-disable-next-line no-var var _azleInitCalled: boolean; // eslint-disable-next-line no-var var _azleInsideCanister: boolean; // eslint-disable-next-line no-var - var _azleMethodMeta: MethodMeta; - // eslint-disable-next-line no-var var _azleNodeWasmEnvironment: boolean; // eslint-disable-next-line no-var var _azlePostUpgradeCalled: boolean; @@ -63,21 +48,8 @@ globalThis._azleInsideCanister = // TODO do we need to disable any other wasmedge-quickjs globals // TODO that we don't think are stable yet? if (globalThis._azleInsideCanister === true) { - globalThis._azleCallbacks = {}; - - globalThis._azleCanisterMethodsIndex = 0; - - globalThis._azleCanisterMethodIdlTypes = {}; - globalThis._azleCanisterMethodNames = {}; - globalThis._azleInitAndPostUpgradeIdlTypes = []; - - globalThis._azleMethodMeta = { - queries: [], - updates: [] - }; - globalThis._azleTimerCallbacks = {}; globalThis._azleIcTimers = {};