@aztec/entrypoints 0.81.0 → 0.82.1-alpha-testnet.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,109 @@
1
+ import { ProtocolContractAddress } from '@aztec/protocol-contracts';
2
+ import { type FunctionAbi, FunctionSelector, encodeArguments } from '@aztec/stdlib/abi';
3
+ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
4
+ import { HashedValues, TxContext, TxExecutionRequest } from '@aztec/stdlib/tx';
5
+
6
+ import { EncodedCallsForEntrypoint } from './encoding.js';
7
+ import type { EntrypointInterface, FeeOptions } from './interfaces.js';
8
+ import type { ExecutionPayload } from './payload.js';
9
+
10
+ /**
11
+ * Implementation for an entrypoint interface that can execute multiple function calls in a single transaction
12
+ */
13
+ export class DefaultMultiCallEntrypoint implements EntrypointInterface {
14
+ constructor(
15
+ private chainId: number,
16
+ private version: number,
17
+ private address: AztecAddress = ProtocolContractAddress.MultiCallEntrypoint,
18
+ ) {}
19
+
20
+ async createTxExecutionRequest(exec: ExecutionPayload, fee: FeeOptions): Promise<TxExecutionRequest> {
21
+ // Initial request with calls, authWitnesses and capsules
22
+ const { calls, authWitnesses, capsules, extraHashedArgs } = exec;
23
+
24
+ // Get the execution payload for the fee, it includes the calls and potentially authWitnesses
25
+ const {
26
+ calls: feeCalls,
27
+ authWitnesses: feeAuthwitnesses,
28
+ extraHashedArgs: feeExtraHashedArgs,
29
+ } = await fee.paymentMethod.getExecutionPayload(fee.gasSettings);
30
+
31
+ // Encode the calls, including the fee calls
32
+ // (since this entrypoint does not distinguish between app and fee calls)
33
+ const encodedCalls = await EncodedCallsForEntrypoint.fromAppExecution(calls.concat(feeCalls));
34
+
35
+ // Obtain the entrypoint hashed args, built from the encoded calls
36
+ const abi = this.getEntrypointAbi();
37
+ const entrypointHashedArgs = await HashedValues.fromArgs(encodeArguments(abi, [encodedCalls]));
38
+
39
+ // Assemble the tx request
40
+ const txRequest = TxExecutionRequest.from({
41
+ firstCallArgsHash: entrypointHashedArgs.hash,
42
+ origin: this.address,
43
+ functionSelector: await FunctionSelector.fromNameAndParameters(abi.name, abi.parameters),
44
+ txContext: new TxContext(this.chainId, this.version, fee.gasSettings),
45
+ argsOfCalls: [...encodedCalls.hashedArguments, entrypointHashedArgs, ...extraHashedArgs, ...feeExtraHashedArgs],
46
+ authWitnesses: [...feeAuthwitnesses, ...authWitnesses],
47
+ capsules,
48
+ });
49
+
50
+ return Promise.resolve(txRequest);
51
+ }
52
+
53
+ private getEntrypointAbi() {
54
+ return {
55
+ name: 'entrypoint',
56
+ isInitializer: false,
57
+ functionType: 'private',
58
+ isInternal: false,
59
+ isStatic: false,
60
+ parameters: [
61
+ {
62
+ name: 'app_payload',
63
+ type: {
64
+ kind: 'struct',
65
+ path: 'authwit::entrypoint::app::AppPayload',
66
+ fields: [
67
+ {
68
+ name: 'function_calls',
69
+ type: {
70
+ kind: 'array',
71
+ length: 4,
72
+ type: {
73
+ kind: 'struct',
74
+ path: 'authwit::entrypoint::function_call::FunctionCall',
75
+ fields: [
76
+ { name: 'args_hash', type: { kind: 'field' } },
77
+ {
78
+ name: 'function_selector',
79
+ type: {
80
+ kind: 'struct',
81
+ path: 'authwit::aztec::protocol_types::abis::function_selector::FunctionSelector',
82
+ fields: [{ name: 'inner', type: { kind: 'integer', sign: 'unsigned', width: 32 } }],
83
+ },
84
+ },
85
+ {
86
+ name: 'target_address',
87
+ type: {
88
+ kind: 'struct',
89
+ path: 'authwit::aztec::protocol_types::address::AztecAddress',
90
+ fields: [{ name: 'inner', type: { kind: 'field' } }],
91
+ },
92
+ },
93
+ { name: 'is_public', type: { kind: 'boolean' } },
94
+ { name: 'is_static', type: { kind: 'boolean' } },
95
+ ],
96
+ },
97
+ },
98
+ },
99
+ { name: 'nonce', type: { kind: 'field' } },
100
+ ],
101
+ },
102
+ visibility: 'public',
103
+ },
104
+ ],
105
+ returnTypes: [],
106
+ errorTypes: {},
107
+ } as FunctionAbi;
108
+ }
109
+ }
@@ -0,0 +1,230 @@
1
+ import { GeneratorIndex } from '@aztec/constants';
2
+ import { padArrayEnd } from '@aztec/foundation/collection';
3
+ import { poseidon2HashWithSeparator } from '@aztec/foundation/crypto';
4
+ import { Fr } from '@aztec/foundation/fields';
5
+ import type { Tuple } from '@aztec/foundation/serialize';
6
+ import { FunctionCall, FunctionType } from '@aztec/stdlib/abi';
7
+ import { HashedValues } from '@aztec/stdlib/tx';
8
+
9
+ // These must match the values defined in:
10
+ // - noir-projects/aztec-nr/aztec/src/entrypoint/app.nr
11
+ const APP_MAX_CALLS = 4;
12
+ // - and noir-projects/aztec-nr/aztec/src/entrypoint/fee.nr
13
+ const FEE_MAX_CALLS = 2;
14
+
15
+ /* eslint-disable camelcase */
16
+ /** Encoded function call for an Aztec entrypoint */
17
+ export type EncodedFunctionCall = {
18
+ /** Arguments hash for the call. */
19
+ /** This should be the calldata hash `hash([function_selector, ...args])` if `is_public` is true. */
20
+ args_hash: Fr;
21
+ /** Selector of the function to call */
22
+ function_selector: Fr;
23
+ /** Address of the contract to call */
24
+ target_address: Fr;
25
+ /** Whether the function is public or private */
26
+ is_public: boolean;
27
+ /** Whether the function can alter state */
28
+ is_static: boolean;
29
+ };
30
+ /* eslint-enable camelcase */
31
+
32
+ /** Type that represents function calls ready to be sent to a circuit for execution */
33
+ export type EncodedCalls = {
34
+ /** Function calls in the expected format (Noir's convention) */
35
+ encodedFunctionCalls: EncodedFunctionCall[];
36
+ /** The hashed args for the call, ready to be injected in the execution cache */
37
+ hashedArguments: HashedValues[];
38
+ };
39
+
40
+ /**
41
+ * Entrypoints derive their arguments from the calls that they'll ultimate make.
42
+ * This utility class helps in creating the payload for the entrypoint by taking into
43
+ * account how the calls are encoded and hashed.
44
+ * */
45
+ export abstract class EncodedCallsForEntrypoint implements EncodedCalls {
46
+ constructor(
47
+ /** Function calls in the expected format (Noir's convention) */
48
+ public encodedFunctionCalls: EncodedFunctionCall[],
49
+ /** The hashed args for the call, ready to be injected in the execution cache */
50
+ public hashedArguments: HashedValues[],
51
+ /** The index of the generator to use for hashing */
52
+ public generatorIndex: number,
53
+ /** The nonce for the payload, used to emit a nullifier identifying the call */
54
+ public nonce: Fr,
55
+ ) {}
56
+
57
+ /* eslint-disable camelcase */
58
+ /**
59
+ * The function calls to execute. This uses snake_case naming so that it is compatible with Noir encoding
60
+ * @internal
61
+ */
62
+ get function_calls() {
63
+ return this.encodedFunctionCalls;
64
+ }
65
+ /* eslint-enable camelcase */
66
+
67
+ /**
68
+ * Serializes the payload to an array of fields
69
+ * @returns The fields of the payload
70
+ */
71
+ abstract toFields(): Fr[];
72
+
73
+ /**
74
+ * Hashes the payload
75
+ * @returns The hash of the payload
76
+ */
77
+ hash() {
78
+ return poseidon2HashWithSeparator(this.toFields(), this.generatorIndex);
79
+ }
80
+
81
+ /** Serializes the function calls to an array of fields. */
82
+ protected functionCallsToFields() {
83
+ return this.encodedFunctionCalls.flatMap(call => [
84
+ call.args_hash,
85
+ call.function_selector,
86
+ call.target_address,
87
+ new Fr(call.is_public),
88
+ new Fr(call.is_static),
89
+ ]);
90
+ }
91
+
92
+ /**
93
+ * Encodes a set of function calls for a dapp entrypoint
94
+ * @param functionCalls - The function calls to execute
95
+ * @returns The encoded calls
96
+ */
97
+ static async fromFunctionCalls(functionCalls: FunctionCall[]) {
98
+ const encoded = await encode(functionCalls);
99
+ return new EncodedAppEntrypointCalls(encoded.encodedFunctionCalls, encoded.hashedArguments, 0, Fr.random());
100
+ }
101
+
102
+ /**
103
+ * Encodes the functions for the app-portion of a transaction from a set of function calls and a nonce
104
+ * @param functionCalls - The function calls to execute
105
+ * @param nonce - The nonce for the payload, used to emit a nullifier identifying the call
106
+ * @returns The encoded calls
107
+ */
108
+ static async fromAppExecution(
109
+ functionCalls: FunctionCall[] | Tuple<FunctionCall, typeof APP_MAX_CALLS>,
110
+ nonce = Fr.random(),
111
+ ) {
112
+ if (functionCalls.length > APP_MAX_CALLS) {
113
+ throw new Error(`Expected at most ${APP_MAX_CALLS} function calls, got ${functionCalls.length}`);
114
+ }
115
+ const paddedCalls = padArrayEnd(functionCalls, FunctionCall.empty(), APP_MAX_CALLS);
116
+ const encoded = await encode(paddedCalls);
117
+ return new EncodedAppEntrypointCalls(
118
+ encoded.encodedFunctionCalls,
119
+ encoded.hashedArguments,
120
+ GeneratorIndex.SIGNATURE_PAYLOAD,
121
+ nonce,
122
+ );
123
+ }
124
+
125
+ /**
126
+ * Creates an encoded set of functions to pay the fee for a transaction
127
+ * @param functionCalls - The calls generated by the payment method
128
+ * @param isFeePayer - Whether the sender should be appointed as fee payer
129
+ * @returns The encoded calls
130
+ */
131
+ static async fromFeeCalls(
132
+ functionCalls: FunctionCall[] | Tuple<FunctionCall, typeof FEE_MAX_CALLS>,
133
+ isFeePayer: boolean,
134
+ ) {
135
+ const paddedCalls = padArrayEnd(functionCalls, FunctionCall.empty(), FEE_MAX_CALLS);
136
+ const encoded = await encode(paddedCalls);
137
+ return new EncodedFeeEntrypointCalls(
138
+ encoded.encodedFunctionCalls,
139
+ encoded.hashedArguments,
140
+ GeneratorIndex.FEE_PAYLOAD,
141
+ Fr.random(),
142
+ isFeePayer,
143
+ );
144
+ }
145
+ }
146
+
147
+ /** Encoded calls for app phase execution. */
148
+ export class EncodedAppEntrypointCalls extends EncodedCallsForEntrypoint {
149
+ constructor(
150
+ encodedFunctionCalls: EncodedFunctionCall[],
151
+ hashedArguments: HashedValues[],
152
+ generatorIndex: number,
153
+ nonce: Fr,
154
+ ) {
155
+ super(encodedFunctionCalls, hashedArguments, generatorIndex, nonce);
156
+ }
157
+
158
+ override toFields(): Fr[] {
159
+ return [...this.functionCallsToFields(), this.nonce];
160
+ }
161
+ }
162
+
163
+ /** Encoded calls for fee payment */
164
+ export class EncodedFeeEntrypointCalls extends EncodedCallsForEntrypoint {
165
+ #isFeePayer: boolean;
166
+
167
+ constructor(
168
+ encodedFunctionCalls: EncodedFunctionCall[],
169
+ hashedArguments: HashedValues[],
170
+ generatorIndex: number,
171
+ nonce: Fr,
172
+ isFeePayer: boolean,
173
+ ) {
174
+ super(encodedFunctionCalls, hashedArguments, generatorIndex, nonce);
175
+ this.#isFeePayer = isFeePayer;
176
+ }
177
+
178
+ override toFields(): Fr[] {
179
+ return [...this.functionCallsToFields(), this.nonce, new Fr(this.#isFeePayer)];
180
+ }
181
+
182
+ /* eslint-disable camelcase */
183
+ /** Whether the sender should be appointed as fee payer. */
184
+ get is_fee_payer() {
185
+ return this.#isFeePayer;
186
+ }
187
+ /* eslint-enable camelcase */
188
+ }
189
+
190
+ /**
191
+ * Computes a hash of a combined set of app and fee calls.
192
+ * @param appCalls - A set of app calls.
193
+ * @param feeCalls - A set of calls used to pay fees.
194
+ * @returns A hash of a combined call set.
195
+ */
196
+ export async function computeCombinedPayloadHash(
197
+ appPayload: EncodedAppEntrypointCalls,
198
+ feePayload: EncodedFeeEntrypointCalls,
199
+ ): Promise<Fr> {
200
+ return poseidon2HashWithSeparator(
201
+ [await appPayload.hash(), await feePayload.hash()],
202
+ GeneratorIndex.COMBINED_PAYLOAD,
203
+ );
204
+ }
205
+
206
+ /** Encodes FunctionCalls for execution, following Noir's convention */
207
+ export async function encode(calls: FunctionCall[]): Promise<EncodedCalls> {
208
+ const hashedArguments: HashedValues[] = [];
209
+ for (const call of calls) {
210
+ const hashed =
211
+ call.type === FunctionType.PUBLIC
212
+ ? await HashedValues.fromCalldata([call.selector.toField(), ...call.args])
213
+ : await HashedValues.fromArgs(call.args);
214
+ hashedArguments.push(hashed);
215
+ }
216
+
217
+ /* eslint-disable camelcase */
218
+ const encodedFunctionCalls: EncodedFunctionCall[] = calls.map((call, index) => ({
219
+ args_hash: hashedArguments[index].hash,
220
+ function_selector: call.selector.toField(),
221
+ target_address: call.to.toField(),
222
+ is_public: call.type == FunctionType.PUBLIC,
223
+ is_static: call.isStatic,
224
+ }));
225
+
226
+ return {
227
+ encodedFunctionCalls,
228
+ hashedArguments,
229
+ };
230
+ }
package/src/index.ts CHANGED
@@ -8,3 +8,7 @@
8
8
 
9
9
  export * from './account_entrypoint.js';
10
10
  export * from './dapp_entrypoint.js';
11
+ export * from './interfaces.js';
12
+ export * from './default_entrypoint.js';
13
+ export * from './encoding.js';
14
+ export * from './default_multi_call_entrypoint.js';
@@ -0,0 +1,93 @@
1
+ import type { Fr } from '@aztec/foundation/fields';
2
+ import type { FieldsOf } from '@aztec/foundation/types';
3
+ import type { AuthWitness } from '@aztec/stdlib/auth-witness';
4
+ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
5
+ import type { GasSettings } from '@aztec/stdlib/gas';
6
+ import type { TxExecutionRequest } from '@aztec/stdlib/tx';
7
+
8
+ import type { ExecutionPayload } from './payload.js';
9
+
10
+ /**
11
+ * General options for the tx execution.
12
+ */
13
+ export type TxExecutionOptions = {
14
+ /** Whether the transaction can be cancelled. */
15
+ cancellable?: boolean;
16
+ /** The nonce to use for the transaction. */
17
+ nonce?: Fr;
18
+ };
19
+
20
+ /**
21
+ * Creates transaction execution requests out of a set of function calls, a fee payment method and
22
+ * general options for the transaction
23
+ */
24
+ export interface EntrypointInterface {
25
+ /**
26
+ * Generates an execution request out of set of function calls.
27
+ * @param exec - The execution intents to be run.
28
+ * @param fee - The fee options for the transaction.
29
+ * @param options - Nonce and whether the transaction is cancellable.
30
+ * @returns The authenticated transaction execution request.
31
+ */
32
+ createTxExecutionRequest(
33
+ exec: ExecutionPayload,
34
+ fee: FeeOptions,
35
+ options: TxExecutionOptions,
36
+ ): Promise<TxExecutionRequest>;
37
+ }
38
+
39
+ /** Creates authorization witnesses. */
40
+ export interface AuthWitnessProvider {
41
+ /**
42
+ * Computes an authentication witness from either a message hash
43
+ * @param messageHash - The message hash to approve
44
+ * @returns The authentication witness
45
+ */
46
+ createAuthWit(messageHash: Fr | Buffer): Promise<AuthWitness>;
47
+ }
48
+
49
+ /**
50
+ * Holds information about how the fee for a transaction is to be paid.
51
+ */
52
+ export interface FeePaymentMethod {
53
+ /** The asset used to pay the fee. */
54
+ getAsset(): Promise<AztecAddress>;
55
+ /**
56
+ * Returns the data to be added to the final execution request
57
+ * to pay the fee in the given asset
58
+ * @param gasSettings - The gas limits and max fees.
59
+ * @returns The function calls to pay the fee.
60
+ */
61
+ getExecutionPayload(gasSettings: GasSettings): Promise<ExecutionPayload>;
62
+ /**
63
+ * The expected fee payer for this tx.
64
+ * @param gasSettings - The gas limits and max fees.
65
+ */
66
+ getFeePayer(gasSettings: GasSettings): Promise<AztecAddress>;
67
+ }
68
+
69
+ /**
70
+ * Fee payment options for a transaction.
71
+ */
72
+ export type FeeOptions = {
73
+ /** The fee payment method to use */
74
+ paymentMethod: FeePaymentMethod;
75
+ /** The gas settings */
76
+ gasSettings: GasSettings;
77
+ };
78
+
79
+ // docs:start:user_fee_options
80
+ /** Fee options as set by a user. */
81
+ export type UserFeeOptions = {
82
+ /** The fee payment method to use */
83
+ paymentMethod?: FeePaymentMethod;
84
+ /** The gas settings */
85
+ gasSettings?: Partial<FieldsOf<GasSettings>>;
86
+ /** Percentage to pad the base fee by, if empty, defaults to 0.5 */
87
+ baseFeePadding?: number;
88
+ /** Whether to run an initial simulation of the tx with high gas limit to figure out actual gas settings. */
89
+ estimateGas?: boolean;
90
+ /** Percentage to pad the estimated gas limits by, if empty, defaults to 0.1. Only relevant if estimateGas is set. */
91
+ estimatedGasPadding?: number;
92
+ };
93
+ // docs:end:user_fee_options
package/src/payload.ts ADDED
@@ -0,0 +1,35 @@
1
+ import { FunctionCall } from '@aztec/stdlib/abi';
2
+ import type { AuthWitness } from '@aztec/stdlib/auth-witness';
3
+ import { Capsule, HashedValues } from '@aztec/stdlib/tx';
4
+
5
+ /**
6
+ * Represents data necessary to perform an action in the network successfully.
7
+ * This class can be considered Aztec's "minimal execution unit".
8
+ * */
9
+ export class ExecutionPayload {
10
+ public constructor(
11
+ /** The function calls to be executed. */
12
+ public calls: FunctionCall[],
13
+ /** Any transient auth witnesses needed for this execution */
14
+ public authWitnesses: AuthWitness[],
15
+ /** Data passed through an oracle for this execution. */
16
+ public capsules: Capsule[],
17
+ /** Extra hashed values to be injected in the execution cache */
18
+ public extraHashedArgs: HashedValues[] = [],
19
+ ) {}
20
+
21
+ static empty() {
22
+ return new ExecutionPayload([], [], []);
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Merges an array ExecutionPayloads combining their calls, authWitnesses, capsules and extraArgHashes.
28
+ */
29
+ export function mergeExecutionPayloads(requests: ExecutionPayload[]): ExecutionPayload {
30
+ const calls = requests.map(r => r.calls).flat();
31
+ const combinedAuthWitnesses = requests.map(r => r.authWitnesses ?? []).flat();
32
+ const combinedCapsules = requests.map(r => r.capsules ?? []).flat();
33
+ const combinedextraHashedArgs = requests.map(r => r.extraHashedArgs ?? []).flat();
34
+ return new ExecutionPayload(calls, combinedAuthWitnesses, combinedCapsules, combinedextraHashedArgs);
35
+ }