Skip to content

Commit 438bf68

Browse files
authored
Merge pull request #79 from algorandfoundation/feat/static-bytes
feat: Add length generic to bytes type for declaring statically sized byte sequences
2 parents 8a47c6b + 80c3f11 commit 438bf68

22 files changed

+145
-129
lines changed

.github/workflows/pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
pipx install algokit --python 3.12.6
2222
algokit localnet reset --update
2323
pipx install puyapy --python 3.12.6
24-
node-version: 20.x
24+
node-version: 22.x
2525
run-build: true
2626
run-commit-lint: true
2727
audit-script: npm run audit

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
pipx install algokit
2525
algokit localnet reset --update
2626
pipx install puyapy
27-
node-version: 20.x
27+
node-version: 22.x
2828
run-build: true
2929
run-commit-lint: true
3030
audit-script: npm run audit
@@ -44,7 +44,7 @@ jobs:
4444
- name: Use Node.js 20.x
4545
uses: actions/setup-node@v4
4646
with:
47-
node-version: 20.x
47+
node-version: 22.x
4848

4949
- run: npm ci --ignore-scripts
5050

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

0 commit comments

Comments
 (0)