@@ -15,9 +15,9 @@ import { AccountMap } from '../collections/custom-key-map'
1515import { MAX_BOX_SIZE } from '../constants'
1616import { lazyContext } from '../context-helpers/internal-context'
1717import type { TypeInfo } from '../encoders'
18- import { getEncoder , toBytes } from '../encoders'
19- import { AssertError , InternalError } from '../errors'
20- import { getGenericTypeInfo } from '../runtime-helpers'
18+ import { getEncoder , minLengthForType , toBytes } from '../encoders'
19+ import { AssertError , CodeError , InternalError } from '../errors'
20+ import { getGenericTypeInfo , tryArc4EncodedLengthImpl } from '../runtime-helpers'
2121import { asBytes , asBytesCls , asNumber , asUint8Array , conactUint8Arrays } from '../util'
2222import type { StubBytesCompat , StubUint64Compat } from './primitives'
2323import { Bytes , Uint64 , Uint64Cls } from './primitives'
@@ -136,6 +136,17 @@ export class BoxCls<TValue> {
136136
137137 private readonly _type : string = BoxCls . name
138138
139+ private get valueType ( ) : TypeInfo {
140+ if ( this . #valueType === undefined ) {
141+ const typeInfo = getGenericTypeInfo ( this )
142+ if ( typeInfo === undefined || typeInfo . genericArgs === undefined || typeInfo . genericArgs . length !== 1 ) {
143+ throw new InternalError ( 'Box value type is not set' )
144+ }
145+ this . #valueType = ( typeInfo . genericArgs as TypeInfo [ ] ) [ 0 ]
146+ }
147+ return this . #valueType
148+ }
149+
139150 static [ Symbol . hasInstance ] ( x : unknown ) : x is BoxCls < unknown > {
140151 return x instanceof Object && '_type' in x && ( x as { _type : string } ) [ '_type' ] === BoxCls . name
141152 }
@@ -151,6 +162,35 @@ export class BoxCls<TValue> {
151162 return ( val : Uint8Array ) => getEncoder < TValue > ( valueType ) ( val , valueType )
152163 }
153164
165+ create ( options ?: { size ?: StubUint64Compat } ) : boolean {
166+ const optionSize = options ?. size !== undefined ? asNumber ( options . size ) : undefined
167+ const valueTypeSize = tryArc4EncodedLengthImpl ( this . valueType )
168+
169+ if ( valueTypeSize === undefined && optionSize === undefined ) {
170+ throw new InternalError ( `${ this . valueType . name } does not have a fixed byte size. Please specify a size argument` )
171+ }
172+
173+ if ( valueTypeSize !== undefined && optionSize !== undefined ) {
174+ if ( optionSize < valueTypeSize ) {
175+ throw new InternalError ( `Box size cannot be less than ${ valueTypeSize } ` )
176+ }
177+
178+ if ( optionSize > valueTypeSize ) {
179+ process . emitWarning (
180+ `Box size is set to ${ optionSize } but the value type ${ this . valueType . name } has a fixed size of ${ valueTypeSize } ` ,
181+ )
182+ }
183+ }
184+
185+ lazyContext . ledger . setBox (
186+ this . #app,
187+ this . key ,
188+ new Uint8Array ( Math . max ( asNumber ( options ?. size ?? 0 ) , this . valueType ? minLengthForType ( this . valueType ) : 0 ) ) ,
189+ )
190+
191+ return true
192+ }
193+
154194 get value ( ) : TValue {
155195 if ( ! this . exists ) {
156196 throw new InternalError ( 'Box has not been created' )
@@ -164,8 +204,17 @@ export class BoxCls<TValue> {
164204 lazyContext . ledger . setMatrialisedBox ( this . #app, this . key , materialised )
165205 return materialised
166206 }
207+
167208 set value ( v : TValue ) {
168- lazyContext . ledger . setBox ( this . #app, this . key , asUint8Array ( toBytes ( v ) ) )
209+ const isStaticValueType = tryArc4EncodedLengthImpl ( this . valueType ) !== undefined
210+ const newValueBytes = asUint8Array ( toBytes ( v ) )
211+ if ( isStaticValueType && this . exists ) {
212+ const originalValueBytes = lazyContext . ledger . getBox ( this . #app, this . key )
213+ if ( originalValueBytes . length !== newValueBytes . length ) {
214+ throw new CodeError ( `attempt to box_put wrong size ${ originalValueBytes . length } != ${ newValueBytes . length } ` )
215+ }
216+ }
217+ lazyContext . ledger . setBox ( this . #app, this . key , newValueBytes )
169218 lazyContext . ledger . setMatrialisedBox ( this . #app, this . key , v )
170219 }
171220
0 commit comments