diff --git a/circuits/cpp/src/aztec3/circuits/kernel/public/common.cpp b/circuits/cpp/src/aztec3/circuits/kernel/public/common.cpp index bd08b4c5bdb..87e9df7d6dc 100644 --- a/circuits/cpp/src/aztec3/circuits/kernel/public/common.cpp +++ b/circuits/cpp/src/aztec3/circuits/kernel/public/common.cpp @@ -35,6 +35,8 @@ void common_initialise_end_values(PublicKernelInputs 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; } /** @@ -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 \ No newline at end of file +} // namespace aztec3::circuits::kernel::public_kernel diff --git a/docs/docs/dev_docs/tutorials/writing_token_contract.md b/docs/docs/dev_docs/tutorials/writing_token_contract.md index 872fe819aba..b5857dfc305 100644 --- a/docs/docs/dev_docs/tutorials/writing_token_contract.md +++ b/docs/docs/dev_docs/tutorials/writing_token_contract.md @@ -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. diff --git a/yarn-project/canary/src/utils.ts b/yarn-project/canary/src/utils.ts index 4d89e1a7b4e..efb29ebb328 100644 --- a/yarn-project/canary/src/utils.ts +++ b/yarn-project/canary/src/utils.ts @@ -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}`); diff --git a/yarn-project/end-to-end/src/canary/browser.ts b/yarn-project/end-to-end/src/canary/browser.ts index bf145149e99..c2b7a642cd0 100644 --- a/yarn-project/end-to-end/src/canary/browser.ts +++ b/yarn-project/end-to-end/src/canary/browser.ts @@ -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(); diff --git a/yarn-project/end-to-end/src/canary/cli.ts b/yarn-project/end-to-end/src/canary/cli.ts index 1974ee6573e..2bac457365d 100644 --- a/yarn-project/end-to-end/src/canary/cli.ts +++ b/yarn-project/end-to-end/src/canary/cli.ts @@ -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+(?
0x[a-fA-F0-9]+)/)?.groups?.address; expect(loggedAddress).toBeDefined(); contractAddress = AztecAddress.fromString(loggedAddress!); @@ -131,11 +131,6 @@ export const cliTestSuite = ( const checkResult = findInLogs(/Contract\sfound\sat\s+(?
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); diff --git a/yarn-project/end-to-end/src/e2e_2_pxes.test.ts b/yarn-project/end-to-end/src/e2e_2_pxes.test.ts index f19ee8aebca..e3f8391ff52 100644 --- a/yarn-project/end-to-end/src/e2e_2_pxes.test.ts +++ b/yarn-project/end-to-end/src/e2e_2_pxes.test.ts @@ -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); diff --git a/yarn-project/end-to-end/src/e2e_block_building.test.ts b/yarn-project/end-to-end/src/e2e_block_building.test.ts index a7e589d08ff..9336b1fc264 100644 --- a/yarn-project/end-to-end/src/e2e_block_building.test.ts +++ b/yarn-project/end-to-end/src/e2e_block_building.test.ts @@ -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'; @@ -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; 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()); @@ -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) { @@ -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 @@ -59,8 +102,8 @@ describe('e2e_block_building', () => { let teardown: () => Promise; 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()); @@ -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 () => { diff --git a/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts b/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts index ad0315c5fa0..77d9db582d2 100644 --- a/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts @@ -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(); diff --git a/yarn-project/end-to-end/src/e2e_lending_contract.test.ts b/yarn-project/end-to-end/src/e2e_lending_contract.test.ts index 9ba8808215d..2b9469b922b 100644 --- a/yarn-project/end-to-end/src/e2e_lending_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_lending_contract.test.ts @@ -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); } @@ -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 }; diff --git a/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts b/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts index b1069faedc0..61a50b53866 100644 --- a/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts +++ b/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts @@ -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); diff --git a/yarn-project/end-to-end/src/e2e_sandbox_example.test.ts b/yarn-project/end-to-end/src/e2e_sandbox_example.test.ts index c3ddec3f42a..665dc3714ad 100644 --- a/yarn-project/end-to-end/src/e2e_sandbox_example.test.ts +++ b/yarn-project/end-to-end/src/e2e_sandbox_example.test.ts @@ -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()}`); diff --git a/yarn-project/end-to-end/src/e2e_token_contract.test.ts b/yarn-project/end-to-end/src/e2e_token_contract.test.ts index c0d6a38ef0e..ad35a01bc68 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract.test.ts @@ -40,7 +40,7 @@ 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, @@ -48,7 +48,6 @@ describe('e2e_token_contract', () => { 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 => { diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index 43c02e7bffc..2955f2b2554 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -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}`); diff --git a/yarn-project/end-to-end/src/guides/dapp_testing.test.ts b/yarn-project/end-to-end/src/guides/dapp_testing.test.ts index d0f28f6eac5..5c1bfdebed5 100644 --- a/yarn-project/end-to-end/src/guides/dapp_testing.test.ts +++ b/yarn-project/end-to-end/src/guides/dapp_testing.test.ts @@ -33,8 +33,7 @@ describe('guides/dapp/testing', () => { // docs:end:in-proc-sandbox owner = await createAccount(pxe); recipient = await createAccount(pxe); - token = await TokenContract.deploy(owner).send().deployed(); - await token.methods._initialize(owner.getAddress()).send().wait(); + token = await TokenContract.deploy(owner, owner.getCompleteAddress()).send().deployed(); }, 60_000); // docs:start:stop-in-proc-sandbox @@ -77,8 +76,7 @@ describe('guides/dapp/testing', () => { pxe = createPXEClient(SANDBOX_URL); owner = await createAccount(pxe); recipient = await createAccount(pxe); - token = await TokenContract.deploy(owner).send().deployed(); - await token.methods._initialize(owner.getAddress()).send().wait(); + token = await TokenContract.deploy(owner, owner.getCompleteAddress()).send().deployed(); }, 30_000); it('increases recipient funds on mint', async () => { @@ -110,8 +108,7 @@ describe('guides/dapp/testing', () => { // docs:start:use-existing-wallets pxe = createPXEClient(SANDBOX_URL); [owner, recipient] = await getSandboxAccountsWallets(pxe); - token = await TokenContract.deploy(owner).send().deployed(); - await token.methods._initialize(owner.getAddress()).send().wait(); + token = await TokenContract.deploy(owner, owner.getCompleteAddress()).send().deployed(); // docs:end:use-existing-wallets }, 30_000); @@ -168,8 +165,7 @@ describe('guides/dapp/testing', () => { owner = await createAccount(pxe); recipient = await createAccount(pxe); testContract = await TestContract.deploy(owner).send().deployed(); - token = await TokenContract.deploy(owner).send().deployed(); - await token.methods._initialize(owner.getAddress()).send().wait(); + token = await TokenContract.deploy(owner, owner.getCompleteAddress()).send().deployed(); const ownerAddress = owner.getAddress(); const mintAmount = 100n; diff --git a/yarn-project/end-to-end/src/guides/up_quick_start.sh b/yarn-project/end-to-end/src/guides/up_quick_start.sh index 2ad7e7eab78..c97dd0d5e29 100755 --- a/yarn-project/end-to-end/src/guides/up_quick_start.sh +++ b/yarn-project/end-to-end/src/guides/up_quick_start.sh @@ -12,17 +12,12 @@ ALICE_PRIVATE_KEY="0x2153536ff6628eee01cf4024889ff977a18d9fa61d0e414422f7681cf08 # docs:start:deploy aztec-cli deploy \ TokenContractAbi \ - --salt 0 + --salt 0 \ + --args $ALICE -aztec-cli check-deploy --contract-address 0x2d23acefa3ce07b3c308caf78d86c064cdf8957bcea48b38753cf58441796c8c +aztec-cli check-deploy --contract-address 0x2219e810bff6e04abdefce9f91c2d1dd1e4d52fafa602def3c90b77f4331feca -CONTRACT="0x2d23acefa3ce07b3c308caf78d86c064cdf8957bcea48b38753cf58441796c8c" - -aztec-cli send _initialize \ - --args $ALICE \ - --contract-abi TokenContractAbi \ - --contract-address $CONTRACT \ - --private-key $ALICE_PRIVATE_KEY +CONTRACT="0x2219e810bff6e04abdefce9f91c2d1dd1e4d52fafa602def3c90b77f4331feca" # docs:end:deploy # docs:start:mint-private @@ -77,7 +72,7 @@ aztec-cli get-logs # Test end result BOB_BALANCE=$(aztec-cli call balance_of_private --args $BOB --contract-abi TokenContractAbi --contract-address $CONTRACT) -if ! echo $BOB_BALANCE | grep -q 500; then +if ! echo $BOB_BALANCE | grep -q 500; then echo "Incorrect Bob balance after transaction (expected 500 but got $BOB_BALANCE)" exit 1 -fi \ No newline at end of file +fi diff --git a/yarn-project/end-to-end/src/guides/up_quick_start.test.ts b/yarn-project/end-to-end/src/guides/up_quick_start.test.ts index 855dbcaf4ff..15d135e2459 100644 --- a/yarn-project/end-to-end/src/guides/up_quick_start.test.ts +++ b/yarn-project/end-to-end/src/guides/up_quick_start.test.ts @@ -10,7 +10,7 @@ describe('guides/up_quick_start', () => { `DEBUG="aztec:*" PXE_HOST=\${SANDBOX_URL:-http://localhost:8080} PATH=$PATH:../node_modules/.bin ./src/guides/up_quick_start.sh`, { shell: '/bin/bash', - stdio: 'pipe', + stdio: 'inherit', }, ); }, 90_000); diff --git a/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts b/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts index 4e66831c2d9..f61e3c4c881 100644 --- a/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts +++ b/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts @@ -61,9 +61,8 @@ describe('guides/writing_an_account_contract', () => { logger(`Deployed account contract at ${address}`); // docs:start:account-contract-works - const token = await TokenContract.deploy(wallet).send().deployed(); + const token = await TokenContract.deploy(wallet, { address }).send().deployed(); logger(`Deployed token contract at ${token.address}`); - await token.methods._initialize({ address }).send().wait(); const secret = Fr.random(); const secretHash = await computeMessageSecretHash(secret); diff --git a/yarn-project/end-to-end/src/sample-dapp/deploy.mjs b/yarn-project/end-to-end/src/sample-dapp/deploy.mjs index 7d9c655ac19..866c14889cd 100644 --- a/yarn-project/end-to-end/src/sample-dapp/deploy.mjs +++ b/yarn-project/end-to-end/src/sample-dapp/deploy.mjs @@ -11,8 +11,7 @@ async function main() { const pxe = createPXEClient(SANDBOX_URL); const [owner] = await getSandboxAccountsWallets(pxe); - const token = await Contract.deploy(pxe, TokenContractAbi, []).send().deployed(); - await token.withWallet(owner).methods._initialize(owner.getAddress()).send().wait(); + const token = await Contract.deploy(pxe, TokenContractAbi, [owner.getCompleteAddress()]).send().deployed(); console.log(`Token deployed at ${token.address.toString()}`); diff --git a/yarn-project/end-to-end/src/sample-dapp/index.test.mjs b/yarn-project/end-to-end/src/sample-dapp/index.test.mjs index 04d0bc2a227..19d49926e45 100644 --- a/yarn-project/end-to-end/src/sample-dapp/index.test.mjs +++ b/yarn-project/end-to-end/src/sample-dapp/index.test.mjs @@ -10,8 +10,7 @@ describe('token', () => { owner = await createAccount(pxe); recipient = await createAccount(pxe); - token = await Contract.deploy(owner, TokenContractAbi, []).send().deployed(); - await token.methods._initialize(owner.getAddress()).send().wait(); + token = await Contract.deploy(owner, TokenContractAbi, [owner.getCompleteAddress()]).send().deployed(); const initialBalance = 20n; const secret = Fr.random(); diff --git a/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr index ac5563fa835..f64c89c37dd 100644 --- a/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr @@ -2,7 +2,7 @@ mod util; mod token_interface; // Minimal implementation of the token bridge that can move funds between L1 <> L2. -// The bridge has a corresponding Portal contract on L1 that it is attached to +// The bridge has a corresponding Portal contract on L1 that it is attached to // And corresponds to a Token on L2 that uses the `AuthWit` accounts pattern. // Bridge has to be set as a minter on the token before it can be used @@ -40,10 +40,9 @@ contract TokenBridge { // Constructs the contract. #[aztec(private)] - fn constructor() { - // Currently not possible to execute public calls from constructor as code not yet available to sequencer. - // let selector = compute_selector("_initialize((Field))"); - // let _callStackItem = context.call_public_function(context.this_address(), selector, [context.msg_sender()]); + fn constructor(token: AztecAddress) { + let selector = compute_selector("_initialize((Field))"); + context.call_public_function(context.this_address(), selector, [token.address]); } // docs:start:claim_public @@ -60,7 +59,7 @@ contract TokenBridge { // Consume message and emit nullifier context.consume_l1_to_l2_message(msg_key, content_hash, secret); - // Mint tokens + // Mint tokens Token::at(storage.token.read()).mint_public(context, to.address, amount); 1 @@ -69,7 +68,7 @@ contract TokenBridge { // docs:start:exit_to_l1_public // Burns the appropriate amount of tokens and creates a L2 to L1 withdraw message publicly - // Requires `msg.sender` to give approval to the bridge to burn tokens on their behalf using witness signatures + // Requires `msg.sender` to give approval to the bridge to burn tokens on their behalf using witness signatures #[aztec(public)] fn exit_to_l1_public( recipient: EthereumAddress, // ethereum address to withdraw to @@ -79,11 +78,11 @@ contract TokenBridge { ) -> Field { // Send an L2 to L1 message let content = get_withdraw_content_hash(recipient.address, amount, callerOnL1.address); - context.message_portal(content); + context.message_portal(content); - // Burn tokens + // Burn tokens Token::at(storage.token.read()).burn_public(context, context.msg_sender(), amount, nonce); - + 1 } // docs:end:exit_to_l1_public @@ -102,10 +101,10 @@ contract TokenBridge { let content_hash = get_mint_private_content_hash(amount, secret_hash_for_redeeming_minted_notes, canceller.address); context.consume_l1_to_l2_message(msg_key, content_hash, secret_for_L1_to_L2_message_consumption); - // Mint tokens on L2 - // `mint_private` on token is public. So we call an internal public function + // Mint tokens on L2 + // `mint_private` on token is public. So we call an internal public function // which then calls the public method on the token contract. - // Since the secret_hash is passed, no secret is leaked. + // Since the secret_hash is passed, no secret is leaked. context.call_public_function( context.this_address(), compute_selector("_call_mint_on_token(Field,Field)"), @@ -117,7 +116,7 @@ contract TokenBridge { // docs:start:exit_to_l1_private // Burns the appropriate amount of tokens and creates a L2 to L1 withdraw message privately - // Requires `msg.sender` (caller of the method) to give approval to the bridge to burn tokens on their behalf using witness signatures + // Requires `msg.sender` (caller of the method) to give approval to the bridge to burn tokens on their behalf using witness signatures #[aztec(private)] fn exit_to_l1_private( recipient: EthereumAddress, // ethereum address to withdraw to @@ -130,7 +129,7 @@ contract TokenBridge { let content = get_withdraw_content_hash(recipient.address, amount, callerOnL1.address); context.message_portal(content); - // Assert that user provided token address is same as seen in storage. + // Assert that user provided token address is same as seen in storage. context.call_public_function(context.this_address(), compute_selector("_assert_token_is_same(Field)"), [token.address]); // Burn tokens @@ -141,25 +140,20 @@ contract TokenBridge { /// docs:end:exit_to_l1_private // View function that is callable by other contracts. - // Unconstrained can't be called by others since it isn't safe. + // Unconstrained can't be called by others since it isn't safe. #[aztec(public)] fn get_token() -> Field { storage.token.read() } - - // /// Unconstrained /// + + // /// Unconstrained /// unconstrained fn token() -> Field { storage.token.read() } - /// SHOULD BE Internal /// - - // We cannot do this from the constructor currently - // Since this should be internal, for now, we ignore the safety checks of it, as they are - // enforced by it being internal and only called from the constructor. #[aztec(public)] - fn _initialize(token: AztecAddress) { + internal fn _initialize(token: AztecAddress) { storage.token.write(token.address); } diff --git a/yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr index a100c701794..25163712a36 100644 --- a/yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr @@ -112,10 +112,9 @@ contract Token { // docs:start:constructor #[aztec(private)] - fn constructor() { - // Currently not possible to execute public calls from constructor as code not yet available to sequencer. - // let selector = compute_selector("_initialize((Field))"); - // let _callStackItem = context.call_public_function(context.this_address(), selector, [context.msg_sender()]); + fn constructor(admin: AztecAddress) { + let selector = compute_selector("_initialize((Field))"); + context.call_public_function(context.this_address(), selector, [admin.address]); } // docs:end:constructor @@ -334,7 +333,7 @@ contract Token { 1 } // docs:end:transfer - + // docs:start:burn #[aztec(private)] fn burn( @@ -359,14 +358,9 @@ contract Token { } // docs:end:burn - /// SHOULD BE Internal /// - // docs:start:initialize - // We cannot do this from the constructor currently - // Since this should be internal, for now, we ignore the safety checks of it, as they are - // enforced by it being internal and only called from the constructor. #[aztec(public)] - fn _initialize( + internal fn _initialize( new_admin: AztecAddress, ) { storage.admin.write(new_admin); @@ -398,7 +392,7 @@ contract Token { } // docs:end:reduce_total_supply - /// Unconstrained /// + /// Unconstrained /// // docs:start:admin unconstrained fn admin() -> Field { @@ -453,4 +447,4 @@ contract Token { } // docs:end:compute_note_hash_and_nullifier } -// docs:end:token_all \ No newline at end of file +// docs:end:token_all diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index 40c185cf6ec..2edcae3d43f 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -548,7 +548,10 @@ export class PXEService implements PXE { const unencryptedLogs = new TxL2Logs(collectUnencryptedLogs(executionResult)); const enqueuedPublicFunctions = collectEnqueuedPublicFunctionCalls(executionResult); - const contractData = new ContractData(newContract?.completeAddress.address ?? AztecAddress.ZERO, EthAddress.ZERO); + const contractData = new ContractData( + newContract?.completeAddress.address ?? AztecAddress.ZERO, + newContract?.portalContract ?? EthAddress.ZERO, + ); const extendedContractData = new ExtendedContractData( contractData, newContractPublicFunctions, diff --git a/yarn-project/sequencer-client/src/client/sequencer-client.ts b/yarn-project/sequencer-client/src/client/sequencer-client.ts index d049b30dc3e..90764a5f369 100644 --- a/yarn-project/sequencer-client/src/client/sequencer-client.ts +++ b/yarn-project/sequencer-client/src/client/sequencer-client.ts @@ -55,6 +55,7 @@ export class SequencerClient { blockBuilder, l2BlockSource, l1ToL2MessageSource, + contractDataSource, publicProcessorFactory, config, ); diff --git a/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts b/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts index 1362c3c5a31..ec9fcdf3a9e 100644 --- a/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts @@ -27,17 +27,7 @@ import { makeSelector, } from '@aztec/circuits.js/factories'; import { padArrayEnd } from '@aztec/foundation/collection'; -import { - ContractDataSource, - EncodedContractFunction, - ExtendedContractData, - FunctionCall, - FunctionL2Logs, - SiblingPath, - Tx, - TxL2Logs, - mockTx, -} from '@aztec/types'; +import { ExtendedContractData, FunctionCall, FunctionL2Logs, SiblingPath, Tx, TxL2Logs, mockTx } from '@aztec/types'; import { MerkleTreeOperations, TreeInfo } from '@aztec/world-state'; import { MockProxy, mock } from 'jest-mock-extended'; @@ -45,6 +35,7 @@ import times from 'lodash.times'; import { PublicProver } from '../prover/index.js'; import { PublicKernelCircuitSimulator } from '../simulator/index.js'; +import { ContractsDataSourcePublicDB } from '../simulator/public_executor.js'; import { WasmPublicKernelCircuitSimulator } from '../simulator/public_kernel.js'; import { PublicProcessor } from './public_processor.js'; @@ -52,10 +43,8 @@ describe('public_processor', () => { let db: MockProxy; let publicExecutor: MockProxy; let publicProver: MockProxy; - let contractDataSource: MockProxy; + let publicContractsDB: MockProxy; - let publicFunction: EncodedContractFunction; - let contractData: ExtendedContractData; let proof: Proof; let root: Buffer; @@ -65,18 +54,14 @@ describe('public_processor', () => { db = mock(); publicExecutor = mock(); publicProver = mock(); - contractDataSource = mock(); + publicContractsDB = mock(); - contractData = ExtendedContractData.random(); - publicFunction = EncodedContractFunction.random(); proof = makeEmptyProof(); root = Buffer.alloc(32, 5); publicProver.getPublicCircuitProof.mockResolvedValue(proof); publicProver.getPublicKernelCircuitProof.mockResolvedValue(proof); db.getTreeInfo.mockResolvedValue({ root } as TreeInfo); - contractDataSource.getExtendedContractData.mockResolvedValue(contractData); - contractDataSource.getPublicFunction.mockResolvedValue(publicFunction); }); describe('with mock circuits', () => { @@ -89,9 +74,9 @@ describe('public_processor', () => { publicExecutor, publicKernel, publicProver, - contractDataSource, GlobalVariables.empty(), HistoricBlockData.empty(), + publicContractsDB, ); }); @@ -145,9 +130,9 @@ describe('public_processor', () => { publicExecutor, publicKernel, publicProver, - contractDataSource, GlobalVariables.empty(), HistoricBlockData.empty(), + publicContractsDB, ); }); diff --git a/yarn-project/sequencer-client/src/sequencer/public_processor.ts b/yarn-project/sequencer-client/src/sequencer/public_processor.ts index 3195989c1b2..1f63c6f4b82 100644 --- a/yarn-project/sequencer-client/src/sequencer/public_processor.ts +++ b/yarn-project/sequencer-client/src/sequencer/public_processor.ts @@ -47,7 +47,8 @@ import { MerkleTreeOperations } from '@aztec/world-state'; import { getVerificationKeys } from '../index.js'; import { EmptyPublicProver } from '../prover/empty.js'; import { PublicProver } from '../prover/index.js'; -import { PublicKernelCircuitSimulator, getPublicExecutor } from '../simulator/index.js'; +import { PublicKernelCircuitSimulator } from '../simulator/index.js'; +import { ContractsDataSourcePublicDB, getPublicExecutor } from '../simulator/public_executor.js'; import { WasmPublicKernelCircuitSimulator } from '../simulator/public_kernel.js'; import { FailedTx, ProcessedTx, makeEmptyProcessedTx, makeProcessedTx } from './processed_tx.js'; import { getHistoricBlockData } from './utils.js'; @@ -66,6 +67,7 @@ export class PublicProcessorFactory { * Creates a new instance of a PublicProcessor. * @param prevGlobalVariables - The global variables for the previous block, used to calculate the prev global variables hash. * @param globalVariables - The global variables for the block being processed. + * @param newContracts - Provides access to contract bytecode for public executions. * @returns A new instance of a PublicProcessor. */ public async create( @@ -73,14 +75,15 @@ export class PublicProcessorFactory { globalVariables: GlobalVariables, ): Promise { const blockData = await getHistoricBlockData(this.merkleTree, prevGlobalVariables); + const publicContractsDB = new ContractsDataSourcePublicDB(this.contractDataSource); return new PublicProcessor( this.merkleTree, - getPublicExecutor(this.merkleTree, this.contractDataSource, this.l1Tol2MessagesDataSource, blockData), + getPublicExecutor(this.merkleTree, publicContractsDB, this.l1Tol2MessagesDataSource, blockData), new WasmPublicKernelCircuitSimulator(), new EmptyPublicProver(), - this.contractDataSource, globalVariables, blockData, + publicContractsDB, ); } } @@ -95,9 +98,9 @@ export class PublicProcessor { protected publicExecutor: PublicExecutor, protected publicKernel: PublicKernelCircuitSimulator, protected publicProver: PublicProver, - protected contractDataSource: ContractDataSource, protected globalVariables: GlobalVariables, protected blockData: HistoricBlockData, + protected publicContractsDB: ContractsDataSourcePublicDB, private log = createDebugLogger('aztec:sequencer:public-processor'), ) {} @@ -116,6 +119,8 @@ export class PublicProcessor { for (const tx of txs) { this.log(`Processing tx ${await tx.getTxHash()}`); try { + // add new contracts to the contracts db so that their functions may be found and called + await this.publicContractsDB.addNewContracts(tx); result.push(await this.processTx(tx)); } catch (err) { this.log.warn(`Error processing tx ${await tx.getTxHash()}: ${err}`); @@ -123,8 +128,11 @@ export class PublicProcessor { tx, error: err instanceof Error ? err : new Error('Unknown error'), }); + // remove contracts on failure + await this.publicContractsDB.removeNewContracts(tx); } } + return [result, failed]; } @@ -405,6 +413,7 @@ export class PublicProcessor { PublicDataRead.empty(), MAX_PUBLIC_DATA_READS_PER_TX, ); + // Override kernel output publicInputs.end.publicDataUpdateRequests = padArrayEnd( [ diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts index dc69b5f1b27..1ec5c92280a 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts @@ -6,7 +6,16 @@ import { makeEmptyProof, } from '@aztec/circuits.js'; import { P2P, P2PClientState } from '@aztec/p2p'; -import { L1ToL2MessageSource, L2Block, L2BlockSource, MerkleTreeId, Tx, TxHash, mockTx } from '@aztec/types'; +import { + ContractDataSource, + L1ToL2MessageSource, + L2Block, + L2BlockSource, + MerkleTreeId, + Tx, + TxHash, + mockTx, +} from '@aztec/types'; import { MerkleTreeOperations, WorldStateRunningState, WorldStateSynchronizer } from '@aztec/world-state'; import { MockProxy, mock } from 'jest-mock-extended'; @@ -61,7 +70,7 @@ describe('sequencer', () => { }); publicProcessorFactory = mock({ - create: (_, __) => Promise.resolve(publicProcessor), + create: (_a, _b_) => Promise.resolve(publicProcessor), }); l2BlockSource = mock({ @@ -73,6 +82,8 @@ describe('sequencer', () => { getBlockNumber: () => Promise.resolve(lastBlockNumber), }); + const contractDataSource = mock({}); + sequencer = new TestSubject( publisher, globalVariableBuilder, @@ -81,6 +92,7 @@ describe('sequencer', () => { blockBuilder, l2BlockSource, l1ToL2MessageSource, + contractDataSource, publicProcessorFactory, { chainId: Number(chainId.value), diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index f4cbe839c76..487b227f5d1 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -3,7 +3,7 @@ import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; import { RunningPromise } from '@aztec/foundation/running-promise'; import { P2P } from '@aztec/p2p'; -import { L1ToL2MessageSource, L2Block, L2BlockSource, MerkleTreeId, Tx } from '@aztec/types'; +import { ContractDataSource, L1ToL2MessageSource, L2Block, L2BlockSource, MerkleTreeId, Tx } from '@aztec/types'; import { WorldStateStatus, WorldStateSynchronizer } from '@aztec/world-state'; import times from 'lodash.times'; @@ -41,6 +41,7 @@ export class Sequencer { private blockBuilder: BlockBuilder, private l2BlockSource: L2BlockSource, private l1ToL2MessageSource: L1ToL2MessageSource, + private contractDataSource: ContractDataSource, private publicProcessorFactory: PublicProcessorFactory, config: SequencerConfig, private log = createDebugLogger('aztec:sequencer'), diff --git a/yarn-project/sequencer-client/src/simulator/index.ts b/yarn-project/sequencer-client/src/simulator/index.ts index 0cc49389219..a9ede566376 100644 --- a/yarn-project/sequencer-client/src/simulator/index.ts +++ b/yarn-project/sequencer-client/src/simulator/index.ts @@ -8,8 +8,6 @@ import { RootRollupPublicInputs, } from '@aztec/circuits.js'; -export { getPublicExecutor } from './public_executor.js'; - /** * Circuit simulator for the rollup circuits. */ diff --git a/yarn-project/sequencer-client/src/simulator/public_executor.ts b/yarn-project/sequencer-client/src/simulator/public_executor.ts index 1296dcae50a..6928cff3ffc 100644 --- a/yarn-project/sequencer-client/src/simulator/public_executor.ts +++ b/yarn-project/sequencer-client/src/simulator/public_executor.ts @@ -6,7 +6,7 @@ import { PublicStateDB, } from '@aztec/acir-simulator'; import { AztecAddress, CircuitsWasm, EthAddress, Fr, FunctionSelector, HistoricBlockData } from '@aztec/circuits.js'; -import { ContractDataSource, L1ToL2MessageSource, MerkleTreeId } from '@aztec/types'; +import { ContractDataSource, ExtendedContractData, L1ToL2MessageSource, MerkleTreeId, Tx } from '@aztec/types'; import { MerkleTreeOperations, computePublicDataTreeLeafIndex } from '@aztec/world-state'; /** @@ -17,13 +17,13 @@ import { MerkleTreeOperations, computePublicDataTreeLeafIndex } from '@aztec/wor */ export function getPublicExecutor( merkleTree: MerkleTreeOperations, - contractDataSource: ContractDataSource, + publicContractsDB: PublicContractsDB, l1toL2MessageSource: L1ToL2MessageSource, blockData: HistoricBlockData, ) { return new PublicExecutor( new WorldStatePublicDB(merkleTree), - new ContractsDataSourcePublicDB(contractDataSource), + publicContractsDB, new WorldStateDB(merkleTree, l1toL2MessageSource), blockData, ); @@ -31,17 +31,63 @@ export function getPublicExecutor( /** * Implements the PublicContractsDB using a ContractDataSource. + * Progresively records contracts in transaction as they are processed in a block. */ -class ContractsDataSourcePublicDB implements PublicContractsDB { +export class ContractsDataSourcePublicDB implements PublicContractsDB { + cache = new Map(); + constructor(private db: ContractDataSource) {} + + /** + * Add new contracts from a transaction + * @param tx - The transaction to add contracts from. + */ + public addNewContracts(tx: Tx): Promise { + for (const contract of tx.newContracts) { + const contractAddress = contract.contractData.contractAddress; + + if (contractAddress.isZero()) { + continue; + } + + this.cache.set(contractAddress.toString(), contract); + } + + return Promise.resolve(); + } + + /** + * Removes new contracts added from transactions + * @param tx - The tx's contracts to be removed + */ + public removeNewContracts(tx: Tx): Promise { + for (const contract of tx.newContracts) { + const contractAddress = contract.contractData.contractAddress; + + if (contractAddress.isZero()) { + continue; + } + + this.cache.delete(contractAddress.toString()); + } + return Promise.resolve(); + } + async getBytecode(address: AztecAddress, selector: FunctionSelector): Promise { - return (await this.db.getPublicFunction(address, selector))?.bytecode; + const contract = await this.#getContract(address); + return contract?.getPublicFunction(selector)?.bytecode; } async getIsInternal(address: AztecAddress, selector: FunctionSelector): Promise { - return (await this.db.getPublicFunction(address, selector))?.isInternal; + const contract = await this.#getContract(address); + return contract?.getPublicFunction(selector)?.isInternal; } async getPortalContractAddress(address: AztecAddress): Promise { - return (await this.db.getContractData(address))?.portalContractAddress; + const contract = await this.#getContract(address); + return contract?.contractData.portalContractAddress; + } + + async #getContract(address: AztecAddress): Promise { + return this.cache.get(address.toString()) ?? (await this.db.getExtendedContractData(address)); } }