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 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
## Nominal Changes

These are changes to the way things are named, but the functionality remains the same.

| 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.

| 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 |
| Methods, classes, and types are in global scope | Methods, classes, and types must be imported | |

## 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() {
```

#### PuyaTS

Decorators must be used to specify the action/OC.

```ts
@abimethod({ onCreate: "require", allowActions: "NoOp" })
createApplication() {
```

### Native vs ABI types

#### TEALScript

TEALScript does ABI encoding/decoding automatically where appropriate.

```ts
addToBox(x: uint64) {
if (!this.boxOfArray.exists) {
this.boxOfArray.value = [x];
} else {
this.boxOfArray.value.push(x);
}
}
```

#### PuyaTS

Puya requires explicit ABI encoding/decoding in some scenarios, resulting in a "native" `uint64` type and a `UintN<64>` type. This also means that arrays must be initialized with a constructor rather than using array literals.

```ts
addToBox(x: uint64) {
if (!this.boxOfArray.exists) {
this.boxOfArray.value = new DynamicArray(new UintN<64>(x));
} else {
this.boxOfArray.value.push(new UintN<64>(x));
}
}
```

### 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 to return the result of a math operation.

```ts
getSum(x: uint64, y: uint64): uint64 {
const sum = UintN<64>(sum);
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"));
```
80 changes: 53 additions & 27 deletions tealscript_contracts/kitchen-sink-tealscript.algo.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,96 @@
import { Contract } from "@algorandfoundation/tealscript";
import {
abimethod,
Account,
Application,
assert,
assertMatch,
BigUint,
biguint,
Box,
BoxMap,
BoxRef,
bytes,
Bytes,
Contract,
Global,
GlobalState,
gtxn,
LocalState,
Txn,
Uint64,
uint64,
} from "@algorandfoundation/algorand-typescript";
import {
DynamicArray,
UintN,
} from "@algorandfoundation/algorand-typescript/arc4";

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<DynamicArray<UintN<64>>>({ 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];
this.boxOfArray.value = new DynamicArray(new UintN<64>(x));
} else {
this.boxOfArray.value.push(x);
this.boxOfArray.value.push(new UintN<64>(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";
}
}