import { AccountId, AccountTiming } from './account.js'; import { AccountUpdateAuthorization, AccountUpdateAuthorizationEnvironment, AccountUpdateAuthorizationKind, AccountUpdateAuthorizationKindIdentifier, AccountUpdateAuthorizationKindWithZkappContext, } from './authorization.js'; import { Option, TokenId, Update, ZkappUri, mapUndefined } from './core.js'; import { Permissions, PermissionsDescription } from './permissions.js'; import { Preconditions, PreconditionsDescription } from './preconditions.js'; import { GenericStateUpdates, StateDefinition, StateLayout, StateUpdates } from './state.js'; import { Pickles } from '../../../bindings.js'; import { Bool } from '../../provable/bool.js'; import { Field } from '../../provable/field.js'; import { Int64, UInt64 } from '../../provable/int.js'; import { Proof } from '../../proof-system/zkprogram.js'; import { emptyHashWithPrefix, hashWithPrefix, packToFields, TokenSymbol, } from '../../provable/crypto/poseidon.js'; import { PublicKey } from '../../provable/crypto/signature.js'; import { HashInput } from '../../provable/types/provable-derivers.js'; import { Provable } from '../../provable/types/provable-intf.js'; import { mocks, prefixes } from '../../../bindings/crypto/constants.js'; import * as Bindings from '../../../bindings/mina-transaction/v2/index.js'; import * as PoseidonBigint from '../../../mina-signer/src/poseidon-bigint.js'; import { Signature, signFieldElement, zkAppBodyPrefix, } from '../../../mina-signer/src/signature.js'; import { NetworkId } from '../../../mina-signer/src/types.js'; import { Struct } from '../../provable/types/struct.js'; import { VerificationKey } from '../../../lib/proof-system/verification-key.js'; // TODO: make private abstractions over many fields (eg new apis for Update and Constraint.*) // TODO: replay checks export { AccountUpdate, Authorized, GenericData, AccountUpdateTree, AccountUpdateTreeDescription, ContextFreeAccountUpdateDescription, ContextFreeAccountUpdate, DynamicProvable, AccountUpdateCommitment, CommittedList, EventsHashConfig, ActionsHashConfig, }; class AccountUpdateCommitment extends Struct({ accountUpdateCommitment: Field, }) { constructor(accountUpdateCommitment: Field) { super({ accountUpdateCommitment }); } } // TODO: move elsewhere type DynamicProvable = | Provable | { toFields(x: T): Field[]; toAuxiliary(x: T): any[]; fromFieldsDynamic(fields: Field[], aux: any[]): { value: T; fieldsConsumed: number }; }; // TODO: move elsewhere const GenericData: DynamicProvable = { toFields(x: Field[]): Field[] { return x; }, toAuxiliary(x: Field[]): any[] { return [x.length]; }, fromFieldsDynamic(fields: Field[], aux: any[]): { value: Field[]; fieldsConsumed: number } { const [_len] = aux; let len = _len ?? fields.length; return { value: fields.slice(0, len), fieldsConsumed: len }; }, }; // TODO: move elsewhere /* interface Hashable { hash(): Field; } */ // TODO: move elsewhere interface HashableDataConfig { readonly emptyPrefix: string; readonly consPrefix: string; hash(item: Item): Field; } function EventsHashConfig(T: DynamicProvable): HashableDataConfig { return { emptyPrefix: 'MinaZkappEventsEmpty', consPrefix: prefixes.events, hash(x: T): Field { const fields = T.toFields(x); return hashWithPrefix(prefixes.event, fields); }, }; } function ActionsHashConfig(T: DynamicProvable): HashableDataConfig { return { emptyPrefix: 'MinaZkappActionsEmpty', consPrefix: prefixes.sequenceEvents, hash(x: T): Field { const fields = T.toFields(x); return hashWithPrefix(prefixes.event, fields); }, }; } // TODO: move elsewhere class CommittedList { readonly Item: DynamicProvable; readonly data: Item[]; readonly hash: Field; constructor({ Item, data, hash }: { Item: DynamicProvable; data: Item[]; hash: Field }) { this.Item = Item; this.data = data; this.hash = hash; } toInternalRepr(): { data: Field[][]; hash: Field } { return { data: this.data.map(this.Item.toFields), hash: this.hash, }; } // IMPORTANT: It is the callers responsibility to ensure the commitment will compute the same // after mapping the list (this function does not check this for you at runtime). mapUnsafe(NewItem: DynamicProvable, f: (a: Item) => B): CommittedList { return new CommittedList({ Item: NewItem, data: this.data.map(f), hash: this.hash, }); } static hashList(config: HashableDataConfig, items: Item[]): Field { let hash = emptyHashWithPrefix(config.emptyPrefix); for (let i = items.length - 1; i >= 0; i--) { const item = items[i]; hash = hashWithPrefix(config.consPrefix, [hash, config.hash(item)]); } return hash; } static from( Item: DynamicProvable, config: HashableDataConfig, value: undefined | Item[] | CommittedList | Bindings.Leaves.CommittedList ): CommittedList { if (value instanceof CommittedList) return value; let items: Item[]; //let hash; if (value === undefined) { items = []; } else if (value instanceof Array) { items = value; } else { // TODO: think about this a bit more... we don't have the aux data here, so we should do // something to restrict the types if ('fromFields' in Item) { items = value.data.map((fields) => Item.fromFields(fields, [])); } else { items = value.data.map((fields) => { const { value: result, fieldsConsumed } = Item.fromFieldsDynamic(fields, []); if (fieldsConsumed !== fields.length) throw new Error('expected all fields to be consumed when casting dynamic item'); return result; }); } //hash = value.hash; } //hash = hash ?? CommittedList.hashList(config, items); return new CommittedList({ Item, data: items, hash: CommittedList.hashList(config, items), }); } } interface MayUseToken { parentsOwnToken: Bool; inheritFromParent: Bool; } type AccountUpdateTreeDescription = RootDescription & { // TODO: support using commitments? children?: AccountUpdateTree[]; }; // TODO: CONSIDER -- merge this logic into AccountUpdate // class AccountUpdateTree { class AccountUpdateTree { constructor( public rootAccountUpdate: Root, public children: AccountUpdateTree[] ) {} // depth first traversal (parents before children) static forEachNode( tree: AccountUpdateTree, depth: number, f: (accountUpdate: T, depth: number) => void ): void { f(tree.rootAccountUpdate, depth); tree.children.forEach((child) => AccountUpdateTree.forEachNode(child, depth + 1, f)); } // inverted depth first traversal (children before parents) static forEachNodeInverted( tree: AccountUpdateTree, depth: number, f: (accountUpdate: T, depth: number) => void ): void { tree.children.forEach((child) => AccountUpdateTree.forEachNodeInverted(child, depth + 1, f)); f(tree.rootAccountUpdate, depth); } static reduce(tree: AccountUpdateTree, f: (accountUpdate: T, childValues: R[]) => R): R { const childValues = tree.children.map((child) => AccountUpdateTree.reduce(child, f)); return f(tree.rootAccountUpdate, childValues); } // TODO: delete (realized I didn't need it part way through writing) // // build context as we descend the tree, map, then reduce as we ascend // static mapReduceWithContext( // tree: AccountUpdateTree, // context: Ctx, // mapContext: (ctx: Ctx, index: number) => Ctx, // map: (ctx: Ctx, accountUpdate: T) => R, // reduce: (values: R[]) => R // ): R { // if(tree.children.length === 0) { // return map(context, tree.rootAccountUpdate); // } else { // const reducedValues = tree.children.map((child, index) => // AccountUpdateTree.mapReduceWithContext( // child, // mapContext(context, index), // mapContext, // map, // reduce // ); // ); // return reduce(reducedValues); // } // } // TODO: refactor the type parameter interfaces static mapRoot( tree: AccountUpdateTree, f: (accountUpdate: RootIn) => RootOut ): AccountUpdateTree { const newAccountUpdate = f(tree.rootAccountUpdate); return new AccountUpdateTree(newAccountUpdate, tree.children); } static async map( tree: AccountUpdateTree, f: (accountUpdate: A) => Promise ): Promise> { const newAccountUpdate = await f(tree.rootAccountUpdate); const newChildren = await AccountUpdateTree.mapForest(tree.children, f); return new AccountUpdateTree(newAccountUpdate, newChildren); } static mapForest( forest: AccountUpdateTree[], f: (a: A) => Promise ): Promise[]> { return Promise.all(forest.map((tree) => AccountUpdateTree.map(tree, f))); } // TODO: I think this can be safely made polymorphic over the account update state, actions, and events representations // TODO: Field, not bigint static hash(tree: AccountUpdateTree, networkId: NetworkId): bigint { // TODO: is it ok to do this and ignore the toValue encodings entirely? const accountUpdateFieldInput = tree.rootAccountUpdate.toInput(); const accountUpdateBigintInput = { fields: accountUpdateFieldInput.fields?.map((f: Field) => f.toBigInt()), packed: accountUpdateFieldInput.packed?.map(([f, n]: [Field, number]): [bigint, number] => [ f.toBigInt(), n, ]), }; // TODO: negotiate between this implementation and AccountUpdate#hash to figure out what is correct // TODO NOW: ^ this was done, but we need to update this function to share code still const accountUpdateCommitment = PoseidonBigint.hashWithPrefix( zkAppBodyPrefix(networkId), PoseidonBigint.packToFields(accountUpdateBigintInput) ); const childrenCommitment = AccountUpdateTree.hashForest(networkId, tree.children); return PoseidonBigint.hashWithPrefix(prefixes.accountUpdateNode, [ accountUpdateCommitment, childrenCommitment, ]); } // TODO: Field, not bigint static hashForest(networkId: NetworkId, forest: AccountUpdateTree[]): bigint { const consHash = (acc: bigint, tree: AccountUpdateTree) => PoseidonBigint.hashWithPrefix(prefixes.accountUpdateCons, [ AccountUpdateTree.hash(tree, networkId), acc, ]); return [...forest].reverse().reduce(consHash, 0n); } static unrollForest( forest: AccountUpdateTree[], f: (accountUpdate: AccountUpdateType, depth: number) => Return ): Return[] { const seq: Return[] = []; forest.forEach((tree) => AccountUpdateTree.forEachNode(tree, 0, (accountUpdate, depth) => seq.push(f(accountUpdate, depth)) ) ); return seq; } static sizeInFields(): number { return AccountUpdate.sizeInFields(); } static toFields(x: AccountUpdateTree): Field[] { return AccountUpdate.toFields(x.rootAccountUpdate); } static toAuxiliary(x?: AccountUpdateTree): any[] { return [AccountUpdate.toAuxiliary(x?.rootAccountUpdate), x?.children ?? []]; } static fromFields(fields: Field[], aux: any[]): AccountUpdateTree { return new AccountUpdateTree(AccountUpdate.fromFields(fields, aux[0]), aux[1]); } static toValue(x: AccountUpdateTree): AccountUpdateTree { return x; } static fromValue(x: AccountUpdateTree): AccountUpdateTree { return x; } static check(_x: AccountUpdateTree): void { // TODO } static from( descr: AccountUpdateTreeDescription, createAccountUpdate: (descr: RootDescription) => Root ): AccountUpdateTree { return new AccountUpdateTree(createAccountUpdate(descr), descr.children ?? []); } } interface ContextFreeAccountUpdateDescription< State extends StateLayout = 'GenericState', Event = Field[], Action = Field[], > { // TODO: accept identifiers for authorization kind authorizationKind: AccountUpdateAuthorizationKindIdentifier | AccountUpdateAuthorizationKind; preconditions?: PreconditionsDescription | Preconditions; balanceChange?: Int64; incrementNonce?: Bool; useFullCommitment?: Bool; implicitAccountCreationFee?: Bool; mayUseToken?: MayUseToken; pushEvents?: Event[] | CommittedList; pushActions?: Action[] | CommittedList; setState?: StateUpdates; setPermissions?: PermissionsDescription | Permissions | Update; setDelegate?: PublicKey | Update; setVerificationKey?: VerificationKey | Update; setZkappUri?: string | ZkappUri | Update; setTokenSymbol?: string | TokenSymbol | Update; setTiming?: AccountTiming | Update; setVotingFor?: Field | Update; } // in a ZkModule context: ContextFreeAccountUpdate is an AccountUpdate without an account id and call data class ContextFreeAccountUpdate< State extends StateLayout = 'GenericState', Event = Field[], Action = Field[], > { readonly State: StateDefinition; authorizationKind: AccountUpdateAuthorizationKind; preconditions: Preconditions; balanceChange: Int64; incrementNonce: Bool; useFullCommitment: Bool; implicitAccountCreationFee: Bool; mayUseToken: MayUseToken; pushEvents: CommittedList; pushActions: CommittedList; // TODO: standardize on these being set* for *Update, don't do both stateUpdates: StateUpdates; permissionsUpdate: Update; delegateUpdate: Update; verificationKeyUpdate: Update; zkappUriUpdate: Update; tokenSymbolUpdate: Update; timingUpdate: Update; votingForUpdate: Update; constructor( State: StateDefinition, Event: DynamicProvable, Action: DynamicProvable, descr: | ContextFreeAccountUpdateDescription | ContextFreeAccountUpdate ) { function castUpdate( value: undefined | A | Update, defaultValue: B, f: (a: A) => B ): Update { if (value instanceof Update) { return value; } else { return Update.from(mapUndefined(value, f), defaultValue); } } this.State = State; this.authorizationKind = AccountUpdateAuthorizationKind.from(descr.authorizationKind); this.preconditions = mapUndefined(descr.preconditions, (x) => Preconditions.from(State, x)) ?? Preconditions.emptyPoly(State); this.balanceChange = descr.balanceChange ?? Int64.create(UInt64.zero); this.incrementNonce = descr.incrementNonce ?? new Bool(false); this.useFullCommitment = descr.useFullCommitment ?? new Bool(false); this.implicitAccountCreationFee = descr.implicitAccountCreationFee ?? new Bool(false); this.mayUseToken = descr.mayUseToken ?? { parentsOwnToken: new Bool(false), inheritFromParent: new Bool(false), }; this.pushEvents = CommittedList.from(Event, EventsHashConfig(Event), descr.pushEvents); this.pushActions = CommittedList.from(Action, ActionsHashConfig(Action), descr.pushActions); if (descr instanceof ContextFreeAccountUpdate) { this.stateUpdates = descr.stateUpdates; this.permissionsUpdate = descr.permissionsUpdate; this.delegateUpdate = descr.delegateUpdate; this.verificationKeyUpdate = descr.verificationKeyUpdate; this.zkappUriUpdate = descr.zkappUriUpdate; this.tokenSymbolUpdate = descr.tokenSymbolUpdate; this.timingUpdate = descr.timingUpdate; this.votingForUpdate = descr.votingForUpdate; } else { this.stateUpdates = descr.setState ?? StateUpdates.empty(State); this.permissionsUpdate = castUpdate( descr.setPermissions, Permissions.empty(), Permissions.from ); this.delegateUpdate = Update.from(descr.setDelegate, PublicKey.empty()); this.verificationKeyUpdate = Update.from(descr.setVerificationKey, VerificationKey.empty()); this.zkappUriUpdate = castUpdate(descr.setZkappUri, ZkappUri.empty(), ZkappUri.from); this.tokenSymbolUpdate = castUpdate( descr.setTokenSymbol, TokenSymbol.empty(), TokenSymbol.from ); this.timingUpdate = Update.from(descr.setTiming, AccountTiming.empty()); this.votingForUpdate = Update.from(descr.setVotingFor, Field.empty()); } } toGeneric(): ContextFreeAccountUpdate { return ContextFreeAccountUpdate.generic({ authorizationKind: this.authorizationKind, preconditions: this.preconditions.toGeneric(), balanceChange: this.balanceChange, incrementNonce: this.incrementNonce, useFullCommitment: this.useFullCommitment, implicitAccountCreationFee: this.implicitAccountCreationFee, mayUseToken: this.mayUseToken, pushEvents: this.pushEvents.mapUnsafe(GenericData, this.pushEvents.Item.toFields), pushActions: this.pushActions.mapUnsafe(GenericData, this.pushActions.Item.toFields), setState: StateUpdates.toGeneric(this.State, this.stateUpdates), setPermissions: this.permissionsUpdate, setDelegate: this.delegateUpdate, setVerificationKey: this.verificationKeyUpdate, setZkappUri: this.zkappUriUpdate, setTokenSymbol: this.tokenSymbolUpdate, setTiming: this.timingUpdate, setVotingFor: this.votingForUpdate, }); } static fromGeneric( x: ContextFreeAccountUpdate, State: StateDefinition, Event: DynamicProvable, Action: DynamicProvable ): ContextFreeAccountUpdate { // TODO: this method is broken because we aren't storing aux data in the generic format... return new ContextFreeAccountUpdate(State, Event, Action, { authorizationKind: x.authorizationKind, preconditions: Preconditions.fromGeneric(x.preconditions, State), balanceChange: x.balanceChange, incrementNonce: x.incrementNonce, useFullCommitment: x.useFullCommitment, implicitAccountCreationFee: x.implicitAccountCreationFee, mayUseToken: x.mayUseToken, pushEvents: x.pushEvents.mapUnsafe(Event, (fields) => // TODO: this is really unsafe, make it safe 'fromFieldsDynamic' in Event ? Event.fromFieldsDynamic(fields, []).value : Event.fromFields(fields, []) ), pushActions: x.pushActions.mapUnsafe(Action, (fields) => // TODO: this is really unsafe, make it safe 'fromFieldsDynamic' in Action ? Action.fromFieldsDynamic(fields, []).value : Action.fromFields(fields, []) ), setState: StateUpdates.fromGeneric(x.stateUpdates, State), setPermissions: x.permissionsUpdate, setDelegate: x.delegateUpdate, setVerificationKey: x.verificationKeyUpdate, setZkappUri: x.zkappUriUpdate, setTokenSymbol: x.tokenSymbolUpdate, setTiming: x.timingUpdate, setVotingFor: x.votingForUpdate, }); } static generic(descr: ContextFreeAccountUpdateDescription): ContextFreeAccountUpdate { return new ContextFreeAccountUpdate('GenericState', GenericData, GenericData, descr); } static emptyPoly( State: StateDefinition, Event: DynamicProvable, Action: DynamicProvable ) { return new ContextFreeAccountUpdate(State, Event, Action, { authorizationKind: AccountUpdateAuthorizationKind.None(), }); } static empty(): ContextFreeAccountUpdate { return ContextFreeAccountUpdate.emptyPoly('GenericState', GenericData, GenericData); } static from( State: StateDefinition, Event: DynamicProvable, Action: DynamicProvable, x: | ContextFreeAccountUpdateDescription | ContextFreeAccountUpdate | undefined ): ContextFreeAccountUpdate { if (x instanceof ContextFreeAccountUpdate) return x; if (x === undefined) return ContextFreeAccountUpdate.emptyPoly(State, Event, Action); return new ContextFreeAccountUpdate(State, Event, Action, x); } } type AccountUpdateDescription = ( | { update: ContextFreeAccountUpdate; proof?: Proof; } | ContextFreeAccountUpdateDescription ) & { accountId: AccountId; verificationKeyHash?: Field; callData?: Field; }; class AccountUpdate< State extends StateLayout = 'GenericState', Event = Field[], Action = Field[], > extends ContextFreeAccountUpdate { accountId: AccountId; verificationKeyHash: Field; callData: Field; // TODO: this probable shouldn't live here, but somewhere else proof: 'NoProofRequired' | 'ProofPending' | Proof; // TODO: circuit friendly representation (we really don't want to toBoolean() in the constructor here...) // proof: {pending: true, isRequired: Bool} | Proof; constructor( State: StateDefinition, Event: DynamicProvable, Action: DynamicProvable, descr: AccountUpdateDescription ) { // TODO NOW: THIS PATTERN IS BROKEN (we are casting and update into a description, which only works because the missing fields are all optional) const superInput = 'update' in descr ? descr.update : descr; super(State, Event, Action, superInput); if (this.authorizationKind.isProved.toBoolean()) { this.proof = 'proof' in descr && descr.proof !== undefined ? descr.proof : 'ProofPending'; } else { if ('proof' in descr && descr.proof !== undefined) { throw new Error( 'proof was provided when constructing an AccountUpdate that does not require a proof' ); } this.proof = 'NoProofRequired'; } this.accountId = descr.accountId; this.verificationKeyHash = descr.verificationKeyHash ?? new Field(mocks.dummyVerificationKeyHash); this.callData = descr.callData ?? new Field(0); } get authorizationKindWithZkappContext(): AccountUpdateAuthorizationKindWithZkappContext { return new AccountUpdateAuthorizationKindWithZkappContext( this.authorizationKind, this.verificationKeyHash ); } toInternalRepr(callDepth: number): Bindings.Layout.AccountUpdateBody { return { authorizationKind: this.authorizationKindWithZkappContext, publicKey: this.accountId.publicKey, tokenId: this.accountId.tokenId.value, callData: this.callData, callDepth: callDepth, balanceChange: this.balanceChange, incrementNonce: this.incrementNonce, useFullCommitment: this.useFullCommitment, implicitAccountCreationFee: this.implicitAccountCreationFee, mayUseToken: this.mayUseToken, events: this.pushEvents.toInternalRepr(), actions: this.pushActions.toInternalRepr(), preconditions: this.preconditions.toInternalRepr(), update: { appState: StateUpdates.toFieldUpdates(this.State, this.stateUpdates).map((update) => update.toOption() ), delegate: this.delegateUpdate.toOption(), verificationKey: Option.map(this.verificationKeyUpdate.toOption(), (data) => data instanceof VerificationKey ? new VerificationKey(data) : data ), permissions: this.permissionsUpdate.toOption(), zkappUri: this.zkappUriUpdate.toOption(), tokenSymbol: this.tokenSymbolUpdate.toOption(), timing: this.timingUpdate.toOption(), votingFor: this.votingForUpdate.toOption(), }, }; } toInput(): HashInput { return Bindings.Layout.AccountUpdateBody.toInput(this.toInternalRepr(0)); } commit(networkId: NetworkId): AccountUpdateCommitment { const commitment = hashWithPrefix(zkAppBodyPrefix(networkId), packToFields(this.toInput())); return new AccountUpdateCommitment(commitment); } toGeneric(): AccountUpdate { return new AccountUpdate('GenericState', GenericData, GenericData, { update: super.toGeneric(), proof: this.proof instanceof Proof ? this.proof : undefined, accountId: this.accountId, verificationKeyHash: this.verificationKeyHash, callData: this.callData, }); } static fromGeneric( x: AccountUpdate, State: StateDefinition, Event: DynamicProvable, Action: DynamicProvable ): AccountUpdate { return new AccountUpdate(State, Event, Action, { update: ContextFreeAccountUpdate.fromGeneric(x, State, Event, Action), proof: x.proof instanceof Proof ? x.proof : undefined, accountId: x.accountId, verificationKeyHash: x.verificationKeyHash, callData: x.callData, }); } async authorize( authEnv: AccountUpdateAuthorizationEnvironment ): Promise> { let proof = null; let signature = null; switch (this.proof) { case 'NoProofRequired': if (this.authorizationKind.isProved.toBoolean()) { throw new Error( `account update proof was marked as not required, but authorization kind was ${this.authorizationKind.identifier()}` ); } else { break; } case 'ProofPending': if (this.authorizationKind.isProved.toBoolean()) { throw new Error( `account update proof is still pending; a proof must be generated and assigned to an account update before calling authorize` ); } else { console.warn( `account update is marked to required a proof, but the authorization kind is ${this.authorizationKind.identifier()} (and the proof is still pending)` ); break; } default: if (this.authorizationKind.isProved.toBoolean()) { proof = Pickles.proofToBase64Transaction(this.proof.proof); } else { console.warn( `account update has a proof, but no proof is required by authorization kind ${this.authorizationKind.identifier()}, so it will not be included` ); } } if (this.authorizationKind.isSigned.toBoolean()) { let txnCommitment; if (this.useFullCommitment.toBoolean()) { if (authEnv.fullTransactionCommitment === undefined) { throw new Error( 'unable to authorize account update: useFullCommitment is true, but not full transaction commitment was provided in authorization environment' ); } txnCommitment = authEnv.fullTransactionCommitment; } else { txnCommitment = authEnv.accountUpdateForestCommitment; } const privateKey = await authEnv.getPrivateKey(this.accountId.publicKey); const sig = signFieldElement(txnCommitment, privateKey.toBigInt(), authEnv.networkId); signature = Signature.toBase58(sig); } return new Authorized({ proof, signature }, this); } static create(x: AccountUpdateDescription<'GenericState', Field[], Field[]>): AccountUpdate { return new AccountUpdate('GenericState', GenericData, GenericData, x); } static fromInternalRepr(x: Bindings.Layout.AccountUpdateBody): AccountUpdate { return new AccountUpdate('GenericState', GenericData, GenericData, { accountId: new AccountId(x.publicKey, new TokenId(x.tokenId)), verificationKeyHash: x.authorizationKind.verificationKeyHash, authorizationKind: new AccountUpdateAuthorizationKind(x.authorizationKind), callData: x.callData, balanceChange: Int64.create(x.balanceChange.magnitude, x.balanceChange.sgn), incrementNonce: x.incrementNonce, useFullCommitment: x.useFullCommitment, implicitAccountCreationFee: x.implicitAccountCreationFee, mayUseToken: x.mayUseToken, pushEvents: CommittedList.from(GenericData, EventsHashConfig(GenericData), x.events), pushActions: CommittedList.from(GenericData, ActionsHashConfig(GenericData), x.actions), preconditions: Preconditions.fromInternalRepr(x.preconditions), setState: new GenericStateUpdates(x.update.appState.map(Update.fromOption)), setDelegate: Update.fromOption(x.update.delegate), setVerificationKey: Update.fromOption(x.update.verificationKey), setPermissions: Update.fromOption( Option.map(x.update.permissions, Permissions.fromInternalRepr) ), setZkappUri: Update.fromOption(Option.map(x.update.zkappUri, (uri) => new ZkappUri(uri))), setTokenSymbol: Update.fromOption( Option.map(x.update.tokenSymbol, (symbol) => new TokenSymbol(symbol)) ), setTiming: Update.fromOption( Option.map(x.update.timing, (timing) => new AccountTiming(timing)) ), setVotingFor: Update.fromOption(x.update.votingFor), }); } static sizeInFields(): number { return Bindings.Layout.AccountUpdateBody.sizeInFields(); } static toFields(x: AccountUpdate): Field[] { return Bindings.Layout.AccountUpdateBody.toFields(x.toInternalRepr(0)); } static toAuxiliary(x?: AccountUpdate, callDepth?: number): any[] { return Bindings.Layout.AccountUpdateBody.toAuxiliary(x?.toInternalRepr(callDepth ?? 0)); } static fromFields(fields: Field[], aux: any[]): AccountUpdate { return AccountUpdate.fromInternalRepr( Bindings.Layout.AccountUpdateBody.fromFields(fields, aux) ); } static toValue(x: AccountUpdate): AccountUpdate { return x; } static fromValue(x: AccountUpdate): AccountUpdate { return x; } static check(_x: AccountUpdate): void { // TODO } static empty(): AccountUpdate { return new AccountUpdate('GenericState', GenericData, GenericData, { accountId: AccountId.empty(), callData: Field.empty(), update: ContextFreeAccountUpdate.empty(), }); } } // TODO NOW: un-namespace this /* type ContextFreeDescription< State extends StateLayout = 'GenericState', Event = Field[], Action = Field[] > = ContextFreeAccountUpdateDescription; type ContextFree< State extends StateLayout = 'GenericState', Event = Field[], Action = Field[] > = ContextFreeAccountUpdate; const ContextFree = ContextFreeAccountUpdate; */ // TODO: can we enforce that Authorized account updates are immutable? class Authorized { authorization: AccountUpdateAuthorization; private update: AccountUpdate; constructor( authorization: AccountUpdateAuthorization, update: AccountUpdate ) { this.authorization = authorization; this.update = update; } toAccountUpdate(): AccountUpdate { return this.update; } get State(): StateDefinition { return this.update.State; } get authorizationKind(): AccountUpdateAuthorizationKind { return this.update.authorizationKind; } get accountId(): AccountId { return this.update.accountId; } get verificationKeyHash(): Field { return this.update.verificationKeyHash; } get callData(): Field { return this.update.callData; } get preconditions(): Preconditions { return this.update.preconditions; } get balanceChange(): Int64 { return this.update.balanceChange; } get incrementNonce(): Bool { return this.update.incrementNonce; } get useFullCommitment(): Bool { return this.update.useFullCommitment; } get implicitAccountCreationFee(): Bool { return this.update.implicitAccountCreationFee; } get mayUseToken(): MayUseToken { return this.update.mayUseToken; } get pushEvents(): CommittedList { return this.update.pushEvents; } get pushActions(): CommittedList { return this.update.pushActions; } get stateUpdates(): StateUpdates { return this.update.stateUpdates; } get permissionsUpdate(): Update { return this.update.permissionsUpdate; } get delegateUpdate(): Update { return this.update.delegateUpdate; } get verificationKeyUpdate(): Update { return this.update.verificationKeyUpdate; } get zkappUriUpdate(): Update { return this.update.zkappUriUpdate; } get tokenSymbolUpdate(): Update { return this.update.tokenSymbolUpdate; } get timingUpdate(): Update { return this.update.timingUpdate; } get votingForUpdate(): Update { return this.update.votingForUpdate; } get authorizationKindWithZkappContext(): AccountUpdateAuthorizationKindWithZkappContext { return this.update.authorizationKindWithZkappContext; } hash(netId: NetworkId): Field { let input = Bindings.Layout.ZkappAccountUpdate.toInput(this.toInternalRepr(0)); return hashWithPrefix(zkAppBodyPrefix(netId), packToFields(input)); } toInternalRepr(callDepth: number): Bindings.Layout.ZkappAccountUpdate { return { authorization: { proof: this.authorization.proof === null ? undefined : this.authorization.proof, signature: this.authorization.signature === null ? undefined : this.authorization.signature, }, body: this.update.toInternalRepr(callDepth), }; } static fromInternalRepr(x: Bindings.Layout.ZkappAccountUpdate): Authorized { return new Authorized( { // when the internal representation is returned from the previous version when casting from fields, // (if there is no proof or authorization, values are set to false rather than to undefined) proof: (x.authorization.proof as any) !== false ? (x.authorization.proof ?? null) : null, signature: (x.authorization.proof as any) !== false ? (x.authorization.signature ?? null) : null, }, AccountUpdate.fromInternalRepr(x.body) ); } toJSON(callDepth: number): any { return Authorized.toJSON(this, callDepth); } toInput(): HashInput { return Authorized.toInput(this); } toFields(): Field[] { return Authorized.toFields(this); } static empty(): Authorized { return new Authorized({ proof: null, signature: null }, AccountUpdate.empty()); } static sizeInFields(): number { return Bindings.Layout.ZkappAccountUpdate.sizeInFields(); } static toJSON( x: Authorized, callDepth: number ): any { return Bindings.Layout.ZkappAccountUpdate.toJSON(x.toInternalRepr(callDepth)); } static toInput( x: Authorized ): HashInput { return Bindings.Layout.ZkappAccountUpdate.toInput(x.toInternalRepr(0)); } static toFields( x: Authorized ): Field[] { return Bindings.Layout.ZkappAccountUpdate.toFields(x.toInternalRepr(0)); } static toAuxiliary( x?: Authorized, callDepth?: number ): any[] { return Bindings.Layout.ZkappAccountUpdate.toAuxiliary(x?.toInternalRepr(callDepth ?? 0)); } static fromFields(fields: Field[], aux: any[]): Authorized { return Authorized.fromInternalRepr(Bindings.Layout.ZkappAccountUpdate.fromFields(fields, aux)); } static toValue( x: Authorized ): Authorized { return x; } static fromValue( x: Authorized ): Authorized { return x; } static check( _x: Authorized ) { throw new Error('TODO'); } }