Skip to content

Commit

Permalink
feat: add batched signerless contract calls
Browse files Browse the repository at this point in the history
  • Loading branch information
alexghr committed Mar 19, 2024
1 parent 6f3eebf commit 9524f75
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 7 deletions.
1 change: 1 addition & 0 deletions noir-projects/noir-contracts/Nargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ members = [
"contracts/token_bridge_contract",
"contracts/uniswap_contract",
"contracts/reader_contract",
"contracts/signerless_entrypoint_contract",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "signerless_entrypoint_contract"
authors = [""]
compiler_version = ">=0.18.0"
type = "contract"

[dependencies]
aztec = { path = "../../../aztec-nr/aztec" }
authwit = { path = "../../../aztec-nr/authwit" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// An entrypoint contract that allows everything to go through. Only used for testing
// Pair this with SignerlessWallet to perform multiple actions before any account contracts are deployed (and without authentication)
contract SignerlessEntrypoint {
use dep::std;

use dep::aztec::prelude::AztecAddress;
use dep::authwit::entrypoint::app::AppPayload;

#[aztec(private)]
fn entrypoint(app_payload: pub AppPayload) {
app_payload.execute_calls(&mut context);
}
}
89 changes: 89 additions & 0 deletions yarn-project/aztec.js/src/entrypoint/signerless_entrypoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { FunctionCall, PackedArguments, TxExecutionRequest } from '@aztec/circuit-types';
import { AztecAddress, FunctionData, TxContext } from '@aztec/circuits.js';
import { FunctionAbi, encodeArguments } from '@aztec/foundation/abi';

import { EntrypointInterface } from '../account/interface.js';
import { DEFAULT_CHAIN_ID, DEFAULT_VERSION } from './constants.js';
import { buildAppPayload } from './entrypoint_payload.js';

/**
* Interface to a contract that forwards functions calls directly, without any authentication or signing.
*/
export class SignerlessEntrypoint implements EntrypointInterface {
constructor(
private address: AztecAddress,
private chainId: number = DEFAULT_CHAIN_ID,
private version: number = DEFAULT_VERSION,
) {}

createTxExecutionRequest(executions: FunctionCall[]): Promise<TxExecutionRequest> {
const { payload: appPayload, packedArguments: appPackedArguments } = buildAppPayload(executions);

const abi = this.getEntrypointAbi();
const entrypointPackedArgs = PackedArguments.fromArgs(encodeArguments(abi, [appPayload]));
const txRequest = TxExecutionRequest.from({
argsHash: entrypointPackedArgs.hash,
origin: this.address,
functionData: FunctionData.fromAbi(abi),
txContext: TxContext.empty(this.chainId, this.version),
packedArguments: [...appPackedArguments, entrypointPackedArgs],
authWitnesses: [],
});

return Promise.resolve(txRequest);
}

private getEntrypointAbi() {
return {
name: 'entrypoint',
isInitializer: false,
functionType: 'secret',
isInternal: false,
parameters: [
{
name: 'app_payload',
type: {
kind: 'struct',
path: 'authwit::entrypoint::app::AppPayload',
fields: [
{
name: 'function_calls',
type: {
kind: 'array',
length: 4,
type: {
kind: 'struct',
path: 'authwit::entrypoint::function_call::FunctionCall',
fields: [
{ name: 'args_hash', type: { kind: 'field' } },
{
name: 'function_selector',
type: {
kind: 'struct',
path: 'authwit::aztec::protocol_types::abis::function_selector::FunctionSelector',
fields: [{ name: 'inner', type: { kind: 'integer', sign: 'unsigned', width: 32 } }],
},
},
{
name: 'target_address',
type: {
kind: 'struct',
path: 'authwit::aztec::protocol_types::address::AztecAddress',
fields: [{ name: 'inner', type: { kind: 'field' } }],
},
},
{ name: 'is_public', type: { kind: 'boolean' } },
],
},
},
},
{ name: 'nonce', type: { kind: 'field' } },
],
},
visibility: 'public',
},
],
returnTypes: [],
} as FunctionAbi;
}
}
35 changes: 32 additions & 3 deletions yarn-project/aztec.js/src/wallet/signerless_wallet.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
import { AuthWitness, FunctionCall, PackedArguments, TxExecutionRequest } from '@aztec/circuit-types';
import { CompleteAddress, Fr, TxContext } from '@aztec/circuits.js';
import { AuthWitness, FunctionCall, PXE, PackedArguments, TxExecutionRequest } from '@aztec/circuit-types';
import { AztecAddress, CompleteAddress, Fr, TxContext } from '@aztec/circuits.js';

import { SignerlessEntrypoint } from '../entrypoint/signerless_entrypoint.js';
import { BaseWallet } from './base_wallet.js';

/**
* Wallet implementation which creates a transaction request directly to the requested contract without any signing.
* Optionally supports batched calls, if a signerless account contract is provided.
*/
export class SignerlessWallet extends BaseWallet {
async createTxExecutionRequest(executions: FunctionCall[]): Promise<TxExecutionRequest> {
/**
* Create a new SignerlessWallet instance.
* @param pxe - The PXE service to use for creating transaction requests.
* @param signerlessEntrypointContract - The address of the signerless contract to use for batched calls. Optional. If not provided only single calls are supported.
*/
constructor(pxe: PXE, private signerlessEntrypointContract: AztecAddress = AztecAddress.ZERO) {
super(pxe);
}

createTxExecutionRequest(executions: FunctionCall[]): Promise<TxExecutionRequest> {
if (this.signerlessEntrypointContract.isZero()) {
return this.#createSingleTxExecutionRequest(executions);
} else {
return this.#createBatchTxExecutionRequest(executions);
}
}

async #createSingleTxExecutionRequest(executions: FunctionCall[]): Promise<TxExecutionRequest> {
if (executions.length !== 1) {
throw new Error(`Unexpected number of executions. Expected 1 but received ${executions.length}.`);
}
Expand All @@ -27,6 +46,16 @@ export class SignerlessWallet extends BaseWallet {
);
}

async #createBatchTxExecutionRequest(executions: FunctionCall[]): Promise<TxExecutionRequest> {
if (this.signerlessEntrypointContract.isZero()) {
throw new Error('No address to signerless account contract provided');
}

const { chainId, protocolVersion } = await this.pxe.getNodeInfo();
const entrypoint = new SignerlessEntrypoint(this.signerlessEntrypointContract, chainId, protocolVersion);
return entrypoint.createTxExecutionRequest(executions);
}

getCompleteAddress(): CompleteAddress {
throw new Error('Method not implemented.');
}
Expand Down
25 changes: 21 additions & 4 deletions yarn-project/aztec/src/sandbox.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
#!/usr/bin/env -S node --no-warnings
import { AztecNodeConfig, AztecNodeService, getConfigEnvVars } from '@aztec/aztec-node';
import { AztecAddress, SignerlessWallet, Wallet } from '@aztec/aztec.js';
import {
AztecAddress,
BatchCall,
Fr,
SignerlessWallet,
Wallet,
getContractInstanceFromDeployParams,
} from '@aztec/aztec.js';
import { deployInstance, registerContractClass } from '@aztec/aztec.js/deployment';
import { AztecNode } from '@aztec/circuit-types';
import {
Expand Down Expand Up @@ -29,6 +36,7 @@ import {
RollupAbi,
RollupBytecode,
} from '@aztec/l1-artifacts';
import { SignerlessEntrypointContractArtifact } from '@aztec/noir-contracts.js';
import { getCanonicalGasToken } from '@aztec/protocol-contracts/gas-token';
import { PXEServiceConfig, createPXEService, getPXEServiceConfig } from '@aztec/pxe';

Expand Down Expand Up @@ -165,8 +173,12 @@ async function deployCanonicalL2GasToken(deployer: Wallet, l1ContractAddresses:
return;
}

await (await registerContractClass(deployer, canonicalGasToken.artifact)).send().wait();
await deployInstance(deployer, canonicalGasToken.instance).send().wait();
const batch = new BatchCall(deployer, [
(await registerContractClass(deployer, canonicalGasToken.artifact)).request(),
deployInstance(deployer, canonicalGasToken.instance).request(),
]);

await batch.send().wait();

logger(`Deployed Gas Token on L2 at ${canonicalGasToken.address}`);
}
Expand Down Expand Up @@ -200,7 +212,12 @@ export async function createSandbox(config: Partial<SandboxConfig> = {}) {
const pxe = await createAztecPXE(node);

if (config.enableGas) {
const deployer = new SignerlessWallet(pxe);
const instance = getContractInstanceFromDeployParams(SignerlessEntrypointContractArtifact, {
salt: Fr.ZERO,
});
await pxe.registerContract({ instance });

const deployer = new SignerlessWallet(pxe, instance.address);
await deployCanonicalL2GasToken(deployer, aztecNodeConfig.l1Contracts);
}

Expand Down

0 comments on commit 9524f75

Please sign in to comment.