Skip to content

Commit

Permalink
test(e2e): multiple accounts, same encryption key (#1137)
Browse files Browse the repository at this point in the history
  • Loading branch information
benesjan authored Jul 24, 2023
1 parent d4477e3 commit 4a44add
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 55 deletions.
13 changes: 13 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,17 @@ jobs:
name: "Test"
command: cond_spot_run_tests end-to-end e2e_2_rpc_servers.test.ts

e2e-multiple-accounts-1-enc-key:
docker:
- image: aztecprotocol/alpine-build-image
resource_class: small
steps:
- *checkout
- *setup_env
- run:
name: "Test"
command: cond_spot_run_tests end-to-end e2e_multiple_accounts_1_enc_key.test.ts

e2e-deploy-contract:
docker:
- image: aztecprotocol/alpine-build-image
Expand Down Expand Up @@ -843,6 +854,7 @@ workflows:
- e2e-block-building: *e2e_test
- e2e-nested-contract: *e2e_test
- e2e-non-contract-account: *e2e_test
- e2e-multiple-accounts-1-enc-key: *e2e_test
- e2e-public-token-contract: *e2e_test
- e2e-cross-chain-messaging: *e2e_test
- e2e-public-cross-chain-messaging: *e2e_test
Expand All @@ -863,6 +875,7 @@ workflows:
- e2e-block-building
- e2e-nested-contract
- e2e-non-contract-account
- e2e-multiple-accounts-1-enc-key
- e2e-public-token-contract
- e2e-cross-chain-messaging
- e2e-public-cross-chain-messaging
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/end-to-end/src/e2e_2_rpc_servers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ describe('e2e_2_rpc_servers', () => {
const isUserSynchronised = async () => {
return await wallet.isAccountSynchronised(owner);
};
await retryUntil(isUserSynchronised, owner.toString(), 5);
await retryUntil(isUserSynchronised, owner.toString(), 10);

// Then check the balance
const contractWithWallet = new ZkTokenContract(contractWithWalletA.address, wallet);
Expand Down
56 changes: 3 additions & 53 deletions yarn-project/end-to-end/src/e2e_account_contracts.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import { AztecRPCServer } from '@aztec/aztec-rpc';
import {
AccountImplementation,
AccountWallet,
ContractDeployer,
Fr,
SingleKeyAccountContract,
StoredKeyAccountContract,
generatePublicKey,
} from '@aztec/aztec.js';
import { AztecAddress, PartialContractAddress, Point, getContractDeploymentInfo } from '@aztec/circuits.js';
import { AccountWallet, Fr, SingleKeyAccountContract, StoredKeyAccountContract } from '@aztec/aztec.js';
import { AztecAddress, PartialContractAddress, Point } from '@aztec/circuits.js';
import { Ecdsa, Schnorr } from '@aztec/circuits.js/barretenberg';
import { ContractAbi } from '@aztec/foundation/abi';
import { toBigInt } from '@aztec/foundation/serialize';
Expand All @@ -18,52 +10,10 @@ import {
SchnorrSingleKeyAccountContractAbi,
} from '@aztec/noir-contracts/artifacts';
import { ChildContract } from '@aztec/noir-contracts/types';
import { AztecRPC, PublicKey } from '@aztec/types';

import { randomBytes } from 'crypto';

import { setup } from './utils.js';

async function deployContract(
aztecRpcServer: AztecRPC,
publicKey: PublicKey,
abi: ContractAbi,
args: any[],
contractAddressSalt?: Fr,
) {
const deployer = new ContractDeployer(abi, aztecRpcServer, publicKey);
const deployMethod = deployer.deploy(...args);
await deployMethod.create({ contractAddressSalt });
const tx = deployMethod.send();
expect(await tx.isMined(0, 0.1)).toBeTruthy();
const receipt = await tx.getReceipt();
return { address: receipt.contractAddress!, partialContractAddress: deployMethod.partialContractAddress! };
}

async function createNewAccount(
aztecRpcServer: AztecRPC,
abi: ContractAbi,
args: any[],
encryptionPrivateKey: Buffer,
useProperKey: boolean,
createAccountImpl: CreateAccountImplFn,
) {
const salt = Fr.random();
const publicKey = await generatePublicKey(encryptionPrivateKey);
const { address, partialAddress } = await getContractDeploymentInfo(abi, args, salt, publicKey);
await aztecRpcServer.addAccount(encryptionPrivateKey, address, partialAddress);
await deployContract(aztecRpcServer, publicKey, abi, args, salt);
const account = await createAccountImpl(address, useProperKey, partialAddress, encryptionPrivateKey);
const wallet = new AccountWallet(aztecRpcServer, account);
return { wallet, address, partialAddress };
}

type CreateAccountImplFn = (
address: AztecAddress,
useProperKey: boolean,
partialAddress: PartialContractAddress,
encryptionPrivateKey: Buffer,
) => Promise<AccountImplementation>;
import { CreateAccountImplFn, createNewAccount, deployContract, setup } from './utils.js';

function itShouldBehaveLikeAnAccountContract(
abi: ContractAbi,
Expand Down
154 changes: 154 additions & 0 deletions yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { AztecNodeService } from '@aztec/aztec-node';
import { AztecRPCServer, Fr } from '@aztec/aztec-rpc';
import { AztecAddress, StoredKeyAccountContract, Wallet, generatePublicKey } from '@aztec/aztec.js';
import { Schnorr } from '@aztec/circuits.js/barretenberg';
import { DebugLogger } from '@aztec/foundation/log';
import { SchnorrMultiKeyAccountContractAbi } from '@aztec/noir-contracts/artifacts';
import { ZkTokenContract } from '@aztec/noir-contracts/types';
import { AztecRPC, TxStatus } from '@aztec/types';

import { randomBytes } from 'crypto';

import {
createNewAccount,
expectUnencryptedLogsFromLastBlockToBe,
expectsNumOfEncryptedLogsInTheLastBlockToBe,
setup,
} from './utils.js';

describe('e2e_multiple_accounts_1_enc_key', () => {
let aztecNode: AztecNodeService | undefined;
let aztecRpcServer: AztecRPC;
const wallets: Wallet[] = [];
const accounts: AztecAddress[] = [];
let logger: DebugLogger;

let zkTokenAddress: AztecAddress;

const initialBalance = 987n;
const numAccounts = 3;

beforeEach(async () => {
({ aztecNode, aztecRpcServer, logger } = await setup(0));

const encryptionPrivateKey = randomBytes(32);
for (let i = 0; i < numAccounts; i++) {
logger(`Deploying account contract ${i}/3...`);
const signingPrivateKey = randomBytes(32);
const createWallet = async (address: AztecAddress, useProperKey: boolean) =>
new StoredKeyAccountContract(address, useProperKey ? signingPrivateKey : randomBytes(32), await Schnorr.new());

const schnorr = await Schnorr.new();
const signingPublicKey = schnorr.computePublicKey(signingPrivateKey);
const constructorArgs = [
Fr.fromBuffer(signingPublicKey.subarray(0, 32)),
Fr.fromBuffer(signingPublicKey.subarray(32, 64)),
];

const { wallet, address } = await createNewAccount(
aztecRpcServer,
SchnorrMultiKeyAccountContractAbi,
constructorArgs,
encryptionPrivateKey,
true,
createWallet,
);
wallets.push(wallet);
accounts.push(address);
}
logger('Account contracts deployed');

// Verify that all accounts use the same encryption key
const encryptionPublicKey = await generatePublicKey(encryptionPrivateKey);
for (let i = 0; i < numAccounts; i++) {
const accountEncryptionPublicKey = await aztecRpcServer.getPublicKey(accounts[i]);
expect(accountEncryptionPublicKey).toEqual(encryptionPublicKey);
}

logger(`Deploying ZK Token...`);
const tx = ZkTokenContract.deploy(aztecRpcServer, initialBalance, accounts[0]).send();
const receipt = await tx.getReceipt();
zkTokenAddress = receipt.contractAddress!;
await tx.isMined(0, 0.1);
const minedReceipt = await tx.getReceipt();
expect(minedReceipt.status).toEqual(TxStatus.MINED);
logger('ZK Token deployed');
}, 100_000);

afterEach(async () => {
await aztecNode?.stop();
if (aztecRpcServer instanceof AztecRPCServer) {
await aztecRpcServer?.stop();
}
});

const expectBalance = async (userIndex: number, expectedBalance: bigint) => {
const wallet = wallets[userIndex];
const owner = accounts[userIndex];

// Then check the balance
const contractWithWallet = new ZkTokenContract(zkTokenAddress, wallet);
const [balance] = await contractWithWallet.methods.getBalance(owner).view({ from: owner });
logger(`Account ${owner} balance: ${balance}`);
expect(balance).toBe(expectedBalance);
};

const transfer = async (
senderIndex: number,
receiverIndex: number,
transferAmount: bigint,
expectedBalances: bigint[],
) => {
logger(`Transfer ${transferAmount} from ${accounts[senderIndex]} to ${accounts[receiverIndex]}...`);

const sender = accounts[senderIndex];
const receiver = accounts[receiverIndex];

const contractWithWallet = new ZkTokenContract(zkTokenAddress, wallets[senderIndex]);

const tx = contractWithWallet.methods.transfer(transferAmount, sender, receiver).send({ origin: sender });
await tx.isMined(0, 0.1);
const receipt = await tx.getReceipt();

expect(receipt.status).toBe(TxStatus.MINED);

for (let i = 0; i < expectedBalances.length; i++) {
await expectBalance(i, expectedBalances[i]);
}

await expectsNumOfEncryptedLogsInTheLastBlockToBe(aztecNode, 2);
await expectUnencryptedLogsFromLastBlockToBe(aztecNode, ['Coins transferred']);

logger(`Transfer ${transferAmount} from ${sender} to ${receiver} successful`);
};

/**
* Tests the ability of the Aztec RPC server to handle multiple accounts under the same encryption key.
*/
it('spends notes from multiple account under the same encryption key', async () => {
const transferAmount1 = 654n; // account 0 -> account 1
const transferAmount2 = 123n; // account 0 -> account 2
const transferAmount3 = 210n; // account 1 -> account 2

await expectBalance(0, initialBalance);
await expectBalance(1, 0n);
await expectBalance(2, 0n);

const expectedBalancesAfterTransfer1 = [initialBalance - transferAmount1, transferAmount1, 0n];
await transfer(0, 1, transferAmount1, expectedBalancesAfterTransfer1);

const expectedBalancesAfterTransfer2 = [
expectedBalancesAfterTransfer1[0] - transferAmount2,
expectedBalancesAfterTransfer1[1],
transferAmount2,
];
await transfer(0, 2, transferAmount2, expectedBalancesAfterTransfer2);

const expectedBalancesAfterTransfer3 = [
expectedBalancesAfterTransfer2[0],
expectedBalancesAfterTransfer2[1] - transferAmount3,
expectedBalancesAfterTransfer2[2] + transferAmount3,
];
await transfer(1, 2, transferAmount3, expectedBalancesAfterTransfer3);
}, 180_000);
});
78 changes: 77 additions & 1 deletion yarn-project/end-to-end/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AztecNodeConfig, AztecNodeService, getConfigEnvVars } from '@aztec/azte
import { RpcServerConfig, createAztecRPCServer, getConfigEnvVars as getRpcConfigEnvVars } from '@aztec/aztec-rpc';
import {
AccountCollection,
AccountImplementation,
AccountWallet,
AztecAddress,
Contract,
Expand All @@ -15,7 +16,13 @@ import {
generatePublicKey,
getL1ContractAddresses,
} from '@aztec/aztec.js';
import { CircuitsWasm, DeploymentInfo, getContractDeploymentInfo } from '@aztec/circuits.js';
import {
CircuitsWasm,
DeploymentInfo,
PartialContractAddress,
PublicKey,
getContractDeploymentInfo,
} from '@aztec/circuits.js';
import { Schnorr, pedersenPlookupCommitInputs } from '@aztec/circuits.js/barretenberg';
import { DeployL1Contracts, deployL1Contract, deployL1Contracts } from '@aztec/ethereum';
import { ContractAbi } from '@aztec/foundation/abi';
Expand Down Expand Up @@ -321,6 +328,75 @@ export async function setup(numberOfAccounts = 1): Promise<{
};
}

/**
* Deploys a smart contract on L2.
* @param aztecRpcServer - An instance of AztecRPC that will be used for contract deployment.
* @param publicKey - The encryption public key.
* @param abi - The Contract ABI (Application Binary Interface) that defines the contract's interface.
* @param args - An array of arguments to be passed to the contract constructor during deployment.
* @param contractAddressSalt - A random value used as a salt to generate the contract address. If not provided, the contract address will be deterministic.
* @returns An object containing the deployed contract's address and partial contract address.
*/
export async function deployContract(
aztecRpcServer: AztecRPC,
publicKey: PublicKey,
abi: ContractAbi,
args: any[],
contractAddressSalt?: Fr,
) {
const deployer = new ContractDeployer(abi, aztecRpcServer, publicKey);
const deployMethod = deployer.deploy(...args);
await deployMethod.create({ contractAddressSalt });
const tx = deployMethod.send();
expect(await tx.isMined(0, 0.1)).toBeTruthy();
const receipt = await tx.getReceipt();
return { address: receipt.contractAddress!, partialContractAddress: deployMethod.partialContractAddress! };
}

/**
* Represents a function that creates an AccountImplementation object asynchronously.
*
* @param address - The Aztec address associated with the account.
* @param useProperKey - A flag indicating whether the proper key should be used during account creation.
* @param partialAddress - The partial contract address associated with the account.
* @param encryptionPrivateKey - The encryption private key used during account creation.
* @returns A Promise that resolves to an AccountImplementation object.
*/
export type CreateAccountImplFn = (
address: AztecAddress,
useProperKey: boolean,
partialAddress: PartialContractAddress,
encryptionPrivateKey: Buffer,
) => Promise<AccountImplementation>;

/**
* Creates a new account.
* @param aztecRpcServer - The AztecRPC server to interact with.
* @param abi - The ABI (Application Binary Interface) of the account contract.
* @param args - The arguments to pass to the account contract's constructor.
* @param encryptionPrivateKey - The encryption private key used by the account.
* @param useProperKey - A flag indicating whether the proper key should be used during account creation.
* @param createAccountImpl - A function that creates an AccountImplementation object.
* @returns A Promise that resolves to an object containing the created wallet, account address, and partial address.
*/
export async function createNewAccount(
aztecRpcServer: AztecRPC,
abi: ContractAbi,
args: any[],
encryptionPrivateKey: Buffer,
useProperKey: boolean,
createAccountImpl: CreateAccountImplFn,
) {
const salt = Fr.random();
const publicKey = await generatePublicKey(encryptionPrivateKey);
const { address, partialAddress } = await getContractDeploymentInfo(abi, args, salt, publicKey);
await aztecRpcServer.addAccount(encryptionPrivateKey, address, partialAddress);
await deployContract(aztecRpcServer, publicKey, abi, args, salt);
const account = await createAccountImpl(address, useProperKey, partialAddress, encryptionPrivateKey);
const wallet = new AccountWallet(aztecRpcServer, account);
return { wallet, address, partialAddress };
}

/**
* Sets the timestamp of the next block.
* @param rpcUrl - rpc url of the blockchain instance to connect to
Expand Down

0 comments on commit 4a44add

Please sign in to comment.