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 05dbf6c
Show file tree
Hide file tree
Showing 11 changed files with 186 additions and 24 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);
}
}
95 changes: 77 additions & 18 deletions yarn-project/aztec.js/src/wallet/signerless_wallet.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,35 @@
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 { CompleteAddress, Fr, FunctionData, TxContext } from '@aztec/circuits.js';
import { FunctionAbi, encodeArguments } from '@aztec/foundation/abi';
import { getCanonicalSignerlessEntrypointAddress } from '@aztec/protocol-contracts/signerless-entrypoint';

import { buildAppPayload } from '../entrypoint/entrypoint_payload.js';
import { BaseWallet } from './base_wallet.js';

/**
* Wallet implementation which creates a transaction request directly to the requested contract without any signing.
*/
export class SignerlessWallet extends BaseWallet {
constructor(pxe: PXE) {
super(pxe);
}

async createTxExecutionRequest(executions: FunctionCall[]): Promise<TxExecutionRequest> {
if (executions.length !== 1) {
throw new Error(`Unexpected number of executions. Expected 1 but received ${executions.length}.`);
}
const [execution] = executions;
const packedArguments = PackedArguments.fromArgs(execution.args);
const { chainId, protocolVersion } = await this.pxe.getNodeInfo();
const txContext = TxContext.empty(chainId, protocolVersion);
return Promise.resolve(
new TxExecutionRequest(
execution.to,
execution.functionData,
packedArguments.hash,
txContext,
[packedArguments],
[],
),
);
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: getCanonicalSignerlessEntrypointAddress(),
functionData: FunctionData.fromAbi(abi),
txContext: TxContext.empty(chainId, protocolVersion),
packedArguments: [...appPackedArguments, entrypointPackedArgs],
authWitnesses: [],
});

return txRequest;
}

