From 3ba8c4b272a220d9549cf370baf30eebfdb2bd0f Mon Sep 17 00:00:00 2001 From: querolita Date: Mon, 4 Nov 2024 20:06:33 +0100 Subject: [PATCH] added insert functionality --- src/lib/provable/dynamic-array.ts | 36 ++++++++-- .../provable/test/dynamic-array.unit-test.ts | 66 +++++++++++++++++++ 2 files changed, 95 insertions(+), 7 deletions(-) diff --git a/src/lib/provable/dynamic-array.ts b/src/lib/provable/dynamic-array.ts index 992807f50..8148a144f 100644 --- a/src/lib/provable/dynamic-array.ts +++ b/src/lib/provable/dynamic-array.ts @@ -303,7 +303,7 @@ class DynamicArrayBase { * Increments the length of the current array by n elements, checking that the * new length is within the capacity. */ - increaseLength(n: Field): void { + increaseLengthBy(n: Field): void { let newLength = this.length.add(n).seal(); newLength.lessThanOrEqual(new Field(this.capacity)).assertTrue(); this.length = newLength; @@ -313,7 +313,7 @@ class DynamicArrayBase { * Decrements the length of the current array by n elements, checking that the * n is less or equal than the current length. */ - decreaseLength(n: Field): void { + decreaseLengthBy(n: Field): void { let oldLength = this.length; n.assertLessThanOrEqual(this.length); this.length = oldLength.sub(n).seal(); @@ -333,7 +333,7 @@ class DynamicArrayBase { */ push(value: T): void { let oldLength = this.length; - this.increaseLength(new Field(1)); + this.increaseLengthBy(new Field(1)); this.setOrDoNothing(oldLength, value); } @@ -346,7 +346,7 @@ class DynamicArrayBase { */ pop(n?: Field): void { let dec = n !== undefined ? n : new Field(1); - this.decreaseLength(dec); + this.decreaseLengthBy(dec); let NULL: T = ProvableType.synthesize(this.innerType); if (n !== undefined) { @@ -391,7 +391,7 @@ class DynamicArrayBase { NULL ); } - this.decreaseLength(n); + this.decreaseLengthBy(n); } /** @@ -402,7 +402,7 @@ class DynamicArrayBase { * @param n */ shiftRight(n: Field): void { - this.increaseLength(n); + this.increaseLengthBy(n); let NULL = ProvableType.synthesize(this.innerType); for (let i = this.capacity - 1; i >= 0; i--) { @@ -470,9 +470,31 @@ class DynamicArrayBase { return res; } + /** + * Inserts a value at index i, shifting all elements after that position to + * the right by one. The length of the array is increased by one, which must + * result in less than or equal to the capacity. + * + * @param i + * @param value + */ + insert(index: Field, value: T): void { + const right = this.slice(index, this.length); + this.increaseLengthBy(new Field(1)); + this.set(index, value); + for (let i = 0; i < this.capacity; i++) { + let offset = new Field(i).sub(index).sub(new Field(1)); + this.array[i] = Provable.if( + new Field(i).lessThanOrEqual(index), + this.innerType, + this.getOrUnconstrained(new Field(i)), + right.getOrUnconstrained(offset) + ); + } + } + // TODO: // - includes - // - insert // cached variables to not duplicate constraints if we do something like // array.get(i), array.set(i, ..) on the same index diff --git a/src/lib/provable/test/dynamic-array.unit-test.ts b/src/lib/provable/test/dynamic-array.unit-test.ts index 52c54058e..df531b3c0 100644 --- a/src/lib/provable/test/dynamic-array.unit-test.ts +++ b/src/lib/provable/test/dynamic-array.unit-test.ts @@ -221,6 +221,22 @@ let List = ZkProgram({ .value.equals(bytes.get(new Field(2)).value) ); + // Cannot slice out-of-bounds positions + try { + bytes.slice(new Field(1), new Field(5)); + } catch (error) { + console.log('Cannot slice out-of-bounds positions'); + } + + // Cannot slice with end position smaller than start position + try { + bytes.slice(new Field(2), new Field(1)); + } catch (error) { + console.log( + 'Cannot slice with end position smaller than start position' + ); + } + // Concatenate two empty arrays gives an empty array let emptyLeft = new Bytestring(); let emptyRight = new Bytestring(); @@ -283,6 +299,56 @@ let List = ZkProgram({ .value.equals(right.get(new Field(i)).value) ); } + + // Inserting elements at the beginning of the array + bytes = new Bytestring([ + new UInt8(2), + new UInt8(3), + new UInt8(4), + new UInt8(6), + new UInt8(7), + ]); + bytes.insert(new Field(0), new UInt8(1)); + assert(bytes.length.equals(new Field(6))); + assert(bytes.get(new Field(0)).value.equals(new Field(1))); + assert(bytes.get(new Field(1)).value.equals(new Field(2))); + assert(bytes.get(new Field(2)).value.equals(new Field(3))); + assert(bytes.get(new Field(3)).value.equals(new Field(4))); + assert(bytes.get(new Field(4)).value.equals(new Field(6))); + assert(bytes.get(new Field(5)).value.equals(new Field(7))); + + // Inserting elements at the end of the array + bytes.insert(bytes.length, new UInt8(8)); + assert(bytes.length.equals(new Field(7))); + assert(bytes.get(new Field(0)).value.equals(new Field(1))); + assert(bytes.get(new Field(1)).value.equals(new Field(2))); + assert(bytes.get(new Field(2)).value.equals(new Field(3))); + assert(bytes.get(new Field(3)).value.equals(new Field(4))); + assert(bytes.get(new Field(4)).value.equals(new Field(6))); + assert(bytes.get(new Field(5)).value.equals(new Field(7))); + assert(bytes.get(new Field(6)).value.equals(new Field(8))); + + // Inserting elements in the middle of the array + bytes.insert(new Field(4), new UInt8(5)); + assert(bytes.length.equals(new Field(8))); + for (let i = 0; i < 8; i++) { + assert(bytes.get(new Field(i)).value.equals(new Field(i + 1))); + } + + // Cannot insert elements exceeding capacity + try { + bytes.insert(new Field(1), new UInt8(0)); + } catch (error) { + console.log('Cannot insert above capacity'); + } + + // Cannot insert elements out-of-bounds + bytes = new Bytestring([new UInt8(1), new UInt8(2), new UInt8(3)]); + try { + bytes.insert(new Field(4), new UInt8(5)); + } catch (error) { + console.log('Cannot insert out-of-bounds'); + } }, }, },