Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TEALScript -> PuyaTS migration (DO NOT MERGE) #3

Draft
wants to merge 15 commits into
base: feat/native_types
Choose a base branch
from
194 changes: 194 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -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<n>` 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<uint64>(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 }
);
```
73 changes: 47 additions & 26 deletions tealscript_contracts/kitchen-sink-tealscript.algo.ts
Original file line number Diff line number Diff line change
@@ -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<uint64>();
globalString = GlobalStateKey<string>({ key: "customKey" });
globalInt = GlobalState({ initialValue: Uint64(4) });
globalString = GlobalState<string>({ key: "customKey" });

localBigInt = LocalStateKey<uint<512>>();
localBigInt = LocalState<biguint>();

boxOfArray = BoxKey<uint64[]>({ key: "b" });
boxMap = BoxMap<Address, bytes>({ prefix: "" });
boxRef = BoxKey<bytes>({ key: "FF" });
boxOfArray = Box<uint64[]>({ key: "b" });
boxMap = BoxMap<Account, bytes>({ 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 = <uint<512>>(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";
}
}