getCompleteAddress(): CompleteAddress {
Expand All @@ -34,4 +39,58 @@ export class SignerlessWallet extends BaseWallet {
createAuthWit(_message: Fr): Promise<AuthWitness> {
throw new Error('Method not implemented.');
}

#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;
}
}
13 changes: 8 additions & 5 deletions yarn-project/aztec/src/sandbox.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/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, SignerlessWallet, Wallet } from '@aztec/aztec.js';
import { deployInstance, registerContractClass } from '@aztec/aztec.js/deployment';
import { AztecNode } from '@aztec/circuit-types';
import {
Expand Down Expand Up @@ -165,8 +165,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,8 +204,7 @@ export async function createSandbox(config: Partial<SandboxConfig> = {}) {
const pxe = await createAztecPXE(node);

if (config.enableGas) {
const deployer = new SignerlessWallet(pxe);
await deployCanonicalL2GasToken(deployer, aztecNodeConfig.l1Contracts);
await deployCanonicalL2GasToken(new SignerlessWallet(pxe), aztecNodeConfig.l1Contracts);
}

const stop = async () => {
Expand Down
3 changes: 2 additions & 1 deletion yarn-project/protocol-contracts/scripts/copy-contracts.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ contracts=(
contract_class_registerer_contract-ContractClassRegisterer
contract_instance_deployer_contract-ContractInstanceDeployer
gas_token_contract-GasToken
signerless_entrypoint_contract-SignerlessEntrypoint
)

for contract in "${contracts[@]}"; do
cp "../../noir-projects/noir-contracts/target/$contract.json" ./src/artifacts/${contract#*-}.json
done
done
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`GasToken returns canonical protocol contract 1`] = `
{
"address": AztecAddress<0x27970e5b54a9263933858c6450179726ee51346a6cb61893a6ca667e0d6906f3>,
"instance": {
"address": AztecAddress<0x27970e5b54a9263933858c6450179726ee51346a6cb61893a6ca667e0d6906f3>,
"contractClassId": Fr<0x03ebe4598137a75cba2503a8d8286ee8500473a821877b1f4a1950590d00364c>,
"deployer": AztecAddress<0x0000000000000000000000000000000000000000000000000000000000000000>,
"initializationHash": Fr<0x0000000000000000000000000000000000000000000000000000000000000000>,
"portalContractAddress": EthAddress<0x0000000000000000000000000000000000000000>,
"publicKeysHash": Fr<0x27b1d0839a5b23baf12a8d195b18ac288fcf401afb2f70b8a4b529ede5fa9fed>,
"salt": Fr<0x0000000000000000000000000000000000000000000000000000000000000001>,
"version": 1,
},
}
`;

exports[`GasToken returns canonical protocol contract 2`] = `
{
"artifactHash": Fr<0x0a52d996440db1be04a93b2bc3d827c729e76abf6703374008e1d5163cd2b934>,
"id": Fr<0x03ebe4598137a75cba2503a8d8286ee8500473a821877b1f4a1950590d00364c>,
"privateFunctions": [
{
"isInternal": false,
"selector": Selector<0x37889ba1>,
"vkHash": Fr<0x0000000000000000000000000000000000000000000000000000000000000000>,
},
],
"privateFunctionsRoot": Fr<0x2b9863e7fc2300212371e9b566c072dc000aaa9f5025eb761d6af73ce6f8b7a4>,
"publicBytecodeCommitment": Fr<0x1dae27cc7fe2af345f160253be3875d449a7e9ba6bd68747d87d4e7054b81115>,
"version": 1,
}
`;

exports[`GasToken returns canonical protocol contract 3`] = `[]`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { loadContractArtifact } from '@aztec/types/abi';
import { NoirCompiledContract } from '@aztec/types/noir';

import SignerlessEntrypoint from '../artifacts/SignerlessEntrypoint.json' assert { type: 'json' };

export const SignerlessEntrypointArtifact = loadContractArtifact(SignerlessEntrypoint as NoirCompiledContract);
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { setupCustomSnapshotSerializers } from '@aztec/foundation/testing';

import omit from 'lodash.omit';

import { getCanonicalSignerlessEntrypointContract } from './index.js';

describe('GasToken', () => {
setupCustomSnapshotSerializers(expect);
it('returns canonical protocol contract', () => {
const contract = getCanonicalSignerlessEntrypointContract();
expect(omit(contract, ['artifact', 'contractClass'])).toMatchSnapshot();

// bytecode is very large
expect(omit(contract.contractClass, ['packedBytecode', 'publicFunctions'])).toMatchSnapshot();

// this contract has public bytecode
expect(contract.contractClass.publicFunctions.map(x => omit(x, 'bytecode'))).toMatchSnapshot();
expect(contract.contractClass.packedBytecode.length).toBeGreaterThan(0);
});
});
12 changes: 12 additions & 0 deletions yarn-project/protocol-contracts/src/signerless-entrypoint/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { AztecAddress, EthAddress, Point } from '@aztec/circuits.js';

import { ProtocolContract, getCanonicalProtocolContract } from '../protocol_contract.js';
import { SignerlessEntrypointArtifact } from './artifact.js';

export function getCanonicalSignerlessEntrypointContract(): ProtocolContract {
return getCanonicalProtocolContract(SignerlessEntrypointArtifact, 1, [], Point.ZERO, EthAddress.ZERO);
}

export function getCanonicalSignerlessEntrypointAddress(): AztecAddress {
return getCanonicalSignerlessEntrypointContract().address;
}
2 changes: 2 additions & 0 deletions yarn-project/pxe/src/pxe_service/create_pxe_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { initStoreForRollup } from '@aztec/kv-store/utils';
import { getCanonicalClassRegisterer } from '@aztec/protocol-contracts/class-registerer';
import { getCanonicalGasToken } from '@aztec/protocol-contracts/gas-token';
import { getCanonicalInstanceDeployer } from '@aztec/protocol-contracts/instance-deployer';
import { getCanonicalSignerlessEntrypointContract } from '@aztec/protocol-contracts/signerless-entrypoint';

import { join } from 'path';

Expand Down Expand Up @@ -46,6 +47,7 @@ export async function createPXEService(
for (const contract of [
getCanonicalClassRegisterer(),
getCanonicalInstanceDeployer(),
getCanonicalSignerlessEntrypointContract(),
getCanonicalGasToken(l1Contracts.gasPortalAddress),
]) {
await server.registerContract(contract);
Expand Down

0 comments on commit 05dbf6c

Please sign in to comment.