diff --git a/README.md b/README.md index 4b17b0aa5981..9dcda3e8e498 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ The Aztec 3 system consists of the following sub projects. - `acir-simulator` - `archiver` - `aztec-cli` +- `aztec-rpc` - `aztec.js` - `ethereum.js` - `kernel-simulator` @@ -13,4 +14,3 @@ The Aztec 3 system consists of the following sub projects. - `prover-client` - `public-client` - `sequencer-client` -- `wallet` diff --git a/bootstrap.sh b/bootstrap.sh index e127219de253..5c0706b494a8 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -45,6 +45,7 @@ PROJECTS=( # "circuits:./bootstrap.sh db_cli rollup_cli" # "yarn-project/acir-simulator:yarn build" # "yarn-project/aztec-cli:yarn build" + "yarn-project/aztec-rpc:yarn build" "yarn-project/aztec.js:yarn build" "yarn-project/archiver:yarn build" # "yarn-project/ethereum.js:yarn build" @@ -54,7 +55,6 @@ PROJECTS=( # "yarn-project/prover-client:yarn build" # "yarn-project/public-client:yarn build" # "yarn-project/sequencer-client:yarn build" - # "yarn-project/wallet:yarn build" ) for E in "${PROJECTS[@]}"; do diff --git a/build_manifest.json b/build_manifest.json index 019b9da2db25..bb31ff6ca13c 100644 --- a/build_manifest.json +++ b/build_manifest.json @@ -59,6 +59,13 @@ "rebuildPatterns": ["^yarn-project/aztec-cli/"], "dependencies": ["yarn-project-base"] }, + "aztec-rpc": { + "buildDir": "yarn-project", + "projectDir": "yarn-project/aztec-rpc", + "dockerfile": "aztec-rpc/Dockerfile", + "rebuildPatterns": ["^yarn-project/aztec-rpc/"], + "dependencies": ["yarn-project-base"] + }, "aztec.js": { "buildDir": "yarn-project", "projectDir": "yarn-project/aztec.js", @@ -128,12 +135,5 @@ "dockerfile": "sequencer-client/Dockerfile", "rebuildPatterns": ["^yarn-project/sequencer-client/"], "dependencies": ["yarn-project-base"] - }, - "wallet": { - "buildDir": "yarn-project", - "projectDir": "yarn-project/wallet", - "dockerfile": "wallet/Dockerfile", - "rebuildPatterns": ["^yarn-project/wallet/"], - "dependencies": ["yarn-project-base"] } } diff --git a/build_manifest.sh b/build_manifest.sh index a376b9a2a47e..1ed6ed6d72cf 100755 --- a/build_manifest.sh +++ b/build_manifest.sh @@ -14,6 +14,7 @@ PROJECTS=( # acir-simulator:yarn-project # archiver:yarn-project # aztec-cli:yarn-project + # aztec-rpc:yarn-project # aztec.js:yarn-project # end-to-end:yarn-project # ethereum.js:yarn-project @@ -23,5 +24,4 @@ PROJECTS=( # prover-client:yarn-project # public-client:yarn-project # sequencer-client:yarn-project - # wallet:yarn-project ) diff --git a/yarn-project/aztec-rpc/.eslintrc.cjs b/yarn-project/aztec-rpc/.eslintrc.cjs new file mode 100644 index 000000000000..333ff38eab81 --- /dev/null +++ b/yarn-project/aztec-rpc/.eslintrc.cjs @@ -0,0 +1,21 @@ +require('@rushstack/eslint-patch/modern-module-resolution'); + +module.exports = { + extends: ['@aztec/eslint-config'], + parserOptions: { tsconfigRootDir: __dirname }, + rules: { + 'tsdoc/syntax': 'off', + 'jsdoc/require-jsdoc': 'off', + 'jsdoc/require-description': 'off', + 'jsdoc/require-description-complete-sentence': 'off', + 'jsdoc/require-hyphen-before-param-description': 'off', + 'jsdoc/require-param': 'off', + 'jsdoc/require-param-description': 'off', + 'jsdoc/require-param-name': 'off', + 'jsdoc/require-property': 'off', + 'jsdoc/require-property-description': 'off', + 'jsdoc/require-property-name': 'off', + 'jsdoc/require-returns': 'off', + 'jsdoc/require-returns-description': 'off', + }, +}; diff --git a/yarn-project/wallet/Dockerfile b/yarn-project/aztec-rpc/Dockerfile similarity index 60% rename from yarn-project/wallet/Dockerfile rename to yarn-project/aztec-rpc/Dockerfile index 11131ce67564..12c4ff17ca80 100644 --- a/yarn-project/wallet/Dockerfile +++ b/yarn-project/aztec-rpc/Dockerfile @@ -1,7 +1,7 @@ FROM 278380418400.dkr.ecr.eu-west-2.amazonaws.com/yarn-project-base AS builder -COPY wallet wallet -WORKDIR /usr/src/yarn-project/wallet +COPY aztec-rpc aztec-rpc +WORKDIR /usr/src/yarn-project/aztec-rpc RUN yarn build && yarn formatting && yarn test # Prune dev dependencies. See comment in base image. @@ -9,6 +9,6 @@ RUN yarn cache clean RUN yarn workspaces focus --production > /dev/null FROM node:18-alpine -COPY --from=builder /usr/src/yarn-project/wallet /usr/src/yarn-project/wallet -WORKDIR /usr/src/yarn-project/wallet +COPY --from=builder /usr/src/yarn-project/aztec-rpc /usr/src/yarn-project/aztec-rpc +WORKDIR /usr/src/yarn-project/aztec-rpc ENTRYPOINT ["yarn"] \ No newline at end of file diff --git a/yarn-project/aztec-rpc/README.md b/yarn-project/aztec-rpc/README.md new file mode 100644 index 000000000000..b1e338ba6945 --- /dev/null +++ b/yarn-project/aztec-rpc/README.md @@ -0,0 +1 @@ +# Aztec RPC Server & Client diff --git a/yarn-project/wallet/package.json b/yarn-project/aztec-rpc/package.json similarity index 97% rename from yarn-project/wallet/package.json rename to yarn-project/aztec-rpc/package.json index 0c99e9b856a8..692df8a5d5cb 100644 --- a/yarn-project/wallet/package.json +++ b/yarn-project/aztec-rpc/package.json @@ -1,5 +1,5 @@ { - "name": "@aztec/wallet", + "name": "@aztec/aztec-rpc", "version": "0.0.0", "type": "module", "exports": "./dest/index.js", diff --git a/yarn-project/aztec-rpc/src/abi_coder/index.ts b/yarn-project/aztec-rpc/src/abi_coder/index.ts new file mode 100644 index 000000000000..0a37a288f75a --- /dev/null +++ b/yarn-project/aztec-rpc/src/abi_coder/index.ts @@ -0,0 +1,11 @@ +import { keccak256 } from '../foundation.js'; +import { ABIParameter } from '../noir.js'; + +export function generateFunctionSignature(name: string, parameters: ABIParameter[]) { + return name === 'constructor' ? name : `${name}(${parameters.map(p => p.type.kind).join(',')})`; +} + +export function generateFunctionSelector(name: string, parameters: ABIParameter[]) { + const signature = generateFunctionSignature(name, parameters); + return keccak256(Buffer.from(signature)).slice(0, 4); +} diff --git a/yarn-project/aztec-rpc/src/account_state/account_state.ts b/yarn-project/aztec-rpc/src/account_state/account_state.ts new file mode 100644 index 000000000000..7fbb7994c916 --- /dev/null +++ b/yarn-project/aztec-rpc/src/account_state/account_state.ts @@ -0,0 +1,11 @@ +import { TxHash } from '../aztec_node.js'; +import { AztecAddress } from '../circuits.js'; +import { Database } from '../database/index.js'; + +export class AccountState { + constructor(public readonly publicKey: AztecAddress, private db: Database) {} + + getTx(txHash: TxHash) { + return this.db.getTx(txHash); + } +} diff --git a/yarn-project/aztec-rpc/src/account_state/index.ts b/yarn-project/aztec-rpc/src/account_state/index.ts new file mode 100644 index 000000000000..fa1fec1b824e --- /dev/null +++ b/yarn-project/aztec-rpc/src/account_state/index.ts @@ -0,0 +1 @@ +export * from './account_state.js'; diff --git a/yarn-project/aztec-rpc/src/acir_simulator.ts b/yarn-project/aztec-rpc/src/acir_simulator.ts new file mode 100644 index 000000000000..c5c5c78fa50d --- /dev/null +++ b/yarn-project/aztec-rpc/src/acir_simulator.ts @@ -0,0 +1,9 @@ +// TODO use @aztce/acir-simulator + +import { PreviousKernelData, PrivateCallData, TxRequest } from './circuits.js'; + +export class AcirSimulator { + public simulate(txRequest: TxRequest) { + return Promise.resolve({ kernelData: new PreviousKernelData(), callData: new PrivateCallData() }); + } +} diff --git a/yarn-project/aztec-rpc/src/aztec_node.ts b/yarn-project/aztec-rpc/src/aztec_node.ts new file mode 100644 index 000000000000..760d1745b297 --- /dev/null +++ b/yarn-project/aztec-rpc/src/aztec_node.ts @@ -0,0 +1,29 @@ +import { AccumulatedTxData } from './circuits.js'; + +export class TxHash { + public static SIZE = 32; + + constructor(public readonly buffer: Buffer) {} + + public equals(rhs: TxHash) { + return this.buffer.equals(rhs.buffer); + } +} + +export class Tx { + constructor(public readonly proofData: Buffer, public readonly data: AccumulatedTxData) {} + + get txHash() { + return new TxHash(Buffer.alloc(32)); + } +} + +export class AztecNode { + sendTx(tx: Tx) { + return Promise.resolve(tx.txHash); + } + + getBlocks() { + return Promise.resolve([]); + } +} diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_client/aztec_rpc_client.ts b/yarn-project/aztec-rpc/src/aztec_rpc_client/aztec_rpc_client.ts new file mode 100644 index 000000000000..cb699a54fd90 --- /dev/null +++ b/yarn-project/aztec-rpc/src/aztec_rpc_client/aztec_rpc_client.ts @@ -0,0 +1,23 @@ +import { Tx, TxHash } from '../aztec_node.js'; +import { AztecAddress, EthAddress, Fr, Signature, TxRequest } from '../circuits.js'; +import { ContractAbi } from '../noir.js'; +import { TxReceipt } from '../tx/index.js'; + +export interface AztecRPCClient { + addAccount(): Promise; + getAccounts(): Promise; + getCode(contract: AztecAddress, functionSelector?: Buffer): Promise; + createDeploymentTxRequest( + abi: ContractAbi, + args: Fr[], + portalContract: EthAddress, + contractAddressSalt: Fr, + from: AztecAddress, + ): Promise; + createTxRequest(functionSelector: Buffer, args: Fr[], to: AztecAddress, from: AztecAddress): Promise; + signTxRequest(txRequest: TxRequest): Promise; + createTx(txRequest: TxRequest, signature: Signature): Promise; + sendTx(tx: Tx): Promise; + // callTx(functionSelector: Buffer, args: Fr[], to: AztecAddress, from: AztecAddress): Promise; + getTxReceipt(txHash: TxHash): Promise; +} diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_client/index.ts b/yarn-project/aztec-rpc/src/aztec_rpc_client/index.ts new file mode 100644 index 000000000000..d70b35ea79b1 --- /dev/null +++ b/yarn-project/aztec-rpc/src/aztec_rpc_client/index.ts @@ -0,0 +1 @@ +export * from './aztec_rpc_client.js'; diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.ts b/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.ts new file mode 100644 index 000000000000..cc0027efdfc4 --- /dev/null +++ b/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.ts @@ -0,0 +1,155 @@ +import { generateFunctionSelector } from '../abi_coder/index.js'; +import { AcirSimulator } from '../acir_simulator.js'; +import { AztecNode, Tx, TxHash } from '../aztec_node.js'; +import { AztecRPCClient } from '../aztec_rpc_client/index.js'; +import { + AztecAddress, + ContractDeploymentData, + EthAddress, + Fr, + generateContractAddress, + KernelPrivateInputs, + Signature, + TxContext, + TxRequest, +} from '../circuits.js'; +import { Database } from '../database/index.js'; +import { KeyStore } from '../key_store/index.js'; +import { ContractAbi } from '../noir.js'; +import { ProofGenerator } from '../proof_generator/index.js'; +import { Synchroniser } from '../synchroniser/index.js'; + +export class AztecRPCServer implements AztecRPCClient { + constructor( + private keyStore: KeyStore, + private synchroniser: Synchroniser, + private simulator: AcirSimulator, + private proofGenerator: ProofGenerator, + private node: AztecNode, + private db: Database, + ) {} + + public addAccount() { + return this.keyStore.addAccount(); + } + + public getAccounts() { + return this.keyStore.getAccounts(); + } + + public getCode(contract: AztecAddress, functionSelector?: Buffer) { + return this.db.getCode(contract, functionSelector || generateFunctionSelector('constructor', [])); + } + + public async createDeploymentTxRequest( + abi: ContractAbi, + args: Fr[], + portalContract: EthAddress, + contractAddressSalt: Fr, + from: AztecAddress, + ) { + const constructorAbi = abi.functions.find(f => f.name === 'constructor'); + if (!constructorAbi) { + throw new Error('Cannot find constructor in the ABI.'); + } + + const functionData = { + functionSelector: generateFunctionSelector(constructorAbi.name, constructorAbi.parameters), + isSecret: true, + isContructor: true, + }; + + const contractDataHash = Fr.ZERO; + const functionTreeRoot = Fr.ZERO; + const constructorHash = Fr.ZERO; + const contractDeploymentData = new ContractDeploymentData( + contractDataHash, + functionTreeRoot, + constructorHash, + contractAddressSalt, + portalContract, + ); + const txContext = new TxContext(false, false, false, contractDeploymentData); + + const contractAddress = generateContractAddress(from, contractAddressSalt, args); + await this.db.addContract(contractAddress, abi, false); + + return new TxRequest( + from, + AztecAddress.ZERO, // to + functionData, + args, + txContext, + Fr.random(), // nonce + Fr.ZERO, // chainId + ); + } + + public async createTxRequest(functionSelector: Buffer, args: Fr[], to: AztecAddress, from: AztecAddress) { + const abi = await this.db.getContract(to); + if (!abi) { + throw new Error('Unknown contract.'); + } + + const functionAbi = abi.functions.find(f => f.selector.equals(functionSelector)); + if (!functionAbi) { + throw new Error('Unknown function.'); + } + + const functionData = { + functionSelector, + isSecret: functionAbi.isSecret, + isContructor: false, + }; + + const txContext = new TxContext(false, false, false, ContractDeploymentData.EMPTY); + + return new TxRequest( + from, + to, + functionData, + args, + txContext, + Fr.random(), // nonce + Fr.ZERO, // chainId + ); + } + + public signTxRequest(txRequest: TxRequest) { + return this.keyStore.signTxRequest(txRequest); + } + + public async createTx(txRequest: TxRequest, signature: Signature) { + const { kernelData, callData } = await this.simulator.simulate(txRequest); + const privateInputs = new KernelPrivateInputs(txRequest, signature, kernelData, callData); + const { proofData, accumulatedTxData } = await this.proofGenerator.createProof(privateInputs); + return new Tx(proofData, accumulatedTxData); + } + + public sendTx(tx: Tx) { + return this.node.sendTx(tx); + } + + public async getTxReceipt(txHash: TxHash) { + const tx = await this.db.getTx(txHash); + if (!tx) { + return; + } + + const account = this.synchroniser.getAccount(tx.from); + if (!account) { + throw new Error('Unauthorised account.'); + } + + return { + txHash: tx.txHash, + blockHash: tx.blockHash, + blockNumber: tx.blockNumber, + from: tx.from, + to: tx.to, + contractAddress: tx.contractAddress, + error: tx.error, + status: !tx.error, + }; + } +} diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_server/create_aztec_rpc_server.ts b/yarn-project/aztec-rpc/src/aztec_rpc_server/create_aztec_rpc_server.ts new file mode 100644 index 000000000000..8c8a1228de84 --- /dev/null +++ b/yarn-project/aztec-rpc/src/aztec_rpc_server/create_aztec_rpc_server.ts @@ -0,0 +1,33 @@ +import { AcirSimulator } from '../acir_simulator.js'; +import { AztecNode } from '../aztec_node.js'; +import { KernelCircuitProver } from '../circuits.js'; +import { MemoryDB } from '../database/index.js'; +import { KeyStore, TestKeyStore } from '../key_store/index.js'; +import { ProofGenerator } from '../proof_generator/index.js'; +import { Synchroniser } from '../synchroniser/index.js'; +import { AztecRPCServer } from './aztec_rpc_server.js'; + +export function createAztecRPCServer({ + keyStore, + node, + db, + synchroniser, + simulator, + proofGenerator, +}: { + keyStore?: KeyStore; + node?: AztecNode; + db?: MemoryDB; + synchroniser?: Synchroniser; + simulator?: AcirSimulator; + proofGenerator?: ProofGenerator; +} = {}) { + keyStore = keyStore || new TestKeyStore(); + node = node || new AztecNode(); + db = db || new MemoryDB(); + synchroniser = synchroniser || new Synchroniser(node, db); + simulator = simulator || new AcirSimulator(); + proofGenerator = proofGenerator || new ProofGenerator(new KernelCircuitProver()); + + return Promise.resolve(new AztecRPCServer(keyStore, synchroniser, simulator, proofGenerator, node, db)); +} diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_server/index.ts b/yarn-project/aztec-rpc/src/aztec_rpc_server/index.ts new file mode 100644 index 000000000000..3af43b1a499e --- /dev/null +++ b/yarn-project/aztec-rpc/src/aztec_rpc_server/index.ts @@ -0,0 +1,2 @@ +export * from './aztec_rpc_server.js'; +export * from './create_aztec_rpc_server.js'; diff --git a/yarn-project/aztec-rpc/src/circuits.ts b/yarn-project/aztec-rpc/src/circuits.ts new file mode 100644 index 000000000000..3a387b84cd6e --- /dev/null +++ b/yarn-project/aztec-rpc/src/circuits.ts @@ -0,0 +1,128 @@ +import { randomBytes } from './foundation.js'; + +export class Fr { + public static ZERO = new Fr(Buffer.alloc(32)); + + public static random() { + return new Fr(randomBytes(32)); + } + + constructor(public readonly buffer: Buffer) {} +} + +export class EthAddress { + public static ZERO = new EthAddress(Buffer.alloc(20)); + + public static random() { + return new EthAddress(randomBytes(20)); + } + + constructor(public readonly buffer: Buffer) {} +} + +export class AztecAddress { + public static SIZE = 64; + + public static ZERO = new AztecAddress(Buffer.alloc(AztecAddress.SIZE)); + + public static random() { + return new AztecAddress(randomBytes(AztecAddress.SIZE)); + } + + constructor(public readonly buffer: Buffer) {} + + public equals(rhs: AztecAddress) { + return this.buffer.equals(rhs.buffer); + } +} + +export class Signature { + public static SIZE = 64; + + public static random() { + return new EthAddress(randomBytes(Signature.SIZE)); + } + + constructor(public readonly buffer: Buffer) {} +} + +export interface FunctionData { + functionSelector: Buffer; + isSecret: boolean; + isContructor: boolean; +} + +export class ContractDeploymentData { + public static EMPTY = new ContractDeploymentData(Fr.ZERO, Fr.ZERO, Fr.ZERO, Fr.ZERO, Fr.ZERO); + + constructor( + public readonly contractDataHash: Fr, + public readonly functionTreeRoot: Fr, + public readonly constructorHash: Fr, + public readonly contractAddressSalt: Fr, + public readonly portalContractAddress: Fr, + ) {} +} + +export class TxContext { + constructor( + public readonly isFeePaymentTx: boolean, + public readonly isRebatePaymentTx: boolean, + public readonly isContractDeploymentTx: boolean, + public readonly contractDeploymentData: ContractDeploymentData, + ) {} +} + +export class TxRequest { + constructor( + public readonly from: AztecAddress, + public readonly to: AztecAddress, + public readonly functionData: FunctionData, + public readonly args: Fr[], + public readonly txContext: TxContext, + public readonly nonce: Fr, + public readonly chainId: Fr, + ) {} + + toBuffer() { + return Buffer.alloc(0); + } +} + +export class PreviousKernelData {} + +export class PrivateCallData {} + +export class AccumulatedTxData {} + +export class KernelPrivateInputs { + constructor( + public readonly txRequest: TxRequest, + public readonly signature: Signature, + public readonly previousKernelData: PreviousKernelData, + public readonly privateCallData: PrivateCallData, + ) {} +} + +export class KernelProofData { + public readonly accumulatedTxData: AccumulatedTxData; + + constructor(public readonly proofData: Buffer) { + this.accumulatedTxData = new AccumulatedTxData(); + } +} + +export class KernelCircuitProver { + public createProof(inputs: KernelPrivateInputs) { + return Promise.resolve(new KernelProofData(Buffer.alloc(100))); + } +} + +export function generateContractAddress( + deployerAddress: AztecAddress, + salt: Fr, + args: Fr[], + // functionLeaves: Fr[], +) { + return AztecAddress.random(); +} diff --git a/yarn-project/aztec-rpc/src/contract_data_source/contract_dao.ts b/yarn-project/aztec-rpc/src/contract_data_source/contract_dao.ts new file mode 100644 index 000000000000..d15712687ae2 --- /dev/null +++ b/yarn-project/aztec-rpc/src/contract_data_source/contract_dao.ts @@ -0,0 +1,29 @@ +import { generateFunctionSelector } from '../abi_coder/index.js'; +import { AztecAddress } from '../circuits.js'; +import { ContractAbi, FunctionAbi } from '../noir.js'; + +export interface ContractFunctionDao extends FunctionAbi { + selector: Buffer; +} + +export interface ContractDao extends ContractAbi { + address: AztecAddress; + functions: ContractFunctionDao[]; + deployed: boolean; +} + +export function functionAbiToFunctionDao(abi: FunctionAbi) { + const selector = generateFunctionSelector(abi.name, abi.parameters); + return { + ...abi, + selector, + }; +} + +export function contractAbiToContractDao(address: AztecAddress, abi: ContractAbi, deployed: boolean): ContractDao { + return { + address, + functions: abi.functions.map(functionAbiToFunctionDao), + deployed, + }; +} diff --git a/yarn-project/aztec-rpc/src/contract_data_source/contract_data_source.ts b/yarn-project/aztec-rpc/src/contract_data_source/contract_data_source.ts new file mode 100644 index 000000000000..f086dc05cc7b --- /dev/null +++ b/yarn-project/aztec-rpc/src/contract_data_source/contract_data_source.ts @@ -0,0 +1,9 @@ +import { AztecAddress } from '../circuits.js'; +import { ContractAbi } from '../noir.js'; +import { ContractDao } from './contract_dao.js'; + +export interface ContractDataSource { + addContract(address: AztecAddress, abi: ContractAbi, deployed?: boolean): Promise; + getContract(address: AztecAddress): Promise; + getCode(contractAddress: AztecAddress, functionSelector: Buffer): Promise; +} diff --git a/yarn-project/aztec-rpc/src/contract_data_source/index.ts b/yarn-project/aztec-rpc/src/contract_data_source/index.ts new file mode 100644 index 000000000000..c1fad74451a6 --- /dev/null +++ b/yarn-project/aztec-rpc/src/contract_data_source/index.ts @@ -0,0 +1,3 @@ +export * from './contract_dao.js'; +export * from './contract_data_source.js'; +export * from './memory_contract_data_source.js'; diff --git a/yarn-project/aztec-rpc/src/contract_data_source/memory_contract_data_source.ts b/yarn-project/aztec-rpc/src/contract_data_source/memory_contract_data_source.ts new file mode 100644 index 000000000000..3f40eb39b771 --- /dev/null +++ b/yarn-project/aztec-rpc/src/contract_data_source/memory_contract_data_source.ts @@ -0,0 +1,22 @@ +import { AztecAddress } from '../circuits.js'; +import { ContractAbi } from '../noir.js'; +import { contractAbiToContractDao, ContractDao } from './contract_dao.js'; +import { ContractDataSource } from './contract_data_source.js'; + +export class MemoryContractDataSource implements ContractDataSource { + private contracts: ContractDao[] = []; + + public addContract(address: AztecAddress, abi: ContractAbi, deployed = false) { + this.contracts.push(contractAbiToContractDao(address, abi, deployed)); + return Promise.resolve(); + } + + public getContract(address: AztecAddress) { + return Promise.resolve(this.contracts.find(c => c.address.equals(address))); + } + + public async getCode(contractAddress: AztecAddress, functionSelector: Buffer) { + const contract = await this.getContract(contractAddress); + return contract?.functions.find(f => f.selector.equals(functionSelector))?.bytecode; + } +} diff --git a/yarn-project/aztec-rpc/src/database/database.ts b/yarn-project/aztec-rpc/src/database/database.ts new file mode 100644 index 000000000000..a0522bf61673 --- /dev/null +++ b/yarn-project/aztec-rpc/src/database/database.ts @@ -0,0 +1,7 @@ +import { TxHash } from '../aztec_node.js'; +import { ContractDataSource } from '../contract_data_source/index.js'; +import { TxDao } from './tx_dao.js'; + +export interface Database extends ContractDataSource { + getTx(txHash: TxHash): Promise; +} diff --git a/yarn-project/aztec-rpc/src/database/index.ts b/yarn-project/aztec-rpc/src/database/index.ts new file mode 100644 index 000000000000..695723d1a2e2 --- /dev/null +++ b/yarn-project/aztec-rpc/src/database/index.ts @@ -0,0 +1,3 @@ +export * from './database.js'; +export * from './memory_db.js'; +export * from './tx_dao.js'; diff --git a/yarn-project/aztec-rpc/src/database/memory_db.ts b/yarn-project/aztec-rpc/src/database/memory_db.ts new file mode 100644 index 000000000000..cda0907910a2 --- /dev/null +++ b/yarn-project/aztec-rpc/src/database/memory_db.ts @@ -0,0 +1,12 @@ +import { TxHash } from '../aztec_node.js'; +import { MemoryContractDataSource } from '../contract_data_source/index.js'; +import { Database } from './database.js'; +import { TxDao } from './tx_dao.js'; + +export class MemoryDB extends MemoryContractDataSource implements Database { + private txs: TxDao[] = []; + + public getTx(txHash: TxHash) { + return Promise.resolve(this.txs.find(tx => tx.txHash.equals(txHash))); + } +} diff --git a/yarn-project/aztec-rpc/src/database/tx_dao.ts b/yarn-project/aztec-rpc/src/database/tx_dao.ts new file mode 100644 index 000000000000..f8c3bbb93467 --- /dev/null +++ b/yarn-project/aztec-rpc/src/database/tx_dao.ts @@ -0,0 +1,14 @@ +import { TxHash } from '../aztec_node.js'; +import { AztecAddress } from '../circuits.js'; + +export class TxDao { + constructor( + public readonly txHash: TxHash, + public readonly blockHash: Buffer, + public readonly blockNumber: number, + public readonly from: AztecAddress, + public readonly to: AztecAddress | undefined, + public readonly contractAddress: AztecAddress | undefined, + public readonly error: string, + ) {} +} diff --git a/yarn-project/aztec-rpc/src/foundation.ts b/yarn-project/aztec-rpc/src/foundation.ts new file mode 100644 index 000000000000..aa664f0d46f3 --- /dev/null +++ b/yarn-project/aztec-rpc/src/foundation.ts @@ -0,0 +1,12 @@ +import nodeCrypto from 'crypto'; +import { Keccak } from 'sha3'; + +export const randomBytes = (len: number) => { + return nodeCrypto.randomBytes(len) as Buffer; +}; + +export function keccak256(input: Buffer | string) { + const inputBuf = typeof input === 'string' ? Buffer.from(input) : input; + const hash = new Keccak(256); + return hash.update(inputBuf).digest(); +} diff --git a/yarn-project/aztec-rpc/src/index.ts b/yarn-project/aztec-rpc/src/index.ts new file mode 100644 index 000000000000..134755851ae3 --- /dev/null +++ b/yarn-project/aztec-rpc/src/index.ts @@ -0,0 +1,9 @@ +export * from './abi_coder/index.js'; +export * from './aztec_rpc_client/index.js'; +export * from './aztec_rpc_server/index.js'; +export * from './tx/index.js'; + +// TODO - only export necessary stuffs +export * from './aztec_node.js'; +export * from './circuits.js'; +export * from './noir.js'; diff --git a/yarn-project/aztec-rpc/src/key_store/index.ts b/yarn-project/aztec-rpc/src/key_store/index.ts new file mode 100644 index 000000000000..6cf083f0a035 --- /dev/null +++ b/yarn-project/aztec-rpc/src/key_store/index.ts @@ -0,0 +1,3 @@ +// TODO - move to yarn-project/key-store +export * from './key_store.js'; +export * from './test_key_store.js'; diff --git a/yarn-project/aztec-rpc/src/key_store/key_pair.ts b/yarn-project/aztec-rpc/src/key_store/key_pair.ts new file mode 100644 index 000000000000..c0c7c9077a3b --- /dev/null +++ b/yarn-project/aztec-rpc/src/key_store/key_pair.ts @@ -0,0 +1,34 @@ +import { AztecAddress, Signature } from '../circuits.js'; +import { randomBytes } from '../foundation.js'; + +export interface KeyPair { + getPublicKey(): AztecAddress; + getPrivateKey(): Promise; + signMessage(message: Buffer): Promise; +} + +export class ConstantKeyPair implements KeyPair { + public static random() { + const privateKey = randomBytes(32); + const publicKey = AztecAddress.random(); + return new ConstantKeyPair(publicKey, privateKey); + } + + constructor(private publicKey: AztecAddress, private privateKey: Buffer) {} + + public getPublicKey() { + return this.publicKey; + } + + public getPrivateKey() { + return Promise.resolve(this.privateKey); + } + + public signMessage(message: Buffer) { + if (!message.length) { + throw new Error('Cannot sign over empty message.'); + } + + return Promise.resolve(Signature.random()); + } +} diff --git a/yarn-project/aztec-rpc/src/key_store/key_store.ts b/yarn-project/aztec-rpc/src/key_store/key_store.ts new file mode 100644 index 000000000000..e9fd0a684311 --- /dev/null +++ b/yarn-project/aztec-rpc/src/key_store/key_store.ts @@ -0,0 +1,8 @@ +import { AztecAddress, Signature, TxRequest } from '../circuits.js'; + +export interface KeyStore { + addAccount(): Promise; + getAccounts(): Promise; + getSigningPublicKeys(): Promise; + signTxRequest(txRequest: TxRequest): Promise; +} diff --git a/yarn-project/aztec-rpc/src/key_store/test_key_store.ts b/yarn-project/aztec-rpc/src/key_store/test_key_store.ts new file mode 100644 index 000000000000..a95423a7013d --- /dev/null +++ b/yarn-project/aztec-rpc/src/key_store/test_key_store.ts @@ -0,0 +1,34 @@ +import { TxRequest } from '../circuits.js'; +import { ConstantKeyPair, KeyPair } from './key_pair.js'; +import { KeyStore } from './key_store.js'; + +export class TestKeyStore implements KeyStore { + private accounts: KeyPair[] = []; + + constructor() { + this.accounts.push(ConstantKeyPair.random()); + } + + public addAccount() { + const keyPair = ConstantKeyPair.random(); + this.accounts.push(keyPair); + return Promise.resolve(keyPair.getPublicKey()); + } + + getAccounts() { + return Promise.resolve(this.accounts.map(a => a.getPublicKey())); + } + + getSigningPublicKeys() { + return this.getAccounts(); + } + + signTxRequest(txRequest: TxRequest) { + const account = this.accounts.find(a => a.getPublicKey().equals(txRequest.from)); + if (!account) { + throw new Error('Unknown account.'); + } + + return account.signMessage(txRequest.toBuffer()); + } +} diff --git a/yarn-project/aztec-rpc/src/noir.ts b/yarn-project/aztec-rpc/src/noir.ts new file mode 100644 index 000000000000..bb91ea35b56b --- /dev/null +++ b/yarn-project/aztec-rpc/src/noir.ts @@ -0,0 +1,123 @@ +/** + * A named type. + */ +export interface ABIVariable { + /** + * The name of the variable. + */ + name: string; + /** + * The type of the variable. + */ + type: ABIType; +} + +/** + * A function parameter. + */ +export interface ABIParameter extends ABIVariable { + /** + * Whether the parameter is unpacked. + */ + unpacked: boolean; +} + +/** + * A basic type. + */ +export interface BasicType { + /** + * The kind of the type. + */ + kind: T; +} + +/** + * A variable type. + */ +export type ABIType = BasicType<'field'> | BasicType<'boolean'> | IntegerType | ArrayType | StringType | StructType; + +/** + * An integer type. + */ +export interface IntegerType extends BasicType<'integer'> { + /** + * The sign of the integer. + */ + sign: string; + /** + * The width of the integer in bits. + */ + width: number; +} + +/** + * An array type. + */ +export interface ArrayType extends BasicType<'array'> { + /** + * The length of the array. + */ + length: number; + /** + * The type of the array elements. + */ + type: ABIType; +} + +/** + * A string type. + */ +export interface StringType extends BasicType<'string'> { + /** + * The length of the string. + */ + length: number; +} + +/** + * A struct type. + */ +export interface StructType extends BasicType<'struct'> { + /** + * The fields of the struct. + */ + fields: ABIVariable[]; +} + +/** + * The ABI entry of a function. + */ +export interface FunctionAbi { + /** + * The name of the function. + */ + name: string; + /** + * Whether the function is secret. + */ + isSecret: boolean; + /** + * Function parameters. + */ + parameters: ABIParameter[]; + /** + * The types of the return values. + */ + returnTypes: ABIType[]; + /** + * The ACIR bytecode of the function. + */ + bytecode: string; + /** + * The verification key of the function. + */ + verificationKey: string; +} + +export interface ContractAbi { + /** + * The functions of the contract. + */ + functions: FunctionAbi[]; +} diff --git a/yarn-project/aztec-rpc/src/proof_generator/index.ts b/yarn-project/aztec-rpc/src/proof_generator/index.ts new file mode 100644 index 000000000000..0907f3cd0065 --- /dev/null +++ b/yarn-project/aztec-rpc/src/proof_generator/index.ts @@ -0,0 +1 @@ +export * from './proof_generator.js'; diff --git a/yarn-project/aztec-rpc/src/proof_generator/proof_generator.ts b/yarn-project/aztec-rpc/src/proof_generator/proof_generator.ts new file mode 100644 index 000000000000..adddc5135f4e --- /dev/null +++ b/yarn-project/aztec-rpc/src/proof_generator/proof_generator.ts @@ -0,0 +1,10 @@ +import { KernelCircuitProver, KernelPrivateInputs } from '../circuits.js'; + +export class ProofGenerator { + constructor(private prover: KernelCircuitProver) {} + + createProof(inputs: KernelPrivateInputs) { + // TODO - iterate + return this.prover.createProof(inputs); + } +} diff --git a/yarn-project/aztec-rpc/src/synchroniser/index.ts b/yarn-project/aztec-rpc/src/synchroniser/index.ts new file mode 100644 index 000000000000..bf023056508e --- /dev/null +++ b/yarn-project/aztec-rpc/src/synchroniser/index.ts @@ -0,0 +1 @@ +export * from './synchroniser.js'; diff --git a/yarn-project/aztec-rpc/src/synchroniser/synchroniser.ts b/yarn-project/aztec-rpc/src/synchroniser/synchroniser.ts new file mode 100644 index 000000000000..7234f751574d --- /dev/null +++ b/yarn-project/aztec-rpc/src/synchroniser/synchroniser.ts @@ -0,0 +1,14 @@ +import { AccountState } from '../account_state/index.js'; +import { AztecNode } from '../aztec_node.js'; +import { AztecAddress } from '../circuits.js'; +import { Database } from '../database/index.js'; + +export class Synchroniser { + private accountStates: AccountState[] = []; + + constructor(private node: AztecNode, private db: Database) {} + + getAccount(account: AztecAddress) { + return this.accountStates.find(as => as.publicKey.equals(account)); + } +} diff --git a/yarn-project/aztec-rpc/src/tx/index.ts b/yarn-project/aztec-rpc/src/tx/index.ts new file mode 100644 index 000000000000..0576563fb7f8 --- /dev/null +++ b/yarn-project/aztec-rpc/src/tx/index.ts @@ -0,0 +1 @@ +export * from './tx_receipt.js'; diff --git a/yarn-project/aztec-rpc/src/tx/tx_receipt.ts b/yarn-project/aztec-rpc/src/tx/tx_receipt.ts new file mode 100644 index 000000000000..e4671e59c51c --- /dev/null +++ b/yarn-project/aztec-rpc/src/tx/tx_receipt.ts @@ -0,0 +1,14 @@ +import { TxHash } from '../aztec_node.js'; +import { AztecAddress } from '../circuits.js'; + +export interface TxReceipt { + txHash: TxHash; + // txIndex: number; + blockHash: Buffer; + blockNumber: number; + from: AztecAddress; + to?: AztecAddress; + contractAddress?: AztecAddress; + error?: string; + status: boolean; +} diff --git a/yarn-project/wallet/tsconfig.dest.json b/yarn-project/aztec-rpc/tsconfig.dest.json similarity index 100% rename from yarn-project/wallet/tsconfig.dest.json rename to yarn-project/aztec-rpc/tsconfig.dest.json diff --git a/yarn-project/wallet/tsconfig.json b/yarn-project/aztec-rpc/tsconfig.json similarity index 100% rename from yarn-project/wallet/tsconfig.json rename to yarn-project/aztec-rpc/tsconfig.json diff --git a/yarn-project/aztec.js/.eslintrc.cjs b/yarn-project/aztec.js/.eslintrc.cjs index 9cf806b1500f..333ff38eab81 100644 --- a/yarn-project/aztec.js/.eslintrc.cjs +++ b/yarn-project/aztec.js/.eslintrc.cjs @@ -3,4 +3,19 @@ require('@rushstack/eslint-patch/modern-module-resolution'); module.exports = { extends: ['@aztec/eslint-config'], parserOptions: { tsconfigRootDir: __dirname }, + rules: { + 'tsdoc/syntax': 'off', + 'jsdoc/require-jsdoc': 'off', + 'jsdoc/require-description': 'off', + 'jsdoc/require-description-complete-sentence': 'off', + 'jsdoc/require-hyphen-before-param-description': 'off', + 'jsdoc/require-param': 'off', + 'jsdoc/require-param-description': 'off', + 'jsdoc/require-param-name': 'off', + 'jsdoc/require-property': 'off', + 'jsdoc/require-property-description': 'off', + 'jsdoc/require-property-name': 'off', + 'jsdoc/require-returns': 'off', + 'jsdoc/require-returns-description': 'off', + }, }; diff --git a/yarn-project/aztec.js/package.json b/yarn-project/aztec.js/package.json index 988056465c37..7eff00fb239e 100644 --- a/yarn-project/aztec.js/package.json +++ b/yarn-project/aztec.js/package.json @@ -29,6 +29,8 @@ "rootDir": "./src" }, "dependencies": { + "@aztec/aztec-rpc": "workspace:^", + "sha3": "^2.1.4", "tslib": "^2.4.0" }, "devDependencies": { @@ -38,6 +40,7 @@ "@types/jest": "^29.4.0", "@types/node": "^18.7.23", "jest": "^28.1.3", + "jest-mock-extended": "^3.0.3", "ts-jest": "^28.0.7", "ts-node": "^10.9.1", "typescript": "^4.9.5" diff --git a/yarn-project/aztec.js/src/abi_coder/hex_string.ts b/yarn-project/aztec.js/src/abi_coder/hex_string.ts new file mode 100644 index 000000000000..fd6eb9ebe385 --- /dev/null +++ b/yarn-project/aztec.js/src/abi_coder/hex_string.ts @@ -0,0 +1,3 @@ +export function hexToBuffer(h: string) { + return Buffer.from((h.length % 2 ? '0' : '') + h.replace(/^0x/, ''), 'hex'); +} diff --git a/yarn-project/aztec.js/src/abi_coder/index.ts b/yarn-project/aztec.js/src/abi_coder/index.ts new file mode 100644 index 000000000000..8a6dacd1fa56 --- /dev/null +++ b/yarn-project/aztec.js/src/abi_coder/index.ts @@ -0,0 +1,15 @@ +import { ABIParameter } from '@aztec/aztec-rpc'; + +export * from './hex_string.js'; + +function pack(parameter: ABIParameter, value: any) { + return Buffer.alloc(32); +} + +export function encodeParameters(parameters: ABIParameter[], args: any[]) { + if (parameters.length !== args.length) { + throw new Error(`Incorrect number of args. Expect ${parameters.length}. Got ${args.length}.`); + } + + return parameters.map((p, i) => pack(p, args[i])); +} diff --git a/yarn-project/aztec.js/src/contract/contract_function.ts b/yarn-project/aztec.js/src/contract/contract_function.ts new file mode 100644 index 000000000000..6305c3e3a731 --- /dev/null +++ b/yarn-project/aztec.js/src/contract/contract_function.ts @@ -0,0 +1,14 @@ +import { FunctionAbi, generateFunctionSelector } from '@aztec/aztec-rpc'; +import { encodeParameters } from '../abi_coder/index.js'; + +export class ContractFunction { + constructor(private abi: FunctionAbi) {} + + public encodeABI() { + return generateFunctionSelector(this.abi.name, this.abi.parameters); + } + + public encodeParameters(args: any[]) { + return encodeParameters(this.abi.parameters, args); + } +} diff --git a/yarn-project/aztec.js/src/contract/index.ts b/yarn-project/aztec.js/src/contract/index.ts new file mode 100644 index 000000000000..c434ae962571 --- /dev/null +++ b/yarn-project/aztec.js/src/contract/index.ts @@ -0,0 +1,3 @@ +export * from './contract_function.js'; +export * from './send_method.js'; +export * from './sent_tx.js'; diff --git a/yarn-project/aztec.js/src/contract/send_method.ts b/yarn-project/aztec.js/src/contract/send_method.ts new file mode 100644 index 000000000000..c3e094552dee --- /dev/null +++ b/yarn-project/aztec.js/src/contract/send_method.ts @@ -0,0 +1,73 @@ +import { AztecAddress, AztecRPCClient, Fr, Signature, Tx, TxHash, TxRequest } from '@aztec/aztec-rpc'; +import { ContractFunction } from './contract_function.js'; +import { SentTx } from './sent_tx.js'; + +export interface SendMethodOptions { + from?: AztecAddress; + nonce?: Fr; +} + +/** + * This is the class that is returned when calling e.g. `contract.methods.myMethod(arg0, arg1)`. + * It contains available interactions one can call on a `send` method. + */ +export class SendMethod { + protected txRequest?: TxRequest; + private signature?: Signature; + private tx?: Tx; + + constructor( + protected arc: AztecRPCClient, + protected contractAddress: AztecAddress, + protected entry: ContractFunction, + protected args: any[], + protected defaultOptions: SendMethodOptions = {}, + ) {} + + public async request(options: SendMethodOptions = {}) { + const { from } = { ...this.defaultOptions, ...options }; + this.txRequest = await this.arc.createTxRequest( + this.entry.encodeABI(), + this.entry.encodeParameters(this.args).map(p => new Fr(p)), + this.contractAddress, + from || AztecAddress.ZERO, + ); + return this.txRequest; + } + + public async sign(options: SendMethodOptions = {}) { + if (!this.txRequest) { + await this.request(options); + } + + this.signature = await this.arc.signTxRequest(this.txRequest!); + return this.signature; + } + + public async create(options: SendMethodOptions = {}) { + if (!this.signature) { + await this.sign(options); + } + + this.tx = await this.arc.createTx(this.txRequest!, this.signature!); + return this.tx; + } + + public send(options: SendMethodOptions = {}) { + let promise: Promise; + if (this.tx) { + promise = this.arc.sendTx(this.tx); + } else { + promise = (async () => { + await this.create(options); + return this.arc.sendTx(this.tx!); + })(); + } + + return new SentTx(this.arc, promise); + } + + public encodeABI() { + return this.entry.encodeABI(); + } +} diff --git a/yarn-project/aztec.js/src/contract/sent_tx.ts b/yarn-project/aztec.js/src/contract/sent_tx.ts new file mode 100644 index 000000000000..ad1dbce0bea8 --- /dev/null +++ b/yarn-project/aztec.js/src/contract/sent_tx.ts @@ -0,0 +1,22 @@ +import { AztecRPCClient, TxHash, TxReceipt } from '@aztec/aztec-rpc'; +import { retryUntil } from '../foundation.js'; + +export class SentTx { + private receipt?: TxReceipt; + + constructor(private arc: AztecRPCClient, private txHashPromise: Promise) {} + + public async getTxHash() { + return await this.txHashPromise; + } + + public async getReceipt(timeout = 0, interval = 1) { + if (this.receipt) { + return this.receipt; + } + + const txHash = await this.getTxHash(); + this.receipt = await retryUntil(() => this.arc.getTxReceipt(txHash), 'getReceipt', timeout, interval); + return this.receipt; + } +} diff --git a/yarn-project/aztec.js/src/contract_deployer/constructor_method.ts b/yarn-project/aztec.js/src/contract_deployer/constructor_method.ts new file mode 100644 index 000000000000..ea21d42197a0 --- /dev/null +++ b/yarn-project/aztec.js/src/contract_deployer/constructor_method.ts @@ -0,0 +1,50 @@ +import { AztecAddress, AztecRPCClient, ContractAbi, EthAddress, Fr } from '@aztec/aztec-rpc'; +import { ContractFunction, SendMethod, SendMethodOptions } from '../contract/index.js'; + +export interface ConstructorOptions extends SendMethodOptions { + contractAddressSalt?: Fr; +} + +/** + * Extends the SendMethodInteraction to create TxRequest for constructors (deployments). + */ +export class ConstructorMethod extends SendMethod { + constructor( + arc: AztecRPCClient, + private abi: ContractAbi, + private portalContract: EthAddress, + args: any[], + defaultOptions: ConstructorOptions = {}, + ) { + const constructorAbi = abi.functions.find(f => f.name === 'constructor'); + if (!constructorAbi) { + throw new Error('Cannot find constructor in the ABI.'); + } + + super(arc, AztecAddress.ZERO, new ContractFunction(constructorAbi), args, defaultOptions); + } + + public async request(options: ConstructorOptions = {}) { + const { contractAddressSalt, from } = { ...this.defaultOptions, ...options }; + this.txRequest = await this.arc.createDeploymentTxRequest( + this.abi, + this.entry.encodeParameters(this.args).map(p => new Fr(p)), + this.portalContract, + contractAddressSalt || Fr.random(), + from || AztecAddress.ZERO, + ); + return this.txRequest; + } + + public sign(options: ConstructorOptions = {}) { + return super.sign(options); + } + + public create(options: ConstructorOptions = {}) { + return super.create(options); + } + + public send(options: ConstructorOptions = {}) { + return super.send(options); + } +} diff --git a/yarn-project/aztec.js/src/contract_deployer/contract_deployer.test.ts b/yarn-project/aztec.js/src/contract_deployer/contract_deployer.test.ts new file mode 100644 index 000000000000..3e2b5f3aafeb --- /dev/null +++ b/yarn-project/aztec.js/src/contract_deployer/contract_deployer.test.ts @@ -0,0 +1,112 @@ +import { mock } from 'jest-mock-extended'; +import { + AztecAddress, + AztecRPCClient, + ContractAbi, + EthAddress, + Fr, + Signature, + Tx, + TxHash, + TxReceipt, + TxRequest, +} from '@aztec/aztec-rpc'; +import { ContractDeployer } from './contract_deployer.js'; + +describe('Contract Deployer', () => { + let arc: ReturnType>; + + const abi: ContractAbi = { + functions: [ + { + name: 'constructor', + isSecret: true, + parameters: [], + returnTypes: [], + bytecode: '0x01234567', + verificationKey: '0x98765432', + }, + ], + }; + + const portalContract = EthAddress.random(); + const contractAddressSalt = Fr.random(); + const account = AztecAddress.random(); + + const mockTxRequest = { type: 'TxRequest' } as any as TxRequest; + const mockSignature = { type: 'Signature' } as any as Signature; + const mockTx = { type: 'Tx' } as any as Tx; + const mockTxHash = { type: 'TxHash' } as any as TxHash; + const mockTxReceipt = { type: 'TxReceipt' } as any as TxReceipt; + + beforeEach(() => { + arc = mock(); + arc.createDeploymentTxRequest.mockResolvedValue(mockTxRequest); + arc.createTxRequest.mockResolvedValue(mockTxRequest); + arc.signTxRequest.mockResolvedValue(mockSignature); + arc.createTx.mockResolvedValue(mockTx); + arc.sendTx.mockResolvedValue(mockTxHash); + arc.getTxReceipt.mockResolvedValue(mockTxReceipt); + }); + + it('should request, sign, craete and send a contract deployment tx', async () => { + const deployer = new ContractDeployer(abi, arc); + const sentTx = deployer.deploy(portalContract).send({ + contractAddressSalt, + from: account, + }); + const txHash = await sentTx.getTxHash(); + const receipt = await sentTx.getReceipt(); + + expect(txHash).toBe(mockTxHash); + expect(receipt).toBe(mockTxReceipt); + expect(arc.createDeploymentTxRequest).toHaveBeenCalledTimes(1); + expect(arc.createDeploymentTxRequest).toHaveBeenCalledWith(abi, [], portalContract, contractAddressSalt, account); + expect(arc.createTxRequest).toHaveBeenCalledTimes(0); + expect(arc.signTxRequest).toHaveBeenCalledTimes(1); + expect(arc.signTxRequest).toHaveBeenCalledWith(mockTxRequest); + expect(arc.createTx).toHaveBeenCalledTimes(1); + expect(arc.createTx).toHaveBeenCalledWith(mockTxRequest, mockSignature); + expect(arc.sendTx).toHaveBeenCalledTimes(1); + expect(arc.sendTx).toHaveBeenCalledWith(mockTx); + }); + + it('should be able to deploy a contract step by step', async () => { + const deployer = new ContractDeployer(abi, arc); + const deployment = deployer.deploy(portalContract); + const txRequest = await deployment.request({ + contractAddressSalt, + from: account, + }); + const signature = await deployment.sign(); + const tx = await deployment.create(); + const receipt = await deployment.send().getReceipt(); + + expect(txRequest).toBe(mockTxRequest); + expect(signature).toBe(mockSignature); + expect(tx).toBe(mockTx); + expect(receipt).toBe(mockTxReceipt); + expect(arc.createDeploymentTxRequest).toHaveBeenCalledTimes(1); + expect(arc.createDeploymentTxRequest).toHaveBeenCalledWith(abi, [], portalContract, contractAddressSalt, account); + expect(arc.createTxRequest).toHaveBeenCalledTimes(0); + expect(arc.signTxRequest).toHaveBeenCalledTimes(1); + expect(arc.createTx).toHaveBeenCalledTimes(1); + expect(arc.sendTx).toHaveBeenCalledTimes(1); + }); + + it('should assign zeros or generate random values for undefined options', async () => { + const deployer = new ContractDeployer(abi, arc); + const deployment = deployer.deploy(); + await deployment.request(); + expect(arc.createDeploymentTxRequest).toHaveBeenCalledWith( + abi, + [], + EthAddress.ZERO, // portalContract + expect.anything(), // contractAddressSalt + AztecAddress.ZERO, // account + ); + const defaultContractAddressSalt = arc.createDeploymentTxRequest.mock.calls[0][3]; + expect(defaultContractAddressSalt).not.toEqual(contractAddressSalt); + expect(defaultContractAddressSalt).not.toEqual(Fr.ZERO); + }); +}); diff --git a/yarn-project/aztec.js/src/contract_deployer/contract_deployer.ts b/yarn-project/aztec.js/src/contract_deployer/contract_deployer.ts new file mode 100644 index 000000000000..8f863bccccad --- /dev/null +++ b/yarn-project/aztec.js/src/contract_deployer/contract_deployer.ts @@ -0,0 +1,13 @@ +import { AztecRPCClient, ContractAbi, EthAddress } from '@aztec/aztec-rpc'; +import { ConstructorMethod, ConstructorOptions } from './constructor_method.js'; + +/** + * A class for deploying contract. + */ +export class ContractDeployer { + constructor(private abi: ContractAbi, private arc: AztecRPCClient, private defaultOptions: ConstructorOptions = {}) {} + + public deploy(portalContract = EthAddress.ZERO, ...args: any[]) { + return new ConstructorMethod(this.arc, this.abi, portalContract, args, this.defaultOptions); + } +} diff --git a/yarn-project/aztec.js/src/contract_deployer/index.ts b/yarn-project/aztec.js/src/contract_deployer/index.ts new file mode 100644 index 000000000000..587eeb108ce7 --- /dev/null +++ b/yarn-project/aztec.js/src/contract_deployer/index.ts @@ -0,0 +1 @@ +export * from './contract_deployer.js'; diff --git a/yarn-project/aztec.js/src/foundation.ts b/yarn-project/aztec.js/src/foundation.ts new file mode 100644 index 000000000000..b9b48b88eeb1 --- /dev/null +++ b/yarn-project/aztec.js/src/foundation.ts @@ -0,0 +1,63 @@ +export function sleep(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +export class Timer { + private start: number; + + constructor() { + this.start = new Date().getTime(); + } + + public ms() { + return new Date().getTime() - this.start; + } + + public s() { + return (new Date().getTime() - this.start) / 1000; + } +} + +export function* backoffGenerator() { + const v = [1, 1, 1, 2, 4, 8, 16, 32, 64]; + let i = 0; + while (true) { + yield v[Math.min(i++, v.length - 1)]; + } +} + +export async function retry(fn: () => Promise, name = 'Operation', backoff = backoffGenerator()) { + while (true) { + try { + return await fn(); + } catch (err: any) { + const s = backoff.next().value; + if (s === undefined) { + throw err; + } + console.log(`${name} failed. Will retry in ${s}s...`); + console.log(err); + await sleep(s * 1000); + continue; + } + } +} + +// Call `fn` repeatedly until it returns true or timeout. +// Both `interval` and `timeout` are seconds. +// Will never timeout if the value is 0. +export async function retryUntil(fn: () => Promise, name = '', timeout = 0, interval = 1) { + const timer = new Timer(); + while (true) { + const result = await fn(); + if (result) { + return result; + } + + await sleep(interval * 1000); + + if (timeout && timer.s() > timeout) { + throw new Error(name ? `Timeout awaiting ${name}` : 'Timeout'); + } + } +} diff --git a/yarn-project/aztec.js/src/index.test.ts b/yarn-project/aztec.js/src/index.test.ts deleted file mode 100644 index ce33cfbe10c7..000000000000 --- a/yarn-project/aztec.js/src/index.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Aztec } from './index.js'; - -describe('Aztec', () => { - it('Initialise Aztec', () => { - expect(() => new Aztec()).not.toThrow(); - }); -}); diff --git a/yarn-project/aztec.js/src/index.ts b/yarn-project/aztec.js/src/index.ts index dec50f231ab5..64f67ec0e271 100644 --- a/yarn-project/aztec.js/src/index.ts +++ b/yarn-project/aztec.js/src/index.ts @@ -1,4 +1,5 @@ -/** - * A placeholder for Aztec js. - */ -export class Aztec {} +export * from './contract/index.js'; +export * from './contract_deployer/index.js'; + +// TODO - only export necessary stuffs +export * from '@aztec/aztec-rpc'; diff --git a/yarn-project/package.json b/yarn-project/package.json index 5b68d11e9601..0e8d4f3d2be0 100644 --- a/yarn-project/package.json +++ b/yarn-project/package.json @@ -10,6 +10,7 @@ "acir-simulator", "archiver", "aztec-cli", + "aztec-rpc", "aztec.js", "docs", "end-to-end", @@ -23,8 +24,7 @@ "prettier-config", "prover-client", "public-client", - "sequencer-client", - "wallet" + "sequencer-client" ], "prettier": "./prettier-config", "devDependencies": { diff --git a/yarn-project/typedoc.json b/yarn-project/typedoc.json index caf8497b4a8f..1bdaa3857199 100644 --- a/yarn-project/typedoc.json +++ b/yarn-project/typedoc.json @@ -5,6 +5,7 @@ "acir-simulator", "archiver", "aztec-cli", + "aztec-rpc", "aztec.js", "ethereum.js", "kernel-simulator", @@ -13,7 +14,6 @@ "p2p", "prover-client", "public-client", - "sequencer-client", - "wallet" + "sequencer-client" ] } diff --git a/yarn-project/wallet/.eslintrc.cjs b/yarn-project/wallet/.eslintrc.cjs deleted file mode 100644 index 9cf806b1500f..000000000000 --- a/yarn-project/wallet/.eslintrc.cjs +++ /dev/null @@ -1,6 +0,0 @@ -require('@rushstack/eslint-patch/modern-module-resolution'); - -module.exports = { - extends: ['@aztec/eslint-config'], - parserOptions: { tsconfigRootDir: __dirname }, -}; diff --git a/yarn-project/wallet/README.md b/yarn-project/wallet/README.md deleted file mode 100644 index 456644fc5565..000000000000 --- a/yarn-project/wallet/README.md +++ /dev/null @@ -1 +0,0 @@ -# Wallet diff --git a/yarn-project/wallet/src/index.ts b/yarn-project/wallet/src/index.ts deleted file mode 100644 index 95d6e81d27f4..000000000000 --- a/yarn-project/wallet/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * A placeholder for the Wallet. - */ -export class Wallet {} diff --git a/yarn-project/yarn-project-base/Dockerfile b/yarn-project/yarn-project-base/Dockerfile index 5ccdf96f7f1a..baab92c1206c 100644 --- a/yarn-project/yarn-project-base/Dockerfile +++ b/yarn-project/yarn-project-base/Dockerfile @@ -14,6 +14,7 @@ WORKDIR /usr/src/yarn-project COPY acir-simulator/package.json acir-simulator/package.json COPY archiver/package.json archiver/package.json COPY aztec-cli/package.json aztec-cli/package.json +COPY aztec-rpc/package.json aztec-rpc/package.json COPY aztec.js/package.json aztec.js/package.json COPY docs/package.json docs/package.json COPY end-to-end/package.json end-to-end/package.json @@ -26,7 +27,6 @@ COPY p2p/package.json p2p/package.json COPY prover-client/package.json prover-client/package.json COPY public-client/package.json public-client/package.json COPY sequencer-client/package.json sequencer-client/package.json -COPY wallet/package.json wallet/package.json # All workspaces use the linting config, so always include it. COPY eslint-config eslint-config COPY prettier-config prettier-config diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 7e1ba2c84230..0f4b1884c4f5 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -78,16 +78,36 @@ __metadata: languageName: unknown linkType: soft +"@aztec/aztec-rpc@workspace:^, @aztec/aztec-rpc@workspace:aztec-rpc": + version: 0.0.0-use.local + resolution: "@aztec/aztec-rpc@workspace:aztec-rpc" + dependencies: + "@aztec/eslint-config": "workspace:^" + "@jest/globals": ^29.4.3 + "@rushstack/eslint-patch": ^1.1.4 + "@types/jest": ^29.4.0 + "@types/node": ^18.7.23 + jest: ^28.1.3 + ts-jest: ^28.0.7 + ts-node: ^10.9.1 + tslib: ^2.4.0 + typescript: ^4.9.5 + languageName: unknown + linkType: soft + "@aztec/aztec.js@workspace:^, @aztec/aztec.js@workspace:aztec.js": version: 0.0.0-use.local resolution: "@aztec/aztec.js@workspace:aztec.js" dependencies: + "@aztec/aztec-rpc": "workspace:^" "@aztec/eslint-config": "workspace:^" "@jest/globals": ^29.4.3 "@rushstack/eslint-patch": ^1.1.4 "@types/jest": ^29.4.0 "@types/node": ^18.7.23 jest: ^28.1.3 + jest-mock-extended: ^3.0.3 + sha3: ^2.1.4 ts-jest: ^28.0.7 ts-node: ^10.9.1 tslib: ^2.4.0 @@ -320,23 +340,6 @@ __metadata: languageName: unknown linkType: soft -"@aztec/wallet@workspace:wallet": - version: 0.0.0-use.local - resolution: "@aztec/wallet@workspace:wallet" - dependencies: - "@aztec/eslint-config": "workspace:^" - "@jest/globals": ^29.4.3 - "@rushstack/eslint-patch": ^1.1.4 - "@types/jest": ^29.4.0 - "@types/node": ^18.7.23 - jest: ^28.1.3 - ts-jest: ^28.0.7 - ts-node: ^10.9.1 - tslib: ^2.4.0 - typescript: ^4.9.5 - languageName: unknown - linkType: soft - "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.18.6": version: 7.18.6 resolution: "@babel/code-frame@npm:7.18.6" @@ -4427,7 +4430,7 @@ __metadata: languageName: node linkType: hard -"jest-mock-extended@npm:^3.0.1": +"jest-mock-extended@npm:^3.0.1, jest-mock-extended@npm:^3.0.3": version: 3.0.3 resolution: "jest-mock-extended@npm:3.0.3" dependencies: