Skip to content

Commit

Permalink
fix: call public fn in contract constructor (#2549)
Browse files Browse the repository at this point in the history
This PR aims to enable calling of public functions in Noir contract
constructors. In order to fix this issue this PR does the following two
changes:

- previously the `PublicExecutor` only looked at deployed contracts. Now
it also looks at the contracts being deployed in the current block
- the public kernel simulator 'lost' the contract's address so when
publishing the block it would have contract data as all zeros. This PR
updates the common initialisation function to pass on `new_contracts`.

Fix #2509
Related to #2249 

# Checklist:
Remove the checklist to signal you've completed it. Enable auto-merge if
the PR is ready to merge.
- [ ] If the pull request requires a cryptography review (e.g.
cryptographic algorithm implementations) I have added the 'crypto' tag.
- [x] I have reviewed my diff in github, line by line and removed
unexpected formatting changes, testing logs, or commented-out code.
- [x] Every change is related to the PR description.
- [x] I have
[linked](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue)
this pull request to relevant issues (if any exist).
  • Loading branch information
alexghr authored Oct 3, 2023
1 parent a5b763f commit 14ab6d6
Show file tree
Hide file tree
Showing 29 changed files with 212 additions and 176 deletions.
4 changes: 3 additions & 1 deletion circuits/cpp/src/aztec3/circuits/kernel/public/common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ void common_initialise_end_values(PublicKernelInputs<NT> const& public_kernel_in
// Public kernel does not modify encrypted logs values --> we just copy them to output
end.encrypted_logs_hash = start.encrypted_logs_hash;
end.encrypted_log_preimages_length = start.encrypted_log_preimages_length;

end.new_contracts = start.new_contracts;
}

/**
Expand Down Expand Up @@ -70,4 +72,4 @@ void validate_this_public_call_hash(DummyBuilder& builder,
") at the top of the call stack"),
CircuitErrorCode::PUBLIC_KERNEL__CALCULATED_PUBLIC_CALL_HASH_AND_PROVIDED_PUBLIC_CALL_HASH_MISMATCH);
};
} // namespace aztec3::circuits::kernel::public_kernel
} // namespace aztec3::circuits::kernel::public_kernel
2 changes: 1 addition & 1 deletion docs/docs/dev_docs/tutorials/writing_token_contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ Internal functions are functions that can only be called by this contract. The f

#### `_initialize`

This function is called via the [constructor](#constructor). Note that it is not actually marked `internal` right now--this is because this functionality is still being worked on.
This function is called via the [constructor](#constructor).

This function sets the creator of the contract (passed as `msg_sender` from the constructor) as the admin and makes them a minter.

Expand Down
27 changes: 7 additions & 20 deletions yarn-project/canary/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,40 +66,27 @@ export async function deployAndInitializeTokenAndBridgeContracts(
});

// deploy l2 token
const deployTx = TokenContract.deploy(wallet).send();

// deploy l2 token bridge and attach to the portal
const bridgeTx = TokenBridgeContract.deploy(wallet).send({
portalContract: tokenPortalAddress,
contractAddressSalt: Fr.random(),
});
const deployTx = TokenContract.deploy(wallet, owner).send();

// now wait for the deploy txs to be mined. This way we send all tx in the same rollup.
const deployReceipt = await deployTx.wait();
if (deployReceipt.status !== TxStatus.MINED) throw new Error(`Deploy token tx status is ${deployReceipt.status}`);
const token = await TokenContract.at(deployReceipt.contractAddress!, wallet);

// deploy l2 token bridge and attach to the portal
const bridgeTx = TokenBridgeContract.deploy(wallet, token.address).send({
portalContract: tokenPortalAddress,
contractAddressSalt: Fr.random(),
});

const bridgeReceipt = await bridgeTx.wait();
if (bridgeReceipt.status !== TxStatus.MINED) throw new Error(`Deploy bridge tx status is ${bridgeReceipt.status}`);
const bridge = await TokenBridgeContract.at(bridgeReceipt.contractAddress!, wallet);
await bridge.attach(tokenPortalAddress);
const bridgeAddress = bridge.address.toString() as `0x${string}`;

// initialize l2 token
const initializeTx = token.methods._initialize(owner).send();

// initialize bridge
const initializeBridgeTx = bridge.methods._initialize(token.address).send();

// now we wait for the txs to be mined. This way we send all tx in the same rollup.
const initializeReceipt = await initializeTx.wait();
if (initializeReceipt.status !== TxStatus.MINED)
throw new Error(`Initialize token tx status is ${initializeReceipt.status}`);
if ((await token.methods.admin().view()) !== owner.toBigInt()) throw new Error(`Token admin is not ${owner}`);

const initializeBridgeReceipt = await initializeBridgeTx.wait();
if (initializeBridgeReceipt.status !== TxStatus.MINED)
throw new Error(`Initialize token bridge tx status is ${initializeBridgeReceipt.status}`);
if ((await bridge.methods.token().view()) !== token.address.toBigInt())
throw new Error(`Bridge token is not ${token.address}`);

Expand Down
5 changes: 3 additions & 2 deletions yarn-project/end-to-end/src/canary/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,13 +176,14 @@ export const browserTestSuite = (setup: () => Server, pageLogger: AztecJs.DebugL
}
const [owner] = await getSandboxAccountsWallets(pxe);
const ownerAddress = owner.getAddress();
const tx = new DeployMethod(accounts[0].publicKey, pxe, TokenContractAbi).send();
const tx = new DeployMethod(accounts[0].publicKey, pxe, TokenContractAbi, [
owner.getCompleteAddress(),
]).send();
await tx.wait();
const receipt = await tx.getReceipt();
console.log(`Contract Deployed: ${receipt.contractAddress}`);

const token = await Contract.at(receipt.contractAddress!, TokenContractAbi, owner);
await token.methods._initialize(ownerAddress).send().wait();
const secret = Fr.random();
const secretHash = await computeMessageSecretHash(secret);
const mintPrivateReceipt = await token.methods.mint_private(initialBalance, secretHash).send().wait();
Expand Down
7 changes: 1 addition & 6 deletions yarn-project/end-to-end/src/canary/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export const cliTestSuite = (
const ownerAddress = AztecAddress.fromString(foundAddress!);

debug('Deploy Token Contract using created account.');
await run(`deploy TokenContractAbi --salt 0`);
await run(`deploy TokenContractAbi --salt 0 --args ${ownerAddress}`);
const loggedAddress = findInLogs(/Contract\sdeployed\sat\s+(?<address>0x[a-fA-F0-9]+)/)?.groups?.address;
expect(loggedAddress).toBeDefined();
contractAddress = AztecAddress.fromString(loggedAddress!);
Expand All @@ -131,11 +131,6 @@ export const cliTestSuite = (
const checkResult = findInLogs(/Contract\sfound\sat\s+(?<address>0x[a-fA-F0-9]+)/)?.groups?.address;
expect(checkResult).toEqual(deployedContract?.contractAddress.toString());

debug('Initialize token contract.');
await run(
`send _initialize --args ${ownerAddress} --contract-abi TokenContractAbi --contract-address ${contractAddress.toString()} --private-key ${privKey}`,
);

const secret = Fr.random();
const secretHash = await computeMessageSecretHash(secret);

Expand Down
3 changes: 1 addition & 2 deletions yarn-project/end-to-end/src/e2e_2_pxes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,7 @@ describe('e2e_2_pxes', () => {

const deployTokenContract = async (initialAdminBalance: bigint, admin: AztecAddress, pxe: PXE) => {
logger(`Deploying Token contract...`);
const contract = await TokenContract.deploy(walletA).send().deployed();
expect((await contract.methods._initialize(admin).send().wait()).status).toBe(TxStatus.MINED);
const contract = await TokenContract.deploy(walletA, admin).send().deployed();

if (initialAdminBalance > 0n) {
await mintTokens(contract, admin, initialAdminBalance, pxe);
Expand Down
59 changes: 51 additions & 8 deletions yarn-project/end-to-end/src/e2e_block_building.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { BatchCall, ContractDeployer, Fr, Wallet, isContractDeployed } from '@aztec/aztec.js';
import {
BatchCall,
ContractDeployer,
ContractFunctionInteraction,
Fr,
Wallet,
isContractDeployed,
} from '@aztec/aztec.js';
import { CircuitsWasm } from '@aztec/circuits.js';
import { pedersenPlookupCommitInputs } from '@aztec/circuits.js/barretenberg';
import { DebugLogger } from '@aztec/foundation/log';
import { TestContractAbi } from '@aztec/noir-contracts/artifacts';
import { TestContract } from '@aztec/noir-contracts/types';
import { TestContract, TokenContract } from '@aztec/noir-contracts/types';
import { PXE, TxStatus } from '@aztec/types';

import times from 'lodash.times';
Expand All @@ -13,14 +20,20 @@ import { setup } from './fixtures/utils.js';
describe('e2e_block_building', () => {
let pxe: PXE;
let logger: DebugLogger;
let wallet: Wallet;
let owner: Wallet;
let minter: Wallet;
let teardown: () => Promise<void>;

describe('multi-txs block', () => {
const abi = TestContractAbi;

beforeAll(async () => {
({ teardown, pxe, logger, wallet } = await setup(1));
({
teardown,
pxe,
logger,
wallets: [owner, minter],
} = await setup(2));
}, 100_000);

afterAll(() => teardown());
Expand All @@ -29,7 +42,7 @@ describe('e2e_block_building', () => {
// Assemble N contract deployment txs
// We need to create them sequentially since we cannot have parallel calls to a circuit
const TX_COUNT = 8;
const deployer = new ContractDeployer(abi, wallet);
const deployer = new ContractDeployer(abi, owner);
const methods = times(TX_COUNT, () => deployer.deploy());

for (const i in methods) {
Expand All @@ -51,6 +64,36 @@ describe('e2e_block_building', () => {
const areDeployed = await Promise.all(receipts.map(r => isContractDeployed(pxe, r.contractAddress!)));
expect(areDeployed).toEqual(times(TX_COUNT, () => true));
}, 60_000);

it('can call public function from different tx in same block', async () => {
// Deploy a contract in the first transaction
// In the same block, call a public method on the contract
const deployer = TokenContract.deploy(owner, owner.getCompleteAddress());
await deployer.create();

// We can't use `TokenContract.at` to call a function because it checks the contract is deployed
// but we are in the same block as the deployment transaction
const callInteraction = new ContractFunctionInteraction(
owner,
deployer.completeAddress!.address,
TokenContract.abi.functions.find(x => x.name === 'set_minter')!,
[minter.getCompleteAddress(), true],
);

await deployer.simulate({});
await callInteraction.simulate({
// we have to skip simulation of public calls simulation is done on individual transactions
// and the tx deploying the contract might go in the same block as this one
skipPublicSimulation: true,
});

const [deployTxReceipt, callTxReceipt] = await Promise.all([
deployer.send().wait(),
callInteraction.send({ skipPublicSimulation: true }).wait(),
]);

expect(deployTxReceipt.blockNumber).toEqual(callTxReceipt.blockNumber);
}, 60_000);
});

// Regressions for https://github.com/AztecProtocol/aztec-packages/issues/2502
Expand All @@ -59,8 +102,8 @@ describe('e2e_block_building', () => {
let teardown: () => Promise<void>;

beforeAll(async () => {
({ teardown, pxe, logger, wallet } = await setup(1));
contract = await TestContract.deploy(wallet).send().deployed();
({ teardown, pxe, logger, wallet: owner } = await setup(1));
contract = await TestContract.deploy(owner).send().deployed();
}, 100_000);

afterAll(() => teardown());
Expand All @@ -86,7 +129,7 @@ describe('e2e_block_building', () => {
it('drops tx with two equal nullifiers', async () => {
const nullifier = Fr.random();
const calls = times(2, () => contract.methods.emit_nullifier(nullifier).request());
await expect(new BatchCall(wallet, calls).send().wait()).rejects.toThrowError(/dropped/);
await expect(new BatchCall(owner, calls).send().wait()).rejects.toThrowError(/dropped/);
});

it('drops tx with private nullifier already emitted from public on the same block', async () => {
Expand Down
4 changes: 1 addition & 3 deletions yarn-project/end-to-end/src/e2e_escrow_contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,7 @@ describe('e2e_escrow_contract', () => {
logger(`Escrow contract deployed at ${escrowContract.address}`);

// Deploy Private Token contract and mint funds for the escrow contract
token = await TokenContract.deploy(wallet).send().deployed();

expect((await token.methods._initialize(owner).send().wait()).status).toBe(TxStatus.MINED);
token = await TokenContract.deploy(wallet, owner).send().deployed();

const mintAmount = 100n;
const secret = Fr.random();
Expand Down
6 changes: 2 additions & 4 deletions yarn-project/end-to-end/src/e2e_lending_contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ describe('e2e_lending_contract', () => {

{
logger(`Deploying collateral asset feed contract...`);
const receipt = await waitForSuccess(TokenContract.deploy(wallet).send());
const receipt = await waitForSuccess(TokenContract.deploy(wallet, accounts[0]).send());
logger(`Collateral asset deployed to ${receipt.contractAddress}`);
collateralAsset = await TokenContract.at(receipt.contractAddress!, wallet);
}

{
logger(`Deploying stable coin contract...`);
const receipt = await waitForSuccess(TokenContract.deploy(wallet).send());
const receipt = await waitForSuccess(TokenContract.deploy(wallet, accounts[0]).send());
logger(`Stable coin asset deployed to ${receipt.contractAddress}`);
stableCoin = await TokenContract.at(receipt.contractAddress!, wallet);
}
Expand All @@ -69,9 +69,7 @@ describe('e2e_lending_contract', () => {
lendingContract = await LendingContract.at(receipt.contractAddress!, wallet);
}

await waitForSuccess(collateralAsset.methods._initialize(accounts[0]).send());
await waitForSuccess(collateralAsset.methods.set_minter(lendingContract.address, true).send());
await waitForSuccess(stableCoin.methods._initialize(accounts[0]).send());
await waitForSuccess(stableCoin.methods.set_minter(lendingContract.address, true).send());

return { priceFeedContract, lendingContract, collateralAsset, stableCoin };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,10 @@ describe('e2e_multiple_accounts_1_enc_key', () => {
}

logger(`Deploying Token...`);
const token = await TokenContract.deploy(wallets[0]).send().deployed();
const token = await TokenContract.deploy(wallets[0], accounts[0]).send().deployed();
tokenAddress = token.address;
logger(`Token deployed at ${tokenAddress}`);

expect((await token.methods._initialize(accounts[0]).send().wait()).status).toBe(TxStatus.MINED);

const secret = Fr.random();
const secretHash = await computeMessageSecretHash(secret);

Expand Down
5 changes: 2 additions & 3 deletions yarn-project/end-to-end/src/e2e_sandbox_example.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,12 @@ describe('e2e_sandbox_example', () => {
const initialSupply = 1_000_000n;

logger(`Deploying token contract minting an initial ${initialSupply} tokens to Alice...`);
const contract = await TokenContract.deploy(pxe).send().deployed();
const contract = await TokenContract.deploy(pxe, alice).send().deployed();

// Create the contract abstraction and link to Alice's wallet for future signing
const tokenContractAlice = await TokenContract.at(contract.address, accounts[0]);

// Initialize the contract and add Bob as a minter
await tokenContractAlice.methods._initialize(alice).send().wait();
// add Bob as a minter
await tokenContractAlice.methods.set_minter(bob, true).send().wait();

logger(`Contract successfully deployed at address ${contract.address.toShortString()}`);
Expand Down
3 changes: 1 addition & 2 deletions yarn-project/end-to-end/src/e2e_token_contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,14 @@ describe('e2e_token_contract', () => {
beforeAll(async () => {
({ teardown, logger, wallets, accounts } = await setup(3));

asset = await TokenContract.deploy(wallets[0]).send().deployed();
asset = await TokenContract.deploy(wallets[0], accounts[0]).send().deployed();
logger(`Token deployed to ${asset.address}`);
tokenSim = new TokenSimulator(
asset,
logger,
accounts.map(a => a.address),
);

await asset.methods._initialize(accounts[0].address).send().wait();
expect(await asset.methods.admin().view()).toBe(accounts[0].address.toBigInt());

asset.abi.functions.forEach(fn => {
Expand Down
26 changes: 6 additions & 20 deletions yarn-project/end-to-end/src/fixtures/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,40 +400,26 @@ export async function deployAndInitializeTokenAndBridgeContracts(
});

// deploy l2 token
const deployTx = TokenContract.deploy(wallet).send();

// deploy l2 token bridge and attach to the portal
const bridgeTx = TokenBridgeContract.deploy(wallet).send({
portalContract: tokenPortalAddress,
contractAddressSalt: Fr.random(),
});
const deployTx = TokenContract.deploy(wallet, owner).send();

// now wait for the deploy txs to be mined. This way we send all tx in the same rollup.
const deployReceipt = await deployTx.wait();
if (deployReceipt.status !== TxStatus.MINED) throw new Error(`Deploy token tx status is ${deployReceipt.status}`);
const token = await TokenContract.at(deployReceipt.contractAddress!, wallet);

// deploy l2 token bridge and attach to the portal
const bridgeTx = TokenBridgeContract.deploy(wallet, token.address).send({
portalContract: tokenPortalAddress,
contractAddressSalt: Fr.random(),
});
const bridgeReceipt = await bridgeTx.wait();
if (bridgeReceipt.status !== TxStatus.MINED) throw new Error(`Deploy bridge tx status is ${bridgeReceipt.status}`);
const bridge = await TokenBridgeContract.at(bridgeReceipt.contractAddress!, wallet);
await bridge.attach(tokenPortalAddress);
const bridgeAddress = bridge.address.toString() as `0x${string}`;

// initialize l2 token
const initializeTx = token.methods._initialize(owner).send();

// initialize bridge
const initializeBridgeTx = bridge.methods._initialize(token.address).send();

// now we wait for the txs to be mined. This way we send all tx in the same rollup.
const initializeReceipt = await initializeTx.wait();
if (initializeReceipt.status !== TxStatus.MINED)
throw new Error(`Initialize token tx status is ${initializeReceipt.status}`);
if ((await token.methods.admin().view()) !== owner.toBigInt()) throw new Error(`Token admin is not ${owner}`);

const initializeBridgeReceipt = await initializeBridgeTx.wait();
if (initializeBridgeReceipt.status !== TxStatus.MINED)
throw new Error(`Initialize token bridge tx status is ${initializeBridgeReceipt.status}`);
if ((await bridge.methods.token().view()) !== token.address.toBigInt())
throw new Error(`Bridge token is not ${token.address}`);

Expand Down
Loading

0 comments on commit 14ab6d6

Please sign in to comment.