Skip to content

Commit e507637

Browse files
committed
feat: Add length generic to bytes type for declaring statically sized byte sequences
1 parent 8a47c6b commit e507637

20 files changed

+142
-126
lines changed

examples/voting/contract.algo.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export class VotingRoundApp extends arc4.Contract {
4747
tallyBox = BoxRef({ key: Bytes`V` })
4848
votesByAccount = BoxMap<Account, VoteIndexArray>({ keyPrefix: Bytes() })
4949
voteId = GlobalState<string>()
50-
snapshotPublicKey = GlobalState<bytes>()
50+
snapshotPublicKey = GlobalState<bytes<32>>()
5151
metadataIpfsCid = GlobalState<string>()
5252
startTime = GlobalState<uint64>()
5353
nftImageUrl = GlobalState<string>()
@@ -60,7 +60,7 @@ export class VotingRoundApp extends arc4.Contract {
6060
@abimethod({ onCreate: 'require' })
6161
public create(
6262
voteId: string,
63-
snapshotPublicKey: bytes,
63+
snapshotPublicKey: bytes<32>,
6464
metadataIpfsCid: string,
6565
startTime: uint64,
6666
endTime: uint64,
@@ -158,7 +158,7 @@ export class VotingRoundApp extends arc4.Contract {
158158
}
159159

160160
@abimethod({ readonly: true })
161-
public getPreconditions(signature: bytes): VotingPreconditions {
161+
public getPreconditions(signature: bytes<64>): VotingPreconditions {
162162
return {
163163
is_allowed_to_vote: Uint64(this.allowedToVote(signature)),
164164
is_voting_open: Uint64(this.votingOpen()),
@@ -167,7 +167,7 @@ export class VotingRoundApp extends arc4.Contract {
167167
}
168168
}
169169

170-
private allowedToVote(signature: bytes): boolean {
170+
private allowedToVote(signature: bytes<64>): boolean {
171171
ensureBudget(2000)
172172
return op.ed25519verifyBare(Txn.sender.bytes, signature, this.snapshotPublicKey.value)
173173
}
@@ -176,7 +176,7 @@ export class VotingRoundApp extends arc4.Contract {
176176
return this.votesByAccount(Txn.sender).exists
177177
}
178178

179-
public vote(fundMinBalReq: gtxn.PaymentTxn, signature: bytes, answerIds: VoteIndexArray): void {
179+
public vote(fundMinBalReq: gtxn.PaymentTxn, signature: bytes<64>, answerIds: VoteIndexArray): void {
180180
ensureBudget(7700, OpUpFeeSource.GroupCredit)
181181
assert(this.allowedToVote(signature), 'Not allowed to vote')
182182
assert(this.votingOpen(), 'Voting not open')

examples/voting/contract.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe('VotingRoundApp', () => {
1616

1717
const createContract = () => {
1818
const contract = ctx.contract.create(VotingRoundApp)
19-
const snapshotPublicKey = Bytes(keyPair.publicKey)
19+
const snapshotPublicKey = Bytes(keyPair.publicKey).toFixed({ length: 32 })
2020
const metadataIpfsCid = ctx.any.string(16)
2121
const startTime = ctx.any.uint64(Date.now() - 10_000, Date.now())
2222
const endTime = ctx.any.uint64(Date.now() + 10_000, Date.now() + 100_000)

package-lock.json

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@
6666
"vitest": "3.1.3"
6767
},
6868
"dependencies": {
69-
"@algorandfoundation/algorand-typescript": "1.0.0-alpha.59",
70-
"@algorandfoundation/puya-ts": "1.0.0-alpha.59",
69+
"@algorandfoundation/algorand-typescript": "1.0.0-alpha.61",
70+
"@algorandfoundation/puya-ts": "1.0.0-alpha.61",
7171
"elliptic": "^6.6.1",
7272
"js-sha256": "^0.11.0",
7373
"js-sha3": "^0.9.3",

src/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const DEFAULT_GLOBAL_GENESIS_HASH = Bytes(
2525
133, 89, 181, 20, 120, 253, 137, 193, 118, 67, 208, 93, 21, 168, 174, 107, 16, 171, 71, 187, 109, 138, 49, 136, 17, 86, 230, 189, 59,
2626
174, 149, 209,
2727
]),
28-
)
28+
).toFixed({ length: 32 })
2929

3030
// algorand encoded address of 32 zero bytes
3131
export const ZERO_ADDRESS = Bytes.fromBase32('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA')

src/impl/asset-params.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ export const AssetParams: typeof op.AssetParams = {
5050
const asset = getAsset(a)
5151
return asset === undefined ? [Bytes(), false] : [asset.url, true]
5252
},
53-
assetMetadataHash(a: AssetType | StubUint64Compat): readonly [bytes, boolean] {
53+
assetMetadataHash(a: AssetType | StubUint64Compat): readonly [bytes<32>, boolean] {
5454
const asset = getAsset(a)
55-
return asset === undefined ? [Bytes(), false] : [asset.metadataHash, true]
55+
return asset === undefined ? [Bytes() as bytes<32>, false] : [asset.metadataHash, true]
5656
},
5757
assetManager(a: AssetType | StubUint64Compat): readonly [AccountType, boolean] {
5858
const asset = getAsset(a)

src/impl/block.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,24 @@ import { Uint64, type StubUint64Compat } from './primitives'
55
import { Account } from './reference'
66

77
export class BlockData {
8-
seed: bytes
8+
seed: bytes<32>
99
timestamp: uint64
1010
proposer: AccountType
1111
feesCollected: uint64
1212
bonus: uint64
13-
branch: bytes
13+
branch: bytes<32>
1414
feeSink: AccountType
1515
protocol: bytes
1616
txnCounter: uint64
1717
proposerPayout: uint64
1818

1919
constructor() {
20-
this.seed = getRandomBytes(32).asAlgoTs()
20+
this.seed = getRandomBytes(32).asAlgoTs().toFixed({ length: 32 })
2121
this.timestamp = asUint64(Date.now())
2222
this.proposer = Account()
2323
this.feesCollected = Uint64(0)
2424
this.bonus = Uint64(0)
25-
this.branch = getRandomBytes(32).asAlgoTs()
25+
this.branch = getRandomBytes(32).asAlgoTs().toFixed({ length: 32 })
2626
this.feeSink = Account()
2727
this.protocol = getRandomBytes(32).asAlgoTs()
2828
this.txnCounter = Uint64(0)
@@ -31,7 +31,7 @@ export class BlockData {
3131
}
3232

3333
export const Block: typeof op.Block = {
34-
blkSeed: function (a: StubUint64Compat): bytes {
34+
blkSeed: function (a: StubUint64Compat): bytes<32> {
3535
return lazyContext.ledger.getBlockData(a).seed
3636
},
3737
blkTimestamp: function (a: StubUint64Compat): uint64 {
@@ -46,7 +46,7 @@ export const Block: typeof op.Block = {
4646
blkBonus: function (a: uint64): uint64 {
4747
return lazyContext.ledger.getBlockData(a).bonus
4848
},
49-
blkBranch: function (a: uint64): bytes {
49+
blkBranch: function (a: uint64): bytes<32> {
5050
return lazyContext.ledger.getBlockData(a).branch
5151
},
5252
blkFeeSink: function (a: uint64): AccountType {

src/impl/crypto.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,32 @@ import { asBytes, asBytesCls, asUint8Array, conactUint8Arrays } from '../util'
1212
import type { StubBytesCompat, StubUint64Compat } from './primitives'
1313
import { Bytes, BytesCls, Uint64Cls } from './primitives'
1414

15-
export const sha256 = (a: StubBytesCompat): bytes => {
15+
export const sha256 = (a: StubBytesCompat): bytes<32> => {
1616
const bytesA = BytesCls.fromCompat(a)
1717
const hashArray = js_sha256.sha256.create().update(bytesA.asUint8Array()).digest()
1818
const hashBytes = BytesCls.fromCompat(new Uint8Array(hashArray))
19-
return hashBytes.asAlgoTs()
19+
return hashBytes.asAlgoTs().toFixed({ length: 32 })
2020
}
2121

22-
export const sha3_256 = (a: StubBytesCompat): bytes => {
22+
export const sha3_256 = (a: StubBytesCompat): bytes<32> => {
2323
const bytesA = BytesCls.fromCompat(a)
2424
const hashArray = js_sha3.sha3_256.create().update(bytesA.asUint8Array()).digest()
2525
const hashBytes = BytesCls.fromCompat(new Uint8Array(hashArray))
26-
return hashBytes.asAlgoTs()
26+
return hashBytes.asAlgoTs().toFixed({ length: 32 })
2727
}
2828

29-
export const keccak256 = (a: StubBytesCompat): bytes => {
29+
export const keccak256 = (a: StubBytesCompat): bytes<32> => {
3030
const bytesA = BytesCls.fromCompat(a)
3131
const hashArray = js_sha3.keccak256.create().update(bytesA.asUint8Array()).digest()
3232
const hashBytes = BytesCls.fromCompat(new Uint8Array(hashArray))
33-
return hashBytes.asAlgoTs()
33+
return hashBytes.asAlgoTs().toFixed({ length: 32 })
3434
}
3535

36-
export const sha512_256 = (a: StubBytesCompat): bytes => {
36+
export const sha512_256 = (a: StubBytesCompat): bytes<32> => {
3737
const bytesA = BytesCls.fromCompat(a)
3838
const hashArray = js_sha512.sha512_256.create().update(bytesA.asUint8Array()).digest()
3939
const hashBytes = BytesCls.fromCompat(new Uint8Array(hashArray))
40-
return hashBytes.asAlgoTs()
40+
return hashBytes.asAlgoTs().toFixed({ length: 32 })
4141
}
4242

4343
export const ed25519verifyBare = (a: StubBytesCompat, b: StubBytesCompat, c: StubBytesCompat): boolean => {
@@ -88,7 +88,7 @@ export const ecdsaPkRecover = (
8888
b: StubUint64Compat,
8989
c: StubBytesCompat,
9090
d: StubBytesCompat,
91-
): readonly [bytes, bytes] => {
91+
): readonly [bytes<32>, bytes<32>] => {
9292
if (v !== Ecdsa.Secp256k1) {
9393
throw new InternalError(`Unsupported ECDSA curve: ${v}`)
9494
}
@@ -106,10 +106,10 @@ export const ecdsaPkRecover = (
106106

107107
const x = pubKey.getX().toArray('be')
108108
const y = pubKey.getY().toArray('be')
109-
return [Bytes(x), Bytes(y)]
109+
return [Bytes(x).toFixed({ length: 32 }), Bytes(y).toFixed({ length: 32 })]
110110
}
111111

112-
export const ecdsaPkDecompress = (v: Ecdsa, a: StubBytesCompat): readonly [bytes, bytes] => {
112+
export const ecdsaPkDecompress = (v: Ecdsa, a: StubBytesCompat): readonly [bytes<32>, bytes<32>] => {
113113
const bytesA = BytesCls.fromCompat(a)
114114

115115
const ecdsa = new elliptic.ec(curveMap[v])
@@ -118,10 +118,10 @@ export const ecdsaPkDecompress = (v: Ecdsa, a: StubBytesCompat): readonly [bytes
118118

119119
const x = pubKey.getX().toArray('be')
120120
const y = pubKey.getY().toArray('be')
121-
return [Bytes(new Uint8Array(x)), Bytes(new Uint8Array(y))]
121+
return [Bytes(new Uint8Array(x)).toFixed({ length: 32 }), Bytes(new Uint8Array(y)).toFixed({ length: 32 })]
122122
}
123123

124-
export const vrfVerify = (_s: VrfVerify, _a: StubBytesCompat, _b: StubBytesCompat, _c: StubBytesCompat): readonly [bytes, boolean] => {
124+
export const vrfVerify = (_s: VrfVerify, _a: StubBytesCompat, _b: StubBytesCompat, _c: StubBytesCompat): readonly [bytes<64>, boolean] => {
125125
throw new NotImplementedError('vrfVerify')
126126
}
127127

src/impl/encoded-types.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -938,16 +938,16 @@ export class DynamicBytesImpl extends DynamicBytes {
938938
}
939939
}
940940

941-
export class StaticBytesImpl extends StaticBytes {
942-
private value: StaticArrayImpl<ByteImpl, number>
941+
export class StaticBytesImpl<TLength extends uint64 = 0> extends StaticBytes<TLength> {
942+
private value: StaticArrayImpl<ByteImpl, TLength>
943943
typeInfo: TypeInfo
944944

945-
constructor(typeInfo: TypeInfo | string, value?: bytes | string) {
946-
super(value)
945+
constructor(typeInfo: TypeInfo | string, value?: bytes<TLength>) {
946+
super(value ?? (Bytes() as bytes<TLength>))
947947
this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo
948948
const uint8ArrayValue = asUint8Array(value ?? new Uint8Array(StaticBytesImpl.getMaxBytesLength(this.typeInfo)))
949-
this.value = StaticArrayImpl.fromBytesImpl(uint8ArrayValue, typeInfo) as StaticArrayImpl<ByteImpl, number>
950-
return new Proxy(this, arrayProxyHandler<ByteImpl>()) as StaticBytesImpl
949+
this.value = StaticArrayImpl.fromBytesImpl(uint8ArrayValue, typeInfo) as StaticArrayImpl<ByteImpl, TLength>
950+
return new Proxy(this, arrayProxyHandler<ByteImpl>()) as StaticBytesImpl<TLength>
951951
}
952952

953953
get bytes(): bytes {
@@ -965,8 +965,8 @@ export class StaticBytesImpl extends StaticBytes {
965965
return this.value.length
966966
}
967967

968-
get native(): bytes {
969-
return this.value.bytes
968+
get native(): bytes<TLength> {
969+
return this.value.bytes as bytes<TLength>
970970
}
971971

972972
get items(): ByteImpl[] {
@@ -994,7 +994,7 @@ export class StaticBytesImpl extends StaticBytes {
994994
static fromBytesImpl(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): StaticBytesImpl {
995995
const staticArrayValue = StaticArrayImpl.fromBytesImpl(value, typeInfo, prefix) as StaticArrayImpl<ByteImpl, number>
996996
const result = new StaticBytesImpl(typeInfo)
997-
result.value = staticArrayValue
997+
result.value = staticArrayValue as StaticArrayImpl<ByteImpl, 0>
998998
return result
999999
}
10001000

src/impl/global.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ export class GlobalData {
2424
logicSigVersion?: uint64
2525
round?: uint64
2626
latestTimestamp?: uint64
27-
groupId?: bytes
27+
groupId?: bytes<32>
2828
callerApplicationId: uint64
2929
assetCreateMinBalance: uint64
3030
assetOptInMinBalance: uint64
31-
genesisHash: bytes
31+
genesisHash: bytes<32>
3232
opcodeBudget?: uint64
3333
payoutsEnabled: boolean
3434
payoutsGoOnlineFee: uint64
@@ -146,7 +146,7 @@ export const Global: typeof op.Global = {
146146
/**
147147
* ID of the transaction group. 32 zero bytes if the transaction is not part of a group.
148148
*/
149-
get groupId(): bytes {
149+
get groupId(): bytes<32> {
150150
const data = getGlobalData()
151151
if (data.groupId !== undefined) return data.groupId
152152
const reference = getObjectReference(lazyContext.activeGroup)
@@ -194,7 +194,7 @@ export const Global: typeof op.Global = {
194194
/**
195195
* The Genesis Hash for the network.
196196
*/
197-
get genesisHash(): bytes {
197+
get genesisHash(): bytes<32> {
198198
return getGlobalData().genesisHash
199199
},
200200

0 commit comments

Comments
 (0)