diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 0000000..c54aeb2 --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,194 @@ +## Nominal Changes + +These are changes to the way things are named, but the functionality remains the same. This list is not exhaustive. + +| TEALScript | PuyaTS | Notes | +| ------------------------------------ | ----------------------------- | ------------------------------------------------ | +| `GlobalStateKey` | `GlobalState` | | +| `LocalStateKey` | `LocalState` | | +| `BoxKey` | `BoxRef` | | +| `prefix` | `keyPrefix` | The prefix option for BoxMap | +| `this.txn` | `Txn` | | +| `this.app` | `Global.currentApplicationId` | | +| `isOptedInToApp`, `isOptedInToAsset` | `isOptedIn` | The Puya function accepts a union of these types | +| `size` | `length` | The size of a box | + +## Minor Changes + +These are minor changes to the syntax of the language/API. This list is not exhaustive. + +| TEALScript | PuyaTS | Notes | +| ----------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------ | +| `this.boxRef.create(boxSize)` | `this.boxRef.create({ size: boxSize })` | The size option is now a property of the create method | +| Explcicit method return types are required | Implicit method return types are allowed | | +| `verify...Txn` | `assertMatch` | `assertMatch` accepts any object. This means, however, that txn types must be explicitly checked if the verified fields apply to more than one txn type | +| Methods, classes, and types are in global scope | Methods, classes, and types must be imported | | +| `forEach` is supported | `forEach` is not supported, but `for ... of` is supported | `for ... of` also enables continue/break | + +## Major Changes + +These are major changes to the syntax of the language/API. + +### Method Routing + +#### TEALScript + +Each action (OnCompletes and create) has a method name that is used to route to the correct method when that action/OC is performed. + +```ts +createApplication() { +``` + +Alternatively, decorators can be used for more complex contracts + +```ts +@allow.create('NoOp') +myCustomCreateMethod() { +``` + +#### PuyaTS + +Decorators must be used to specify the action/OC. + +```ts +@abimethod({ onCreate: "require", allowActions: "NoOp" }) +createApplication() { +``` + +### Math Typing + +#### TEALScript + +TEALScript supports math operators on any `uint` type and returns the result as the same type. + +```ts +getSum(x: uint64, y: uint64): uint64 { + const sum = x + y; + return sum; +} +``` + +#### PuyaTS + +PuyaTS requires explicit usage of a constructor or type hint to return the result of a math operation. This is required so TypeScript (and thus your IDE and other tooling) knows the type of the result is not `number` (which is unavoidable TypeScript behavior). + +```ts +getSum(x: uint64, y: uint64): uint64 { + const sum = Uint64(x + y); + return sum; +} +``` + +or + + +```ts +getSum(x: uint64, y: uint64): uint64 { + const sum: uint64 = x + y; + return sum; +} +``` + +### As Casting + +#### TEALScript + +TEALScript allows casting between types using the `as` keyword. + +```ts +const x: uint8 = 10; +const y = x as uint64; +``` + +#### PuyaTS + +PuyaTS does not support casting between types with `as`. Instead, the respective constructor must be used. + +```ts +const x: uint8 = 10; +const y = UintN<64>(x); +``` + +### String vs Bytes + +#### TEALScript + +In TEALScript, bytes and strings are the same type and can be used interchangeably. + +```ts +assert(swapAsset.assetName === "SWAP"); +``` + +#### PuyaTS + +In PuyaTS, bytes and strings are distinct types. Most functions acccept `bytes | string`, but outputs will always be `bytes` + +```ts +assert(swapAsset.assetName === Bytes("SWAP")); +``` + +### Array Mutability + +#### TEALScript + +TEALScript supports mutable native TypeScript arrays + +```ts +const myArray: uint64[] = [1, 2, 3]; +myArray.push(4); +``` + +#### PuyaTS + +In PuyaTS native arrays are always immutable. + +```ts +let myArray: uint64[] = [1, 2, 3]; +myArray = [...myArray, 4]; +``` + +If you want mutability, there is an explcit `MutableArray` type: + +```ts +let myArray = new MutableArray(1, 2, 3]); +myArray.push(4); +``` + +This type, however, can only be used internally. It cannot be put in storage, events, scratch, or used as a public ABI method parameter/return type. + +### Object Mutability + +#### TEALScript + +In TEALScript, objects are mutable + +```ts +type Favorites { + color: string, + number: uint64, +} +``` + +```ts +updateFavoriteNumber(n: uint64) { + this.favorites(this.txn.sender).value.number = n; +``` + +#### PuyaTS + +Like Arrays, all objects are immutable. + +```ts +type Favorites { + color: string, + number: uint64, +} +``` + +```ts +updateFavoriteNumber(n: uint64) { + this.favorites.set( + Txn.sender, + { ...this.listings.get(Txn.sender), number: n } + ); +``` diff --git a/tealscript_contracts/kitchen-sink-tealscript.algo.ts b/tealscript_contracts/kitchen-sink-tealscript.algo.ts index cc2d027..33aaefb 100644 --- a/tealscript_contracts/kitchen-sink-tealscript.algo.ts +++ b/tealscript_contracts/kitchen-sink-tealscript.algo.ts @@ -1,70 +1,91 @@ -import { Contract } from "@algorandfoundation/tealscript"; +import { + abimethod, + Account, + assert, + assertMatch, + BigUint, + biguint, + Box, + BoxMap, + BoxRef, + bytes, + Bytes, + Contract, + Global, + GlobalState, + gtxn, + LocalState, + Txn, + Uint64, + uint64, +} from "@algorandfoundation/algorand-typescript"; export class KitchenSinkContract extends Contract { - globalInt = GlobalStateKey(); - globalString = GlobalStateKey({ key: "customKey" }); + globalInt = GlobalState({ initialValue: Uint64(4) }); + globalString = GlobalState({ key: "customKey" }); - localBigInt = LocalStateKey>(); + localBigInt = LocalState(); - boxOfArray = BoxKey({ key: "b" }); - boxMap = BoxMap({ prefix: "" }); - boxRef = BoxKey({ key: "FF" }); + boxOfArray = Box({ key: "b" }); + boxMap = BoxMap({ keyPrefix: "" }); + boxRef = BoxRef({ key: Bytes.fromHex("FF") }); useState(a: uint64, b: string, c: uint64) { this.globalInt.value *= a; - if (this.globalString.exists) { + if (this.globalString.hasValue) { this.globalString.value += b; } else { this.globalString.value = b; } - if (this.txn.sender.isOptedInToApp(this.app.id)) { - this.localBigInt(this.txn.sender).value = >(c * a); + if (Txn.sender.isOptedIn(Global.currentApplicationId)) { + this.localBigInt(Txn.sender).value = BigUint(c) * BigUint(a); } } - createApplication() { - this.globalInt.value = 4; - this.globalInt.value = this.app.id; + @abimethod({ onCreate: "require", allowActions: "NoOp" }) + createApp() { + this.globalInt.value = Global.currentApplicationId.id; } - optInToApplication() {} + @abimethod({ allowActions: ["OptIn"] }) + optIn() {} addToBox(x: uint64) { if (!this.boxOfArray.exists) { this.boxOfArray.value = [x]; } else { - this.boxOfArray.value.push(x); + this.boxOfArray.value = [...this.boxOfArray.value, x]; } } addToBoxMap(x: string) { - this.boxMap(this.txn.sender).value = x; + this.boxMap.set(Txn.sender, Bytes(x)); } insertIntoBoxRef(content: bytes, offset: uint64, boxSize: uint64) { assert(offset + content.length < boxSize); if (this.boxRef.exists) { - this.boxRef.create(boxSize); - } else if (this.boxRef.size !== boxSize) { + this.boxRef.create({ size: boxSize }); + } else if (this.boxRef.length !== boxSize) { this.boxRef.resize(boxSize); } this.boxRef.splice(offset, offset + content.length, content); } sayHello(name: string, a: uint64): string { - return this.getHello() + name + itob(a); + return `${this.getHello()} ${name} ${Bytes(a)}`; } - checkTransaction(pay: PayTxn) { - verifyPayTxn(pay, { - amount: { greaterThan: 1000, lessThan: 2000 }, - lastValid: { greaterThan: globals.round }, - sender: this.txn.sender, - receiver: this.app.address, + checkTransaction(pay: gtxn.PaymentTxn) { + assertMatch(pay, { + amount: { between: [1000, 2000] }, + lastValid: { greaterThan: Global.round }, + sender: Txn.sender, + receiver: Global.currentApplicationId.address, }); } - private getHello(): string { + private getHello() { return "Hello"; } }