diff --git a/lib/binable.unit-test.ts b/lib/binable.unit-test.ts index da40d9ee..e62fcc99 100644 --- a/lib/binable.unit-test.ts +++ b/lib/binable.unit-test.ts @@ -95,10 +95,10 @@ test.negative(Random.field, (field) => FieldWithFailingCheck.fromBytes(FieldWithFailingCheck.toBytes(field)) ); -let notABool = Random.map(Random.uint32, (x) => x + 2n); +let notABool = Random.map(Random.uint32, (x) => Number(x) + 2); test.negative(notABool, (bool) => // should fail to decode when check fails - Bool.fromBytes(Bool.toBytes(bool as Bool)) + Bool.fromBytes([bool]) ); // record combinator @@ -133,7 +133,7 @@ test(randomAccountBalance, (accountBalance) => { let MessedUpBool = defineBinable({ toBytes: Bool.toBytes, readBytes(bytes, offset) { - let value = BigInt(bytes[offset++]) as Bool; + let value = !!bytes[offset++]; return [value, offset + 1]; // by accident, offset is incremented twice }, }); diff --git a/lib/from-layout.ts b/lib/from-layout.ts index 47b5e25f..14327c57 100644 --- a/lib/from-layout.ts +++ b/lib/from-layout.ts @@ -41,12 +41,19 @@ type TypeMapValues< [K in keyof TypeMap & keyof JsonMap]: BaseType; }; -type TypeMapProvable = { - [K in keyof TypeMap & keyof JsonMap]: GenericProvableExtended< - TypeMap[K], - JsonMap[K], - TypeMap['Field'] - >; +type TypeMapProvable< + TypeMap extends AnyTypeMap, + ValueMap extends AnyTypeMap, + JsonMap extends AnyTypeMap +> = { + [K in keyof TypeMap & keyof JsonMap]: K extends keyof ValueMap + ? GenericProvableExtended< + TypeMap[K], + ValueMap[K], + JsonMap[K], + TypeMap['Field'] + > + : never; }; type TypeMapSignable = { [K in keyof TypeMap & keyof JsonMap]: GenericSignable< @@ -284,17 +291,18 @@ function SignableFromLayout< function ProvableFromLayout< TypeMap extends AnyTypeMap, + ValueMap extends AnyTypeMap, JsonMap extends AnyTypeMap >( - TypeMap: TypeMapProvable, + TypeMap: TypeMapProvable, customTypes: Record< string, - GenericProvableExtended + GenericProvableExtended > ) { type Field = TypeMap['Field']; const Field = TypeMap.Field; - type BaseType = GenericProvableExtended; + type BaseType = GenericProvableExtended; type HashInput = { fields?: Field[]; packed?: [Field, number][] }; type Layout = GenericLayout; @@ -306,8 +314,17 @@ function ProvableFromLayout< function layoutFold(spec: FoldSpec, typeData: Layout, value?: T) { return genericLayoutFold(TypeMap, customTypes, spec, typeData, value); } + function layoutMap( + map: (typeData: BaseType, value: T) => R, + typeData: Layout, + value: T + ) { + return genericLayoutMap(TypeMap, customTypes, map, typeData, value); + } - function provableFromLayout(typeData: Layout) { + function provableFromLayout( + typeData: Layout + ): GenericProvableExtended { return { sizeInFields(): number { return sizeInFields(typeData); @@ -336,6 +353,12 @@ function ProvableFromLayout< empty(): T { return empty(typeData); }, + toValue(value: T): TValue { + return toValue(typeData, value); + }, + fromValue(value: TValue | T): T { + return fromValue(typeData, value); + }, }; } @@ -346,7 +369,7 @@ function ProvableFromLayout< return type.toFields(value); }, reduceArray(array) { - return array!.flat(); + return array.flat(); }, reduceObject(keys, object) { return keys.map((key) => object![key]).flat(); @@ -485,6 +508,22 @@ function ProvableFromLayout< ); } + function toValue(typeData: Layout, value: any) { + return layoutMap( + (type, value) => type.toValue(value), + typeData, + value + ); + } + + function fromValue(typeData: Layout, value: any) { + return layoutMap( + (type, value) => type.fromValue(value), + typeData, + value + ); + } + return { provableFromLayout, toJSONEssential, empty }; } @@ -591,6 +630,44 @@ function genericLayoutFold< return spec.map((TypeMap as any)[typeData.type], value, typeData.type); } +function genericLayoutMap< + BaseType, + T = any, + R = any, + TypeMap extends AnyTypeMap = AnyTypeMap, + JsonMap extends AnyTypeMap = AnyTypeMap +>( + TypeMap: TypeMapValues, + customTypes: Record, + map: (typeData: BaseType, value: T) => R, + typeData: GenericLayout, + value: T +): R { + return genericLayoutFold( + TypeMap, + customTypes, + { + map(type, value) { + return map(type, value!); + }, + reduceArray(array) { + return array; + }, + reduceObject(_, object) { + return object; + }, + reduceFlaggedOption(option) { + return option; + }, + reduceOrUndefined(value) { + return value; + }, + }, + typeData, + value + ); +} + // types type WithChecked = { diff --git a/lib/generic.ts b/lib/generic.ts index 9720930f..e9f83d4b 100644 --- a/lib/generic.ts +++ b/lib/generic.ts @@ -19,15 +19,17 @@ export { EmptyVoid, }; -type GenericProvable = { +type GenericProvable = { toFields: (x: T) => Field[]; toAuxiliary: (x?: T) => any[]; fromFields: (x: Field[], aux: any[]) => T; sizeInFields(): number; check: (x: T) => void; + toValue: (x: T) => TValue; + fromValue: (x: T | TValue) => T; }; -type GenericProvablePure = Omit< - GenericProvable, +type GenericProvablePure = Omit< + GenericProvable, 'fromFields' > & { fromFields: (x: Field[]) => T; @@ -40,30 +42,35 @@ type GenericSignable = { empty: () => T; }; -type GenericProvableExtended = GenericProvable & +type GenericProvableExtended = GenericProvable< + T, + TValue, + Field +> & GenericSignable; -type GenericProvableExtendedPure = GenericProvablePure< +type GenericProvableExtendedPure = GenericProvablePure< T, + TValue, Field > & GenericSignable; type GenericSignableField = (( - value: number | string | bigint + value: number | string | bigint | Field ) => Field) & GenericSignable & - Binable & { sizeInBytes: number }; + Binable & { sizeInBytes: number; toBigint: (x: Field) => bigint }; type GenericField = GenericSignableField & - GenericProvable; + GenericProvable; type GenericSignableBool = ((value: boolean) => Bool) & GenericSignable & Binable & { sizeInBytes: number }; type GenericBool = GenericSignableBool & - GenericProvable; + GenericProvable; type GenericHashInput = { fields?: Field[]; packed?: [Field, number][] }; @@ -77,6 +84,8 @@ const emptyType = { toJSON: () => null, fromJSON: () => null, empty: () => null, + toValue: () => null, + fromValue: () => null, }; const undefinedType = { @@ -85,31 +94,41 @@ const undefinedType = { toJSON: () => null, fromJSON: () => undefined, empty: () => undefined, + toValue: () => undefined, + fromValue: () => undefined, }; let primitiveTypes = new Set(['number', 'string', 'null']); -function EmptyNull(): GenericProvableExtended & - GenericProvablePure { +function EmptyNull(): GenericProvableExtendedPure< + null, + null, + null, + Field +> { return emptyType; } -function EmptyUndefined(): GenericProvableExtended< +function EmptyUndefined(): GenericProvableExtendedPure< + undefined, undefined, null, Field -> & - GenericProvablePure { +> { return undefinedType; } -function EmptyVoid(): GenericProvableExtended & - GenericProvablePure { +function EmptyVoid(): GenericProvableExtendedPure< + void, + void, + null, + Field +> { return undefinedType; } type PrimitiveTypeMap = { - number: GenericProvableExtended; - string: GenericProvableExtended; - null: GenericProvableExtended; + number: GenericProvableExtended; + string: GenericProvableExtended; + null: GenericProvableExtended; }; const primitiveTypeMap: PrimitiveTypeMap = { @@ -120,6 +139,8 @@ const primitiveTypeMap: PrimitiveTypeMap = { fromJSON: (value) => value, fromFields: (_, [value]) => value, empty: () => 0, + toValue: (value) => value, + fromValue: (value) => value, }, string: { ...emptyType, @@ -128,6 +149,8 @@ const primitiveTypeMap: PrimitiveTypeMap = { fromJSON: (value) => value, fromFields: (_, [value]) => value, empty: () => '', + toValue: (value) => value, + fromValue: (value) => value, }, null: emptyType, }; diff --git a/lib/provable-generic.ts b/lib/provable-generic.ts index affc7b4e..a8749ec6 100644 --- a/lib/provable-generic.ts +++ b/lib/provable-generic.ts @@ -15,8 +15,10 @@ export { NonMethods, InferProvable, InferJson, + InferValue, InferredProvable, IsPure, + From, Constructor, }; @@ -26,19 +28,19 @@ type ProvableConstructor = ( ) => InferredProvable; type SignableConstructor = (typeObj: A) => InferredSignable; +let complexTypes = new Set(['object', 'function']); +let primitives = new Set([Number, String, Boolean, BigInt, null, undefined]); + function createDerivers(): { provable: ProvableConstructor; signable: SignableConstructor; } { - let complexTypes = new Set(['object', 'function']); - let primitives = new Set([Number, String, Boolean, BigInt, null, undefined]); - type Signable = GenericSignable; - type ProvableExtended = GenericProvableExtended< + type ProvableExtended< T, - TJson, - Field - >; + TValue = any, + TJson = JSONValue + > = GenericProvableExtended; type HashInput = GenericHashInput; const HashInput = createHashInput(); @@ -47,6 +49,7 @@ function createDerivers(): { options?: { isPure?: boolean } ): InferredProvable { type T = InferProvable; + type V = InferValue; type J = InferJson; let objectKeys = typeof typeObj === 'object' && typeObj !== null @@ -147,6 +150,9 @@ function createDerivers(): { ); } + const toValue = createMap('toValue'); + const fromValue = createMap('fromValue'); + let { empty, fromJSON, toJSON, toInput } = signable(typeObj); type S = InferSignable; @@ -158,12 +164,18 @@ function createDerivers(): { toAuxiliary: () => [], fromFields: (fields: Field[]) => fromFields(typeObj, fields, [], true) as T, + check: (obj: T) => check(typeObj, obj, true), + toValue(x) { + return toValue(typeObj, x); + }, + fromValue(v) { + return fromValue(typeObj, v); + }, toInput: (obj: T) => toInput(obj as S), toJSON: (obj: T) => toJSON(obj as S) satisfies J, fromJSON: (json: J) => fromJSON(json) as T, - check: (obj: T) => check(typeObj, obj, true), empty: () => empty() as T, - } satisfies ProvableExtended as InferredProvable; + } satisfies ProvableExtended as InferredProvable; } return { sizeInFields: () => sizeInFields(typeObj), @@ -171,12 +183,18 @@ function createDerivers(): { toAuxiliary: (obj?: T) => toAuxiliary(typeObj, obj, true), fromFields: (fields: Field[], aux: any[]) => fromFields(typeObj, fields, aux, true) as T, + check: (obj: T) => check(typeObj, obj, true), + toValue(x) { + return toValue(typeObj, x); + }, + fromValue(v) { + return fromValue(typeObj, v); + }, toInput: (obj: T) => toInput(obj as S), toJSON: (obj: T) => toJSON(obj as S) satisfies J, fromJSON: (json: J) => fromJSON(json) as T, - check: (obj: T) => check(typeObj, obj, true), empty: () => empty() as T, - } satisfies ProvableExtended as InferredProvable; + } satisfies ProvableExtended as InferredProvable; } function signable(typeObj: A): InferredSignable { @@ -277,6 +295,20 @@ function createDerivers(): { return { provable, signable }; } +function createMap(name: S) { + function map(typeObj: any, obj: any): any { + if (primitives.has(typeObj)) return obj; + if (!complexTypes.has(typeof typeObj)) + throw Error(`provable: unsupported type "${typeObj}"`); + if (Array.isArray(typeObj)) return typeObj.map((t, i) => map(t, obj[i])); + if (name in typeObj) return typeObj[name](obj); + return Object.fromEntries( + Object.keys(typeObj).map((k) => [k, map(typeObj[k], obj[k])]) + ); + } + return map; +} + function createHashInput() { type HashInput = GenericHashInput; return { @@ -302,7 +334,12 @@ type JSONValue = | Array | { [key: string]: JSONValue }; -type Struct = GenericProvableExtended, any, Field> & +type Struct = GenericProvableExtended< + NonMethods, + any, + any, + Field +> & Constructor & { _isStruct: true }; type NonMethodKeys = { @@ -334,6 +371,21 @@ type InferPrimitive

= P extends typeof String : P extends undefined ? undefined : any; + +type InferPrimitiveValue

= P extends typeof String + ? string + : P extends typeof Number + ? number + : P extends typeof Boolean + ? boolean + : P extends typeof BigInt + ? bigint + : P extends null + ? null + : P extends undefined + ? undefined + : any; + type InferPrimitiveJson

= P extends typeof String ? string : P extends typeof Number @@ -349,14 +401,18 @@ type InferPrimitiveJson

= P extends typeof String : JSONValue; type InferProvable = A extends Constructor - ? A extends GenericProvable + ? A extends GenericProvable ? U : A extends Struct ? U : InferProvableBase : InferProvableBase; -type InferProvableBase = A extends GenericProvable +type InferProvableBase = A extends GenericProvable< + infer U, + any, + Field +> ? U : A extends Primitive ? InferPrimitive @@ -372,6 +428,22 @@ type InferProvableBase = A extends GenericProvable } : never; +type InferValue = A extends GenericProvable + ? U + : A extends Primitive + ? InferPrimitiveValue + : A extends Tuple + ? { + [I in keyof A]: InferValue; + } + : A extends (infer U)[] + ? InferValue[] + : A extends Record + ? { + [K in keyof A]: InferValue; + } + : never; + type WithJson = { toJSON: (x: any) => J }; type InferJson = A extends WithJson @@ -392,9 +464,9 @@ type InferJson = A extends WithJson type IsPure = IsPureBase extends true ? true : false; -type IsPureBase = A extends GenericProvablePure +type IsPureBase = A extends GenericProvablePure ? true - : A extends GenericProvable + : A extends GenericProvable ? false : A extends Primitive ? false @@ -407,8 +479,18 @@ type IsPureBase = A extends GenericProvablePure : false; type InferredProvable = IsPure extends true - ? GenericProvableExtendedPure, InferJson, Field> - : GenericProvableExtended, InferJson, Field>; + ? GenericProvableExtendedPure< + InferProvable, + InferValue, + InferJson, + Field + > + : GenericProvableExtended< + InferProvable, + InferValue, + InferJson, + Field + >; // signable @@ -433,3 +515,25 @@ type InferredSignable = GenericSignable< InferJson, Field >; + +// deep union type for flexible fromValue + +type From = A extends { + fromValue: (x: infer U) => any; +} & GenericProvable + ? U | InferProvable + : A extends GenericProvable + ? InferProvable | InferValue + : A extends Primitive + ? InferPrimitiveValue + : A extends Tuple + ? { + [I in keyof A]: From; + } + : A extends (infer U)[] + ? From[] + : A extends Record + ? { + [K in keyof A]: From; + } + : never; diff --git a/mina-transaction/derived-leaves.ts b/mina-transaction/derived-leaves.ts index 3e9711c4..2de1b693 100644 --- a/mina-transaction/derived-leaves.ts +++ b/mina-transaction/derived-leaves.ts @@ -123,7 +123,7 @@ function createTokenSymbol< Field, Base extends GenericSignable, any, Field> >(base: Base, Field: GenericSignableField) { - return { + let self = { ...(base as Omit), toInput({ field }: TokenSymbol): GenericHashInput { return { packed: [[field, 48]] }; @@ -140,6 +140,7 @@ function createTokenSymbol< return { symbol, field: prefixToField(Field, symbol) }; }, }; + return self; } type AuthRequired = { @@ -210,7 +211,7 @@ function createZkappUri( return HashHelpers.hashWithPrefix(prefixes.zkappUri, packed); } - return dataAsHash({ + return dataAsHash({ empty() { let hash = HashHelpers.hashWithPrefix(prefixes.zkappUri, [ Field(0), @@ -218,11 +219,18 @@ function createZkappUri( ]); return { data: '', hash }; }, + toValue(data) { + return data; + }, + fromValue(value) { + return value; + }, toJSON(data: string) { return data; }, fromJSON(json: string) { return { data: json, hash: hashZkappUri(json) }; }, + Field, }); } diff --git a/mina-transaction/gen/transaction.ts b/mina-transaction/gen/transaction.ts index ba81a905..d70c915a 100644 --- a/mina-transaction/gen/transaction.ts +++ b/mina-transaction/gen/transaction.ts @@ -22,6 +22,7 @@ import { import { GenericProvableExtended } from '../../lib/generic.js'; import { ProvableFromLayout, GenericLayout } from '../../lib/from-layout.js'; import * as Json from './transaction-json.js'; +import * as Value from './transaction-bigint.js'; import { jsLayout } from './js-layout.js'; export { customTypes, ZkappCommand, AccountUpdate, Account }; @@ -41,7 +42,11 @@ type TypeMap = { }; const TypeMap: { - [K in keyof TypeMap]: ProvableExtended; + [K in keyof TypeMap]: ProvableExtended< + TypeMap[K], + Value.TypeMap[K], + Json.TypeMap[K] + >; } = { PublicKey, UInt64, @@ -53,16 +58,29 @@ const TypeMap: { Sign, }; -type ProvableExtended = GenericProvableExtended; +type ProvableExtended = GenericProvableExtended< + T, + TValue, + TJson, + Field +>; type Layout = GenericLayout; type CustomTypes = { - TransactionVersion: ProvableExtended; + TransactionVersion: ProvableExtended< + UInt32, + Value.TypeMap['UInt32'], + Json.TypeMap['UInt32'] + >; ZkappUri: ProvableExtended< { data: string; hash: Field; }, + { + data: string; + hash: Value.TypeMap['Field']; + }, string >; TokenSymbol: ProvableExtended< @@ -70,14 +88,26 @@ type CustomTypes = { symbol: string; field: Field; }, + { + symbol: string; + field: Value.TypeMap['Field']; + }, string >; - StateHash: ProvableExtended; + StateHash: ProvableExtended< + Field, + Value.TypeMap['Field'], + Json.TypeMap['Field'] + >; Events: ProvableExtended< { data: Field[][]; hash: Field; }, + { + data: Value.TypeMap['Field'][][]; + hash: Value.TypeMap['Field']; + }, Json.TypeMap['Field'][][] >; Actions: ProvableExtended< @@ -85,11 +115,27 @@ type CustomTypes = { data: Field[][]; hash: Field; }, + { + data: Value.TypeMap['Field'][][]; + hash: Value.TypeMap['Field']; + }, Json.TypeMap['Field'][][] >; - ActionState: ProvableExtended; - VerificationKeyHash: ProvableExtended; - ReceiptChainHash: ProvableExtended; + ActionState: ProvableExtended< + Field, + Value.TypeMap['Field'], + Json.TypeMap['Field'] + >; + VerificationKeyHash: ProvableExtended< + Field, + Value.TypeMap['Field'], + Json.TypeMap['Field'] + >; + ReceiptChainHash: ProvableExtended< + Field, + Value.TypeMap['Field'], + Json.TypeMap['Field'] + >; }; let customTypes: CustomTypes = { TransactionVersion, @@ -104,6 +150,7 @@ let customTypes: CustomTypes = { }; let { provableFromLayout, toJSONEssential, empty } = ProvableFromLayout< TypeMap, + Value.TypeMap, Json.TypeMap >(TypeMap, customTypes); @@ -319,9 +366,11 @@ type ZkappCommand = { memo: string; }; -let ZkappCommand = provableFromLayout( - jsLayout.ZkappCommand as any -); +let ZkappCommand = provableFromLayout< + ZkappCommand, + Value.ZkappCommand, + Json.ZkappCommand +>(jsLayout.ZkappCommand as any); type AccountUpdate = { body: { @@ -523,9 +572,11 @@ type AccountUpdate = { }; }; -let AccountUpdate = provableFromLayout( - jsLayout.AccountUpdate as any -); +let AccountUpdate = provableFromLayout< + AccountUpdate, + Value.AccountUpdate, + Json.AccountUpdate +>(jsLayout.AccountUpdate as any); type Account = { publicKey: PublicKey; @@ -576,6 +627,6 @@ type Account = { }; }; -let Account = provableFromLayout( +let Account = provableFromLayout( jsLayout.Account as any );