Skip to content

Commit 97dc9cd

Browse files
committed
feat: add more tests for box
1 parent 9fef4e4 commit 97dc9cd

File tree

8 files changed

+552
-59
lines changed

8 files changed

+552
-59
lines changed

src/impl/box.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ export const Box: internal.opTypes.BoxType = {
3939
throw new internal.errors.InternalError('Box does not exist')
4040
}
4141
const boxContent = lazyContext.ledger.getBox(app, name)
42+
if (boxContent instanceof Uint8Array) {
43+
return toBytes(boxContent.slice(start, start + length))
44+
}
4245
const result = toBytes(boxContent).slice(start, start + length)
4346
return result
4447
},
@@ -62,7 +65,7 @@ export const Box: internal.opTypes.BoxType = {
6265
if (lazyContext.ledger.boxExists(app, name)) {
6366
const boxContent = lazyContext.ledger.getBox(app, name)
6467
const bytesContent = toBytes(boxContent)
65-
if (bytesContent.length !== newContent.length) {
68+
if (asNumber(bytesContent.length) !== asNumber(newContent.length)) {
6669
throw new internal.errors.InternalError('New content length does not match existing box length')
6770
}
6871
}
@@ -100,9 +103,10 @@ export const Box: internal.opTypes.BoxType = {
100103
}
101104
const boxContent = lazyContext.ledger.getBox(app, name)
102105
const bytesContent = toBytes(boxContent)
106+
const size = asNumber(bytesContent.length)
103107
let updatedContent
104-
if (newSize > bytesContent.length) {
105-
updatedContent = bytesContent.concat(Bytes(Array(newSize - bytesContent.length).fill(0)))
108+
if (newSize > size) {
109+
updatedContent = bytesContent.concat(Bytes(Array(newSize - size).fill(0)))
106110
} else {
107111
updatedContent = bytesContent.slice(0, newSize)
108112
}
@@ -124,17 +128,17 @@ export const Box: internal.opTypes.BoxType = {
124128
}
125129
const boxContent = lazyContext.ledger.getBox(app, name)
126130
const bytesContent = toBytes(boxContent)
127-
if (start > bytesContent.length) {
131+
const size = asNumber(bytesContent.length)
132+
if (start > size) {
128133
throw new internal.errors.InternalError('Start index exceeds box size')
129134
}
130-
const end = Math.min(start + length, bytesContent.length)
135+
const end = Math.min(start + length, size)
131136
let updatedContent = bytesContent.slice(0, start).concat(newContent).concat(bytesContent.slice(end))
132-
133137
// Adjust the size if necessary
134-
if (updatedContent.length > bytesContent.length) {
135-
updatedContent = updatedContent.slice(0, bytesContent.length)
136-
} else if (updatedContent.length < bytesContent.length) {
137-
updatedContent = updatedContent.concat(Bytes(Array(bytesContent.length - updatedContent.length).fill(0)))
138+
if (updatedContent.length > size) {
139+
updatedContent = updatedContent.slice(0, size)
140+
} else if (updatedContent.length < size) {
141+
updatedContent = updatedContent.concat(Bytes(Array(size - asNumber(updatedContent.length)).fill(0)))
138142
}
139143
lazyContext.ledger.setBox(app, name, Bytes(updatedContent))
140144
},

src/impl/state.ts

Lines changed: 57 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
BoxMap as BoxMapType,
55
BoxRef as BoxRefType,
66
Box as BoxType,
7-
Bytes,
87
bytes,
98
GlobalStateOptions,
109
GlobalState as GlobalStateType,
@@ -17,7 +16,7 @@ import {
1716
import { AccountMap } from '../collections/custom-key-map'
1817
import { MAX_BOX_SIZE } from '../constants'
1918
import { lazyContext } from '../context-helpers/internal-context'
20-
import { asBytes, asNumber, toBytes } from '../util'
19+
import { asBytes, asBytesCls, asNumber, toBytes } from '../util'
2120

2221
export class GlobalStateCls<ValueType> {
2322
private readonly _type: string = GlobalStateCls.name
@@ -272,7 +271,10 @@ export class BoxRefCls {
272271
}
273272

274273
get value(): bytes {
275-
return lazyContext.ledger.getBox(this.#app, this.key)
274+
if (!this.exists) {
275+
throw new internal.errors.InternalError('Box has not been created')
276+
}
277+
return toBytes(this.backingValue)
276278
}
277279

278280
get exists(): boolean {
@@ -284,14 +286,14 @@ export class BoxRefCls {
284286
if (size > MAX_BOX_SIZE) {
285287
throw new internal.errors.InternalError(`Box size cannot exceed ${MAX_BOX_SIZE}`)
286288
}
287-
const [content, exists] = this.maybe()
288-
if (exists && content.length !== size) {
289+
const content = this.backingValue
290+
if (this.exists && content.length !== size) {
289291
throw new internal.errors.InternalError('Box already exists with a different size')
290292
}
291-
if (exists) {
293+
if (this.exists) {
292294
return false
293295
}
294-
lazyContext.ledger.setBox(this.#app, this.key, Bytes(Array(size).fill(0)))
296+
this.backingValue = new Uint8Array(Array(size).fill(0))
295297
return true
296298
}
297299

@@ -301,68 +303,69 @@ export class BoxRefCls {
301303
}
302304

303305
put(value: internal.primitives.StubBytesCompat): void {
304-
const bytesValue = asBytes(value)
305-
const [content, exists] = this.maybe()
306-
if (exists && content.length !== bytesValue.length) {
306+
const bytesValue = asBytesCls(value)
307+
const content = this.backingValue
308+
if (this.exists && content.length !== bytesValue.length.asNumber()) {
307309
throw new internal.errors.InternalError('Box already exists with a different size')
308310
}
309-
lazyContext.ledger.setBox(this.#app, this.key, bytesValue)
311+
this.backingValue = bytesValue.asUint8Array()
310312
}
311313

312314
splice(
313315
start: internal.primitives.StubUint64Compat,
314316
length: internal.primitives.StubUint64Compat,
315317
value: internal.primitives.StubBytesCompat,
316318
): void {
317-
const [content, exists] = this.maybe()
319+
const content = this.backingValue
318320
const startNumber = asNumber(start)
319321
const lengthNumber = asNumber(length)
320-
const valueBytes = asBytes(value)
321-
if (!exists) {
322+
const valueBytes = asBytesCls(value)
323+
if (!this.exists) {
322324
throw new internal.errors.InternalError('Box has not been created')
323325
}
324326
if (startNumber > content.length) {
325327
throw new internal.errors.InternalError('Start index exceeds box size')
326328
}
327329
const end = Math.min(startNumber + lengthNumber, content.length)
328-
let updatedContent = content.slice(0, startNumber).concat(valueBytes).concat(content.slice(end))
330+
let updatedContent = this.concat(content.slice(0, startNumber), valueBytes.asUint8Array(), content.slice(end))
329331

330332
if (updatedContent.length > content.length) {
331333
updatedContent = updatedContent.slice(0, content.length)
332334
} else if (updatedContent.length < content.length) {
333-
updatedContent = updatedContent.concat(Bytes(Array(content.length - updatedContent.length).fill(0)))
335+
updatedContent = this.concat(updatedContent, new Uint8Array(Array(content.length - updatedContent.length).fill(0)))
334336
}
335-
lazyContext.ledger.setBox(this.#app, this.key, updatedContent)
337+
this.backingValue = updatedContent
336338
}
337339

338340
replace(start: internal.primitives.StubUint64Compat, value: internal.primitives.StubBytesCompat): void {
339-
const [content, exists] = this.maybe()
341+
const content = this.backingValue
340342
const startNumber = asNumber(start)
341-
const valueBytes = asBytes(value)
342-
if (!exists) {
343+
const valueBytes = asBytesCls(value)
344+
if (!this.exists) {
343345
throw new internal.errors.InternalError('Box has not been created')
344346
}
345-
if (startNumber + valueBytes.length > content.length) {
347+
if (startNumber + asNumber(valueBytes.length) > content.length) {
346348
throw new internal.errors.InternalError('Replacement content exceeds box size')
347349
}
348-
const updatedContent = content
349-
.slice(0, startNumber)
350-
.concat(valueBytes)
351-
.concat(content.slice(startNumber + valueBytes.length))
352-
lazyContext.ledger.setBox(this.#app, this.key, updatedContent)
350+
const updatedContent = this.concat(
351+
content.slice(0, startNumber),
352+
valueBytes.asUint8Array(),
353+
content.slice(startNumber + valueBytes.length.asNumber()),
354+
)
355+
this.backingValue = updatedContent
353356
}
354357

355358
extract(start: internal.primitives.StubUint64Compat, length: internal.primitives.StubUint64Compat): bytes {
356-
const [content, exists] = this.maybe()
359+
const content = this.backingValue
357360
const startNumber = asNumber(start)
358361
const lengthNumber = asNumber(length)
359-
if (!exists) {
362+
if (!this.exists) {
360363
throw new internal.errors.InternalError('Box has not been created')
361364
}
362365
if (startNumber + lengthNumber > content.length) {
363366
throw new internal.errors.InternalError('Index out of bounds')
364367
}
365-
return content.slice(startNumber, startNumber + lengthNumber)
368+
return toBytes(content.slice(startNumber, startNumber + lengthNumber))
366369
}
367370
delete(): boolean {
368371
return lazyContext.ledger.deleteBox(this.#app, this.key)
@@ -373,29 +376,46 @@ export class BoxRefCls {
373376
if (newSizeNumber > MAX_BOX_SIZE) {
374377
throw new internal.errors.InternalError(`Box size cannot exceed ${MAX_BOX_SIZE}`)
375378
}
376-
const [content, exists] = this.maybe()
377-
if (!exists) {
379+
const content = this.backingValue
380+
if (!this.exists) {
378381
throw new internal.errors.InternalError('Box has not been created')
379382
}
380383
let updatedContent
381384
if (newSizeNumber > content.length) {
382-
updatedContent = content.concat(Bytes(Array(newSizeNumber - content.length).fill(0)))
385+
updatedContent = this.concat(content, new Uint8Array(Array(newSizeNumber - content.length).fill(0)))
383386
} else {
384387
updatedContent = content.slice(0, newSize)
385388
}
386-
lazyContext.ledger.setBox(this.#app, this.key, updatedContent)
389+
this.backingValue = updatedContent
387390
}
388391

389392
maybe(): readonly [bytes, boolean] {
390-
return [lazyContext.ledger.getBox(this.#app, this.key), lazyContext.ledger.boxExists(this.#app, this.key)]
393+
return [asBytes(lazyContext.ledger.getBox(this.#app, this.key)), lazyContext.ledger.boxExists(this.#app, this.key)]
391394
}
392395

393396
get length(): uint64 {
394-
const [value, exists] = this.maybe()
395-
if (!exists) {
397+
if (!this.exists) {
396398
throw new internal.errors.InternalError('Box has not been created')
397399
}
398-
return value.length
400+
return this.backingValue.length
401+
}
402+
403+
private get backingValue(): Uint8Array {
404+
return lazyContext.ledger.getBox(this.#app, this.key)
405+
}
406+
407+
private set backingValue(value: Uint8Array) {
408+
lazyContext.ledger.setBox(this.#app, this.key, value)
409+
}
410+
411+
private concat(...values: Uint8Array[]): Uint8Array {
412+
const result = new Uint8Array(values.reduce((acc, value) => acc + value.length, 0))
413+
let index = 0
414+
for (const value of values) {
415+
result.set(value, index)
416+
index += value.length
417+
}
418+
return result
399419
}
400420
}
401421

src/subcontexts/transaction-context.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export class DeferredAppCall<TParams extends unknown[], TReturn> {
5353
readonly txns: Transaction[],
5454
private readonly method: (...args: TParams) => TReturn,
5555
private readonly args: TParams,
56-
) { }
56+
) {}
5757

5858
submit(): TReturn {
5959
// TODO: check_routing_conditions
@@ -66,7 +66,7 @@ export class TransactionContext {
6666
#activeGroup: TransactionGroup | undefined
6767

6868
createScope(group: Array<Transaction | DeferredAppCall<unknown[], unknown>>, activeTransactionIndex?: number): ExecutionScope {
69-
const transactions = group.map((t) => t instanceof DeferredAppCall ? t.txns : [t]).flat()
69+
const transactions = group.map((t) => (t instanceof DeferredAppCall ? t.txns : [t])).flat()
7070
const transactionGroup = new TransactionGroup(transactions, activeTransactionIndex)
7171
const previousGroup = this.#activeGroup
7272
this.#activeGroup = transactionGroup

src/util.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,14 @@ export const toBytes = (val: unknown): bytes => {
4949
if (uint64Val !== undefined) {
5050
return uint64Val.toBytes().asAlgoTs()
5151
}
52-
const bigUintVal = asMaybeBigUintCls(val)
53-
if (bigUintVal !== undefined) {
54-
return bigUintVal.toBytes().asAlgoTs()
55-
}
5652
const bytesVal = asMaybeBytesCls(val)
5753
if (bytesVal !== undefined) {
5854
return bytesVal.asAlgoTs()
5955
}
56+
const bigUintVal = asMaybeBigUintCls(val)
57+
if (bigUintVal !== undefined) {
58+
return bigUintVal.toBytes().asAlgoTs()
59+
}
6060
if (val instanceof BytesBackedCls) {
6161
return val.bytes
6262
}

tests/artifacts/box-contract/contract.algo.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ export class BoxContract extends arc4.Contract {
44
oca = Box<arc4.OnCompleteAction>()
55
txn = Box<TransactionType>()
66

7-
87
@arc4.abimethod()
98
public storeEnums(): void {
109
this.oca.value = arc4.OnCompleteAction.OptIn
@@ -13,8 +12,8 @@ export class BoxContract extends arc4.Contract {
1312

1413
@arc4.abimethod()
1514
public read_enums(): readonly [uint64, uint64] {
16-
assert(op.Box.get(Bytes("oca"))[0] === op.itob(this.oca.value))
17-
assert(op.Box.get(Bytes("txn"))[0] === op.itob(this.txn.value))
15+
assert(op.Box.get(Bytes('oca'))[0] === op.itob(this.oca.value))
16+
assert(op.Box.get(Bytes('txn'))[0] === op.itob(this.txn.value))
1817

1918
return [Uint64(this.oca.value), Uint64(this.txn.value)]
2019
// TODO: use arc4 types when available

0 commit comments

Comments
 (0)