Skip to content

Commit

Permalink
test(e2e): user calls a public function on a contract deployed by a d…
Browse files Browse the repository at this point in the history
…ifferent user (#1159)
  • Loading branch information
benesjan authored Jul 24, 2023
1 parent 918f7e4 commit 428b66c
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,15 @@ export class AztecRPCServer implements AztecRPC {
);
}

/**
* Return true if the top level block synchronisation is up to date
* This indicates that blocks and transactions are synched even if notes are not
* @returns True if there are no outstanding blocks to be synched
*/
public async isSynchronised() {
return await this.synchroniser.isSynchronised();
}

/**
* Returns true if the account specified by the given address is synched to the latest block
* @param account - The aztec address for which to query the sync status
Expand Down
3 changes: 3 additions & 0 deletions yarn-project/aztec.js/src/aztec_rpc_client/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ export abstract class BaseWallet implements Wallet {
getPublicKeyAndPartialAddress(address: AztecAddress): Promise<[PublicKey, PartialContractAddress]> {
return this.rpc.getPublicKeyAndPartialAddress(address);
}
isSynchronised() {
return this.rpc.isSynchronised();
}
isAccountSynchronised(account: AztecAddress) {
return this.rpc.isAccountSynchronised(account);
}
Expand Down
137 changes: 100 additions & 37 deletions yarn-project/end-to-end/src/e2e_2_rpc_servers.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { AztecNodeService } from '@aztec/aztec-node';
import { AztecRPCServer, EthAddress } from '@aztec/aztec-rpc';
import { AztecRPCServer, EthAddress, Fr } from '@aztec/aztec-rpc';
import { AztecAddress, Wallet } from '@aztec/aztec.js';
import { DebugLogger } from '@aztec/foundation/log';
import { retryUntil } from '@aztec/foundation/retry';
import { ZkTokenContract } from '@aztec/noir-contracts/types';
import { toBigInt } from '@aztec/foundation/serialize';
import { ChildContract, ZkTokenContract } from '@aztec/noir-contracts/types';
import { AztecRPC, TxStatus } from '@aztec/types';

import {
Expand All @@ -25,17 +26,10 @@ describe('e2e_2_rpc_servers', () => {
let userB: AztecAddress;
let logger: DebugLogger;

let contractWithWalletA: ZkTokenContract;
let contractWithWalletB: ZkTokenContract;

const initialBalance = 987n;
const transferAmount1 = 654n;
const transferAmount2 = 323n;

beforeEach(async () => {
// this test can't be run against the sandbox as it requires 2 RPC Servers
// this test can't be run against the sandbox as it requires 2 RPC servers
if (SANDBOX_URL) {
throw new Error(`Test can't be run against the sandbox as 2 rpc servers are required`);
throw new Error(`Test can't be run against the sandbox as 2 RPC servers are required`);
}
let accounts: AztecAddress[] = [];
({ aztecNode, aztecRpcServer: aztecRpcServerA, accounts, wallet: walletA, logger } = await setup(1));
Expand All @@ -47,17 +41,6 @@ describe('e2e_2_rpc_servers', () => {
wallet: walletB,
} = await setupAztecRPCServer(1, aztecNode!, null, undefined, true));
[userB] = accounts;

logger(`Deploying L2 contract...`);
const tx = ZkTokenContract.deploy(aztecRpcServerA, initialBalance, userA).send();
const receipt = await tx.getReceipt();
contractWithWalletA = new ZkTokenContract(receipt.contractAddress!, walletA);
contractWithWalletB = new ZkTokenContract(receipt.contractAddress!, walletB);

await tx.isMined(0, 0.1);
const minedReceipt = await tx.getReceipt();
expect(minedReceipt.status).toEqual(TxStatus.MINED);
logger('L2 contract deployed');
}, 100_000);

afterEach(async () => {
Expand All @@ -70,44 +53,72 @@ describe('e2e_2_rpc_servers', () => {
}
});

const expectBalance = async (wallet: Wallet, owner: AztecAddress, expectedBalance: bigint) => {
// First wait until the corresponding RPC server has synchronised the account
const awaitUserSynchronised = async (wallet: Wallet, owner: AztecAddress) => {
const isUserSynchronised = async () => {
return await wallet.isAccountSynchronised(owner);
};
await retryUntil(isUserSynchronised, owner.toString(), 10);
await retryUntil(isUserSynchronised, `synch of user ${owner.toString()}`, 10);
};

const expectTokenBalance = async (
wallet: Wallet,
tokenAddress: AztecAddress,
owner: AztecAddress,
expectedBalance: bigint,
) => {
// First wait until the corresponding RPC server has synchronised the account
await awaitUserSynchronised(wallet, owner);

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

it('transfers fund from user A to B via RPC Server A followed by transfer from B to A via RPC Server B', async () => {
const deployZkTokenContract = async (initialBalance: bigint, owner: AztecAddress) => {
logger(`Deploying ZkToken contract...`);
const tx = ZkTokenContract.deploy(aztecRpcServerA, initialBalance, owner).send();
const receipt = await tx.getReceipt();
await tx.isMined(0, 0.1);
const minedReceipt = await tx.getReceipt();
expect(minedReceipt.status).toEqual(TxStatus.MINED);
logger('L2 contract deployed');

return receipt.contractAddress!;
};

it('transfers fund from user A to B via RPC server A followed by transfer from B to A via RPC server B', async () => {
const initialBalance = 987n;
const transferAmount1 = 654n;
const transferAmount2 = 323n;

const tokenAddress = await deployZkTokenContract(initialBalance, userA);

// Add account B pub key and partial address to wallet A
const [accountBPubKey, accountBPartialAddress] = await aztecRpcServerB.getPublicKeyAndPartialAddress(userB);
await aztecRpcServerA.addPublicKeyAndPartialAddress(userB, accountBPubKey, accountBPartialAddress);
// Add account A pub key and partial address to wallet B
const [accountAPubKey, accountAPartialAddress] = await aztecRpcServerA.getPublicKeyAndPartialAddress(userA);
await aztecRpcServerB.addPublicKeyAndPartialAddress(userA, accountAPubKey, accountAPartialAddress);

// Add zkToken to rpc server B
// Add zkToken to RPC server B
await aztecRpcServerB.addContracts([
{
abi: ZkTokenContract.abi,
address: contractWithWalletA.address,
address: tokenAddress,
portalContract: EthAddress.ZERO,
},
]);

// Check initial balances and logs are as expected
await expectBalance(walletA, userA, initialBalance);
await expectBalance(walletB, userB, 0n);
await expectTokenBalance(walletA, tokenAddress, userA, initialBalance);
await expectTokenBalance(walletB, tokenAddress, userB, 0n);
await expectsNumOfEncryptedLogsInTheLastBlockToBe(aztecNode, 1);
await expectUnencryptedLogsFromLastBlockToBe(aztecNode, ['Balance set in constructor']);

// Transfer funds from A to B via rpc server A
// Transfer funds from A to B via RPC server A
const contractWithWalletA = new ZkTokenContract(tokenAddress, walletA);
const txAToB = contractWithWalletA.methods.transfer(transferAmount1, userA, userB).send({ origin: userA });

await txAToB.isMined(0, 0.1);
Expand All @@ -116,12 +127,13 @@ describe('e2e_2_rpc_servers', () => {
expect(receiptAToB.status).toBe(TxStatus.MINED);

// Check balances and logs are as expected
await expectBalance(walletA, userA, initialBalance - transferAmount1);
await expectBalance(walletB, userB, transferAmount1);
await expectTokenBalance(walletA, tokenAddress, userA, initialBalance - transferAmount1);
await expectTokenBalance(walletB, tokenAddress, userB, transferAmount1);
await expectsNumOfEncryptedLogsInTheLastBlockToBe(aztecNode, 2);
await expectUnencryptedLogsFromLastBlockToBe(aztecNode, ['Coins transferred']);

// Transfer funds from B to A via rpc server B
// Transfer funds from B to A via RPC server B
const contractWithWalletB = new ZkTokenContract(tokenAddress, walletB);
const txBToA = contractWithWalletB.methods.transfer(transferAmount2, userB, userA).send({ origin: userB });

await txBToA.isMined(0, 0.1);
Expand All @@ -130,9 +142,60 @@ describe('e2e_2_rpc_servers', () => {
expect(receiptBToA.status).toBe(TxStatus.MINED);

// Check balances and logs are as expected
await expectBalance(walletA, userA, initialBalance - transferAmount1 + transferAmount2);
await expectBalance(walletB, userB, transferAmount1 - transferAmount2);
await expectTokenBalance(walletA, tokenAddress, userA, initialBalance - transferAmount1 + transferAmount2);
await expectTokenBalance(walletB, tokenAddress, userB, transferAmount1 - transferAmount2);
await expectsNumOfEncryptedLogsInTheLastBlockToBe(aztecNode, 2);
await expectUnencryptedLogsFromLastBlockToBe(aztecNode, ['Coins transferred']);
}, 120_000);

const deployChildContractViaServerA = async () => {
logger(`Deploying Child contract...`);
const tx = ChildContract.deploy(aztecRpcServerA).send();
const receipt = await tx.getReceipt();
await tx.isMined(0, 0.1);
const minedReceipt = await tx.getReceipt();
expect(minedReceipt.status).toEqual(TxStatus.MINED);
logger('Child contract deployed');

return receipt.contractAddress!;
};

const awaitServerSynchronised = async (server: AztecRPC) => {
const isServerSynchronised = async () => {
return await server.isSynchronised();
};
await retryUntil(isServerSynchronised, 'server sync', 10);
};

const getChildStoredValue = (child: { address: AztecAddress }, aztecRpcServer: AztecRPC) =>
aztecRpcServer.getPublicStorageAt(child.address, new Fr(1)).then(x => toBigInt(x!));

it('user calls a public function on a contract deployed by a different user using a different RPC server', async () => {
const childAddress = await deployChildContractViaServerA();

await awaitServerSynchronised(aztecRpcServerA);

// Add Child to RPC server B
await aztecRpcServerB.addContracts([
{
abi: ChildContract.abi,
address: childAddress,
portalContract: EthAddress.ZERO,
},
]);

const newValueToSet = 256n;

const childContractWithWalletB = new ChildContract(childAddress, walletB);
const tx = childContractWithWalletB.methods.pubStoreValue(newValueToSet).send({ origin: userB });
await tx.isMined(0, 0.1);

const receipt = await tx.getReceipt();
expect(receipt.status).toBe(TxStatus.MINED);

await awaitServerSynchronised(aztecRpcServerA);

const storedValue = await getChildStoredValue({ address: childAddress }, aztecRpcServerB);
expect(storedValue).toBe(newValueToSet);
}, 60_000);
});
1 change: 1 addition & 0 deletions yarn-project/types/src/interfaces/aztec_rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,6 @@ export interface AztecRPC {
partialAddress: PartialContractAddress,
): Promise<void>;
getPublicKeyAndPartialAddress(address: AztecAddress): Promise<[PublicKey, PartialContractAddress]>;
isSynchronised(): Promise<boolean>;
isAccountSynchronised(account: AztecAddress): Promise<boolean>;
}

0 comments on commit 428b66c

Please sign in to comment.