@@ -11,14 +11,27 @@ import {
1111} from 'errors/cairoRunner' ;
1212
1313import { Felt } from 'primitives/felt' ;
14- import { SegmentValue } from 'primitives/segmentValue' ;
14+ import { isFelt , SegmentValue } from 'primitives/segmentValue' ;
1515import { Relocatable } from 'primitives/relocatable' ;
1616import { CairoProgram , CairoZeroProgram , Program } from 'vm/program' ;
17- import { VirtualMachine } from 'vm/virtualMachine' ;
17+ import { RcLimits , VirtualMachine } from 'vm/virtualMachine' ;
1818import { CELLS_PER_INSTANCE , getBuiltin } from 'builtins/builtin' ;
1919import { Hint , Hints } from 'hints/hintSchema' ;
20- import { isSubsequence , Layout , layouts } from './layout' ;
20+ import {
21+ isSubsequence ,
22+ Layout ,
23+ layouts ,
24+ MEMORY_UNITS_PER_STEP ,
25+ } from './layout' ;
2126import { nextPowerOfTwo } from 'primitives/utils' ;
27+ import { ExpectedFelt } from 'errors/primitives' ;
28+ import {
29+ INNER_RC_BOUND_MASK ,
30+ INNER_RC_BOUND_SHIFT ,
31+ RC_N_PARTS ,
32+ RC_N_PARTS_96 ,
33+ } from 'builtins/rangeCheck' ;
34+ import { KECCAK_DILUTED_CELLS } from 'builtins/keccak' ;
2235
2336/**
2437 * Configuration of the run
@@ -36,6 +49,8 @@ export enum RunnerMode {
3649 ProofModeCairo = 'Proof Mode - Cairo' ,
3750}
3851
52+ const MISSING_STEPS_CAPACITY = - 1 ;
53+
3954export class CairoRunner {
4055 program : Program ;
4156 layout : Layout ;
@@ -259,45 +274,15 @@ export class CairoRunner {
259274 /**
260275 * @returns {boolean } Whether there are enough allocated cells for
261276 * generating a proof of this program execution for the chosen layout.
262- * @throws { } - If the number of allocated cells of the layout is insufficient.
277+ * @throws {InsufficientAllocatedCells } - If the number of allocated cells of the layout is insufficient.
263278 *
264279 */
265280 checkCellUsage ( ) : boolean {
266281 const builtinChecks = this . builtins
267- . filter (
268- ( builtin ) =>
269- ! [ 'output' , 'segment_arena' , 'gas_builtin' , 'system' ] . includes (
270- builtin
271- )
272- )
282+ . filter ( ( builtin ) => ! [ 'gas_builtin' , 'system' ] . includes ( builtin ) )
273283 . map ( ( builtin ) => {
274- // const instancesPerComponent = builtin === 'keccak' ? 16 : 1;
275- const segment = this . getBuiltinSegment ( builtin ) ;
276- if ( ! segment ) throw new UndefinedBuiltinSegment ( builtin ) ;
277-
278- const ratio = this . layout . ratios [ builtin ] ;
279- const cellsPerInstance = CELLS_PER_INSTANCE [ builtin ] ;
280- // TODO: set size to segment.length - SEGMENT_ARENA_INITIAL_SIZE (3)
281- // when implementing this builtin
282- const size = segment . length ;
283- let capacity : number = 0 ;
284-
285- if ( this . layout . name === 'dynamic' ) {
286- const instances = Math . ceil ( size / cellsPerInstance ) ;
287- const instancesPerComponent = builtin === 'keccak' ? 16 : 1 ;
288- const components = nextPowerOfTwo ( instances / instancesPerComponent ) ;
289- capacity = cellsPerInstance * instancesPerComponent * components ;
290- } else {
291- const minStep = ratio * cellsPerInstance ;
292- if ( this . vm . currentStep < minStep ) {
293- console . log (
294- `PROOF MODE (${ builtin } ): minimum steps (${ minStep } ) not reached yet: ${ this . vm . currentStep } steps`
295- ) ;
296- return false ;
297- }
298- capacity = ( this . vm . currentStep / ratio ) * cellsPerInstance ;
299- }
300-
284+ const { size, capacity } = this . getSizeAndCapacity ( builtin ) ;
285+ if ( capacity === MISSING_STEPS_CAPACITY ) return false ;
301286 if ( size > capacity ) {
302287 throw new InsufficientAllocatedCells (
303288 this . layout . name ,
@@ -306,8 +291,159 @@ export class CairoRunner {
306291 ) ;
307292 }
308293 return true ;
309- } ) ;
310- return builtinChecks . reduce ( ( prev , curr ) => prev && curr ) ;
294+ } )
295+ . reduce ( ( prev , curr ) => prev && curr ) ;
296+
297+ const initialBounds : RcLimits = { rcMin : 0 , rcMax : 1 } ;
298+ const { rcMin, rcMax } : RcLimits = this . builtins
299+ . filter ( ( builtin ) => [ 'range_check' , 'range_check96' ] . includes ( builtin ) )
300+ . map ( ( builtin ) => {
301+ const segment = this . getBuiltinSegment ( builtin ) ;
302+ if ( ! segment ) throw new UndefinedBuiltinSegment ( builtin ) ;
303+ if ( ! segment . length ) return { rcMin : 0 , rcMax : 0 } ;
304+ return segment . reduce ( ( _ , value ) => {
305+ if ( ! isFelt ( value ) ) throw new ExpectedFelt ( value ) ;
306+ const nParts = builtin === 'range_check' ? RC_N_PARTS : RC_N_PARTS_96 ;
307+ return value
308+ . to64BitsWords ( )
309+ . flatMap ( ( limb ) =>
310+ [ 3 , 2 , 1 , 0 ] . map (
311+ ( i ) =>
312+ ( limb >> BigInt ( i * INNER_RC_BOUND_SHIFT ) ) &
313+ INNER_RC_BOUND_MASK
314+ )
315+ )
316+ . slice ( nParts )
317+ . reduce ( ( bounds , curr ) => {
318+ const x = Number ( curr ) ;
319+ return {
320+ rcMin : Math . min ( bounds . rcMin , x ) ,
321+ rcMax : Math . max ( bounds . rcMax , x ) ,
322+ } ;
323+ } , initialBounds ) ;
324+ } , initialBounds ) ;
325+ } )
326+ . concat ( [ this . vm . rcLimits ] )
327+ . reduce ( ( acc , curr ) => ( {
328+ rcMin : Math . min ( acc . rcMin , curr . rcMin ) ,
329+ rcMax : Math . max ( curr . rcMin , curr . rcMax ) ,
330+ } ) ) ;
331+
332+ const usedRcUnits = this . builtins
333+ . filter ( ( builtin ) => [ 'range_check' , 'range_check96' ] . includes ( builtin ) )
334+ . map ( ( builtin ) => {
335+ const segment = this . getBuiltinSegment ( builtin ) ;
336+ if ( ! segment ) throw new UndefinedBuiltinSegment ( builtin ) ;
337+ return builtin === 'range_check'
338+ ? segment . length * RC_N_PARTS
339+ : segment . length * RC_N_PARTS_96 ;
340+ } )
341+ . reduce ( ( acc , curr ) => acc + curr ) ;
342+
343+ const unusedRcUnits =
344+ ( this . layout . rcUnits - 3 ) * this . vm . currentStep - usedRcUnits ;
345+ const rcUnitsCheck = unusedRcUnits >= rcMax - rcMin ;
346+
347+ const builtinsCapacity = this . builtins
348+ . map ( ( builtin ) => this . getSizeAndCapacity ( builtin ) . capacity )
349+ . reduce ( ( acc , curr ) => acc + curr ) ;
350+ const totalMemoryCapacity = this . vm . currentStep * MEMORY_UNITS_PER_STEP ;
351+ if ( totalMemoryCapacity % this . layout . publicMemoryFraction )
352+ throw new Error (
353+ 'Total memory capacity is not a multiple of public memory fraction.'
354+ ) ;
355+ const publicMemoryCapacity =
356+ totalMemoryCapacity / this . layout . publicMemoryFraction ;
357+
358+ const instructionCapacity = this . vm . currentStep * 4 ;
359+ const unusedMemoryCapacity =
360+ totalMemoryCapacity -
361+ ( publicMemoryCapacity + instructionCapacity + builtinsCapacity ) ;
362+ const memoryHoles = this . vm . memory . segments . reduce (
363+ ( acc , currSegment ) =>
364+ acc + currSegment . reduce ( ( acc , _ ) => acc -- , currSegment . length ) ,
365+ 0
366+ ) ;
367+ const memoryCheck = unusedMemoryCapacity >= memoryHoles ;
368+
369+ let dilutedCheck : boolean = true ;
370+ const dilutedPool = this . layout . dilutedPool ;
371+ if ( dilutedPool ) {
372+ const dilutedUsedCapacity = this . builtins
373+ . filter ( ( builtin ) => [ 'bitwise' , 'keccak' ] . includes ( builtin ) )
374+ . map ( ( builtin ) => {
375+ const multiplier =
376+ this . layout . name === 'dynamic'
377+ ? this . vm . currentStep
378+ : this . vm . currentStep / this . layout . ratios [ builtin ] ;
379+ if ( builtin === 'bitwise' ) {
380+ const totalNBits = 251 ;
381+ const { nBits, spacing } = dilutedPool ;
382+ const step = nBits * spacing ;
383+ const partition : number [ ] = [ ] ;
384+ for ( let i = 0 ; i < totalNBits ; i += step ) {
385+ for ( let j = 0 ; j < spacing ; j ++ ) {
386+ if ( i + j < totalNBits ) {
387+ partition . push ( i + j ) ;
388+ }
389+ }
390+ }
391+ const trimmedNumber = partition . filter (
392+ ( value ) => value + spacing * ( nBits - 1 ) + 1 > totalNBits
393+ ) . length ;
394+
395+ return ( 4 * partition . length + trimmedNumber ) * multiplier ;
396+ }
397+ return ( KECCAK_DILUTED_CELLS / dilutedPool . nBits ) * multiplier ;
398+ } )
399+ . reduce ( ( acc , curr ) => acc + curr ) ;
400+
401+ const dilutedUnits = this . vm . currentStep * dilutedPool . unitsPerStep ;
402+ const unusedDilutedCapacity = dilutedUnits - dilutedUsedCapacity ;
403+ dilutedCheck = unusedDilutedCapacity >= 1 << dilutedPool . nBits ;
404+ }
405+
406+ return builtinChecks && rcUnitsCheck && memoryCheck && dilutedCheck ;
407+ }
408+
409+ /** @returns The size of a builtin and its capacity for the chosen layout. */
410+ private getSizeAndCapacity ( builtin : string ) : {
411+ size : number ;
412+ capacity : number ;
413+ } {
414+ const segment = this . getBuiltinSegment ( builtin ) ;
415+ if ( ! segment ) throw new UndefinedBuiltinSegment ( builtin ) ;
416+ const size =
417+ builtin === 'segment_arena' ? segment . length - 3 : segment . length ;
418+
419+ if ( builtin === 'output' || builtin === 'segment_arena' ) {
420+ return { size, capacity : size } ;
421+ }
422+
423+ const ratio = this . layout . ratios [ builtin ] ;
424+ const cellsPerInstance = CELLS_PER_INSTANCE [ builtin ] ;
425+ let capacity : number = 0 ;
426+
427+ switch ( this . layout . name ) {
428+ case 'dynamic' :
429+ const instances = Math . ceil ( size / cellsPerInstance ) ;
430+ const instancesPerComponent = builtin === 'keccak' ? 16 : 1 ;
431+ const components = nextPowerOfTwo ( instances / instancesPerComponent ) ;
432+ capacity = cellsPerInstance * instancesPerComponent * components ;
433+ break ;
434+ default :
435+ const minStep = ratio * cellsPerInstance ;
436+ if ( this . vm . currentStep < minStep ) {
437+ // console.log(
438+ // `PROOF MODE (${builtin}): minimum steps (${minStep}) not reached yet: ${this.vm.currentStep} steps`
439+ // );
440+ return { size, capacity : MISSING_STEPS_CAPACITY } ;
441+ }
442+ capacity = ( this . vm . currentStep / ratio ) * cellsPerInstance ;
443+ break ;
444+ }
445+
446+ return { size, capacity } ;
311447 }
312448
313449 /**
0 commit comments