Proposed features to implement for PuyaTS 1.0
The following outlines some proposed changes to address feedback from the community about current functionality that is either too verbose, or in some cases infeasible when comparing between TEALScript and Algorand TypeScript beta.
The following will outline the potential changes (with some flexibility for what is actually needed for 1.0) and relevant examples that the changes impact.
For brevity examples 2.x and 3.x have been omitted in this document as in Puya they are additional variants of the basic data types covered by 1.x. In general Puya supports arbitrary levels of dynamic sized types, however in practice a good smart contract design would want to avoid this.
Mutable Native Struct
Section titled “Mutable Native Struct”- Allow direct mutation of struct fields
- Allow native values (no need to convert to/from ARC-4)
- Aliases would still require a .copy() to maintain semantic compatibility
Examples
Section titled “Examples”Mutable Native Fixed Sized array
Section titled “Mutable Native Fixed Sized array”- Allow direct mutation of array members
- Allow native values (no need to convert to/from ARC-4)
- Allow other mutable native types as elements
- Aliases of array would still require a .copy() to maintain semantic compatibility
- Aliases of contents would still require a .copy()
Examples
Section titled “Examples”Mutable Native Dynamic Sized array
Section titled “Mutable Native Dynamic Sized array”- Allow direct mutation of array members
- Allow native values (no need to convert to/from ARC-4)
- Allow other mutable native types as elements
- Aliases of array would still require a .copy() to maintain semantic compatibility
- Aliases of contents would still require a .copy()
Examples
Section titled “Examples”Direct box mutation
Section titled “Direct box mutation”- Allow mutation of box values larger than 4k by directly manipulating the box
- Efficiency will only work with statically sized types or raw Bytes
- Dynamically sized types will fallback to current behaviour, which would fail if total size of box exceeds 4k e.g.
self.box.value.field = someValueWill compile to box_replace instead of a box_get; <modify>; box_put
Examples
Section titled “Examples”Box API enhancements
Section titled “Box API enhancements”- Get a Box from a BoxMap with a key, allows box referencing e.g.
const box = this.myMap.box(someKey)- Get a BoxRef from a Box, allows efficient mutation of underlying box bytes e.g.
const ref = this.someBox.refref.replace(5000, someBytes)Examples
Section titled “Examples”Mutable Native Dynamic Array splice and/or pop support
Section titled “Mutable Native Dynamic Array splice and/or pop support”- Only support fixed size element types
Examples
Section titled “Examples”Reference tracking
Section titled “Reference tracking”Implementing this in the Puya backend in a comprehensive way (such that true reference tracking is broadly supported) is not a feasible option due to the limitations of the AVM.
The other features proposed should reduce the need for copying and/or reference tracking.
Examples
Section titled “Examples”1.1 Dynamic arrays of immutable types
Section titled “1.1 Dynamic arrays of immutable types”TealScript
Section titled “TealScript”const arr: uint64[] = [1, 2, 3];
arr.pop();arr.push(5);
return arr; // [1, 2, 5]PuyaTS Beta
Section titled “PuyaTS Beta”let arr: uint64[] = [1, 2, 3];
arr = [...arr.splice(-1, 1), 5]
return arrPuyaTS 1.0
Section titled “PuyaTS 1.0”const arr: uint64[] = [1, 2, 3];
arr.pop();arr.push(5);
return arr; // [1, 2, 5]Requires
Section titled “Requires”1.2 Tuples of Immutable Types
Section titled “1.2 Tuples of Immutable Types”TealScript
Section titled “TealScript”const tuple: [uint64, uint64] = [1, 2];
tuple[0] = 5;
return tuple; // [5, 2]PuyaTS Beta
Section titled “PuyaTS Beta”let tuple: [uin64, uint64] = [1, 2]
tuple = tuple.with(0, 5);
return tuple; // [5, 2]PuyaTS 1.0
Section titled “PuyaTS 1.0”const tuple: [uint64, uint64] = [1, 2];
tuple[0] = 5;
return tuple; // [5, 2]Currently, PuyaTS maps tuple types to the Puya WTuple type, which is defined as immutable Proposed solution will use the proposed mutable native struct type with synthetic compiled generated field names
Requires
Section titled “Requires”1.3 Structs of Immutable Types
Section titled “1.3 Structs of Immutable Types”TealScript
Section titled “TealScript”type Foo = { a: uint64; b: uint64;};
const foo: Foo = { a: 1, b: 2 };
foo.a = 3;
return foo; // { a: 3, b: 2 }PuyaTS Beta
Section titled “PuyaTS Beta”type Foo = { a: uint64; b: uint64;};
let foo: Foo = { a: 1, b: 2 };
foo = {...foo, a: 3};
return foo; // { a: 3, b: 2 }PuyaTS 1.0
Section titled “PuyaTS 1.0”class Foo extends MutableObject<{ a: uint64 b: uint64}> { }const foo: Foo = new Foo({ a: 1, b: 2 })
foo.a = 3
return foo // { a: 3, b: 2 }Requires
Section titled “Requires”5.1 Reti Registry
Section titled “5.1 Reti Registry”TealScript
Section titled “TealScript”this.validatorList(poolKey.id).value.pools[poolKey.poolId - 1].totalAlgoStaked += algoToAddthis.validatorList(poolKey.id).value.state.totalAlgoStaked += algoToAddthis.validatorList(poolKey.id).value.state.rewardTokenHeldBack += rewardTokenAmountReservedVariant
Section titled “Variant”const validator = this.validatorList(poolKey.id).valuevalidator.pools[poolKey.poolId - 1].totalAlgoStaked += algoToAddvalidator.state.totalAlgoStaked += algoToAddvalidator.state.rewardTokenHeldBack += rewardTokenAmountReservedPuyaTS Beta
Section titled “PuyaTS Beta”const validator = this.validatorList(poolKey.id).value.copy()const poolIdx: uint64 = poolKey.id.native - 1let poolTotalAlgoStaked = validator.pools[poolIdx].totalAlgoStakedpoolTotalAlgoStaked = new arc4.UintN64(poolTotalAlgoStaked.native + algoToAdd)this.validatorList(poolKey.id).value.pools[poolIdx].totalAlgoStaked = poolTotalAlgoStakedthis.validatorList(poolKey.id).value.state.totalAlgoStaked = new arc4.UintN64( validator.state.totalAlgoStaked.native + algoToAdd,)this.validatorList(poolKey.id).value.state.rewardTokenHeldBack = new arc4.UintN64( validator.state.rewardTokenHeldBack.native + rewardTokenAmountReserved,)
this.totalAlgoStaked.value = new arc4.UintN64(this.totalAlgoStaked.value.native + algoToAdd)PuyaTS 1.0
Section titled “PuyaTS 1.0”this.validatorList(poolKey.id).value.pools[poolKey.poolId - 1].totalAlgoStaked += algoToAddthis.validatorList(poolKey.id).value.state.totalAlgoStaked += algoToAddthis.validatorList(poolKey.id).value.state.rewardTokenHeldBack += rewardTokenAmountReservedVariant
Section titled “Variant”const validator = this.validatorList.map(poolKey.id)validator.value.pools[poolKey.poolId - 1].totalAlgoStaked += algoToAddvalidator.value.state.totalAlgoStaked += algoToAddvalidator.value.state.rewardTokenHeldBack += rewardTokenAmountReservedRequires
Section titled “Requires”5.5 Immersive - Reading card fund data
Section titled “5.5 Immersive - Reading card fund data”TealScript
Section titled “TealScript”const cardFundData = this.cardFunds(cardFund).value;const eventData: CardFundCloseEventData = { cardFundOwner: cardFundData.owner, cardFund: cardFund, partnerChannel: cardFundData.partnerChannel, reference: cardFundData.reference};const partnerCardFundOwnerKeyData: PartnerCardFundData = { partnerChannel: cardFundData.partnerChannel, cardFundOwner: cardFundData.owner,};const partnerCardFundOwnerKey = sha256(rawBytes(partnerCardFundOwnerKeyData));
sendPayment({ sender: cardFund, receiver: cardFund, amount: 0, closeRemainderTo: this.txn.sender,});
const boxCost = this.getCardFundBoxMbr(cardFundData.reference);PuyaTS 1.0
Section titled “PuyaTS 1.0”const cardFundData = this.cardFunds(cardFund);const eventData: CardFundCloseEventData = { cardFundOwner: cardFundData.value.owner, cardFund: cardFund, partnerChannel: cardFundData.value.partnerChannel, reference: cardFundData.value.reference};const partnerCardFundOwnerKeyData: PartnerCardFundData = { partnerChannel: cardFundData.value.partnerChannel, cardFundOwner: cardFundData.value.owner,};const partnerCardFundOwnerKey = sha256(arc4.encode(partnerCardFundOwnerKeyData));
sendPayment({ sender: cardFund, receiver: cardFund, amount: 0, closeRemainderTo: this.txn.sender,});
const boxCost = this.getCardFundBoxMbr(cardFundData.value.reference);Requires
Section titled “Requires”6.1 Dynamic native array + splice
Section titled “6.1 Dynamic native array + splice”TealScript
Section titled “TealScript”class ARC75 extends Contract { whitelist = BoxMap<Whitelist, uint64[]>();
setAppWhitelist(appIDs: uint64[]): void { this.whitelist(whitelist).value = appIDs; }
deleteAppFromWhitelist(appID: uint64, index: uint64) { const spliced = this.whitelist(whitelist).value.splice(index, 1); assert(spliced[0] === appID); }}PuyaTS Beta
Section titled “PuyaTS Beta”export class ARC75 extends Contract { whitelist = BoxMap<Whitelist, DynamicArray<UintN64>>({ keyPrefix: Bytes() });
setAppWhitelist(appIDs: uint64[]): void { // error: Not Supported: Accessing member map on Array<uint64> // const newWhitelist = new DynamicArray(...appIDs.map(appID => new UintN64(appID))); const newWhitelist = new DynamicArray<UintN64>(); for (let i: uint64 = 0; i < appIDs.length; i++) { newWhitelist.push(new UintN64(appIDs[i])); } this.whitelist(whitelist).value = newWhitelist.copy(); }
deleteAppFromWhitelist(index: uint64) { // `splice` is not available in `DynamicArray` const spliced = this.whitelist(whitelist).value.at(index); const newWhiteList = new DynamicArray<UintN64>(); for (let i: uint64 = 0; i < this.whitelist(whitelist).value.length; i++) { if (i !== index) { newWhiteList.push(this.whitelist(whitelist).value.at(i)); } } this.whitelist(whitelist).value = newWhiteList.copy(); assert(spliced.native === appID); }}PuyaTS 1.0
Section titled “PuyaTS 1.0”export class ARC75 extends Contract { whitelist = BoxMap<Whitelist, uint64[]>({ keyPrefix: Bytes() });
setAppWhitelist(appIDs: uint64[]): void { this.whitelist(whitelist).value = appIDs }
deleteAppFromWhitelist(appID: uint64, index: uint64) { // `splice` is not available in `DynamicArray` const spliced = this.whitelist(whitelist).value.at(index); const newWhiteList: uint64[] = []; for (let i: uint64 = 0; i < this.whitelist(whitelist).value.length; i++) { if (i !== index) { newWhiteList.push(this.whitelist(whitelist).value.at(i)); } } this.whitelist(whitelist).value = newWhiteList.copy(); assert(spliced === appID); }}PuyaTS Post 1.0
Section titled “PuyaTS Post 1.0”Splice
Section titled “Splice”export class ARC75 extends Contract { whitelist = BoxMap<Whitelist, uint64[]>({ keyPrefix: Bytes() });
setAppWhitelist(appIDs: uint64[]): void { this.whitelist(whitelist).value = appIDs }
deleteAppFromWhitelist(appID: uint64, index: uint64) { const spliced = this.whitelist(whitelist).value.splice(index, 1); assert(spliced[0] === appID); }}export class ARC75 extends Contract { whitelist = BoxMap<Whitelist, uint64[]>({ keyPrefix: Bytes() });
setAppWhitelist(appIDs: uint64[]): void { this.whitelist(whitelist).value = appIDs }
deleteAppFromWhitelist(appID: uint64, index: uint64) { const deleted = this.whitelist(whitelist).value.pop(index); assert(deleted === appID); }}Requires
Section titled “Requires”API array type names
Section titled “API array type names”Array semantic definitions
Section titled “Array semantic definitions”Immutable: Array elements cannot be directly mutated/changed
Mutable: Array elements can be directly mutated/changed
Native: Elements can be native (bytes, uint64) or ARC-4 (uint
- ARC-4
- Elements can be ARC-4 values (uint<N>, byte, bool, address, string etc)
- Fixed
- Elements cannot be added/deleted (size is fixed)
- Dynamic
- Elements can be added/deleted (size is dynamic)
- Value
- By value semantics. Memory is allocated on the stack.
- Reference
- By reference semantics, all references see modifications. May use scratch slots.
| Immutable Native Dynamic Value | Mutable Native* Dynamic Reference | Mutable Native Fixed Value | Mutable Native Dynamic Value | Mutable ARC4 Fixed Value | Mutable ARC4 Dynamic Value | |
|---|---|---|---|---|---|---|
| py (current) | ImmutableArray | Array | NA | NA | arc4.StaticArray | arc4.DynamicArray |
| py (future) | ImmutableArray | ReferenceArray | FixedArray | Array | arc4.StaticArray | arc4.DynamicArray |
| TS (current) | [], Array | MutableArray | NA | NA | arc4.StaticArray | arc4.DynamicArray |
| TS (future) | ImmutableArray | ReferenceArray | FixedArray | [], Array | arc4.StaticArray | arc4.DynamicArray |
Breaking Changes
Section titled “Breaking Changes”Algorand Python
Arrayis renamed toReferenceArray.Arrayis repurposed to have value semantics.
Algorand Typescript
Arrayis renamed toImmutableArrayArrayis repurposed to be mutable.
PuyaTS 1.0
Section titled “PuyaTS 1.0”Mutable Native Struct
Section titled “Mutable Native Struct”Recommendation: 1.0
Decision: 1.0
Mutable Native Fixed Size array
Section titled “Mutable Native Fixed Size array”Recommendation: 1.0
Decision: 1.0
Mutable Native Dynamic Sized array
Section titled “Mutable Native Dynamic Sized array”Recommendation: 1.0
Decision: 1.0
Direct box mutation
Section titled “Direct box mutation”Recommendation: 1.0
Decision: 1.0
Box API enhancements
Section titled “Box API enhancements”Recommendation: 1.0
Decision: 1.0
Mutable Native Dynamic Array splice and/or pop support
Section titled “Mutable Native Dynamic Array splice and/or pop support”Recommendation: Post 1.0
Decision: Post 1.0
Reference tracking
Section titled “Reference tracking”Recommendation: Not Required
Decision: Not Required