From 6e34ad2e17ff1ae2189c9be6081583288793c895 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Wed, 16 Oct 2024 09:39:17 +0800 Subject: [PATCH 1/2] feat: implement AppGlobal via context manager - return Uint64(0) when retrieving a deleted app global state to match AVM behaviour - do not use empty byte value as key if no key value is provided to the constructor --- package-lock.json | 2 +- packages/algo-ts/package.json | 2 +- packages/algo-ts/src/impl/state.ts | 28 +++++++++++++++++++--------- packages/algo-ts/src/op.ts | 1 + 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index c515ddec..7ea5b5d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12965,7 +12965,7 @@ }, "packages/algo-ts/dist": { "name": "@algorandfoundation/algorand-typescript", - "version": "0.0.1-alpha.10", + "version": "0.0.1-alpha.12", "dev": true, "peerDependencies": { "tslib": "^2.6.2" diff --git a/packages/algo-ts/package.json b/packages/algo-ts/package.json index 9cefa04b..506a3b92 100644 --- a/packages/algo-ts/package.json +++ b/packages/algo-ts/package.json @@ -1,6 +1,6 @@ { "name": "@algorandfoundation/algorand-typescript", - "version": "0.0.1-alpha.11", + "version": "0.0.1-alpha.12", "description": "This package contains definitions for the types which comprise Algorand TypeScript which can be compiled to run on the Algorand Virtual Machine using the Puya compiler.", "private": false, "main": "index.js", diff --git a/packages/algo-ts/src/impl/state.ts b/packages/algo-ts/src/impl/state.ts index e3691fbc..577fd778 100644 --- a/packages/algo-ts/src/impl/state.ts +++ b/packages/algo-ts/src/impl/state.ts @@ -1,7 +1,8 @@ -import { bytes } from '../primitives' +import { bytes, Uint64 } from '../primitives' import { Account } from '../reference' +import { uint8ArrayToHex } from './encoding-util' import { AssertError } from './errors' -import { BytesCls } from './primitives' +import { BytesCls, Uint64Cls } from './primitives' export class GlobalStateCls { private readonly _type: string = GlobalStateCls.name @@ -10,7 +11,11 @@ export class GlobalStateCls { key: bytes | undefined delete: () => void = () => { - this.#value = undefined + if (this.#value instanceof Uint64Cls) { + this.#value = Uint64(0) as ValueType + } else { + this.#value = undefined + } } static [Symbol.hasInstance](x: unknown): x is GlobalStateCls { @@ -33,7 +38,7 @@ export class GlobalStateCls { } constructor(key?: bytes | string, value?: ValueType) { - this.key = BytesCls.fromCompat(key).asAlgoTs() + this.key = key !== undefined ? BytesCls.fromCompat(key).asAlgoTs() : undefined this.#value = value } } @@ -41,7 +46,11 @@ export class GlobalStateCls { export class LocalStateCls { #value: ValueType | undefined delete: () => void = () => { - this.#value = undefined + if (this.#value instanceof Uint64Cls) { + this.#value = Uint64(0) as ValueType + } else { + this.#value = undefined + } } get value(): ValueType { if (this.#value === undefined) { @@ -60,12 +69,13 @@ export class LocalStateCls { } export class LocalStateMapCls { - #value = new Map>() + #value = new Map>() getValue(account: Account): LocalStateCls { - if (!this.#value.has(account.bytes)) { - this.#value.set(account.bytes, new LocalStateCls()) + const accountString = uint8ArrayToHex(BytesCls.fromCompat(account.bytes).asUint8Array()) + if (!this.#value.has(accountString)) { + this.#value.set(accountString, new LocalStateCls()) } - return this.#value.get(account.bytes)! + return this.#value.get(accountString)! } } diff --git a/packages/algo-ts/src/op.ts b/packages/algo-ts/src/op.ts index 45c1a2ad..59577794 100644 --- a/packages/algo-ts/src/op.ts +++ b/packages/algo-ts/src/op.ts @@ -132,6 +132,7 @@ export const ITxnCreate: ITxnCreateType = createObjectProxy('ITxnCreate') export const Scratch: ScratchType = createObjectProxy('Scratch') export const AcctParams = createObjectProxy('AcctParams') +export const AppGlobal = createObjectProxy('AppGlobal') export const AppParams = createObjectProxy('AppParams') export const AssetHolding = createObjectProxy('AssetHolding') export const AssetParams = createObjectProxy('AssetParams') From 759d18c62e3a9f4f922c4a0a8732443d0385c121 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 17 Oct 2024 14:05:19 +0800 Subject: [PATCH 2/2] refactor: move state implementation to testing package --- packages/algo-ts/src/execution-context.ts | 6 +- packages/algo-ts/src/impl/state.ts | 81 ----------------------- packages/algo-ts/src/internal.ts | 1 - packages/algo-ts/src/state.ts | 13 ++-- 4 files changed, 9 insertions(+), 92 deletions(-) delete mode 100644 packages/algo-ts/src/impl/state.ts diff --git a/packages/algo-ts/src/execution-context.ts b/packages/algo-ts/src/execution-context.ts index d0bf312b..dd02bf02 100644 --- a/packages/algo-ts/src/execution-context.ts +++ b/packages/algo-ts/src/execution-context.ts @@ -1,4 +1,4 @@ -import { Contract, gtxn, itxn } from '.' +import { Contract, GlobalState, gtxn, itxn, LocalState } from '.' import { AbiMethodConfig, BareMethodConfig } from './arc4' import { OpsNamespace } from './op-types' import { bytes, uint64 } from './primitives' @@ -31,6 +31,10 @@ export type ExecutionContext = { assetFreeze: typeof itxn.assetFreeze applicationCall: typeof itxn.applicationCall } + state: { + createGlobalState: typeof GlobalState + createLocalState: typeof LocalState + } } declare global { diff --git a/packages/algo-ts/src/impl/state.ts b/packages/algo-ts/src/impl/state.ts deleted file mode 100644 index 577fd778..00000000 --- a/packages/algo-ts/src/impl/state.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { bytes, Uint64 } from '../primitives' -import { Account } from '../reference' -import { uint8ArrayToHex } from './encoding-util' -import { AssertError } from './errors' -import { BytesCls, Uint64Cls } from './primitives' - -export class GlobalStateCls { - private readonly _type: string = GlobalStateCls.name - - #value: ValueType | undefined - key: bytes | undefined - - delete: () => void = () => { - if (this.#value instanceof Uint64Cls) { - this.#value = Uint64(0) as ValueType - } else { - this.#value = undefined - } - } - - static [Symbol.hasInstance](x: unknown): x is GlobalStateCls { - return x instanceof Object && '_type' in x && (x as { _type: string })['_type'] === GlobalStateCls.name - } - - get value(): ValueType { - if (this.#value === undefined) { - throw new AssertError('value is not set') - } - return this.#value - } - - set value(v: ValueType) { - this.#value = v - } - - get hasValue(): boolean { - return this.#value !== undefined - } - - constructor(key?: bytes | string, value?: ValueType) { - this.key = key !== undefined ? BytesCls.fromCompat(key).asAlgoTs() : undefined - this.#value = value - } -} - -export class LocalStateCls { - #value: ValueType | undefined - delete: () => void = () => { - if (this.#value instanceof Uint64Cls) { - this.#value = Uint64(0) as ValueType - } else { - this.#value = undefined - } - } - get value(): ValueType { - if (this.#value === undefined) { - throw new AssertError('value is not set') - } - return this.#value - } - - set value(v: ValueType) { - this.#value = v - } - - get hasValue(): boolean { - return this.#value !== undefined - } -} - -export class LocalStateMapCls { - #value = new Map>() - - getValue(account: Account): LocalStateCls { - const accountString = uint8ArrayToHex(BytesCls.fromCompat(account.bytes).asUint8Array()) - if (!this.#value.has(accountString)) { - this.#value.set(accountString, new LocalStateCls()) - } - return this.#value.get(accountString)! - } -} diff --git a/packages/algo-ts/src/internal.ts b/packages/algo-ts/src/internal.ts index 2d2c0f3b..daa562a3 100644 --- a/packages/algo-ts/src/internal.ts +++ b/packages/algo-ts/src/internal.ts @@ -2,5 +2,4 @@ export { ExecutionContext, ctxMgr } from './execution-context' export * as encodingUtil from './impl/encoding-util' export * as errors from './impl/errors' export * as primitives from './impl/primitives' -export * as state from './impl/state' export * as opTypes from './op-types' diff --git a/packages/algo-ts/src/state.ts b/packages/algo-ts/src/state.ts index d13030a5..6e27a5cc 100644 --- a/packages/algo-ts/src/state.ts +++ b/packages/algo-ts/src/state.ts @@ -1,4 +1,4 @@ -import { GlobalStateCls, LocalStateMapCls } from './impl/state' +import { ctxMgr } from './execution-context' import { bytes } from './primitives' import { Account } from './reference' @@ -9,11 +9,11 @@ export type GlobalState = { hasValue: boolean } -type GlobalStateOptions = { key?: bytes | string; initialValue?: ValueType } +export type GlobalStateOptions = { key?: bytes | string; initialValue?: ValueType } /** A single key in global state */ export function GlobalState(options?: GlobalStateOptions): GlobalState { - return new GlobalStateCls(options?.key, options?.initialValue) + return ctxMgr.instance.state.createGlobalState(options) } /** A value saved in local state */ @@ -29,10 +29,5 @@ export type LocalState = { /** A single key in local state */ export function LocalState(options?: { key?: bytes | string }): LocalState { - function localStateInternal(account: Account): LocalStateForAccount { - return localStateInternal.map.getValue(account) - } - localStateInternal.key = options?.key - localStateInternal.map = new LocalStateMapCls() - return localStateInternal + return ctxMgr.instance.state.createLocalState(options) }