diff --git a/.release-please-manifest.json b/.release-please-manifest.json index b3c3305fc6cc..411256bcab3d 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.0.4" + ".": "4.1.0" } diff --git a/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.cpp b/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.cpp index 1af3d5bc6c82..0fb97f41e8cb 100644 --- a/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.cpp +++ b/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.cpp @@ -1,6 +1,7 @@ #include "barretenberg/bbapi/bbapi_chonk.hpp" #include "barretenberg/chonk/chonk_verifier.hpp" #include "barretenberg/chonk/mock_circuit_producer.hpp" +#include "barretenberg/chonk/proof_compression.hpp" #include "barretenberg/common/log.hpp" #include "barretenberg/common/serialize.hpp" #include "barretenberg/common/throw_or_abort.hpp" @@ -253,4 +254,17 @@ ChonkStats::Response ChonkStats::execute([[maybe_unused]] BBApiRequest& request) return response; } +ChonkCompressProof::Response ChonkCompressProof::execute(const BBApiRequest& /*request*/) && +{ + BB_BENCH_NAME(MSGPACK_SCHEMA_NAME); + return { .compressed_proof = ProofCompressor::compress_chonk_proof(proof) }; +} + +ChonkDecompressProof::Response ChonkDecompressProof::execute(const BBApiRequest& /*request*/) && +{ + BB_BENCH_NAME(MSGPACK_SCHEMA_NAME); + size_t mega_num_pub = ProofCompressor::compressed_mega_num_public_inputs(compressed_proof.size()); + return { .proof = ProofCompressor::decompress_chonk_proof(compressed_proof, mega_num_pub) }; +} + } // namespace bb::bbapi diff --git a/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.hpp b/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.hpp index fa9e93ad34c6..f880eef3d849 100644 --- a/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.hpp +++ b/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.hpp @@ -239,4 +239,48 @@ struct ChonkStats { bool operator==(const ChonkStats&) const = default; }; +/** + * @struct ChonkCompressProof + * @brief Compress a Chonk proof to a compact byte representation + * + * @details Uses point compression and uniform 32-byte encoding to reduce proof size (~1.72x). + */ +struct ChonkCompressProof { + static constexpr const char MSGPACK_SCHEMA_NAME[] = "ChonkCompressProof"; + + struct Response { + static constexpr const char MSGPACK_SCHEMA_NAME[] = "ChonkCompressProofResponse"; + std::vector compressed_proof; + MSGPACK_FIELDS(compressed_proof); + bool operator==(const Response&) const = default; + }; + + ChonkProof proof; + Response execute(const BBApiRequest& request = {}) &&; + MSGPACK_FIELDS(proof); + bool operator==(const ChonkCompressProof&) const = default; +}; + +/** + * @struct ChonkDecompressProof + * @brief Decompress a compressed Chonk proof back to field elements + * + * @details Derives mega_num_public_inputs from the compressed size automatically. + */ +struct ChonkDecompressProof { + static constexpr const char MSGPACK_SCHEMA_NAME[] = "ChonkDecompressProof"; + + struct Response { + static constexpr const char MSGPACK_SCHEMA_NAME[] = "ChonkDecompressProofResponse"; + ChonkProof proof; + MSGPACK_FIELDS(proof); + bool operator==(const Response&) const = default; + }; + + std::vector compressed_proof; + Response execute(const BBApiRequest& request = {}) &&; + MSGPACK_FIELDS(compressed_proof); + bool operator==(const ChonkDecompressProof&) const = default; +}; + } // namespace bb::bbapi diff --git a/barretenberg/cpp/src/barretenberg/bbapi/bbapi_execute.hpp b/barretenberg/cpp/src/barretenberg/bbapi/bbapi_execute.hpp index f459e600bcf2..ab16da99a508 100644 --- a/barretenberg/cpp/src/barretenberg/bbapi/bbapi_execute.hpp +++ b/barretenberg/cpp/src/barretenberg/bbapi/bbapi_execute.hpp @@ -28,6 +28,8 @@ using Command = NamedUnion #include "barretenberg/chonk/chonk_verifier.hpp" +#include "barretenberg/chonk/proof_compression.hpp" #include "barretenberg/chonk/test_bench_shared.hpp" #include "barretenberg/common/google_bb_bench.hpp" @@ -58,10 +59,43 @@ BENCHMARK_DEFINE_F(ChonkBench, Full)(benchmark::State& state) } } +/** + * @brief Benchmark proof compression (prover-side cost) + */ +BENCHMARK_DEFINE_F(ChonkBench, ProofCompress)(benchmark::State& state) +{ + size_t NUM_APP_CIRCUITS = 1; + auto precomputed_vks = precompute_vks(NUM_APP_CIRCUITS); + auto [proof, vk_and_hash] = accumulate_and_prove_with_precomputed_vks(NUM_APP_CIRCUITS, precomputed_vks); + + for (auto _ : state) { + benchmark::DoNotOptimize(ProofCompressor::compress_chonk_proof(proof)); + } +} + +/** + * @brief Benchmark proof decompression (verifier-side cost) + */ +BENCHMARK_DEFINE_F(ChonkBench, ProofDecompress)(benchmark::State& state) +{ + size_t NUM_APP_CIRCUITS = 1; + auto precomputed_vks = precompute_vks(NUM_APP_CIRCUITS); + auto [proof, vk_and_hash] = accumulate_and_prove_with_precomputed_vks(NUM_APP_CIRCUITS, precomputed_vks); + + auto compressed = ProofCompressor::compress_chonk_proof(proof); + size_t mega_num_pub_inputs = proof.mega_proof.size() - ChonkProof::HIDING_KERNEL_PROOF_LENGTH_WITHOUT_PUBLIC_INPUTS; + + for (auto _ : state) { + benchmark::DoNotOptimize(ProofCompressor::decompress_chonk_proof(compressed, mega_num_pub_inputs)); + } +} + #define ARGS Arg(ChonkBench::NUM_ITERATIONS_MEDIUM_COMPLEXITY)->Arg(2) BENCHMARK_REGISTER_F(ChonkBench, Full)->Unit(benchmark::kMillisecond)->ARGS; BENCHMARK_REGISTER_F(ChonkBench, VerificationOnly)->Unit(benchmark::kMillisecond); +BENCHMARK_REGISTER_F(ChonkBench, ProofCompress)->Unit(benchmark::kMillisecond); +BENCHMARK_REGISTER_F(ChonkBench, ProofDecompress)->Unit(benchmark::kMillisecond); } // namespace diff --git a/barretenberg/cpp/src/barretenberg/chonk/chonk.test.cpp b/barretenberg/cpp/src/barretenberg/chonk/chonk.test.cpp index d88d5d12a698..ee677801dc41 100644 --- a/barretenberg/cpp/src/barretenberg/chonk/chonk.test.cpp +++ b/barretenberg/cpp/src/barretenberg/chonk/chonk.test.cpp @@ -3,8 +3,10 @@ #include "barretenberg/chonk/chonk.hpp" #include "barretenberg/chonk/chonk_verifier.hpp" #include "barretenberg/chonk/mock_circuit_producer.hpp" +#include "barretenberg/chonk/proof_compression.hpp" #include "barretenberg/chonk/test_bench_shared.hpp" #include "barretenberg/common/assert.hpp" +#include "barretenberg/common/log.hpp" #include "barretenberg/common/mem.hpp" #include "barretenberg/common/test.hpp" #include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp" @@ -563,3 +565,33 @@ TEST_F(ChonkTests, MTailPropagationConsistency) { ChonkTests::test_hiding_kernel_io_propagation(HidingKernelIOField::ECC_OP_TABLES); } + +TEST_F(ChonkTests, ProofCompressionRoundtrip) +{ + TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES }; + auto [proof, vk_and_hash] = accumulate_and_prove_ivc(/*num_app_circuits=*/1, settings); + + auto original_flat = proof.to_field_elements(); + info("Original proof size: ", original_flat.size(), " Fr elements (", original_flat.size() * 32, " bytes)"); + + auto compressed = ProofCompressor::compress_chonk_proof(proof); + double ratio = static_cast(original_flat.size() * 32) / static_cast(compressed.size()); + info("Compressed proof size: ", compressed.size(), " bytes"); + info("Compression ratio: ", ratio, "x"); + + // Compression should achieve at least 1.5x (commitments 4 Fr → 32 bytes, scalars 1:1) + EXPECT_GE(ratio, 1.5) << "Compression ratio " << ratio << "x is below the expected minimum of 1.5x"; + + size_t mega_num_pub_inputs = proof.mega_proof.size() - ChonkProof::HIDING_KERNEL_PROOF_LENGTH_WITHOUT_PUBLIC_INPUTS; + ChonkProof decompressed = ProofCompressor::decompress_chonk_proof(compressed, mega_num_pub_inputs); + + // Verify element-by-element roundtrip + auto decompressed_flat = decompressed.to_field_elements(); + ASSERT_EQ(decompressed_flat.size(), original_flat.size()); + for (size_t i = 0; i < original_flat.size(); i++) { + ASSERT_EQ(decompressed_flat[i], original_flat[i]) << "Mismatch at element " << i; + } + + // Verify the decompressed proof + EXPECT_TRUE(verify_chonk(decompressed, vk_and_hash)); +} diff --git a/barretenberg/cpp/src/barretenberg/chonk/proof_compression.hpp b/barretenberg/cpp/src/barretenberg/chonk/proof_compression.hpp new file mode 100644 index 000000000000..6fd2ca182e23 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/chonk/proof_compression.hpp @@ -0,0 +1,572 @@ +#pragma once + +#include "barretenberg/chonk/chonk_proof.hpp" +#include "barretenberg/common/assert.hpp" +#include "barretenberg/constants.hpp" +#include "barretenberg/ecc/curves/bn254/bn254.hpp" +#include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp" +#include "barretenberg/eccvm/eccvm_flavor.hpp" +#include "barretenberg/flavor/mega_zk_flavor.hpp" +#include "barretenberg/honk/proof_system/types/proof.hpp" +#include "barretenberg/translator_vm/translator_flavor.hpp" +#include +#include +#include + +namespace bb { + +/** + * @brief Compresses Chonk proofs from vector to compact byte representations. + * + * Compression techniques: + * 1. Point compression: store only x-coordinate + sign bit (instead of x and y) + * 2. Fq-as-u256: store each Fq coordinate as 32 bytes (instead of 2 Fr for lo/hi split) + * 3. Fr-as-u256: store each Fr scalar as 32 bytes (uniform encoding) + * + * Every element compresses to exactly 32 bytes regardless of type: + * - BN254 commitment (4 Fr → 32 bytes): point compression on Fq coordinates + * - BN254 scalar (1 Fr → 32 bytes): direct u256 encoding + * - Grumpkin commitment (2 Fr → 32 bytes): point compression on Fr coordinates + * - Grumpkin scalar (2 Fr → 32 bytes): reconstruct Fq, write as u256 + */ +class ProofCompressor { + using Fr = curve::BN254::ScalarField; + using Fq = curve::BN254::BaseField; + + static constexpr uint256_t SIGN_BIT_MASK = uint256_t(1) << 255; + + // Fq values are stored as (lo, hi) Fr pairs split at 2*NUM_LIMB_BITS = 136 bits. + static constexpr uint64_t NUM_LIMB_BITS = 68; + static constexpr uint64_t FQ_SPLIT_BITS = NUM_LIMB_BITS * 2; // 136 + + /** @brief True if y is in the "upper half" of its field, used for point compression sign bit. */ + template static bool y_is_negative(const Field& y) + { + return uint256_t(y) > (uint256_t(Field::modulus) - 1) / 2; + } + + // ========================================================================= + // Serialization helpers + // ========================================================================= + + static void write_u256(std::vector& out, const uint256_t& val) + { + for (int i = 31; i >= 0; --i) { + out.push_back(static_cast(val.data[i / 8] >> (8 * (i % 8)))); + } + } + + static uint256_t read_u256(const std::vector& data, size_t& pos) + { + uint256_t val{ 0, 0, 0, 0 }; + for (int i = 31; i >= 0; --i) { + val.data[i / 8] |= static_cast(data[pos++]) << (8 * (i % 8)); + } + return val; + } + + static Fq reconstruct_fq(const Fr& lo, const Fr& hi) + { + return Fq(uint256_t(lo) + (uint256_t(hi) << FQ_SPLIT_BITS)); + } + + static std::pair split_fq(const Fq& val) + { + constexpr uint256_t LOWER_MASK = (uint256_t(1) << FQ_SPLIT_BITS) - 1; + const uint256_t v = uint256_t(val); + return { Fr(v & LOWER_MASK), Fr(v >> FQ_SPLIT_BITS) }; + } + + // ========================================================================= + // Walk functions — define proof layouts once for compress/decompress + // ========================================================================= + + /** + * @brief Walk a MegaZK proof (BN254, ZK sumcheck). + * @details Layout from MegaZKStructuredProofBase and sumcheck prover code. + */ + template + static void walk_mega_zk_proof(ScalarFn&& process_scalar, + CommitmentFn&& process_commitment, + size_t num_public_inputs) + { + constexpr size_t log_n = MegaZKFlavor::VIRTUAL_LOG_N; + + // Public inputs + for (size_t i = 0; i < num_public_inputs; i++) { + process_scalar(); + } + // Witness commitments (hiding poly + 24 mega witness = NUM_WITNESS_ENTITIES total) + for (size_t i = 0; i < MegaZKFlavor::NUM_WITNESS_ENTITIES; i++) { + process_commitment(); + } + // Libra concatenation commitment + process_commitment(); + // Libra sum + process_scalar(); + // Sumcheck round univariates + for (size_t i = 0; i < log_n * MegaZKFlavor::BATCHED_RELATION_PARTIAL_LENGTH; i++) { + process_scalar(); + } + // Sumcheck evaluations + for (size_t i = 0; i < MegaZKFlavor::NUM_ALL_ENTITIES; i++) { + process_scalar(); + } + // Libra claimed evaluation + process_scalar(); + // Libra grand sum + quotient commitments + process_commitment(); + process_commitment(); + // Gemini fold commitments + for (size_t i = 0; i < log_n - 1; i++) { + process_commitment(); + } + // Gemini fold evaluations + for (size_t i = 0; i < log_n; i++) { + process_scalar(); + } + // Small IPA evaluations (for ZK) + for (size_t i = 0; i < NUM_SMALL_IPA_EVALUATIONS; i++) { + process_scalar(); + } + // Shplonk Q + KZG W + process_commitment(); + process_commitment(); + } + + /** + * @brief Walk a Merge proof (42 Fr, all BN254). + * @details Layout from MergeProver::construct_proof. + */ + template + static void walk_merge_proof(ScalarFn&& process_scalar, CommitmentFn&& process_commitment) + { + // shift_size + process_scalar(); + // 4 merged table commitments + for (size_t i = 0; i < 4; i++) { + process_commitment(); + } + // Reversed batched left tables commitment + process_commitment(); + // 4 left + 4 right + 4 merged table evaluations + 1 reversed eval = 13 scalars + for (size_t i = 0; i < 13; i++) { + process_scalar(); + } + // Shplonk Q + KZG W + process_commitment(); + process_commitment(); + } + + /** + * @brief Walk an ECCVM proof (all Grumpkin). + * @details Layout from ECCVMFlavor::PROOF_LENGTH formula and ECCVM prover code. + * Grumpkin RoundUnivariateHandler commits to each round univariate and sends + * 2 evaluations (at 0 and 1), interleaved per round. + */ + template + static void walk_eccvm_proof(ScalarFn&& process_scalar, CommitmentFn&& process_commitment) + { + constexpr size_t log_n = CONST_ECCVM_LOG_N; + constexpr size_t num_witness = ECCVMFlavor::NUM_WITNESS_ENTITIES + ECCVMFlavor::NUM_MASKING_POLYNOMIALS; + + // Witness commitments (wires + derived + masking poly) + for (size_t i = 0; i < num_witness; i++) { + process_commitment(); + } + // Libra concatenation commitment + process_commitment(); + // Libra sum + process_scalar(); + // Sumcheck round univariates: per round, Grumpkin commits then sends 2 evaluations + for (size_t i = 0; i < log_n; i++) { + process_commitment(); // univariate commitment for round i + process_scalar(); // eval at 0 for round i + process_scalar(); // eval at 1 for round i + } + // Sumcheck evaluations + for (size_t i = 0; i < ECCVMFlavor::NUM_ALL_ENTITIES; i++) { + process_scalar(); + } + // Libra claimed evaluation + process_scalar(); + // Libra grand sum + quotient commitments + process_commitment(); + process_commitment(); + // Gemini fold commitments + for (size_t i = 0; i < log_n - 1; i++) { + process_commitment(); + } + // Gemini fold evaluations + for (size_t i = 0; i < log_n; i++) { + process_scalar(); + } + // Small IPA evaluations (for sumcheck libra) + for (size_t i = 0; i < NUM_SMALL_IPA_EVALUATIONS; i++) { + process_scalar(); + } + // Shplonk Q + process_commitment(); + + // --- Translation section --- + // Translator concatenated masking commitment + process_commitment(); + // 5 translation evaluations (op, Px, Py, z1, z2) + for (size_t i = 0; i < NUM_TRANSLATION_EVALUATIONS; i++) { + process_scalar(); + } + // Translation masking term evaluation + process_scalar(); + // Translation grand sum + quotient commitments + process_commitment(); + process_commitment(); + // Translation SmallSubgroupIPA evaluations + for (size_t i = 0; i < NUM_SMALL_IPA_EVALUATIONS; i++) { + process_scalar(); + } + // Translation Shplonk Q + process_commitment(); + } + + /** + * @brief Walk an IPA proof (64 Fr, all Grumpkin). + * @details IPA_PROOF_LENGTH = 4 * CONST_ECCVM_LOG_N + 4 + */ + template + static void walk_ipa_proof(ScalarFn&& process_scalar, CommitmentFn&& process_commitment) + { + // L and R commitments per round + for (size_t i = 0; i < CONST_ECCVM_LOG_N; i++) { + process_commitment(); // L_i + process_commitment(); // R_i + } + // G_0 commitment + process_commitment(); + // a_0 scalar + process_scalar(); + } + + /** + * @brief Walk a Translator proof (all BN254). + * @details Layout from TranslatorFlavor::PROOF_LENGTH formula. + * Wire commitments = gemini masking poly + non-op-queue wires + ordered range constraints + z_perm. + */ + template + static void walk_translator_proof(ScalarFn&& process_scalar, CommitmentFn&& process_commitment) + { + constexpr size_t log_n = TranslatorFlavor::CONST_TRANSLATOR_LOG_N; + constexpr size_t num_wire_comms = + TranslatorFlavor::NUM_WITNESS_ENTITIES - 3 - TranslatorFlavor::NUM_OP_QUEUE_WIRES; + + // Wire commitments (gemini masking poly, non-op-queue wires, ordered range constraints, z_perm) + for (size_t i = 0; i < num_wire_comms; i++) { + process_commitment(); + } + // Libra concatenation commitment + process_commitment(); + // Libra sum + process_scalar(); + // Sumcheck round univariates + for (size_t i = 0; i < log_n * TranslatorFlavor::BATCHED_RELATION_PARTIAL_LENGTH; i++) { + process_scalar(); + } + // Sumcheck evaluations (all entities) + for (size_t i = 0; i < TranslatorFlavor::NUM_ALL_ENTITIES; i++) { + process_scalar(); + } + // Libra claimed evaluation + process_scalar(); + // Libra grand sum + quotient commitments + process_commitment(); + process_commitment(); + // Gemini fold commitments + for (size_t i = 0; i < log_n - 1; i++) { + process_commitment(); + } + // Gemini fold evaluations + for (size_t i = 0; i < log_n; i++) { + process_scalar(); + } + // Gemini P pos and P neg evaluations + process_scalar(); + process_scalar(); + // Small IPA evaluations + for (size_t i = 0; i < NUM_SMALL_IPA_EVALUATIONS; i++) { + process_scalar(); + } + // Shplonk Q + KZG W + process_commitment(); + process_commitment(); + } + + /** + * @brief Walk a full Chonk proof (5 sub-proofs across two curves). + */ + template + static void walk_chonk_proof(BN254ScalarFn&& bn254_scalar, + BN254CommFn&& bn254_comm, + GrumpkinScalarFn&& grumpkin_scalar, + GrumpkinCommFn&& grumpkin_comm, + size_t mega_num_public_inputs) + { + walk_mega_zk_proof(bn254_scalar, bn254_comm, mega_num_public_inputs); + walk_merge_proof(bn254_scalar, bn254_comm); + walk_eccvm_proof(grumpkin_scalar, grumpkin_comm); + walk_ipa_proof(grumpkin_scalar, grumpkin_comm); + walk_translator_proof(bn254_scalar, bn254_comm); + } + + // ========================================================================= + // Walk count validation — ensure the constants used in walks match PROOF_LENGTH. + // These mirror the walk logic using the same constants; if a PROOF_LENGTH formula + // changes, the static_assert fires, prompting an update to the corresponding walk. + // ========================================================================= + + // Fr-elements per element type for each curve + static constexpr size_t BN254_FRS_PER_SCALAR = 1; + static constexpr size_t BN254_FRS_PER_COMM = 4; // Fq x,y each as (lo,hi) Fr pair + static constexpr size_t GRUMPKIN_FRS_PER_SCALAR = 2; // Fq stored as (lo,hi) Fr pair + static constexpr size_t GRUMPKIN_FRS_PER_COMM = 2; // Fr x,y coordinates + + // clang-format off + // MegaZK (without public inputs) — mirrors walk_mega_zk_proof with num_public_inputs=0 + static constexpr size_t EXPECTED_MEGA_ZK_FRS = + MegaZKFlavor::NUM_WITNESS_ENTITIES * BN254_FRS_PER_COMM + // witness comms + 1 * BN254_FRS_PER_COMM + // libra concat + 1 * BN254_FRS_PER_SCALAR + // libra sum + MegaZKFlavor::VIRTUAL_LOG_N * MegaZKFlavor::BATCHED_RELATION_PARTIAL_LENGTH * BN254_FRS_PER_SCALAR +// sumcheck univariates + MegaZKFlavor::NUM_ALL_ENTITIES * BN254_FRS_PER_SCALAR + // sumcheck evals + 1 * BN254_FRS_PER_SCALAR + // libra claimed eval + 2 * BN254_FRS_PER_COMM + // libra grand sum + quotient + (MegaZKFlavor::VIRTUAL_LOG_N - 1) * BN254_FRS_PER_COMM + // gemini folds + MegaZKFlavor::VIRTUAL_LOG_N * BN254_FRS_PER_SCALAR + // gemini evals + NUM_SMALL_IPA_EVALUATIONS * BN254_FRS_PER_SCALAR + // small IPA evals + 2 * BN254_FRS_PER_COMM; // shplonk Q + KZG W + static_assert(EXPECTED_MEGA_ZK_FRS == ChonkProof::HIDING_KERNEL_PROOF_LENGTH_WITHOUT_PUBLIC_INPUTS); + + // Merge — mirrors walk_merge_proof + static constexpr size_t EXPECTED_MERGE_FRS = + 1 * BN254_FRS_PER_SCALAR + // shift_size + 5 * BN254_FRS_PER_COMM + // 4 merged tables + 1 reversed batched left + 13 * BN254_FRS_PER_SCALAR + // evaluations + 2 * BN254_FRS_PER_COMM; // shplonk Q + KZG W + static_assert(EXPECTED_MERGE_FRS == MERGE_PROOF_SIZE); + + // ECCVM — mirrors walk_eccvm_proof + static constexpr size_t EXPECTED_ECCVM_FRS = + (ECCVMFlavor::NUM_WITNESS_ENTITIES + ECCVMFlavor::NUM_MASKING_POLYNOMIALS) * GRUMPKIN_FRS_PER_COMM + // witnesses + 1 * GRUMPKIN_FRS_PER_COMM + // libra concat + 1 * GRUMPKIN_FRS_PER_SCALAR + // libra sum + CONST_ECCVM_LOG_N * GRUMPKIN_FRS_PER_COMM + // sumcheck univariate comms + 2 * CONST_ECCVM_LOG_N * GRUMPKIN_FRS_PER_SCALAR + // sumcheck univariate evals (2 per round) + ECCVMFlavor::NUM_ALL_ENTITIES * GRUMPKIN_FRS_PER_SCALAR + // sumcheck evals + 1 * GRUMPKIN_FRS_PER_SCALAR + // libra claimed eval + 2 * GRUMPKIN_FRS_PER_COMM + // libra grand sum + quotient + (CONST_ECCVM_LOG_N - 1) * GRUMPKIN_FRS_PER_COMM + // gemini folds + CONST_ECCVM_LOG_N * GRUMPKIN_FRS_PER_SCALAR + // gemini evals + NUM_SMALL_IPA_EVALUATIONS * GRUMPKIN_FRS_PER_SCALAR + // small IPA evals + 1 * GRUMPKIN_FRS_PER_COMM + // shplonk Q + 1 * GRUMPKIN_FRS_PER_COMM + // translator masking comm + NUM_TRANSLATION_EVALUATIONS * GRUMPKIN_FRS_PER_SCALAR + // translation evals + 1 * GRUMPKIN_FRS_PER_SCALAR + // masking term eval + 2 * GRUMPKIN_FRS_PER_COMM + // translation grand sum + quotient + NUM_SMALL_IPA_EVALUATIONS * GRUMPKIN_FRS_PER_SCALAR + // translation small IPA evals + 1 * GRUMPKIN_FRS_PER_COMM; // translation shplonk Q + static_assert(EXPECTED_ECCVM_FRS == ECCVMFlavor::PROOF_LENGTH); + + // IPA — mirrors walk_ipa_proof + static constexpr size_t EXPECTED_IPA_FRS = + 2 * CONST_ECCVM_LOG_N * GRUMPKIN_FRS_PER_COMM + // L and R per round + 1 * GRUMPKIN_FRS_PER_COMM + // G_0 + 1 * GRUMPKIN_FRS_PER_SCALAR; // a_0 + static_assert(EXPECTED_IPA_FRS == IPA_PROOF_LENGTH); + + // Translator — mirrors walk_translator_proof + static constexpr size_t EXPECTED_TRANSLATOR_FRS = + (TranslatorFlavor::NUM_WITNESS_ENTITIES - 3 - TranslatorFlavor::NUM_OP_QUEUE_WIRES) * BN254_FRS_PER_COMM + // wire comms + 1 * BN254_FRS_PER_COMM + // libra concat + 1 * BN254_FRS_PER_SCALAR + // libra sum + TranslatorFlavor::CONST_TRANSLATOR_LOG_N * TranslatorFlavor::BATCHED_RELATION_PARTIAL_LENGTH * BN254_FRS_PER_SCALAR + // sumcheck univariates + TranslatorFlavor::NUM_ALL_ENTITIES * BN254_FRS_PER_SCALAR + // sumcheck evals (all entities) + 1 * BN254_FRS_PER_SCALAR + // libra claimed eval + 2 * BN254_FRS_PER_COMM + // libra grand sum + quotient + (TranslatorFlavor::CONST_TRANSLATOR_LOG_N - 1) * BN254_FRS_PER_COMM + // gemini folds + TranslatorFlavor::CONST_TRANSLATOR_LOG_N * BN254_FRS_PER_SCALAR + // gemini evals + 1 * BN254_FRS_PER_SCALAR + // gemini P pos eval + 1 * BN254_FRS_PER_SCALAR + // gemini P neg eval + NUM_SMALL_IPA_EVALUATIONS * BN254_FRS_PER_SCALAR + // small IPA evals + 2 * BN254_FRS_PER_COMM; // shplonk Q + KZG W + static_assert(EXPECTED_TRANSLATOR_FRS == TranslatorFlavor::PROOF_LENGTH); + // clang-format on + + public: + /** + * @brief Count the total compressed elements for a Chonk proof. + * Each element (scalar or commitment, either curve) compresses to exactly 32 bytes. + */ + static size_t compressed_element_count(size_t mega_num_public_inputs = 0) + { + size_t count = 0; + auto counter = [&]() { count++; }; + walk_chonk_proof(counter, counter, counter, counter, mega_num_public_inputs); + return count; + } + + /** + * @brief Derive mega_num_public_inputs from compressed proof size. + * @param compressed_bytes Total size of the compressed proof in bytes. + */ + static size_t compressed_mega_num_public_inputs(size_t compressed_bytes) + { + BB_ASSERT(compressed_bytes % 32 == 0); + size_t total_elements = compressed_bytes / 32; + size_t fixed_elements = compressed_element_count(0); + BB_ASSERT(total_elements >= fixed_elements); + return total_elements - fixed_elements; + } + + // ========================================================================= + // Chonk proof compression + // ========================================================================= + + static std::vector compress_chonk_proof(const ChonkProof& proof) + { + auto flat = proof.to_field_elements(); + std::vector out; + out.reserve(flat.size() * 32); // upper bound: every element compresses to 32 bytes + size_t offset = 0; + + // BN254 callbacks + auto bn254_scalar = [&]() { write_u256(out, uint256_t(flat[offset++])); }; + + auto bn254_comm = [&]() { + bool is_infinity = flat[offset].is_zero() && flat[offset + 1].is_zero() && flat[offset + 2].is_zero() && + flat[offset + 3].is_zero(); + if (is_infinity) { + write_u256(out, uint256_t(0)); + offset += 4; + return; + } + + Fq x = reconstruct_fq(flat[offset], flat[offset + 1]); + Fq y = reconstruct_fq(flat[offset + 2], flat[offset + 3]); + offset += 4; + + uint256_t x_val = uint256_t(x); + if (y_is_negative(y)) { + x_val |= SIGN_BIT_MASK; + } + write_u256(out, x_val); + }; + + // Grumpkin callbacks + // Grumpkin commitments have coordinates in BN254::ScalarField (Fr), so x and y are each 1 Fr. + auto grumpkin_comm = [&]() { + Fr x = flat[offset]; + Fr y = flat[offset + 1]; + offset += 2; + + if (x.is_zero() && y.is_zero()) { + write_u256(out, uint256_t(0)); + return; + } + + uint256_t x_val = uint256_t(x); + if (y_is_negative(y)) { + x_val |= SIGN_BIT_MASK; + } + write_u256(out, x_val); + }; + + // Grumpkin scalars are Fq values stored as (lo, hi) Fr pairs + auto grumpkin_scalar = [&]() { + Fq fq_val = reconstruct_fq(flat[offset], flat[offset + 1]); + offset += 2; + write_u256(out, uint256_t(fq_val)); + }; + + size_t mega_num_pub_inputs = + proof.mega_proof.size() - ChonkProof::HIDING_KERNEL_PROOF_LENGTH_WITHOUT_PUBLIC_INPUTS; + walk_chonk_proof(bn254_scalar, bn254_comm, grumpkin_scalar, grumpkin_comm, mega_num_pub_inputs); + BB_ASSERT(offset == flat.size()); + return out; + } + + static ChonkProof decompress_chonk_proof(const std::vector& compressed, size_t mega_num_public_inputs) + { + HonkProof flat; + size_t pos = 0; + + // BN254 callbacks + auto bn254_scalar = [&]() { flat.emplace_back(read_u256(compressed, pos)); }; + + auto bn254_comm = [&]() { + uint256_t raw = read_u256(compressed, pos); + bool sign = (raw & SIGN_BIT_MASK) != 0; + uint256_t x_val = raw & ~SIGN_BIT_MASK; + + if (x_val == uint256_t(0) && !sign) { + for (int j = 0; j < 4; j++) { + flat.emplace_back(Fr::zero()); + } + return; + } + + Fq x(x_val); + Fq y_squared = x * x * x + Bn254G1Params::b; + auto [is_square, y] = y_squared.sqrt(); + BB_ASSERT(is_square); + + if (y_is_negative(y) != sign) { + y = -y; + } + + auto [x_lo, x_hi] = split_fq(x); + auto [y_lo, y_hi] = split_fq(y); + flat.emplace_back(x_lo); + flat.emplace_back(x_hi); + flat.emplace_back(y_lo); + flat.emplace_back(y_hi); + }; + + // Grumpkin callbacks + auto grumpkin_comm = [&]() { + uint256_t raw = read_u256(compressed, pos); + bool sign = (raw & SIGN_BIT_MASK) != 0; + uint256_t x_val = raw & ~SIGN_BIT_MASK; + + if (x_val == uint256_t(0) && !sign) { + flat.emplace_back(Fr::zero()); + flat.emplace_back(Fr::zero()); + return; + } + + Fr x(x_val); + // Grumpkin curve: y² = x³ + b, where b = -17 (in BN254::ScalarField) + Fr y_squared = x * x * x + grumpkin::G1Params::b; + auto [is_square, y] = y_squared.sqrt(); + BB_ASSERT(is_square); + + if (y_is_negative(y) != sign) { + y = -y; + } + + flat.emplace_back(x); + flat.emplace_back(y); + }; + + auto grumpkin_scalar = [&]() { + uint256_t raw = read_u256(compressed, pos); + Fq fq_val(raw); + auto [lo, hi] = split_fq(fq_val); + flat.emplace_back(lo); + flat.emplace_back(hi); + }; + + walk_chonk_proof(bn254_scalar, bn254_comm, grumpkin_scalar, grumpkin_comm, mega_num_public_inputs); + BB_ASSERT(pos == compressed.size()); + return ChonkProof::from_field_elements(flat); + } +}; + +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/nodejs_module/avm_simulate/avm_simulate_napi.cpp b/barretenberg/cpp/src/barretenberg/nodejs_module/avm_simulate/avm_simulate_napi.cpp index 283f9488556e..01c19f45e6b5 100644 --- a/barretenberg/cpp/src/barretenberg/nodejs_module/avm_simulate/avm_simulate_napi.cpp +++ b/barretenberg/cpp/src/barretenberg/nodejs_module/avm_simulate/avm_simulate_napi.cpp @@ -281,8 +281,9 @@ Napi::Value AvmSimulateNapi::simulate(const Napi::CallbackInfo& cb_info) **********************************************************/ auto deferred = std::make_shared(env); - // Create async operation that will run on a worker thread - auto* op = new AsyncOperation( + // Create threaded operation that runs on a dedicated std::thread (not libuv pool). + // This prevents libuv thread pool exhaustion when callbacks need libuv threads for I/O. + auto* op = new ThreadedAsyncOperation( env, deferred, [data, tsfns, logger_tsfn, ws_ptr, cancellation_token](msgpack::sbuffer& result_buffer) { // Collect all thread-safe functions including logger for cleanup auto all_tsfns = tsfns.to_vector(); @@ -326,7 +327,6 @@ Napi::Value AvmSimulateNapi::simulate(const Napi::CallbackInfo& cb_info) } }); - // Napi is now responsible for destroying this object op->Queue(); return deferred->Promise(); @@ -368,8 +368,8 @@ Napi::Value AvmSimulateNapi::simulateWithHintedDbs(const Napi::CallbackInfo& cb_ // Create a deferred promise auto deferred = std::make_shared(env); - // Create async operation that will run on a worker thread - auto* op = new AsyncOperation(env, deferred, [data](msgpack::sbuffer& result_buffer) { + // Create threaded operation that runs on a dedicated std::thread (not libuv pool) + auto* op = new ThreadedAsyncOperation(env, deferred, [data](msgpack::sbuffer& result_buffer) { try { // Deserialize inputs from msgpack avm2::AvmProvingInputs inputs; @@ -393,7 +393,6 @@ Napi::Value AvmSimulateNapi::simulateWithHintedDbs(const Napi::CallbackInfo& cb_ } }); - // Napi is now responsible for destroying this object op->Queue(); return deferred->Promise(); diff --git a/barretenberg/cpp/src/barretenberg/nodejs_module/util/async_op.hpp b/barretenberg/cpp/src/barretenberg/nodejs_module/util/async_op.hpp index 3e29d08b5f6b..13a933cd5a81 100644 --- a/barretenberg/cpp/src/barretenberg/nodejs_module/util/async_op.hpp +++ b/barretenberg/cpp/src/barretenberg/nodejs_module/util/async_op.hpp @@ -3,6 +3,7 @@ #include "barretenberg/serialize/msgpack_impl.hpp" #include #include +#include #include namespace bb::nodejs { @@ -65,4 +66,78 @@ class AsyncOperation : public Napi::AsyncWorker { msgpack::sbuffer _result; }; +/** + * @brief Runs work on a dedicated std::thread instead of the libuv thread pool. + * + * Unlike AsyncOperation (which uses Napi::AsyncWorker and occupies a libuv thread), + * this class spawns a new OS thread for each operation. This prevents AVM simulations + * from exhausting the libuv thread pool, which would deadlock when C++ callbacks need + * to invoke JS functions that themselves require libuv threads (e.g., LMDB reads). + * + * The completion callback (resolve/reject) is posted back to the JS main thread via + * a Napi::ThreadSafeFunction, so the event loop returns immediately after launch + * and is woken up only when the work is done. + * + * Usage: `auto* op = new ThreadedAsyncOperation(env, deferred, fn); op->Queue();` + * The object self-destructs after resolving/rejecting the promise. + */ +class ThreadedAsyncOperation { + public: + ThreadedAsyncOperation(Napi::Env env, std::shared_ptr deferred, async_fn fn) + : _fn(std::move(fn)) + , _deferred(std::move(deferred)) + { + // Create a no-op JS function as the TSFN target — we use the native callback form of BlockingCall + // to resolve/reject the promise, so the JS function is never actually called directly. + auto dummy = Napi::Function::New(env, [](const Napi::CallbackInfo&) {}); + _completion_tsfn = Napi::ThreadSafeFunction::New(env, dummy, "ThreadedAsyncOpComplete", 0, 1); + } + + ThreadedAsyncOperation(const ThreadedAsyncOperation&) = delete; + ThreadedAsyncOperation& operator=(const ThreadedAsyncOperation&) = delete; + ThreadedAsyncOperation(ThreadedAsyncOperation&&) = delete; + ThreadedAsyncOperation& operator=(ThreadedAsyncOperation&&) = delete; + + ~ThreadedAsyncOperation() = default; + + void Queue() + { + std::thread([this]() { + try { + _fn(_result); + _success = true; + } catch (const std::exception& e) { + _error = e.what(); + _success = false; + } catch (...) { + _error = "Unknown exception occurred during threaded async operation"; + _success = false; + } + + // Post completion back to the JS main thread + _completion_tsfn.BlockingCall( + this, [](Napi::Env env, Napi::Function /*js_callback*/, ThreadedAsyncOperation* op) { + if (op->_success) { + auto buf = Napi::Buffer::Copy(env, op->_result.data(), op->_result.size()); + op->_deferred->Resolve(buf); + } else { + auto error = Napi::Error::New(env, op->_error); + op->_deferred->Reject(error.Value()); + } + // Release the TSFN and self-destruct + op->_completion_tsfn.Release(); + delete op; + }); + }).detach(); + } + + private: + async_fn _fn; + std::shared_ptr _deferred; + Napi::ThreadSafeFunction _completion_tsfn; + msgpack::sbuffer _result; + bool _success = false; + std::string _error; +}; + } // namespace bb::nodejs diff --git a/barretenberg/rust/barretenberg-rs/Cargo.toml b/barretenberg/rust/barretenberg-rs/Cargo.toml index 04f90e4408cb..f637a2a82a56 100644 --- a/barretenberg/rust/barretenberg-rs/Cargo.toml +++ b/barretenberg/rust/barretenberg-rs/Cargo.toml @@ -10,6 +10,9 @@ readme = "README.md" # crates.io allows max 5 keywords keywords = ["zero-knowledge", "snark", "plonk", "noir", "aztec"] categories = ["cryptography", "api-bindings"] +# Include generated files in the published crate even though they're gitignored. +# They're generated by `yarn generate` in ts/ and must exist on disk before `cargo publish`. +include = ["src/**/*.rs", "build.rs", "README.md"] [dependencies] # Serialization diff --git a/barretenberg/rust/bootstrap.sh b/barretenberg/rust/bootstrap.sh index 7c5c885eb2e3..5b9352241071 100755 --- a/barretenberg/rust/bootstrap.sh +++ b/barretenberg/rust/bootstrap.sh @@ -46,6 +46,30 @@ function test { BB_LIB_DIR="$(cd ../cpp/build/lib && pwd)" RUSTFLAGS="-C link-arg=-Wl,--allow-multiple-definition" denoise "cargo test --release --features ffi" } +function release { + echo_header "barretenberg-rs release" + + local version=${REF_NAME#v} + + # Set the workspace version to match the release tag + sed -i "s/^version = \".*\"/version = \"$version\"/" Cargo.toml + + # Generated files must exist (created during build step) + if [ ! -f barretenberg-rs/src/api.rs ] || [ ! -f barretenberg-rs/src/generated_types.rs ]; then + echo "ERROR: generated files not found. Run 'cd ../ts && yarn generate' first." + exit 1 + fi + + # Publish to crates.io (--allow-dirty because version was just set and generated files are gitignored) + local extra_flags="" + if ! gh release view "v$version" --repo AztecProtocol/aztec-packages &>/dev/null; then + # No matching GitHub release yet — skip verification build (which would try to download libbb-external.a) + echo "No GitHub release found for v$version, adding --no-verify (pass REF_NAME matching a release for full verification)" + extra_flags="--no-verify" + fi + retry "denoise 'cargo publish --allow-dirty $extra_flags -p barretenberg-rs'" +} + function test_download { echo_header "barretenberg-rs download test" @@ -121,6 +145,5 @@ case "$cmd" in $cmd ;; *) - echo "Unknown command: $cmd" - exit 1 + default_cmd_handler "$@" esac diff --git a/barretenberg/rust/tests/Cargo.toml b/barretenberg/rust/tests/Cargo.toml index 676f665a1b25..c52ddf88621e 100644 --- a/barretenberg/rust/tests/Cargo.toml +++ b/barretenberg/rust/tests/Cargo.toml @@ -3,6 +3,7 @@ name = "barretenberg-tests" version.workspace = true edition.workspace = true rust-version.workspace = true +publish = false [features] ffi = ["barretenberg-rs/ffi"] diff --git a/barretenberg/ts/src/index.ts b/barretenberg/ts/src/index.ts index 247c5825ebf0..5fc183d8786b 100644 --- a/barretenberg/ts/src/index.ts +++ b/barretenberg/ts/src/index.ts @@ -21,12 +21,15 @@ export { BBApiException } from './bbapi_exception.js'; export type { Bn254G1Point, Bn254G2Point, + ChonkProof, GrumpkinPoint, Secp256k1Point, Secp256r1Point, Field2, } from './cbind/generated/api_types.js'; +export { toChonkProof } from './cbind/generated/api_types.js'; + // Export curve constants for use in foundation export { BN254_FQ_MODULUS, diff --git a/bootstrap.sh b/bootstrap.sh index db6d2383f7a8..eb45e77ba30a 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -502,6 +502,7 @@ function release { projects=( barretenberg/cpp barretenberg/ts + barretenberg/rust noir l1-contracts noir-projects/aztec-nr diff --git a/boxes/boxes/react/src/hooks/useContract.tsx b/boxes/boxes/react/src/hooks/useContract.tsx index 5b3c7aca373f..ff180dfa5b59 100644 --- a/boxes/boxes/react/src/hooks/useContract.tsx +++ b/boxes/boxes/react/src/hooks/useContract.tsx @@ -24,10 +24,10 @@ export function useContract() { contractAddressSalt: salt, }); - const contract = await toast.promise(deploymentPromise, { + const { contract } = await toast.promise(deploymentPromise, { pending: 'Deploying contract...', success: { - render: ({ data }) => `Address: ${data.address}`, + render: ({ data }) => `Address: ${data.contract.address}`, }, error: 'Error deploying contract', }); diff --git a/boxes/boxes/react/src/hooks/useNumber.tsx b/boxes/boxes/react/src/hooks/useNumber.tsx index 8809b6555b42..7435cf5ebfa7 100644 --- a/boxes/boxes/react/src/hooks/useNumber.tsx +++ b/boxes/boxes/react/src/hooks/useNumber.tsx @@ -11,10 +11,10 @@ export function useNumber({ contract }: { contract: Contract }) { setWait(true); const defaultAccountAddress = deployerEnv.getDefaultAccountAddress(); - const viewTxReceipt = await contract!.methods + const { result } = await contract!.methods .getNumber(defaultAccountAddress) .simulate({ from: defaultAccountAddress }); - toast(`Number is: ${viewTxReceipt.value}`); + toast(`Number is: ${result}`); setWait(false); }; diff --git a/boxes/boxes/vanilla/app/embedded-wallet.ts b/boxes/boxes/vanilla/app/embedded-wallet.ts index 04951e383ee9..bbf32271f6b5 100644 --- a/boxes/boxes/vanilla/app/embedded-wallet.ts +++ b/boxes/boxes/vanilla/app/embedded-wallet.ts @@ -162,7 +162,7 @@ export class EmbeddedWallet extends EmbeddedWalletBase { wait: { timeout: 120 }, }; - const receipt = await deployMethod.send(deployOpts); + const { receipt } = await deployMethod.send(deployOpts); logger.info('Account deployed', receipt); diff --git a/boxes/boxes/vanilla/app/main.ts b/boxes/boxes/vanilla/app/main.ts index fa55a4cae208..ddce11af435c 100644 --- a/boxes/boxes/vanilla/app/main.ts +++ b/boxes/boxes/vanilla/app/main.ts @@ -200,7 +200,7 @@ async function updateVoteTally(wallet: Wallet, from: AztecAddress) { const batchResult = await new BatchCall(wallet, payloads).simulate({ from }); - batchResult.forEach((value, i) => { + batchResult.forEach(({ result: value }, i) => { results[i + 1] = value; }); diff --git a/boxes/boxes/vanilla/scripts/deploy.ts b/boxes/boxes/vanilla/scripts/deploy.ts index b02edaa7e6b1..e0e0f971a739 100644 --- a/boxes/boxes/vanilla/scripts/deploy.ts +++ b/boxes/boxes/vanilla/scripts/deploy.ts @@ -71,7 +71,7 @@ async function deployContract(wallet: Wallet, deployer: AztecAddress) { const sponsoredPFCContract = await getSponsoredPFCContract(); - const contract = await PrivateVotingContract.deploy(wallet, deployer).send({ + const { contract } = await PrivateVotingContract.deploy(wallet, deployer).send({ from: deployer, contractAddressSalt: salt, fee: { diff --git a/boxes/boxes/vite/src/hooks/useContract.tsx b/boxes/boxes/vite/src/hooks/useContract.tsx index 5b3c7aca373f..ff180dfa5b59 100644 --- a/boxes/boxes/vite/src/hooks/useContract.tsx +++ b/boxes/boxes/vite/src/hooks/useContract.tsx @@ -24,10 +24,10 @@ export function useContract() { contractAddressSalt: salt, }); - const contract = await toast.promise(deploymentPromise, { + const { contract } = await toast.promise(deploymentPromise, { pending: 'Deploying contract...', success: { - render: ({ data }) => `Address: ${data.address}`, + render: ({ data }) => `Address: ${data.contract.address}`, }, error: 'Error deploying contract', }); diff --git a/boxes/boxes/vite/src/hooks/useNumber.tsx b/boxes/boxes/vite/src/hooks/useNumber.tsx index 8809b6555b42..7435cf5ebfa7 100644 --- a/boxes/boxes/vite/src/hooks/useNumber.tsx +++ b/boxes/boxes/vite/src/hooks/useNumber.tsx @@ -11,10 +11,10 @@ export function useNumber({ contract }: { contract: Contract }) { setWait(true); const defaultAccountAddress = deployerEnv.getDefaultAccountAddress(); - const viewTxReceipt = await contract!.methods + const { result } = await contract!.methods .getNumber(defaultAccountAddress) .simulate({ from: defaultAccountAddress }); - toast(`Number is: ${viewTxReceipt.value}`); + toast(`Number is: ${result}`); setWait(false); }; diff --git a/docs/docs-developers/docs/resources/migration_notes.md b/docs/docs-developers/docs/resources/migration_notes.md index 01862c1ddfaa..e38996d04e99 100644 --- a/docs/docs-developers/docs/resources/migration_notes.md +++ b/docs/docs-developers/docs/resources/migration_notes.md @@ -9,6 +9,224 @@ Aztec is in active development. Each version may introduce breaking changes that ## TBD +### [Aztec.js] `simulate()`, `send()`, and deploy return types changed to always return objects + +All SDK interaction methods now return structured objects that include offchain output alongside the primary result. This affects `.simulate()`, `.send()`, deploy `.send()`, and `Wallet.sendTx()`. + +**Impact**: Every call site that uses `.simulate()`, `.send()`, or deploy must destructure the result. This is a mechanical transformation. Custom wallet implementations must update `sendTx()` to return the new object shapes, using `extractOffchainOutput` to decode offchain messages from raw effects. + +The offchain output includes two fields: + +- `offchainEffects` — raw offchain effects emitted during execution, other than `offchainMessages` +- `offchainMessages` — decoded messages intended for specific recipients + +We are making this change now so in the future we can add more fields to the responses of this APIs without breaking backwards compatibility, +so this won't ever happen again. + +**`simulate()` — always returns `{ result, offchainEffects, offchainMessages }` object:** + +```diff +- const value = await contract.methods.foo(args).simulate({ from: sender }); ++ const { result: value } = await contract.methods.foo(args).simulate({ from: sender }); +``` + +When using `includeMetadata` or `fee.estimateGas`, `stats` and `estimatedGas` are also available as optional fields on the same object: + +```diff +- const { stats, estimatedGas } = await contract.methods.foo(args).simulate({ ++ const sim = await contract.methods.foo(args).simulate({ + from: sender, + includeMetadata: true, + }); ++ const stats = sim.stats!; ++ const estimatedGas = sim.estimatedGas!; +``` + +`SimulationReturn` is no longer a generic conditional type — it's a single flat type with optional `stats` and `estimatedGas` fields. + +**`send()` — returns `{ receipt, offchainEffects, offchainMessages }` object:** + +```diff +- const receipt = await contract.methods.foo(args).send({ from: sender }); ++ const { receipt } = await contract.methods.foo(args).send({ from: sender }); +``` + +When using `NO_WAIT`, returns `{ txHash, offchainEffects, offchainMessages }` instead of a bare `TxHash`: + +```diff +- const txHash = await contract.methods.foo(args).send({ from: sender, wait: NO_WAIT }); ++ const { txHash } = await contract.methods.foo(args).send({ from: sender, wait: NO_WAIT }); +``` + +Offchain messages emitted by the transaction are available on the result: + +```typescript +const { receipt, offchainMessages } = await contract.methods.foo(args).send({ from: sender }); +for (const msg of offchainMessages) { + console.log(`Message for ${msg.recipient} from contract ${msg.contractAddress}:`, msg.payload); +} +``` + +**Deploy — returns `{ contract, receipt, offchainEffects, offchainMessages }` object:** + +```diff +- const myContract = await MyContract.deploy(wallet, ...args).send({ from: sender }); ++ const { contract: myContract } = await MyContract.deploy(wallet, ...args).send({ from: sender }); +``` + +The deploy receipt is also available via `receipt` if needed (e.g. for `receipt.txHash` or `receipt.transactionFee`). + +**Custom wallet implementations — `sendTx()` must return objects:** + +If you implement the `Wallet` interface (or extend `BaseWallet`), the `sendTx()` method must now return objects that include offchain output. Use `extractOffchainOutput` to split raw effects into decoded messages and remaining effects: + +```diff ++ import { extractOffchainOutput } from '@aztec/aztec.js/contracts'; + + async sendTx(executionPayload, opts) { + const provenTx = await this.pxe.proveTx(...); ++ const offchainOutput = extractOffchainOutput(provenTx.getOffchainEffects()); + const tx = await provenTx.toTx(); + const txHash = tx.getTxHash(); + await this.aztecNode.sendTx(tx); + + if (opts.wait === NO_WAIT) { +- return txHash; ++ return { txHash, ...offchainOutput }; + } + const receipt = await waitForTx(this.aztecNode, txHash, opts.wait); +- return receipt; ++ return { receipt, ...offchainOutput }; + } +``` + +### `aztec new` crate directories are now named after the contract + +`aztec new` and `aztec init` now name the generated crate directories after the contract instead of using generic `contract/` and `test/` names. For example, `aztec new counter` now creates: + +``` +counter/ +├── Nargo.toml # [workspace] members = ["counter_contract", "counter_test"] +├── counter_contract/ +│ ├── src/main.nr +│ └── Nargo.toml # type = "contract" +└── counter_test/ + ├── src/lib.nr + └── Nargo.toml # type = "lib" +``` + +This enables adding multiple contracts to a single workspace. Running `aztec new ` inside an existing workspace (a directory with a `Nargo.toml` containing `[workspace]`) now adds a new `_contract` and `_test` crate pair to the workspace instead of creating a new directory. + +**What changed:** +- Crate directories are now `_contract/` and `_test/` instead of `contract/` and `test/`. +- Contract code is now at `_contract/src/main.nr` instead of `contract/src/main.nr`. +- Contract dependencies go in `_contract/Nargo.toml` instead of `contract/Nargo.toml`. +- Tests import the contract by its new crate name (e.g., `use counter_contract::Main;` instead of `use counter::Main;`). + +### [CLI] `--name` flag removed from `aztec new` and `aztec init` + +The `--name` flag has been removed from both `aztec new` and `aztec init`. For `aztec new`, the positional argument now serves as both the contract name and the directory name. For `aztec init`, the directory name is always used as the contract name. + +**Migration:** + +```diff +- aztec new my_project --name counter ++ aztec new counter +``` + +```diff +- aztec init --name counter ++ aztec init +``` + +**Impact**: If you were using `--name` to set a contract name different from the directory name, rename your directory or use `aztec new` with the desired contract name directly. + +### [Aztec.js] Removed `SingleKeyAccountContract` + +The `SchnorrSingleKeyAccount` contract and its TypeScript wrapper `SingleKeyAccountContract` have been removed. This contract was insecure: it used `ivpk_m` (incoming viewing public key) as its Schnorr signing key, meaning anyone who received a user's viewing key could sign transactions on their behalf. + +**Migration:** + +```diff +- import { SingleKeyAccountContract } from '@aztec/accounts/single_key'; +- const contract = new SingleKeyAccountContract(signingKey); ++ import { SchnorrAccountContract } from '@aztec/accounts/schnorr'; ++ const contract = new SchnorrAccountContract(signingKey); +``` + +**Impact**: If you were using `@aztec/accounts/single_key`, switch to `@aztec/accounts/schnorr` which uses separate keys for encryption and authentication. + +### `aztec new` and `aztec init` now create a 2-crate workspace + +`aztec new` and `aztec init` now create a workspace with two crates instead of a single contract crate: + +- A `contract` crate (type = "contract") for your smart contract code +- A `test` crate (type = "lib") for Noir tests, which depends on the contract crate + +The new project structure looks like: + +``` +my_project/ +├── Nargo.toml # [workspace] members = ["contract", "test"] +├── contract/ +│ ├── src/main.nr +│ └── Nargo.toml # type = "contract" +└── test/ + ├── src/lib.nr + └── Nargo.toml # type = "lib" +``` + +**What changed:** +- The `--contract` and `--lib` flags have been removed from `aztec new` and `aztec init`. These commands now always create a contract workspace. +- Contract code is now at `contract/src/main.nr` instead of `src/main.nr`. +- The `Nargo.toml` in the project root is now a workspace file. Contract dependencies go in `contract/Nargo.toml`. +- Tests should be written in the separate `test` crate (`test/src/lib.nr`) and import the contract by package name (e.g., `use my_contract::MyContract;`) instead of using `crate::`. + +### Scope enforcement for private state access (TXE and PXE) + +Scope enforcement is now active across both TXE (test environment) and PXE (client). Previously, private execution could implicitly access any account's keys and notes. Now, only the caller (`from`) address is in scope by default, and accessing another address's private state requires explicitly granting scope. + +#### Noir developers (TXE) + +TXE now enforces scope isolation, matching PXE behavior. During private execution, only the caller's keys and notes are accessible. If a Noir test accesses private state of an address other than `from`, it will fail. When `from` is the zero address, scopes are empty (deny-all). + +If your TXE tests fail with key or note access errors, ensure the test is calling from the correct address, or restructure the test to match the expected access pattern. + +#### Aztec.js developers (PXE/Wallet) + +The wallet now passes scopes to PXE, and only the `from` address is in scope by default. Auto-expansion of scopes for nested calls to registered accounts has been removed. A new `additionalScopes` option is available on `send()`, `simulate()`, and `deploy()` for cases where private execution needs access to another address's keys or notes. + +**When do you need `additionalScopes`?** + +1. **Deploying contracts whose constructor initializes private storage** (e.g., account contracts, or any contract using `SinglePrivateImmutable`/`SinglePrivateMutable` in the constructor). The contract's own address must be in scope so its nullifier key is accessible during initialization. + +2. **Operations that access another contract's private state** (e.g., withdrawing from an escrow contract that nullifies the contract's own token notes). + +``` + +**Example: deploying a contract with private storage (e.g., `PrivateToken`)** + +```diff + const tokenDeployment = PrivateTokenContract.deployWithPublicKeys( + tokenPublicKeys, wallet, initialBalance, sender, + ); + const tokenInstance = await tokenDeployment.getInstance(); + await wallet.registerContract(tokenInstance, PrivateTokenContract.artifact, tokenSecretKey); + const token = await tokenDeployment.send({ + from: sender, ++ additionalScopes: [tokenInstance.address], + }); +``` + +**Example: withdrawing from an escrow contract** + +```diff + await escrowContract.methods + .withdraw(token.address, amount, recipient) +- .send({ from: owner }); ++ .send({ from: owner, additionalScopes: [escrowContract.address] }); +``` + ### `simulateUtility` renamed to `executeUtility` The `simulateUtility` method and related types have been renamed to `executeUtility` across the entire stack to better reflect that utility functions are executed, not simulated. diff --git a/docs/docs-operate/operators/reference/changelog/v4.md b/docs/docs-operate/operators/reference/changelog/v4.md index baff6badfd71..be85e1cb5713 100644 --- a/docs/docs-operate/operators/reference/changelog/v4.md +++ b/docs/docs-operate/operators/reference/changelog/v4.md @@ -105,6 +105,56 @@ The byte-based block size limit has been removed and replaced with field-based b **Migration**: Remove `SEQ_MAX_BLOCK_SIZE_IN_BYTES` from your configuration. Per-block L2 and DA gas budgets are now derived automatically as `(checkpointLimit / maxBlocks) * multiplier`, where the multiplier defaults to 2. You can still override `SEQ_MAX_L2_BLOCK_GAS` and `SEQ_MAX_DA_BLOCK_GAS` explicitly, but they will be capped at the checkpoint-level limits. Validators can now set independent per-block and per-checkpoint limits via the `VALIDATOR_` env vars; when not set, only checkpoint-level protocol limits are enforced. +### Setup phase allow list requires function selectors + +The transaction setup phase allow list now enforces function selectors, restricting which specific functions can run during setup on whitelisted contracts. Previously, any public function on a whitelisted contract or class was permitted. + +The semantics of the environment variable `TX_PUBLIC_SETUP_ALLOWLIST` have changed: + +**v3.x:** + +```bash +--txPublicSetupAllowList ($TX_PUBLIC_SETUP_ALLOWLIST) +``` + +The variable fully **replaced** the hardcoded defaults. Format allowed entries without selectors: `I:address`, `C:classId`. + +**v4.0.0:** + +```bash +--txPublicSetupAllowListExtend ($TX_PUBLIC_SETUP_ALLOWLIST) +``` + +The variable now **extends** the hardcoded defaults (which are always present). Selectors are now mandatory. An optional flags segment can be appended for additional validation: + +``` +I:address:selector[:flags] +C:classId:selector[:flags] +``` + +Where `flags` is a `+`-separated list of: +- `os` — `onlySelf`: only allow calls where msg_sender == contract address +- `rn` — `rejectNullMsgSender`: reject calls with a null msg_sender +- `cl=N` — `calldataLength`: enforce exact calldata length of N fields + +Example: `C:0xabc:0x1234:os+cl=4` + +**Migration**: If you were using `TX_PUBLIC_SETUP_ALLOWLIST`, ensure all entries include function selectors. Note the variable now adds to defaults rather than replacing them. If you were not setting this variable, no action is needed — the hardcoded defaults now include the correct selectors automatically. + +### Token removed from default setup allowlist + +Token class-based entries (`_increase_public_balance` and `transfer_in_public`) have been removed from the default public setup allowlist. FPC-based fee payments using custom tokens no longer work out of the box. + +This change was made because Token class IDs change with aztec-nr releases, making the allowlist impossible to keep up to date with new library releases. In addition, `transfer_in_public` requires complex additional logic to be built into the node to prevent mass transaction invalidation attacks. **FPC-based fee payment with custom tokens won't work on mainnet alpha**. + +**Migration**: Node operators who need FPC support must manually add Token entries via `TX_PUBLIC_SETUP_ALLOWLIST`. Example: + +```bash +TX_PUBLIC_SETUP_ALLOWLIST="C:::os+cl=3,C:::cl=5" +``` + +Replace `` with the deployed Token contract class ID and ``/`` with the respective function selectors. Keep in mind that this will only work on local network setups, since even if you as an operator add these entries, other nodes will not have them and will not pick up these transactions. + ## Removed features ## New features @@ -184,6 +234,10 @@ P2P_RPC_PRICE_BUMP_PERCENTAGE=10 # default: 10 (percent) Set to `0` to disable the percentage-based bump (still requires strictly higher fee). +### Setup allow list extendable via network config + +The setup phase allow list can now be extended via the network configuration JSON (`txPublicSetupAllowListExtend` field). This allows network operators to distribute additional allowed setup functions to all nodes without requiring code changes. The local environment variable takes precedence over the network-json value. + ### Validator-specific block limits Validators can now enforce per-block and per-checkpoint limits independently from the sequencer (proposer) limits. This allows operators to accept proposals that exceed their own proposer settings, or to reject proposals that are too large even if the proposer's limits allow them. diff --git a/docs/examples/ts/aztecjs_advanced/index.ts b/docs/examples/ts/aztecjs_advanced/index.ts index 85f3a2aef2ca..bd138a88ff79 100644 --- a/docs/examples/ts/aztecjs_advanced/index.ts +++ b/docs/examples/ts/aztecjs_advanced/index.ts @@ -30,7 +30,7 @@ const [aliceAddress, bobAddress] = await Promise.all( // docs:start:deploy_basic_local // wallet and aliceAddress are from the connection guide // Deploy with constructor arguments -const token = await TokenContract.deploy( +const { contract: token } = await TokenContract.deploy( wallet, aliceAddress, "TestToken", @@ -49,7 +49,7 @@ await wallet.registerContract(sponsoredFPCInstance, SponsoredFPCContract.artifac const sponsoredPaymentMethod = new SponsoredFeePaymentMethod(sponsoredFPCInstance.address); // wallet is from the connection guide; sponsoredPaymentMethod is from the fees guide -const sponsoredContract = await TokenContract.deploy( +const { contract: sponsoredContract } = await TokenContract.deploy( wallet, aliceAddress, "SponsoredToken", @@ -62,7 +62,7 @@ const sponsoredContract = await TokenContract.deploy( // wallet and aliceAddress are from the connection guide const customSalt = Fr.random(); -const saltedContract = await TokenContract.deploy( +const { contract: saltedContract } = await TokenContract.deploy( wallet, aliceAddress, "SaltedToken", @@ -95,7 +95,7 @@ console.log(`Contract will deploy at: ${predictedAddress}`); // token is from the deployment step above; aliceAddress is from the connection guide try { // Try calling a view function - const balance = await token.methods + const { result: balance } = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); console.log("Contract is callable, balance:", balance); @@ -128,7 +128,7 @@ await token.methods // docs:start:no_wait_deploy // Use NO_WAIT to get the transaction hash immediately and track deployment -const txHash = await TokenContract.deploy( +const { txHash } = await TokenContract.deploy( wallet, aliceAddress, "AnotherToken", @@ -148,7 +148,7 @@ console.log(`Deployed in block ${receipt.blockNumber}`); // docs:start:no_wait_transaction // Use NO_WAIT for regular transactions too -const transferTxHash = await token.methods +const { txHash: transferTxHash } = await token.methods .transfer(bobAddress, 100n) .send({ from: aliceAddress, wait: NO_WAIT }); @@ -166,7 +166,7 @@ const batch = new BatchCall(wallet, [ token.methods.transfer(bobAddress, 200n), ]); -const batchReceipt = await batch.send({ from: aliceAddress }); +const { receipt: batchReceipt } = await batch.send({ from: aliceAddress }); console.log(`Batch executed in block ${batchReceipt.blockNumber}`); // docs:end:batch_call @@ -193,7 +193,7 @@ console.log( // docs:start:query_tx_status // Query transaction status after sending without waiting -const statusTxHash = await token.methods +const { txHash: statusTxHash } = await token.methods .transfer(bobAddress, 10n) .send({ from: aliceAddress, wait: NO_WAIT }); @@ -207,7 +207,7 @@ console.log(`Transaction fee: ${txReceipt.transactionFee}`); // docs:start:deploy_with_dependencies // Deploy contracts with dependencies - deploy sequentially and pass addresses -const baseToken = await TokenContract.deploy( +const { contract: baseToken } = await TokenContract.deploy( wallet, aliceAddress, "BaseToken", @@ -216,7 +216,7 @@ const baseToken = await TokenContract.deploy( ).send({ from: aliceAddress }); // A second contract could reference the first (example pattern) -const derivedToken = await TokenContract.deploy( +const { contract: derivedToken } = await TokenContract.deploy( wallet, baseToken.address, // Use first contract's address as admin "DerivedToken", @@ -233,13 +233,13 @@ console.log(`Derived token at: ${derivedToken.address.toString()}`); const contracts = await Promise.all([ TokenContract.deploy(wallet, aliceAddress, "Token1", "T1", 18).send({ from: aliceAddress, - }), + }).then(({ contract }) => contract), TokenContract.deploy(wallet, aliceAddress, "Token2", "T2", 18).send({ from: aliceAddress, - }), + }).then(({ contract }) => contract), TokenContract.deploy(wallet, aliceAddress, "Token3", "T3", 18).send({ from: aliceAddress, - }), + }).then(({ contract }) => contract), ]); console.log(`Contract 1 at: ${contracts[0].address}`); @@ -249,7 +249,7 @@ console.log(`Contract 3 at: ${contracts[2].address}`); // docs:start:skip_initialization // Deploy without running the constructor using skipInitialization -const delayedToken = await TokenContract.deploy( +const { contract: delayedToken } = await TokenContract.deploy( wallet, aliceAddress, "DelayedToken", diff --git a/docs/examples/ts/aztecjs_authwit/index.ts b/docs/examples/ts/aztecjs_authwit/index.ts index da0f03a80be0..04833cd90951 100644 --- a/docs/examples/ts/aztecjs_authwit/index.ts +++ b/docs/examples/ts/aztecjs_authwit/index.ts @@ -23,7 +23,7 @@ const [aliceAddress, bobAddress] = await Promise.all( }), ); -const tokenContract = await TokenContract.deploy( +const { contract: tokenContract } = await TokenContract.deploy( wallet, aliceAddress, "TestToken", diff --git a/docs/examples/ts/aztecjs_connection/index.ts b/docs/examples/ts/aztecjs_connection/index.ts index 4267a6597b9b..5b4074c9b5ec 100644 --- a/docs/examples/ts/aztecjs_connection/index.ts +++ b/docs/examples/ts/aztecjs_connection/index.ts @@ -91,7 +91,7 @@ console.log("Account deployed:", metadata.isContractInitialized); // docs:start:deploy_contract import { TokenContract } from "@aztec/noir-contracts.js/Token"; -const token = await TokenContract.deploy( +const { contract: token } = await TokenContract.deploy( wallet, aliceAddress, "TestToken", @@ -103,7 +103,7 @@ console.log(`Token deployed at: ${token.address.toString()}`); // docs:end:deploy_contract // docs:start:send_transaction -const receipt = await token.methods +const { receipt } = await token.methods .mint_to_public(aliceAddress, 1000n) .send({ from: aliceAddress }); @@ -112,7 +112,7 @@ console.log(`Transaction fee: ${receipt.transactionFee}`); // docs:end:send_transaction // docs:start:simulate_function -const balance = await token.methods +const { result: balance } = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); diff --git a/docs/examples/ts/aztecjs_getting_started/index.ts b/docs/examples/ts/aztecjs_getting_started/index.ts index 9b3e7ec27ff5..de5a4ac22eae 100644 --- a/docs/examples/ts/aztecjs_getting_started/index.ts +++ b/docs/examples/ts/aztecjs_getting_started/index.ts @@ -13,7 +13,7 @@ await wallet.createSchnorrAccount(bob.secret, bob.salt); // docs:start:deploy import { TokenContract } from "@aztec/noir-contracts.js/Token"; -const token = await TokenContract.deploy( +const { contract: token } = await TokenContract.deploy( wallet, alice.address, "TokenName", @@ -29,11 +29,11 @@ await token.methods // docs:end:mint // docs:start:check_balances -let aliceBalance = await token.methods +let { result: aliceBalance } = await token.methods .balance_of_private(alice.address) .simulate({ from: alice.address }); console.log(`Alice's balance: ${aliceBalance}`); -let bobBalance = await token.methods +let { result: bobBalance } = await token.methods .balance_of_private(bob.address) .simulate({ from: bob.address }); console.log(`Bob's balance: ${bobBalance}`); @@ -41,9 +41,9 @@ console.log(`Bob's balance: ${bobBalance}`); // docs:start:transfer await token.methods.transfer(bob.address, 10).send({ from: alice.address }); -bobBalance = await token.methods +({ result: bobBalance } = await token.methods .balance_of_private(bob.address) - .simulate({ from: bob.address }); + .simulate({ from: bob.address })); console.log(`Bob's balance: ${bobBalance}`); // docs:end:transfer @@ -55,8 +55,8 @@ await token.methods.set_minter(bob.address, true).send({ from: alice.address }); await token.methods .mint_to_private(bob.address, 100) .send({ from: bob.address }); -bobBalance = await token.methods +({ result: bobBalance } = await token.methods .balance_of_private(bob.address) - .simulate({ from: bob.address }); + .simulate({ from: bob.address })); console.log(`Bob's balance: ${bobBalance}`); // docs:end:bob_mints diff --git a/docs/examples/ts/aztecjs_testing/index.ts b/docs/examples/ts/aztecjs_testing/index.ts index 68af11270b77..0c50ac8e5a33 100644 --- a/docs/examples/ts/aztecjs_testing/index.ts +++ b/docs/examples/ts/aztecjs_testing/index.ts @@ -26,7 +26,7 @@ async function setup() { }), ); - token = await TokenContract.deploy( + ({ contract: token } = await TokenContract.deploy( wallet, aliceAddress, "Test", @@ -34,7 +34,7 @@ async function setup() { 18, ).send({ from: aliceAddress, - }); + })); } // Test: mints tokens to an account @@ -43,7 +43,7 @@ async function testMintTokens() { .mint_to_public(aliceAddress, 1000n) .send({ from: aliceAddress }); - const balance = await token.methods + const { result: balance } = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); @@ -63,10 +63,10 @@ async function testTransferTokens() { // Transfer to bob using the simple transfer method await token.methods.transfer(bobAddress, 100n).send({ from: aliceAddress }); - const aliceBalance = await token.methods + const { result: aliceBalance } = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); - const bobBalance = await token.methods + const { result: bobBalance } = await token.methods .balance_of_public(bobAddress) .simulate({ from: bobAddress }); @@ -77,7 +77,7 @@ async function testTransferTokens() { // Test: reverts when transferring more than balance async function testRevertOnOverTransfer() { - const balance = await token.methods + const { result: balance } = await token.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); diff --git a/docs/examples/ts/bob_token_contract/index.ts b/docs/examples/ts/bob_token_contract/index.ts index ec71b47da703..9b154e1279a3 100644 --- a/docs/examples/ts/bob_token_contract/index.ts +++ b/docs/examples/ts/bob_token_contract/index.ts @@ -15,16 +15,20 @@ async function getBalances( await Promise.all([ contract.methods .public_balance_of(aliceAddress) - .simulate({ from: aliceAddress }), + .simulate({ from: aliceAddress }) + .then(({ result }) => result), contract.methods .private_balance_of(aliceAddress) - .simulate({ from: aliceAddress }), + .simulate({ from: aliceAddress }) + .then(({ result }) => result), contract.methods .public_balance_of(bobAddress) - .simulate({ from: bobAddress }), + .simulate({ from: bobAddress }) + .then(({ result }) => result), contract.methods .private_balance_of(bobAddress) - .simulate({ from: bobAddress }), + .simulate({ from: bobAddress }) + .then(({ result }) => result), ]).then( ([ alicePublicBalance, @@ -69,7 +73,7 @@ async function main() { const aliceAddress = aliceAccountManager.address; const bobClinicAddress = bobClinicAccountManager.address; - const bobToken = await BobTokenContract.deploy(wallet).send({ + const { contract: bobToken } = await BobTokenContract.deploy(wallet).send({ from: giggleAddress, }); diff --git a/docs/examples/ts/recursive_verification/index.ts b/docs/examples/ts/recursive_verification/index.ts index 43712fcd38eb..423d1c0d287d 100644 --- a/docs/examples/ts/recursive_verification/index.ts +++ b/docs/examples/ts/recursive_verification/index.ts @@ -65,7 +65,7 @@ async function main() { // Step 2: Deploy ValueNotEqual contract // Constructor args: initial counter (10), owner, VK hash - const valueNotEqual = await ValueNotEqualContract.deploy( + const { contract: valueNotEqual } = await ValueNotEqualContract.deploy( wallet, 10, // Initial counter value accounts[0].item, // Owner address @@ -84,9 +84,9 @@ async function main() { // Step 3: Read initial counter value // simulate() executes without submitting a transaction - let counterValue = await valueNotEqual.methods + let counterValue = (await valueNotEqual.methods .get_counter(accounts[0].item) - .simulate({ from: accounts[0].item }); + .simulate({ from: accounts[0].item })).result; console.log(`Counter value: ${counterValue}`); // Should be 10 // Step 4: Call increment() with proof data @@ -107,9 +107,9 @@ async function main() { await interaction.send(opts); // Step 6: Read updated counter - counterValue = await valueNotEqual.methods + counterValue = (await valueNotEqual.methods .get_counter(accounts[0].item) - .simulate({ from: accounts[0].item }); + .simulate({ from: accounts[0].item })).result; console.log(`Counter value: ${counterValue}`); // Should be 11 assert(counterValue === 11n, "Counter should be 11 after verification"); diff --git a/docs/examples/ts/token_bridge/index.ts b/docs/examples/ts/token_bridge/index.ts index 74ad78653034..81c410bae820 100644 --- a/docs/examples/ts/token_bridge/index.ts +++ b/docs/examples/ts/token_bridge/index.ts @@ -69,11 +69,11 @@ console.log(`NFTPortal: ${portalAddress}\n`); // docs:start:deploy_l2_contracts console.log("Deploying L2 contracts...\n"); -const l2Nft = await NFTPunkContract.deploy(aztecWallet, account.address).send({ +const { contract: l2Nft } = await NFTPunkContract.deploy(aztecWallet, account.address).send({ from: account.address, }); -const l2Bridge = await NFTBridgeContract.deploy( +const { contract: l2Bridge } = await NFTBridgeContract.deploy( aztecWallet, l2Nft.address, ).send({ from: account.address }); @@ -222,7 +222,7 @@ await mine2Blocks(aztecWallet, account.address); // Check notes before claiming (should be 0) console.log("Checking notes before claim..."); -const notesBefore = await l2Nft.methods +const { result: notesBefore } = await l2Nft.methods .notes_of(account.address) .simulate({ from: account.address }); console.log(` Notes count: ${notesBefore}`); @@ -235,7 +235,7 @@ console.log("NFT claimed on L2\n"); // Check notes after claiming (should be 1) console.log("Checking notes after claim..."); -const notesAfterClaim = await l2Nft.methods +const { result: notesAfterClaim } = await l2Nft.methods .notes_of(account.address) .simulate({ from: account.address }); console.log(` Notes count: ${notesAfterClaim}\n`); @@ -249,7 +249,7 @@ await mine2Blocks(aztecWallet, account.address); const recipientEthAddress = EthAddress.fromString(ownerEthAddress); -const exitReceipt = await l2Bridge.methods +const { receipt: exitReceipt } = await l2Bridge.methods .exit(new Fr(Number(tokenId)), recipientEthAddress) .send({ from: account.address }); @@ -257,7 +257,7 @@ console.log(`Exit message sent (block: ${exitReceipt.blockNumber})\n`); // Check notes after burning (should be 0 again) console.log("Checking notes after burn..."); -const notesAfterBurn = await l2Nft.methods +const { result: notesAfterBurn } = await l2Nft.methods .notes_of(account.address) .simulate({ from: account.address }); console.log(` Notes count: ${notesAfterBurn}\n`); diff --git a/noir-projects/aztec-nr/aztec/src/messages/encryption/aes128.nr b/noir-projects/aztec-nr/aztec/src/messages/encryption/aes128.nr index e4b6a07b1a84..b73fcb96e135 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/encryption/aes128.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/encryption/aes128.nr @@ -247,9 +247,6 @@ impl MessageEncryption for AES128 { // Derive ECDH shared secret with recipient using a fresh ephemeral keypair. let (eph_sk, eph_pk) = generate_positive_ephemeral_key_pair(); - // (not to be confused with the tagging shared secret) TODO (#17158): Currently we unwrap the Option returned - // by derive_ecdh_shared_secret. We need to handle the case where the ephemeral public key is invalid to - // prevent potential DoS vectors. let ciphertext_shared_secret = derive_ecdh_shared_secret( eph_sk, recipient @@ -269,7 +266,6 @@ impl MessageEncryption for AES128 { ) .inner, ); - // TODO: also use this shared secret for deriving note randomness. // AES128-CBC encrypt the plaintext bytes. // It is safe to call the `unsafe` function here, because we know the `shared_secret` was derived using an diff --git a/noir-projects/noir-contracts/contracts/fees/fpc_contract/src/main.nr b/noir-projects/noir-contracts/contracts/fees/fpc_contract/src/main.nr index 62014342f741..c0b209eb6451 100644 --- a/noir-projects/noir-contracts/contracts/fees/fpc_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/fees/fpc_contract/src/main.nr @@ -6,6 +6,10 @@ use aztec::macros::aztec; /// Fee Payment Contract (FPC) allows users to pay for the transaction fee with an arbitrary asset. Supports private /// and public fee payment flows. /// +/// **WARNING**: This is an example/reference implementation. FPC-based fee payment with custom tokens will NOT work +/// on mainnet alpha because Token class IDs change with aztec-nr releases and are not included in the default public +/// setup allowlist. +/// /// ***Note:*** /// Accepted asset funds sent by the users to this contract stay in this contract and later on can /// be pulled by the admin using the `pull_funds` function. diff --git a/playground/src/components/contract/components/FunctionCard.tsx b/playground/src/components/contract/components/FunctionCard.tsx index 6f4c9e5415a2..21f3b2696b2e 100644 --- a/playground/src/components/contract/components/FunctionCard.tsx +++ b/playground/src/components/contract/components/FunctionCard.tsx @@ -90,7 +90,7 @@ export function FunctionCard({ fn, contract, contractArtifact, onSendTxRequested let result; try { const call = contract.methods[fnName](...parameters); - result = await call.simulate({ from, skipFeeEnforcement: true }); + ({ result } = await call.simulate({ from, skipFeeEnforcement: true })); const stringResult = JSON.stringify(result, (key, value) => { if (typeof value === 'bigint') { return value.toString(); diff --git a/playground/src/hooks/useTransaction.tsx b/playground/src/hooks/useTransaction.tsx index 202c6017481f..9f1e6832e6c2 100644 --- a/playground/src/hooks/useTransaction.tsx +++ b/playground/src/hooks/useTransaction.tsx @@ -47,10 +47,10 @@ export function useTransaction() { if (interaction instanceof DeployMethod) { const { from, fee, ...deployOpts } = opts as DeployOptions; - txHash = await interaction.send({ from, fee, ...deployOpts, wait: NO_WAIT }); + ({ txHash } = await interaction.send({ from, fee, ...deployOpts, wait: NO_WAIT })); } else { const { from, fee, authWitnesses, capsules } = opts as SendInteractionOptions; - txHash = await interaction.send({ from, fee, authWitnesses, capsules, wait: NO_WAIT }); + ({ txHash } = await interaction.send({ from, fee, authWitnesses, capsules, wait: NO_WAIT })); } setCurrentTx({ diff --git a/spartan/environments/five-tps-long-epoch.env b/spartan/environments/five-tps-long-epoch.env index ff87b7ef63a2..b5944eb481af 100644 --- a/spartan/environments/five-tps-long-epoch.env +++ b/spartan/environments/five-tps-long-epoch.env @@ -55,7 +55,7 @@ AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS=0 AZTEC_SLASHING_OFFSET_IN_ROUNDS=1 AZTEC_LOCAL_EJECTION_THRESHOLD=90000000000000000000 -SEQ_MAX_TX_PER_BLOCK=180 +SEQ_MAX_TX_PER_CHECKPOINT=180 SEQ_MIN_TX_PER_BLOCK=0 # Override L1 tx utils bump percentages for scenario tests diff --git a/spartan/environments/five-tps-short-epoch.env b/spartan/environments/five-tps-short-epoch.env index 85f36344fc19..503f4d1b0856 100644 --- a/spartan/environments/five-tps-short-epoch.env +++ b/spartan/environments/five-tps-short-epoch.env @@ -55,7 +55,7 @@ AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS=0 AZTEC_SLASHING_OFFSET_IN_ROUNDS=1 AZTEC_LOCAL_EJECTION_THRESHOLD=90000000000000000000 -SEQ_MAX_TX_PER_BLOCK=180 +SEQ_MAX_TX_PER_CHECKPOINT=180 SEQ_MIN_TX_PER_BLOCK=0 # Override L1 tx utils bump percentages for scenario tests diff --git a/spartan/environments/network-defaults.yml b/spartan/environments/network-defaults.yml index daec5d93e53e..6f14c3a02b9c 100644 --- a/spartan/environments/network-defaults.yml +++ b/spartan/environments/network-defaults.yml @@ -275,11 +275,11 @@ networks: SPONSORED_FPC: true TRANSACTIONS_DISABLED: false # Sequencer - # Gives ~0.1 TPS @ 72s slot time, 36s publish time, 6s block time - max 4 blocks per slot - SEQ_MAX_TX_PER_BLOCK: 2 + SEQ_MAX_TX_PER_CHECKPOINT: 72 # 1 TPS # Prover PROVER_REAL_PROOFS: true # P2P + P2P_MAX_PENDING_TX_COUNT: 1000 P2P_TX_POOL_DELETE_TXS_AFTER_REORG: true # Slasher penalties SLASH_PRUNE_PENALTY: 10e18 @@ -326,16 +326,16 @@ networks: # Genesis state - no test accounts, no sponsored FPC TEST_ACCOUNTS: false SPONSORED_FPC: false - TRANSACTIONS_DISABLED: true # Initially disabled + TRANSACTIONS_DISABLED: false # Sequencer - SEQ_MAX_TX_PER_BLOCK: 0 + SEQ_MAX_TX_PER_CHECKPOINT: 72 # Prover PROVER_REAL_PROOFS: true # Sync SYNC_SNAPSHOTS_URLS: "https://aztec-labs-snapshots.com/mainnet/" BLOB_ALLOW_EMPTY_SOURCES: true # P2P - P2P_MAX_PENDING_TX_COUNT: 0 + P2P_MAX_PENDING_TX_COUNT: 1000 P2P_TX_POOL_DELETE_TXS_AFTER_REORG: true # Telemetry PUBLIC_OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: "" diff --git a/spartan/environments/next-net.env b/spartan/environments/next-net.env index b2ef9110eb44..214785578995 100644 --- a/spartan/environments/next-net.env +++ b/spartan/environments/next-net.env @@ -29,8 +29,8 @@ TEST_ACCOUNTS=true SPONSORED_FPC=true SEQ_MIN_TX_PER_BLOCK=0 -# Gives ~0.1 TPS @ 72s slot time, 36s publish time, 6s block time - max 4 blocks per slot -SEQ_MAX_TX_PER_BLOCK=2 + +SEQ_MAX_TX_PER_CHECKPOINT=7 # Build checkpoint even if block is empty. SEQ_BUILD_CHECKPOINT_IF_EMPTY=true diff --git a/spartan/environments/prove-n-tps-fake.env b/spartan/environments/prove-n-tps-fake.env index bed763852285..89cdcce263a2 100644 --- a/spartan/environments/prove-n-tps-fake.env +++ b/spartan/environments/prove-n-tps-fake.env @@ -39,7 +39,7 @@ PUBLISHERS_PER_PROVER=1 PROVER_TEST_DELAY_TYPE=realistic PROVER_TEST_VERIFICATION_DELAY_MS=250 -SEQ_MAX_TX_PER_BLOCK=80 +SEQ_MAX_TX_PER_CHECKPOINT=80 SEQ_MIN_TX_PER_BLOCK=0 P2P_MAX_TX_POOL_SIZE=1000000000 DEBUG_P2P_INSTRUMENT_MESSAGES=true diff --git a/spartan/environments/prove-n-tps-real.env b/spartan/environments/prove-n-tps-real.env index 63af6079257c..962def7f71a8 100644 --- a/spartan/environments/prove-n-tps-real.env +++ b/spartan/environments/prove-n-tps-real.env @@ -37,7 +37,7 @@ PROVER_PUBLISHER_MNEMONIC_START_INDEX=8000 PROVER_AGENT_POLL_INTERVAL_MS=10000 PUBLISHERS_PER_PROVER=1 -SEQ_MAX_TX_PER_BLOCK=18 +SEQ_MAX_TX_PER_CHECKPOINT=72 SEQ_MIN_TX_PER_BLOCK=0 SEQ_BLOCK_DURATION_MS=6000 SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT=36 diff --git a/spartan/environments/staging-public.env b/spartan/environments/staging-public.env index 33d66c685ec1..9bbdd78887b6 100644 --- a/spartan/environments/staging-public.env +++ b/spartan/environments/staging-public.env @@ -26,8 +26,7 @@ TEST_ACCOUNTS=false SPONSORED_FPC=true SEQ_MIN_TX_PER_BLOCK=0 -# Gives ~0.1 TPS @ 72s slot time, 36s publish time, 6s block time - max 4 blocks per slot -SEQ_MAX_TX_PER_BLOCK=2 +SEQ_MAX_TX_PER_CHECKPOINT=7 # 0.1 TPS # Build checkpoint even if block is empty. SEQ_BUILD_CHECKPOINT_IF_EMPTY=true diff --git a/spartan/environments/ten-tps-long-epoch.env b/spartan/environments/ten-tps-long-epoch.env index e3fefc644364..8b8b4679c489 100644 --- a/spartan/environments/ten-tps-long-epoch.env +++ b/spartan/environments/ten-tps-long-epoch.env @@ -55,7 +55,7 @@ AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS=0 AZTEC_SLASHING_OFFSET_IN_ROUNDS=1 AZTEC_LOCAL_EJECTION_THRESHOLD=90000000000000000000 -SEQ_MAX_TX_PER_BLOCK=360 +SEQ_MAX_TX_PER_CHECKPOINT=360 SEQ_MIN_TX_PER_BLOCK=0 # Override L1 tx utils bump percentages for scenario tests diff --git a/spartan/environments/ten-tps-short-epoch.env b/spartan/environments/ten-tps-short-epoch.env index 90f16277c385..86585811d876 100644 --- a/spartan/environments/ten-tps-short-epoch.env +++ b/spartan/environments/ten-tps-short-epoch.env @@ -55,7 +55,7 @@ AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS=0 AZTEC_SLASHING_OFFSET_IN_ROUNDS=1 AZTEC_LOCAL_EJECTION_THRESHOLD=90000000000000000000 -SEQ_MAX_TX_PER_BLOCK=360 +SEQ_MAX_TX_PER_CHECKPOINT=360 SEQ_MIN_TX_PER_BLOCK=0 # Override L1 tx utils bump percentages for scenario tests diff --git a/spartan/environments/testnet.env b/spartan/environments/testnet.env index 8702a5335d4e..f8265adf3cdd 100644 --- a/spartan/environments/testnet.env +++ b/spartan/environments/testnet.env @@ -32,6 +32,7 @@ BOT_TRANSFERS_FOLLOW_CHAIN=PENDING BOT_SWAPS_REPLICAS=0 P2P_TX_POOL_DELETE_TXS_AFTER_REORG=true +SEQ_MAX_TX_PER_CHECKPOINT=72 DEPLOY_ARCHIVAL_NODE=true diff --git a/spartan/environments/tps-scenario.env b/spartan/environments/tps-scenario.env index abdaf40948b0..a9b025f7f4e1 100644 --- a/spartan/environments/tps-scenario.env +++ b/spartan/environments/tps-scenario.env @@ -67,7 +67,7 @@ AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS=0 AZTEC_SLASHING_OFFSET_IN_ROUNDS=1 AZTEC_LOCAL_EJECTION_THRESHOLD=90000000000000000000 -SEQ_MAX_TX_PER_BLOCK=15 # approx 0.2 TPS +SEQ_MAX_TX_PER_CHECKPOINT=15 # approx 0.2 TPS SEQ_MIN_TX_PER_BLOCK=0 # Override L1 tx utils bump percentages for scenario tests diff --git a/spartan/scripts/deploy_network.sh b/spartan/scripts/deploy_network.sh index 93e776ef615a..223cef36b8f7 100755 --- a/spartan/scripts/deploy_network.sh +++ b/spartan/scripts/deploy_network.sh @@ -106,7 +106,8 @@ fi PROVER_FAILED_PROOF_STORE=${PROVER_FAILED_PROOF_STORE:-} SEQ_MIN_TX_PER_BLOCK=${SEQ_MIN_TX_PER_BLOCK:-0} -SEQ_MAX_TX_PER_BLOCK=${SEQ_MAX_TX_PER_BLOCK:-8} +SEQ_MAX_TX_PER_BLOCK=${SEQ_MAX_TX_PER_BLOCK:-null} +SEQ_MAX_TX_PER_CHECKPOINT=${SEQ_MAX_TX_PER_CHECKPOINT:-8} SEQ_BLOCK_DURATION_MS=${SEQ_BLOCK_DURATION_MS:-} SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT=${SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT:-} SEQ_BUILD_CHECKPOINT_IF_EMPTY=${SEQ_BUILD_CHECKPOINT_IF_EMPTY:-} @@ -513,6 +514,7 @@ VALIDATOR_PUBLISHERS_PER_VALIDATOR_KEY = ${PUBLISHERS_PER_VALIDATOR_KEY} VALIDATOR_HA_REPLICAS = ${VALIDATOR_HA_REPLICAS} SEQ_MIN_TX_PER_BLOCK = ${SEQ_MIN_TX_PER_BLOCK} SEQ_MAX_TX_PER_BLOCK = ${SEQ_MAX_TX_PER_BLOCK} +SEQ_MAX_TX_PER_CHECKPOINT = ${SEQ_MAX_TX_PER_CHECKPOINT} SEQ_BLOCK_DURATION_MS = ${SEQ_BLOCK_DURATION_MS:-null} SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT = ${SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT:-null} SEQ_BUILD_CHECKPOINT_IF_EMPTY = ${SEQ_BUILD_CHECKPOINT_IF_EMPTY:-null} diff --git a/spartan/terraform/deploy-aztec-infra/main.tf b/spartan/terraform/deploy-aztec-infra/main.tf index 5f5370cb2995..9b06ef870bed 100644 --- a/spartan/terraform/deploy-aztec-infra/main.tf +++ b/spartan/terraform/deploy-aztec-infra/main.tf @@ -212,6 +212,7 @@ locals { "validator.node.proverRealProofs" = var.PROVER_REAL_PROOFS "validator.node.env.SEQ_MIN_TX_PER_BLOCK" = var.SEQ_MIN_TX_PER_BLOCK "validator.node.env.SEQ_MAX_TX_PER_BLOCK" = var.SEQ_MAX_TX_PER_BLOCK + "validator.node.env.SEQ_MAX_TX_PER_CHECKPOINT" = var.SEQ_MAX_TX_PER_CHECKPOINT "validator.node.env.SEQ_BLOCK_DURATION_MS" = var.SEQ_BLOCK_DURATION_MS "validator.node.env.SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT" = var.SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT "validator.node.env.SEQ_BUILD_CHECKPOINT_IF_EMPTY" = var.SEQ_BUILD_CHECKPOINT_IF_EMPTY diff --git a/spartan/terraform/deploy-aztec-infra/variables.tf b/spartan/terraform/deploy-aztec-infra/variables.tf index a37f80a93790..ecf14df0976b 100644 --- a/spartan/terraform/deploy-aztec-infra/variables.tf +++ b/spartan/terraform/deploy-aztec-infra/variables.tf @@ -343,6 +343,12 @@ variable "SEQ_MAX_TX_PER_BLOCK" { default = "8" } +variable "SEQ_MAX_TX_PER_CHECKPOINT" { + description = "Maximum number of sequencer transactions per checkpoint" + type = string + default = null +} + variable "SEQ_ENFORCE_TIME_TABLE" { description = "Whether to enforce the time table when building blocks" type = string diff --git a/yarn-project/archiver/src/store/message_store.ts b/yarn-project/archiver/src/store/message_store.ts index 4c07ba9f9c86..0408f7f0c3c2 100644 --- a/yarn-project/archiver/src/store/message_store.ts +++ b/yarn-project/archiver/src/store/message_store.ts @@ -137,7 +137,7 @@ export class MessageStore { ); } - // Check the first message in a block has the correct index. + // Check the first message in a checkpoint has the correct index. if ( (!lastMessage || message.checkpointNumber > lastMessage.checkpointNumber) && message.index !== expectedStart diff --git a/yarn-project/archiver/src/test/mock_l2_block_source.ts b/yarn-project/archiver/src/test/mock_l2_block_source.ts index ff4a0fe4af52..da295c09cb96 100644 --- a/yarn-project/archiver/src/test/mock_l2_block_source.ts +++ b/yarn-project/archiver/src/test/mock_l2_block_source.ts @@ -42,6 +42,12 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource { await this.createCheckpoints(numBlocks, 1); } + public getCheckpointNumber(): Promise { + return Promise.resolve( + this.checkpointList.length === 0 ? CheckpointNumber.ZERO : CheckpointNumber(this.checkpointList.length), + ); + } + /** Creates checkpoints, each containing `blocksPerCheckpoint` blocks. */ public async createCheckpoints(numCheckpoints: number, blocksPerCheckpoint: number = 1) { for (let c = 0; c < numCheckpoints; c++) { diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 04b0f7856345..4ea7c746cb74 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -343,9 +343,6 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { deps.p2pClientDeps, ); - // We should really not be modifying the config object - config.txPublicSetupAllowList = config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()); - // We'll accumulate sentinel watchers here const watchers: Watcher[] = []; @@ -391,22 +388,24 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { await validatorClient.registerHandlers(); } } + } - // If there's no validator client but alwaysReexecuteBlockProposals is enabled, - // create a BlockProposalHandler to reexecute block proposals for monitoring - if (!validatorClient && config.alwaysReexecuteBlockProposals) { - log.info('Setting up block proposal reexecution for monitoring'); - createBlockProposalHandler(config, { - checkpointsBuilder: validatorCheckpointsBuilder, - worldState: worldStateSynchronizer, - epochCache, - blockSource: archiver, - l1ToL2MessageSource: archiver, - p2pClient, - dateProvider, - telemetry, - }).registerForReexecution(p2pClient); - } + // If there's no validator client, create a BlockProposalHandler to handle block proposals + // for monitoring or reexecution. Reexecution (default) allows us to follow the pending chain, + // while non-reexecution is used for validating the proposals and collecting their txs. + if (!validatorClient) { + const reexecute = !!config.alwaysReexecuteBlockProposals; + log.info(`Setting up block proposal handler` + (reexecute ? ' with reexecution of proposals' : '')); + createBlockProposalHandler(config, { + checkpointsBuilder: validatorCheckpointsBuilder, + worldState: worldStateSynchronizer, + epochCache, + blockSource: archiver, + l1ToL2MessageSource: archiver, + p2pClient, + dateProvider, + telemetry, + }).register(p2pClient, reexecute); } // Start world state and wait for it to sync to the archiver. @@ -626,7 +625,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { } public async getAllowedPublicSetup(): Promise { - return this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()); + return [...(await getDefaultAllowedSetupFunctions()), ...(this.config.txPublicSetupAllowListExtend ?? [])]; } /** @@ -753,6 +752,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { return await this.blockSource.getCheckpointedL2BlockNumber(); } + public getCheckpointNumber(): Promise { + return this.blockSource.getCheckpointNumber(); + } + /** * Method to fetch the version of the package. * @returns The node package version @@ -1059,11 +1062,9 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { return [witness.index, witness.path]; } - public async getL1ToL2MessageBlock(l1ToL2Message: Fr): Promise { + public async getL1ToL2MessageCheckpoint(l1ToL2Message: Fr): Promise { const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message); - return messageIndex - ? BlockNumber.fromCheckpointNumber(InboxLeaf.checkpointNumberFromIndex(messageIndex)) - : undefined; + return messageIndex ? InboxLeaf.checkpointNumberFromIndex(messageIndex) : undefined; } /** @@ -1325,7 +1326,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { blockNumber, l1ChainId: this.l1ChainId, rollupVersion: this.version, - setupAllowList: this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()), + setupAllowList: [ + ...(await getDefaultAllowedSetupFunctions()), + ...(this.config.txPublicSetupAllowListExtend ?? []), + ], gasFees: await this.getCurrentMinFees(), skipFeeEnforcement, txsPermitted: !this.config.disableTransactions, diff --git a/yarn-project/aztec.js/src/api/contract.ts b/yarn-project/aztec.js/src/api/contract.ts index d3e9a692c67f..37481a99f94f 100644 --- a/yarn-project/aztec.js/src/api/contract.ts +++ b/yarn-project/aztec.js/src/api/contract.ts @@ -48,6 +48,8 @@ export { ContractFunctionInteraction } from '../contract/contract_function_inter export { NO_WAIT, type NoWait, + type OffchainMessage, + type OffchainOutput, type RequestInteractionOptions, type SendInteractionOptions, type ProfileInteractionOptions, @@ -56,7 +58,11 @@ export { type InteractionWaitOptions, type GasSettingsOption, type SendReturn, - type SimulationReturn, + type SimulationResult, + type TxSendResultImmediate, + type TxSendResultMined, + emptyOffchainOutput, + extractOffchainOutput, toProfileOptions, toSendOptions, toSimulateOptions, @@ -67,6 +73,7 @@ export { ContractBase, type ContractMethod, type ContractStorageLayout } from '. export { BatchCall } from '../contract/batch_call.js'; export { type DeployOptions, + type DeployResultMined, type DeployReturn, type DeployTxReceipt, type DeployWaitOptions, diff --git a/yarn-project/aztec.js/src/contract/base_contract_interaction.ts b/yarn-project/aztec.js/src/contract/base_contract_interaction.ts index 8e47f1eb7cfe..12983df3a0ea 100644 --- a/yarn-project/aztec.js/src/contract/base_contract_interaction.ts +++ b/yarn-project/aztec.js/src/contract/base_contract_interaction.ts @@ -9,6 +9,7 @@ import { type SendInteractionOptions, type SendInteractionOptionsWithoutWait, type SendReturn, + type TxSendResultMined, toSendOptions, } from './interaction_options.js'; @@ -41,8 +42,8 @@ export abstract class BaseContractInteraction { * the AztecAddress of the sender, optional fee configuration, and optional wait settings * @returns TReturn (if wait is undefined/WaitOpts) or TxHash (if wait is NO_WAIT) */ - // Overload for when wait is not specified at all - returns TReturn - public send(options: SendInteractionOptionsWithoutWait): Promise; + // Overload for when wait is not specified at all - returns { receipt: TReturn, offchainEffects } + public send(options: SendInteractionOptionsWithoutWait): Promise>; // Generic overload for explicit wait values // eslint-disable-next-line jsdoc/require-jsdoc public send( diff --git a/yarn-project/aztec.js/src/contract/batch_call.test.ts b/yarn-project/aztec.js/src/contract/batch_call.test.ts index 976884e68745..bcb976b1729c 100644 --- a/yarn-project/aztec.js/src/contract/batch_call.test.ts +++ b/yarn-project/aztec.js/src/contract/batch_call.test.ts @@ -119,6 +119,7 @@ describe('BatchCall', () => { nested: [{ values: privateReturnValues }], } as any); txSimResult.getPublicReturnValues.mockReturnValue([{ values: publicReturnValues }] as any); + Object.defineProperty(txSimResult, 'offchainEffects', { value: [] }); // Mock wallet.batch to return both utility results and simulateTx result wallet.batch.mockResolvedValue([ @@ -166,13 +167,13 @@ describe('BatchCall', () => { expect(results).toHaveLength(4); // First utility - decoded from Fr[] to bigint (single field returns the value directly, not as array) - expect(results[0]).toEqual(utilityResult1.result[0].toBigInt()); + expect(results[0].result).toEqual(utilityResult1.result[0].toBigInt()); // Results[1] will be the decoded private values (decoded from privateReturnValues) - expect(results[1]).toEqual(privateReturnValues.map(v => v.toBigInt())); // Private call (decoded) + expect(results[1].result).toEqual(privateReturnValues.map(v => v.toBigInt())); // Private call (decoded) // Second utility - decoded from Fr[] to bigint - expect(results[2]).toEqual(utilityResult2.result[0].toBigInt()); + expect(results[2].result).toEqual(utilityResult2.result[0].toBigInt()); // Results[3] will be the decoded public value (single value is returned directly, not as array) - expect(results[3]).toEqual(publicReturnValues[0].toBigInt()); // Public call (decoded) + expect(results[3].result).toEqual(publicReturnValues[0].toBigInt()); // Public call (decoded) }); it('should handle only utility calls without calling simulateTx', async () => { @@ -215,8 +216,8 @@ describe('BatchCall', () => { // Verify results - decoded from Fr[] to bigint expect(results).toHaveLength(2); - expect(results[0]).toEqual(utilityResult1.result[0].toBigInt()); - expect(results[1]).toEqual(utilityResult2.result[0].toBigInt()); + expect(results[0].result).toEqual(utilityResult1.result[0].toBigInt()); + expect(results[1].result).toEqual(utilityResult2.result[0].toBigInt()); }); it('should handle only private/public calls using wallet.batch with simulateTx', async () => { @@ -236,6 +237,7 @@ describe('BatchCall', () => { nested: [{ values: privateReturnValues }], } as any); txSimResult.getPublicReturnValues.mockReturnValue([{ values: publicReturnValues }] as any); + Object.defineProperty(txSimResult, 'offchainEffects', { value: [] }); wallet.batch.mockResolvedValue([{ name: 'simulateTx', result: txSimResult }] as any); @@ -259,8 +261,8 @@ describe('BatchCall', () => { // Verify results (decoded) expect(results).toHaveLength(2); - expect(results[0]).toEqual(privateReturnValues[0].toBigInt()); // Single value returned directly - expect(results[1]).toEqual(publicReturnValues[0].toBigInt()); // Single value returned directly + expect(results[0].result).toEqual(privateReturnValues[0].toBigInt()); // Single value returned directly + expect(results[1].result).toEqual(publicReturnValues[0].toBigInt()); // Single value returned directly }); it('should handle empty batch', async () => { diff --git a/yarn-project/aztec.js/src/contract/batch_call.ts b/yarn-project/aztec.js/src/contract/batch_call.ts index cd112be6f59b..66c668340687 100644 --- a/yarn-project/aztec.js/src/contract/batch_call.ts +++ b/yarn-project/aztec.js/src/contract/batch_call.ts @@ -6,6 +6,8 @@ import { BaseContractInteraction } from './base_contract_interaction.js'; import { type RequestInteractionOptions, type SimulateInteractionOptions, + emptyOffchainOutput, + extractOffchainOutput, toSimulateOptions, } from './interaction_options.js'; @@ -108,7 +110,10 @@ export class BatchCall extends BaseContractInteraction { const wrappedResult = batchResults[i]; if (wrappedResult.name === 'executeUtility') { const rawReturnValues = (wrappedResult.result as UtilityExecutionResult).result; - results[resultIndex] = rawReturnValues ? decodeFromAbi(call.returnTypes, rawReturnValues) : []; + results[resultIndex] = { + result: rawReturnValues ? decodeFromAbi(call.returnTypes, rawReturnValues) : [], + ...emptyOffchainOutput(), + }; } } @@ -127,7 +132,10 @@ export class BatchCall extends BaseContractInteraction { ? simulatedTx.getPrivateReturnValues()?.nested?.[resultIndex].values : simulatedTx.getPublicReturnValues()?.[resultIndex].values; - results[callIndex] = rawReturnValues ? decodeFromAbi(call.returnTypes, rawReturnValues) : []; + results[callIndex] = { + result: rawReturnValues ? decodeFromAbi(call.returnTypes, rawReturnValues) : [], + ...extractOffchainOutput(simulatedTx.offchainEffects), + }; }); } } diff --git a/yarn-project/aztec.js/src/contract/contract.test.ts b/yarn-project/aztec.js/src/contract/contract.test.ts index 28005f6ecbfc..36519b918f7c 100644 --- a/yarn-project/aztec.js/src/contract/contract.test.ts +++ b/yarn-project/aztec.js/src/contract/contract.test.ts @@ -136,7 +136,7 @@ describe('Contract Class', () => { wallet.simulateTx.mockResolvedValue(mockTxSimulationResult); account.createTxExecutionRequest.mockResolvedValue(mockTxRequest); wallet.registerContract.mockResolvedValue(contractInstance); - wallet.sendTx.mockResolvedValue(mockTxReceipt); + wallet.sendTx.mockResolvedValue({ receipt: mockTxReceipt, offchainEffects: [], offchainMessages: [] }); wallet.executeUtility.mockResolvedValue(mockUtilityResultValue); }); @@ -144,7 +144,7 @@ describe('Contract Class', () => { const fooContract = Contract.at(contractAddress, defaultArtifact, wallet); const param0 = 12; const param1 = 345n; - const receipt = await fooContract.methods.bar(param0, param1).send({ from: account.getAddress() }); + const { receipt } = await fooContract.methods.bar(param0, param1).send({ from: account.getAddress() }); expect(receipt).toBe(mockTxReceipt); expect(wallet.sendTx).toHaveBeenCalledTimes(1); @@ -152,7 +152,7 @@ describe('Contract Class', () => { it('should call view on a utility function', async () => { const fooContract = Contract.at(contractAddress, defaultArtifact, wallet); - const result = await fooContract.methods.qux(123n).simulate({ from: account.getAddress() }); + const { result } = await fooContract.methods.qux(123n).simulate({ from: account.getAddress() }); expect(wallet.executeUtility).toHaveBeenCalledTimes(1); expect(wallet.executeUtility).toHaveBeenCalledWith( expect.objectContaining({ name: 'qux', to: contractAddress }), diff --git a/yarn-project/aztec.js/src/contract/contract_function_interaction.ts b/yarn-project/aztec.js/src/contract/contract_function_interaction.ts index a087d85f0525..30d553d55151 100644 --- a/yarn-project/aztec.js/src/contract/contract_function_interaction.ts +++ b/yarn-project/aztec.js/src/contract/contract_function_interaction.ts @@ -8,7 +8,7 @@ import { } from '@aztec/stdlib/abi'; import type { AuthWitness } from '@aztec/stdlib/auth-witness'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { type Capsule, type HashedValues, type TxProfileResult, collectOffchainEffects } from '@aztec/stdlib/tx'; +import type { Capsule, HashedValues, TxProfileResult } from '@aztec/stdlib/tx'; import { ExecutionPayload, mergeExecutionPayloads } from '@aztec/stdlib/tx'; import type { Wallet } from '../wallet/wallet.js'; @@ -18,7 +18,9 @@ import { type ProfileInteractionOptions, type RequestInteractionOptions, type SimulateInteractionOptions, - type SimulationReturn, + type SimulationResult, + emptyOffchainOutput, + extractOffchainOutput, toProfileOptions, toSimulateOptions, } from './interaction_options.js'; @@ -97,17 +99,9 @@ export class ContractFunctionInteraction extends BaseContractInteraction { * function or a rich object containing extra metadata, such as estimated gas costs (if requested via options), * execution statistics and emitted offchain effects */ - public async simulate( - options: T, - ): Promise['estimateGas']>>; - // eslint-disable-next-line jsdoc/require-jsdoc - public async simulate( - options: T, - ): Promise>; - // eslint-disable-next-line jsdoc/require-jsdoc public async simulate( - options: SimulateInteractionOptions, - ): Promise> { + options: SimulateInteractionOptions = {} as SimulateInteractionOptions, + ): Promise { // docs:end:simulate if (this.functionDao.functionType == FunctionType.UTILITY) { const call = await this.getFunctionCall(); @@ -122,11 +116,11 @@ export class ContractFunctionInteraction extends BaseContractInteraction { if (options.includeMetadata) { return { stats: utilityResult.stats, + ...emptyOffchainOutput(), result: returnValue, }; - } else { - return returnValue; } + return { result: returnValue, ...emptyOffchainOutput() }; } const executionPayload = await this.request(options); @@ -148,6 +142,7 @@ export class ContractFunctionInteraction extends BaseContractInteraction { } const returnValue = rawReturnValues ? decodeFromAbi(this.functionDao.returnTypes, rawReturnValues) : []; + const offchainOutput = extractOffchainOutput(simulatedTx.offchainEffects); if (options.includeMetadata || options.fee?.estimateGas) { const { gasLimits, teardownGasLimits } = getGasLimits(simulatedTx, options.fee?.estimatedGasPadding); @@ -156,13 +151,12 @@ export class ContractFunctionInteraction extends BaseContractInteraction { ); return { stats: simulatedTx.stats, - offchainEffects: collectOffchainEffects(simulatedTx.privateExecutionResult), + ...offchainOutput, result: returnValue, estimatedGas: { gasLimits, teardownGasLimits }, }; - } else { - return returnValue; } + return { result: returnValue, ...offchainOutput }; } /** diff --git a/yarn-project/aztec.js/src/contract/deploy_method.ts b/yarn-project/aztec.js/src/contract/deploy_method.ts index 4fc50d66265a..f91881a5245a 100644 --- a/yarn-project/aztec.js/src/contract/deploy_method.ts +++ b/yarn-project/aztec.js/src/contract/deploy_method.ts @@ -9,14 +9,7 @@ import { getContractInstanceFromInstantiationParams, } from '@aztec/stdlib/contract'; import type { PublicKeys } from '@aztec/stdlib/keys'; -import { - type Capsule, - HashedValues, - TxHash, - type TxProfileResult, - type TxReceipt, - collectOffchainEffects, -} from '@aztec/stdlib/tx'; +import { type Capsule, HashedValues, type TxProfileResult, type TxReceipt } from '@aztec/stdlib/tx'; import { ExecutionPayload, mergeExecutionPayloads } from '@aztec/stdlib/tx'; import { publishContractClass } from '../deployment/publish_class.js'; @@ -29,11 +22,15 @@ import { getGasLimits } from './get_gas_limits.js'; import { NO_WAIT, type NoWait, + type OffchainOutput, type ProfileInteractionOptions, type RequestInteractionOptions, type SendInteractionOptionsWithoutWait, type SimulationInteractionFeeOptions, - type SimulationReturn, + type SimulationResult, + type TxSendResultImmediate, + type TxSendResultMined, + extractOffchainOutput, toProfileOptions, toSendOptions, toSimulateOptions, @@ -89,7 +86,7 @@ export type DeployOptionsWithoutWait = Omit & * is mutually exclusive with "deployer" */ universalDeploy?: boolean; -} & Pick; +} & Pick; /** * Extends the deployment options with the required parameters to send the transaction. @@ -130,20 +127,32 @@ export type DeployTxReceipt = TxR instance: ContractInstanceWithAddress; }; +/** Wait options that request a full receipt instead of just the contract instance. */ +type WaitWithReturnReceipt = { + /** Request the full receipt instead of just the contract instance. */ + returnReceipt: true; +}; + /** * Represents the result type of deploying a contract. * - If wait is NO_WAIT, returns TxHash immediately. * - If wait has returnReceipt: true, returns DeployTxReceipt after waiting. * - Otherwise (undefined or DeployWaitOptions without returnReceipt), returns TContract after waiting. */ +/** Result of deploying a contract when waiting for mining (default case). */ +export type DeployResultMined = { + /** The deployed contract instance. */ + contract: TContract; + /** The deploy transaction receipt. */ + receipt: DeployTxReceipt; +} & OffchainOutput; + +/** Conditional return type for deploy based on wait options. */ export type DeployReturn = W extends NoWait - ? TxHash - : W extends { - // eslint-disable-next-line jsdoc/require-jsdoc - returnReceipt: true; - } - ? DeployTxReceipt - : TContract; + ? TxSendResultImmediate + : W extends WaitWithReturnReceipt + ? TxSendResultMined> + : DeployResultMined; /** * Contract interaction for deployment. @@ -325,8 +334,7 @@ export class DeployMethod extends * @returns TxHash (if wait is NO_WAIT), TContract (if wait is undefined or doesn't have returnReceipt), or DeployTxReceipt (if wait.returnReceipt is true) */ // Overload for when wait is not specified at all - returns the contract - public override send(options: DeployOptionsWithoutWait): Promise; - // Generic overload for explicit wait values + public override send(options: DeployOptionsWithoutWait): Promise>; // eslint-disable-next-line jsdoc/require-jsdoc public override send( options: DeployOptions, @@ -337,12 +345,15 @@ export class DeployMethod extends const sendOptions = this.convertDeployOptionsToSendOptions(options); if (options.wait === NO_WAIT) { - const txHash = await this.wallet.sendTx(executionPayload, sendOptions as SendOptions); - this.log.debug(`Sent deployment tx ${txHash.hash} of ${this.artifact.name} contract`); - return txHash; + const result = await this.wallet.sendTx(executionPayload, sendOptions as SendOptions); + this.log.debug(`Sent deployment tx ${result.txHash.hash} of ${this.artifact.name} contract`); + return result; } - const receipt = await this.wallet.sendTx(executionPayload, sendOptions as SendOptions); + const { receipt, ...offchainOutput } = await this.wallet.sendTx( + executionPayload, + sendOptions as SendOptions, + ); this.log.debug(`Deployed ${this.artifact.name} contract in tx ${receipt.txHash}`); // Attach contract instance @@ -351,10 +362,10 @@ export class DeployMethod extends // Return full receipt if requested, otherwise just the contract if (options.wait && typeof options.wait === 'object' && options.wait.returnReceipt) { - return { ...receipt, contract, instance }; + return { receipt: { ...receipt, contract, instance }, ...offchainOutput }; } - return contract; + return { contract, receipt, ...offchainOutput }; } /** @@ -383,7 +394,7 @@ export class DeployMethod extends * @returns A simulation result object containing metadata of the execution, including gas * estimations (if requested via options), execution statistics and emitted offchain effects */ - public async simulate(options: SimulateDeployOptions): Promise> { + public async simulate(options: SimulateDeployOptions): Promise { const executionPayload = await this.request(this.convertDeployOptionsToRequestOptions(options)); const simulatedTx = await this.wallet.simulateTx(executionPayload, toSimulateOptions(options)); @@ -393,7 +404,7 @@ export class DeployMethod extends ); return { stats: simulatedTx.stats!, - offchainEffects: collectOffchainEffects(simulatedTx.privateExecutionResult), + ...extractOffchainOutput(simulatedTx.offchainEffects), result: undefined, estimatedGas: { gasLimits, teardownGasLimits }, }; diff --git a/yarn-project/aztec.js/src/contract/interaction_options.test.ts b/yarn-project/aztec.js/src/contract/interaction_options.test.ts new file mode 100644 index 000000000000..7818299a3854 --- /dev/null +++ b/yarn-project/aztec.js/src/contract/interaction_options.test.ts @@ -0,0 +1,81 @@ +import { Fr } from '@aztec/foundation/curves/bn254'; +import { AztecAddress } from '@aztec/stdlib/aztec-address'; +import { OFFCHAIN_MESSAGE_IDENTIFIER, type OffchainEffect } from '@aztec/stdlib/tx'; + +import { extractOffchainOutput } from './interaction_options.js'; + +describe('extractOffchainOutput', () => { + const makeEffect = (data: Fr[], contractAddress?: AztecAddress): OffchainEffect => ({ + data, + contractAddress: contractAddress ?? AztecAddress.fromField(Fr.random()), + }); + + const makeMessageEffect = async (recipient?: AztecAddress, payload?: Fr[], contractAddress?: AztecAddress) => + makeEffect( + [ + OFFCHAIN_MESSAGE_IDENTIFIER, + (recipient ?? (await AztecAddress.random())).toField(), + ...(payload ?? [Fr.random()]), + ], + contractAddress, + ); + + it('returns empty output for empty input', () => { + const result = extractOffchainOutput([]); + expect(result.offchainEffects).toEqual([]); + expect(result.offchainMessages).toEqual([]); + }); + + it('keeps non-message effects as-is', () => { + const effects = [makeEffect([Fr.random(), Fr.random()]), makeEffect([Fr.random()])]; + const result = extractOffchainOutput(effects); + expect(result.offchainEffects).toEqual(effects); + expect(result.offchainMessages).toEqual([]); + }); + + it('extracts a message effect into offchainMessages', async () => { + const recipient = await AztecAddress.random(); + const payload = [Fr.random(), Fr.random(), Fr.random()]; + const contractAddress = await AztecAddress.random(); + const effect = await makeMessageEffect(recipient, payload, contractAddress); + + const result = extractOffchainOutput([effect]); + + expect(result.offchainEffects).toEqual([]); + expect(result.offchainMessages).toHaveLength(1); + expect(result.offchainMessages[0]).toEqual({ + recipient, + payload, + contractAddress, + }); + }); + + it('splits a mixed array of effects and messages', async () => { + const plainEffect1 = makeEffect([Fr.random()]); + const plainEffect2 = makeEffect([Fr.random(), Fr.random()]); + const messageEffect = await makeMessageEffect(); + + const result = extractOffchainOutput([plainEffect1, messageEffect, plainEffect2]); + + expect(result.offchainEffects).toEqual([plainEffect1, plainEffect2]); + expect(result.offchainMessages).toHaveLength(1); + }); + + it('handles multiple message effects', async () => { + const msg1 = await makeMessageEffect(); + const msg2 = await makeMessageEffect(); + + const result = extractOffchainOutput([msg1, msg2]); + + expect(result.offchainEffects).toEqual([]); + expect(result.offchainMessages).toHaveLength(2); + }); + + it('does not treat an effect as a message if data has only the identifier (no recipient)', () => { + const effect = makeEffect([OFFCHAIN_MESSAGE_IDENTIFIER]); + const result = extractOffchainOutput([effect]); + + expect(result.offchainEffects).toEqual([effect]); + expect(result.offchainMessages).toEqual([]); + }); +}); diff --git a/yarn-project/aztec.js/src/contract/interaction_options.ts b/yarn-project/aztec.js/src/contract/interaction_options.ts index 2651c9f5f49d..005908dd35b8 100644 --- a/yarn-project/aztec.js/src/contract/interaction_options.ts +++ b/yarn-project/aztec.js/src/contract/interaction_options.ts @@ -1,8 +1,16 @@ +import type { Fr } from '@aztec/foundation/curves/bn254'; import type { FieldsOf } from '@aztec/foundation/types'; import type { AuthWitness } from '@aztec/stdlib/auth-witness'; -import type { AztecAddress } from '@aztec/stdlib/aztec-address'; +import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { GasSettings } from '@aztec/stdlib/gas'; -import type { Capsule, OffchainEffect, SimulationStats, TxHash, TxReceipt } from '@aztec/stdlib/tx'; +import { + type Capsule, + OFFCHAIN_MESSAGE_IDENTIFIER, + type OffchainEffect, + type SimulationStats, + type TxHash, + type TxReceipt, +} from '@aztec/stdlib/tx'; import type { FeePaymentMethod } from '../fee/fee_payment_method.js'; import type { ProfileOptions, SendOptions, SimulateOptions } from '../wallet/index.js'; @@ -82,6 +90,13 @@ export type SendInteractionOptionsWithoutWait = RequestInteractionOptions & { from: AztecAddress; /** The fee options for the transaction. */ fee?: InteractionFeeOptions; + /** + * Extra addresses whose private state (keys, notes) should be accessible during execution. + * Use when a transaction reads or nullifies private state that is owned by a different address, + * e.g. deploying contracts with private storage or withdrawing from an escrow that holds + * its own private notes. + */ + additionalScopes?: AztecAddress[]; }; /** @@ -109,8 +124,8 @@ export type SimulateInteractionOptions = Omit & { skipTxValidation?: boolean; /** Whether to ensure the fee payer is not empty and has enough balance to pay for the fee. */ skipFeeEnforcement?: boolean; - /** Whether to include metadata such as offchain effects and performance statistics (e.g. timing information of the different circuits and oracles) in - * the simulation result, instead of just the return value of the function */ + /** Whether to include metadata such as performance statistics (e.g. timing information of the different circuits and oracles) and gas estimation + * in the simulation result, in addition to the return value and offchain effects */ includeMetadata?: boolean; }; @@ -124,31 +139,89 @@ export type ProfileInteractionOptions = SimulateInteractionOptions & { skipProofGeneration?: boolean; }; +/** A message emitted during execution or proving, to be delivered offchain. */ +export type OffchainMessage = { + /** The intended recipient of the message. */ + recipient: AztecAddress; + /** The message payload (typically encrypted). */ + payload: Fr[]; + /** The contract that emitted the message. */ + contractAddress: AztecAddress; +}; + +/** Groups all unproven outputs from private execution that are returned to the client. */ +export type OffchainOutput = { + /** Raw offchain effects emitted during execution. */ + offchainEffects: OffchainEffect[]; + /** Messages emitted during execution, to be delivered offchain. */ + offchainMessages: OffchainMessage[]; +}; + /** - * Represents the result type of a simulation. - * By default, it will just be the return value of the simulated function - * If `includeMetadata` is set to true in `SimulateInteractionOptions` on the input of `simulate(...)`, - * it will provide extra information. - */ -export type SimulationReturn = T extends true - ? { - /** Additional stats about the simulation */ - stats: SimulationStats; - /** Offchain effects generated during the simulation */ - offchainEffects: OffchainEffect[]; - /** Return value of the function */ - result: any; - /** Gas estimation results */ - estimatedGas: Pick; + * Splits an array of offchain effects into decoded offchain messages and remaining effects. + * Effects whose data starts with `OFFCHAIN_MESSAGE_IDENTIFIER` are parsed as messages and removed + * from the effects array. + */ +export function extractOffchainOutput(effects: OffchainEffect[]): OffchainOutput { + const offchainEffects: OffchainEffect[] = []; + const offchainMessages: OffchainMessage[] = []; + + for (const effect of effects) { + if (effect.data.length >= 2 && effect.data[0].equals(OFFCHAIN_MESSAGE_IDENTIFIER)) { + offchainMessages.push({ + recipient: AztecAddress.fromField(effect.data[1]), + payload: effect.data.slice(2), + contractAddress: effect.contractAddress, + }); + } else { + offchainEffects.push(effect); } - : any; + } + + return { offchainEffects, offchainMessages }; +} + +/** + * Returns an empty `OffchainOutput` (no effects, no messages). + */ +export function emptyOffchainOutput(): OffchainOutput { + return { offchainEffects: [], offchainMessages: [] }; +} + +/** + * Represents the result of a simulation. + * Always includes the return value and offchain output. + * When `includeMetadata` or `fee.estimateGas` is set, also includes stats and gas estimation. + */ +export type SimulationResult = { + /** Return value of the function */ + result: any; + /** Additional stats about the simulation. Present when `includeMetadata` is set. */ + stats?: SimulationStats; + /** Gas estimation results. Present when `includeMetadata` or `fee.estimateGas` is set. */ + estimatedGas?: Pick; +} & OffchainOutput; + +/** Result of sendTx when not waiting for mining. */ +export type TxSendResultImmediate = { + /** The hash of the sent transaction. */ + txHash: TxHash; +} & OffchainOutput; + +/** Result of sendTx when waiting for mining. */ +export type TxSendResultMined = { + /** The transaction receipt. */ + receipt: TReturn; +} & OffchainOutput; /** * Represents the result type of sending a transaction. - * If `wait` is NO_WAIT, returns TxHash immediately without waiting. - * If `wait` is undefined or WaitOpts, returns TReturn (defaults to TxReceipt) after waiting. + * If `wait` is NO_WAIT, returns TxSendResultImmediate. + * Otherwise returns TxSendResultMined. */ -export type SendReturn = T extends NoWait ? TxHash : TReturn; +export type SendReturn = T extends NoWait + ? TxSendResultImmediate + : TxSendResultMined; /** * Transforms and cleans up the higher level SendInteractionOptions defined by the interaction into diff --git a/yarn-project/aztec.js/src/fee/private_fee_payment_method.ts b/yarn-project/aztec.js/src/fee/private_fee_payment_method.ts index 891c52f79a64..61277579681c 100644 --- a/yarn-project/aztec.js/src/fee/private_fee_payment_method.ts +++ b/yarn-project/aztec.js/src/fee/private_fee_payment_method.ts @@ -10,6 +10,7 @@ import type { FeePaymentMethod } from './fee_payment_method.js'; /** * Holds information about how the fee for a transaction is to be paid. + * @deprecated Is not supported on mainnet. Use {@link FeeJuicePaymentMethodWithClaim} or `SponsoredFeePaymentMethod` instead. */ export class PrivateFeePaymentMethod implements FeePaymentMethod { private assetPromise: Promise | null = null; diff --git a/yarn-project/aztec.js/src/fee/public_fee_payment_method.ts b/yarn-project/aztec.js/src/fee/public_fee_payment_method.ts index 2847a40f1dea..98f20c961241 100644 --- a/yarn-project/aztec.js/src/fee/public_fee_payment_method.ts +++ b/yarn-project/aztec.js/src/fee/public_fee_payment_method.ts @@ -11,6 +11,7 @@ import type { FeePaymentMethod } from './fee_payment_method.js'; /** * Holds information about how the fee for a transaction is to be paid. + * @deprecated Is not supported on mainnet. Use {@link FeeJuicePaymentMethodWithClaim} or `SponsoredFeePaymentMethod` instead. */ export class PublicFeePaymentMethod implements FeePaymentMethod { private assetPromise: Promise | null = null; diff --git a/yarn-project/aztec.js/src/utils/authwit.ts b/yarn-project/aztec.js/src/utils/authwit.ts index d0d8b9f1c088..67ac5edf627c 100644 --- a/yarn-project/aztec.js/src/utils/authwit.ts +++ b/yarn-project/aztec.js/src/utils/authwit.ts @@ -5,7 +5,7 @@ import { type ABIParameterVisibility, type FunctionAbi, type FunctionCall, Funct import { AuthWitness, computeInnerAuthWitHash, computeOuterAuthWitHash } from '@aztec/stdlib/auth-witness'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import { computeVarArgsHash } from '@aztec/stdlib/hash'; -import type { TxHash, TxProfileResult, TxReceipt } from '@aztec/stdlib/tx'; +import type { TxProfileResult } from '@aztec/stdlib/tx'; import { ContractFunctionInteraction } from '../contract/contract_function_interaction.js'; import type { @@ -15,7 +15,8 @@ import type { SendInteractionOptionsWithoutWait, SendReturn, SimulateInteractionOptions, - SimulationReturn, + SimulationResult, + TxSendResultMined, } from '../contract/interaction_options.js'; import type { Wallet } from '../wallet/index.js'; @@ -189,10 +190,12 @@ export async function lookupValidity( errorTypes: {}, } as FunctionAbi; try { - results.isValidInPrivate = (await new ContractFunctionInteraction(wallet, onBehalfOf, lookupValidityAbi, [ - consumer, - innerHash, - ]).simulate({ from: onBehalfOf, authWitnesses: [witness] })) as boolean; + results.isValidInPrivate = ( + await new ContractFunctionInteraction(wallet, onBehalfOf, lookupValidityAbi, [consumer, innerHash]).simulate({ + from: onBehalfOf, + authWitnesses: [witness], + }) + ).result as boolean; // TODO: Narrow down the error to make sure simulation failed due to an invalid authwit // eslint-disable-next-line no-empty } catch {} @@ -219,12 +222,12 @@ export async function lookupValidity( returnTypes: [{ kind: 'boolean' }], errorTypes: {}, } as FunctionAbi; - results.isValidInPublic = (await new ContractFunctionInteraction( - wallet, - ProtocolContractAddress.AuthRegistry, - isConsumableAbi, - [onBehalfOf, messageHash], - ).simulate({ from: onBehalfOf })) as boolean; + results.isValidInPublic = ( + await new ContractFunctionInteraction(wallet, ProtocolContractAddress.AuthRegistry, isConsumableAbi, [ + onBehalfOf, + messageHash, + ]).simulate({ from: onBehalfOf }) + ).result as boolean; return results; } @@ -262,14 +265,10 @@ export class SetPublicAuthwitContractInteraction extends ContractFunctionInterac * @param options - An optional object containing additional configuration for the transaction. * @returns The result of the transaction as returned by the contract function. */ - public override simulate( - options: Omit, - ): Promise>; - // eslint-disable-next-line jsdoc/require-jsdoc public override simulate( - options: Omit = {}, - ): Promise> { - return super.simulate({ ...options, from: this.from }); + options: Omit = {} as Omit, + ): Promise { + return super.simulate({ ...options, from: this.from } as SimulateInteractionOptions); } /** @@ -290,8 +289,7 @@ export class SetPublicAuthwitContractInteraction extends ContractFunctionInterac * @param options - An optional object containing 'fee' options information * @returns A TxReceipt (if wait is true/undefined) or TxHash (if wait is false) */ - // Overload for when wait is not specified at all - returns TxReceipt - public override send(options?: Omit): Promise; + public override send(options?: Omit): Promise; // Generic overload for explicit wait values // eslint-disable-next-line jsdoc/require-jsdoc public override send( @@ -300,7 +298,7 @@ export class SetPublicAuthwitContractInteraction extends ContractFunctionInterac // eslint-disable-next-line jsdoc/require-jsdoc public override send( options?: Omit, 'from'>, - ): Promise { + ): Promise> { return super.send({ ...options, from: this.from }); } diff --git a/yarn-project/aztec.js/src/utils/cross_chain.ts b/yarn-project/aztec.js/src/utils/cross_chain.ts index 38c278fe9597..5ba97b49c758 100644 --- a/yarn-project/aztec.js/src/utils/cross_chain.ts +++ b/yarn-project/aztec.js/src/utils/cross_chain.ts @@ -8,17 +8,15 @@ import type { AztecNode } from '@aztec/stdlib/interfaces/client'; * @param l1ToL2MessageHash - Hash of the L1 to L2 message * @param opts - Options */ -export async function waitForL1ToL2MessageReady( - node: Pick, +export function waitForL1ToL2MessageReady( + node: Pick, l1ToL2MessageHash: Fr, opts: { /** Timeout for the operation in seconds */ timeoutSeconds: number; - /** True if the message is meant to be consumed from a public function */ forPublicConsumption: boolean; }, ) { - const messageBlockNumber = await node.getL1ToL2MessageBlock(l1ToL2MessageHash); return retryUntil( - () => isL1ToL2MessageReady(node, l1ToL2MessageHash, { ...opts, messageBlockNumber }), + () => isL1ToL2MessageReady(node, l1ToL2MessageHash), `L1 to L2 message ${l1ToL2MessageHash.toString()} ready`, opts.timeoutSeconds, 1, @@ -29,25 +27,18 @@ export async function waitForL1ToL2MessageReady( * Returns whether the L1 to L2 message is ready to be consumed. * @param node - Aztec node instance used to obtain the information about the message * @param l1ToL2MessageHash - Hash of the L1 to L2 message - * @param opts - Options * @returns True if the message is ready to be consumed, false otherwise */ export async function isL1ToL2MessageReady( - node: Pick, + node: Pick, l1ToL2MessageHash: Fr, - opts: { - /** True if the message is meant to be consumed from a public function */ forPublicConsumption: boolean; - /** Cached synced block number for the message (will be fetched from PXE otherwise) */ messageBlockNumber?: number; - }, ): Promise { - const blockNumber = await node.getBlockNumber(); - const messageBlockNumber = opts.messageBlockNumber ?? (await node.getL1ToL2MessageBlock(l1ToL2MessageHash)); - if (messageBlockNumber === undefined) { + const messageCheckpointNumber = await node.getL1ToL2MessageCheckpoint(l1ToL2MessageHash); + if (messageCheckpointNumber === undefined) { return false; } - // Note that public messages can be consumed 1 block earlier, since the sequencer will include the messages - // in the L1 to L2 message tree before executing the txs for the block. In private, however, we need to wait - // until the message is included so we can make use of the membership witness. - return opts.forPublicConsumption ? blockNumber + 1 >= messageBlockNumber : blockNumber >= messageBlockNumber; + // L1 to L2 messages are included in the first block of a checkpoint + const latestBlock = await node.getBlock('latest'); + return latestBlock !== undefined && latestBlock.checkpointNumber >= messageCheckpointNumber; } diff --git a/yarn-project/aztec.js/src/wallet/wallet.test.ts b/yarn-project/aztec.js/src/wallet/wallet.test.ts index bcee66440e18..73b3bdb95123 100644 --- a/yarn-project/aztec.js/src/wallet/wallet.test.ts +++ b/yarn-project/aztec.js/src/wallet/wallet.test.ts @@ -11,6 +11,7 @@ import type { ContractInstanceWithAddress } from '@aztec/stdlib/contract'; import { PublicKeys } from '@aztec/stdlib/keys'; import { ExecutionPayload, + type OffchainEffect, TxHash, TxProfileResult, TxReceipt, @@ -18,7 +19,12 @@ import { UtilityExecutionResult, } from '@aztec/stdlib/tx'; -import { type InteractionWaitOptions, NO_WAIT, type SendReturn } from '../contract/interaction_options.js'; +import { + type InteractionWaitOptions, + NO_WAIT, + type OffchainMessage, + type SendReturn, +} from '../contract/interaction_options.js'; import type { AppCapabilities, WalletCapabilities } from './capabilities.js'; import type { Aliased, @@ -209,12 +215,14 @@ describe('WalletSchema', () => { const resultWithWait = await context.client.sendTx(exec, { from: await AztecAddress.random(), }); - expect(resultWithWait).toBeInstanceOf(TxReceipt); + expect(resultWithWait.receipt).toBeInstanceOf(TxReceipt); + expect(resultWithWait.offchainEffects).toEqual([]); const resultWithoutWait = await context.client.sendTx(exec, { from: await AztecAddress.random(), wait: NO_WAIT, }); - expect(resultWithoutWait).toBeInstanceOf(TxHash); + expect(resultWithoutWait.txHash).toBeInstanceOf(TxHash); + expect(resultWithoutWait.offchainEffects).toEqual([]); }); it('createAuthWit', async () => { @@ -353,7 +361,10 @@ describe('WalletSchema', () => { expect(results[8]).toEqual({ name: 'simulateTx', result: expect.any(TxSimulationResult) }); expect(results[9]).toEqual({ name: 'executeUtility', result: expect.any(UtilityExecutionResult) }); expect(results[10]).toEqual({ name: 'profileTx', result: expect.any(TxProfileResult) }); - expect(results[11]).toEqual({ name: 'sendTx', result: expect.any(TxReceipt) }); + expect(results[11]).toEqual({ + name: 'sendTx', + result: { receipt: expect.any(TxReceipt), offchainEffects: [], offchainMessages: [] }, + }); expect(results[12]).toEqual({ name: 'createAuthWit', result: expect.any(AuthWitness) }); }); }); @@ -446,9 +457,17 @@ class MockWallet implements Wallet { opts: SendOptions, ): Promise> { if (opts.wait === NO_WAIT) { - return Promise.resolve(TxHash.random()) as Promise>; + return Promise.resolve({ + txHash: TxHash.random(), + offchainEffects: [] as OffchainEffect[], + offchainMessages: [] as OffchainMessage[], + }) as Promise>; } - return Promise.resolve(TxReceipt.empty()) as Promise>; + return Promise.resolve({ + receipt: TxReceipt.empty(), + offchainEffects: [] as OffchainEffect[], + offchainMessages: [] as OffchainMessage[], + }) as Promise>; } createAuthWit(_from: AztecAddress, _messageHashOrIntent: any): Promise { diff --git a/yarn-project/aztec.js/src/wallet/wallet.ts b/yarn-project/aztec.js/src/wallet/wallet.ts index 9918542d297e..0e48f2f42726 100644 --- a/yarn-project/aztec.js/src/wallet/wallet.ts +++ b/yarn-project/aztec.js/src/wallet/wallet.ts @@ -303,6 +303,7 @@ export const SendOptionsSchema = z.object({ capsules: optional(z.array(Capsule.schema)), fee: optional(GasSettingsOptionSchema), wait: optional(z.union([z.literal(NO_WAIT), WaitOptsSchema])), + additionalScopes: optional(z.array(schemas.AztecAddress)), }); export const SimulateOptionsSchema = z.object({ @@ -313,6 +314,7 @@ export const SimulateOptionsSchema = z.object({ skipTxValidation: optional(z.boolean()), skipFeeEnforcement: optional(z.boolean()), includeMetadata: optional(z.boolean()), + additionalScopes: optional(z.array(schemas.AztecAddress)), }); export const ProfileOptionsSchema = SimulateOptionsSchema.extend({ @@ -379,6 +381,7 @@ export const ContractClassMetadataSchema = z.object({ export const ContractFunctionPatternSchema = z.object({ contract: z.union([schemas.AztecAddress, z.literal('*')]), function: z.union([z.string(), z.literal('*')]), + additionalScopes: optional(z.union([z.array(schemas.AztecAddress), z.literal('*')])), }); export const AccountsCapabilitySchema = z.object({ @@ -489,6 +492,22 @@ export const WalletCapabilitiesSchema = z.object({ expiresAt: optional(z.number()), }); +const OffchainEffectSchema = z.object({ + data: z.array(schemas.Fr), + contractAddress: schemas.AztecAddress, +}); + +const OffchainMessageSchema = z.object({ + recipient: schemas.AztecAddress, + payload: z.array(schemas.Fr), + contractAddress: schemas.AztecAddress, +}); + +const OffchainOutputSchema = z.object({ + offchainEffects: z.array(OffchainEffectSchema), + offchainMessages: z.array(OffchainMessageSchema), +}); + /** * Record of all wallet method schemas (excluding batch). * This is the single source of truth for method schemas - batch schemas are derived from this. @@ -532,7 +551,12 @@ const WalletMethodSchemas = { sendTx: z .function() .args(ExecutionPayloadSchema, SendOptionsSchema) - .returns(z.union([TxHash.schema, TxReceipt.schema])), + .returns( + z.union([ + z.object({ txHash: TxHash.schema }).merge(OffchainOutputSchema), + z.object({ receipt: TxReceipt.schema }).merge(OffchainOutputSchema), + ]), + ), createAuthWit: z.function().args(schemas.AztecAddress, MessageHashOrIntentSchema).returns(AuthWitness.schema), requestCapabilities: z.function().args(AppCapabilitiesSchema).returns(WalletCapabilitiesSchema), }; diff --git a/yarn-project/aztec/src/cli/cmds/standby.ts b/yarn-project/aztec/src/cli/cmds/standby.ts new file mode 100644 index 000000000000..c94605c16d80 --- /dev/null +++ b/yarn-project/aztec/src/cli/cmds/standby.ts @@ -0,0 +1,111 @@ +import { getInitialTestAccountsData } from '@aztec/accounts/testing'; +import type { Fr } from '@aztec/aztec.js/fields'; +import { getSponsoredFPCAddress } from '@aztec/cli/cli-utils'; +import { getPublicClient } from '@aztec/ethereum/client'; +import type { GenesisStateConfig } from '@aztec/ethereum/config'; +import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts'; +import type { EthAddress } from '@aztec/foundation/eth-address'; +import { startHttpRpcServer } from '@aztec/foundation/json-rpc/server'; +import type { LogFn } from '@aztec/foundation/log'; +import { retryUntil } from '@aztec/foundation/retry'; +import { AztecAddress } from '@aztec/stdlib/aztec-address'; +import { getGenesisValues } from '@aztec/world-state/testing'; + +import Koa from 'koa'; + +const ROLLUP_POLL_INTERVAL_S = 60; + +/** + * Computes the expected genesis archive root from the genesis state config. + * Reads test accounts and sponsored FPC addresses as specified, then computes + * the genesis values including the archive root and prefilled public data. + */ +export async function computeExpectedGenesisRoot(config: GenesisStateConfig, userLog: LogFn) { + const testAccounts = config.testAccounts ? (await getInitialTestAccountsData()).map(a => a.address) : []; + const sponsoredFPCAccounts = config.sponsoredFPC ? [await getSponsoredFPCAddress()] : []; + const prefundAddresses = (config.prefundAddresses ?? []).map(a => AztecAddress.fromString(a)); + const initialFundedAccounts = testAccounts.concat(sponsoredFPCAccounts).concat(prefundAddresses); + + userLog(`Initial funded accounts: ${initialFundedAccounts.map(a => a.toString()).join(', ')}`); + + const { genesisArchiveRoot, prefilledPublicData } = await getGenesisValues(initialFundedAccounts); + + userLog(`Genesis archive root: ${genesisArchiveRoot.toString()}`); + + return { genesisArchiveRoot, prefilledPublicData }; +} + +/** + * Waits until the canonical rollup's genesis archive root matches the expected local genesis root. + * If the rollup is not yet compatible (e.g. during L1 contract upgrades), enters standby mode: + * starts a lightweight HTTP server for K8s liveness probes and polls every 60s until a compatible rollup appears. + */ +export async function waitForCompatibleRollup( + config: { + l1RpcUrls: string[]; + l1ChainId: number; + l1Contracts: { registryAddress: EthAddress }; + rollupVersion?: number; + }, + expectedGenesisRoot: Fr, + port: number | undefined, + userLog: LogFn, +): Promise { + const publicClient = getPublicClient(config); + const rollupVersion: number | 'canonical' = config.rollupVersion ?? 'canonical'; + + const registry = new RegistryContract(publicClient, config.l1Contracts.registryAddress); + const rollupAddress = await registry.getRollupAddress(rollupVersion); + const rollup = new RollupContract(publicClient, rollupAddress.toString()); + + let l1GenesisRoot: Fr; + try { + l1GenesisRoot = await rollup.getGenesisArchiveTreeRoot(); + } catch (err: any) { + throw new Error( + `Could not retrieve genesis archive root from canonical rollup at ${rollupAddress}: ${err.message}`, + ); + } + + if (l1GenesisRoot.equals(expectedGenesisRoot)) { + return; + } + + userLog( + `Genesis root mismatch: expected ${expectedGenesisRoot}, got ${l1GenesisRoot} from rollup at ${rollupAddress}. ` + + `Entering standby mode. Will poll every ${ROLLUP_POLL_INTERVAL_S}s for a compatible rollup...`, + ); + + const standbyServer = await startHttpRpcServer({ getApp: () => new Koa(), isHealthy: () => true }, { port }); + userLog(`Standby status server listening on port ${standbyServer.port}`); + + try { + await retryUntil( + async () => { + const currentRollupAddress = await registry.getRollupAddress(rollupVersion); + const currentRollup = new RollupContract(publicClient, currentRollupAddress.toString()); + + let currentGenesisRoot: Fr; + try { + currentGenesisRoot = await currentRollup.getGenesisArchiveTreeRoot(); + } catch { + userLog(`Failed to fetch genesis root from rollup at ${currentRollupAddress}. Retrying...`); + return undefined; + } + + if (currentGenesisRoot.equals(expectedGenesisRoot)) { + userLog(`Compatible rollup found at ${currentRollupAddress}. Exiting standby mode.`); + return true; + } + + userLog(`Still waiting. Rollup at ${currentRollupAddress} has genesis root ${currentGenesisRoot}.`); + return undefined; + }, + 'compatible rollup', + 0, + ROLLUP_POLL_INTERVAL_S, + ); + } finally { + await new Promise((resolve, reject) => standbyServer.close(err => (err ? reject(err) : resolve()))); + } +} diff --git a/yarn-project/aztec/src/cli/cmds/start_node.ts b/yarn-project/aztec/src/cli/cmds/start_node.ts index 21839223820f..f84cd10284be 100644 --- a/yarn-project/aztec/src/cli/cmds/start_node.ts +++ b/yarn-project/aztec/src/cli/cmds/start_node.ts @@ -1,20 +1,14 @@ -import { getInitialTestAccountsData } from '@aztec/accounts/testing'; import { type AztecNodeConfig, aztecNodeConfigMappings, getConfigEnvVars } from '@aztec/aztec-node'; import { Fr } from '@aztec/aztec.js/fields'; -import { getSponsoredFPCAddress } from '@aztec/cli/cli-utils'; import { getL1Config } from '@aztec/cli/config'; import { getPublicClient } from '@aztec/ethereum/client'; -import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts'; +import { getGenesisStateConfigEnvVars } from '@aztec/ethereum/config'; import { type NetworkNames, SecretValue } from '@aztec/foundation/config'; -import type { EthAddress } from '@aztec/foundation/eth-address'; import type { NamespacedApiHandlers } from '@aztec/foundation/json-rpc/server'; -import { startHttpRpcServer } from '@aztec/foundation/json-rpc/server'; import { Agent, makeUndiciFetch } from '@aztec/foundation/json-rpc/undici'; import type { LogFn } from '@aztec/foundation/log'; -import { sleep } from '@aztec/foundation/sleep'; import { ProvingJobConsumerSchema, createProvingJobBrokerClient } from '@aztec/prover-client/broker'; import { type CliPXEOptions, type PXEConfig, allPxeConfigMappings } from '@aztec/pxe/config'; -import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { AztecNodeAdminApiSchema, AztecNodeApiSchema } from '@aztec/stdlib/interfaces/client'; import { P2PApiSchema, ProverNodeApiSchema, type ProvingJobBroker } from '@aztec/stdlib/interfaces/server'; import { @@ -24,9 +18,6 @@ import { telemetryClientConfigMappings, } from '@aztec/telemetry-client'; import { EmbeddedWallet } from '@aztec/wallets/embedded'; -import { getGenesisValues } from '@aztec/world-state/testing'; - -import Koa from 'koa'; import { createAztecNode } from '../../local-network/index.js'; import { @@ -36,74 +27,9 @@ import { setupVersionChecker, } from '../util.js'; import { getVersions } from '../versioning.js'; +import { computeExpectedGenesisRoot, waitForCompatibleRollup } from './standby.js'; import { startProverBroker } from './start_prover_broker.js'; -const ROLLUP_POLL_INTERVAL_MS = 600_000; - -/** - * Waits until the canonical rollup's genesis archive root matches the expected local genesis root. - * If the rollup is not yet compatible (e.g. during L1 contract upgrades), enters standby mode: - * starts a lightweight HTTP server for K8s liveness probes and polls until a compatible rollup appears. - */ -async function waitForCompatibleRollup( - publicClient: ReturnType, - registryAddress: EthAddress, - rollupVersion: number | 'canonical', - expectedGenesisRoot: Fr, - port: number | undefined, - userLog: LogFn, -): Promise { - const registry = new RegistryContract(publicClient, registryAddress); - const rollupAddress = await registry.getRollupAddress(rollupVersion); - const rollup = new RollupContract(publicClient, rollupAddress.toString()); - - let l1GenesisRoot: Fr; - try { - l1GenesisRoot = await rollup.getGenesisArchiveTreeRoot(); - } catch (err: any) { - throw new Error( - `Could not retrieve genesis archive root from canonical rollup at ${rollupAddress}: ${err.message}`, - ); - } - - if (l1GenesisRoot.equals(expectedGenesisRoot)) { - return; - } - - userLog( - `Genesis root mismatch: expected ${expectedGenesisRoot}, got ${l1GenesisRoot} from rollup at ${rollupAddress}. ` + - `Entering standby mode. Will poll every ${ROLLUP_POLL_INTERVAL_MS / 1000}s for a compatible rollup...`, - ); - - const standbyServer = await startHttpRpcServer({ getApp: () => new Koa(), isHealthy: () => true }, { port }); - userLog(`Standby status server listening on port ${standbyServer.port}`); - - try { - while (true) { - await sleep(ROLLUP_POLL_INTERVAL_MS); - - const currentRollupAddress = await registry.getRollupAddress(rollupVersion); - const currentRollup = new RollupContract(publicClient, currentRollupAddress.toString()); - - try { - l1GenesisRoot = await currentRollup.getGenesisArchiveTreeRoot(); - } catch { - userLog(`Failed to fetch genesis root from rollup at ${currentRollupAddress}. Retrying...`); - continue; - } - - if (l1GenesisRoot.equals(expectedGenesisRoot)) { - userLog(`Compatible rollup found at ${currentRollupAddress}. Exiting standby mode.`); - return; - } - - userLog(`Still waiting. Rollup at ${currentRollupAddress} has genesis root ${l1GenesisRoot}.`); - } - } finally { - await new Promise((resolve, reject) => standbyServer.close(err => (err ? reject(err) : resolve()))); - } -} - export async function startNode( options: any, signalHandlers: (() => Promise)[], @@ -154,16 +80,8 @@ export async function startNode( await preloadCrsDataForVerifying(nodeConfig, userLog); - const testAccounts = nodeConfig.testAccounts ? (await getInitialTestAccountsData()).map(a => a.address) : []; - const sponsoredFPCAccounts = nodeConfig.sponsoredFPC ? [await getSponsoredFPCAddress()] : []; - const prefundAddresses = (nodeConfig.prefundAddresses ?? []).map(a => AztecAddress.fromString(a)); - const initialFundedAccounts = testAccounts.concat(sponsoredFPCAccounts).concat(prefundAddresses); - - userLog(`Initial funded accounts: ${initialFundedAccounts.map(a => a.toString()).join(', ')}`); - - const { genesisArchiveRoot, prefilledPublicData } = await getGenesisValues(initialFundedAccounts); - - userLog(`Genesis archive root: ${genesisArchiveRoot.toString()}`); + const genesisConfig = getGenesisStateConfigEnvVars(); + const { genesisArchiveRoot, prefilledPublicData } = await computeExpectedGenesisRoot(genesisConfig, userLog); const followsCanonicalRollup = typeof nodeConfig.rollupVersion !== 'number' || (nodeConfig.rollupVersion as unknown as string) === 'canonical'; @@ -174,16 +92,7 @@ export async function startNode( // Wait for a compatible rollup before proceeding with full L1 config fetch. // This prevents crashes when the canonical rollup hasn't been upgraded yet. - const publicClient = getPublicClient(nodeConfig); - const rollupVersion: number | 'canonical' = nodeConfig.rollupVersion ?? 'canonical'; - await waitForCompatibleRollup( - publicClient, - nodeConfig.l1Contracts.registryAddress, - rollupVersion, - genesisArchiveRoot, - options.port, - userLog, - ); + await waitForCompatibleRollup(nodeConfig, genesisArchiveRoot, options.port, userLog); const { addresses, config } = await getL1Config( nodeConfig.l1Contracts.registryAddress, diff --git a/yarn-project/aztec/src/cli/cmds/start_prover_broker.ts b/yarn-project/aztec/src/cli/cmds/start_prover_broker.ts index 75c320265f5b..3ff5bad42808 100644 --- a/yarn-project/aztec/src/cli/cmds/start_prover_broker.ts +++ b/yarn-project/aztec/src/cli/cmds/start_prover_broker.ts @@ -1,4 +1,5 @@ import { getL1Config } from '@aztec/cli/config'; +import { getGenesisStateConfigEnvVars } from '@aztec/ethereum/config'; import type { NamespacedApiHandlers } from '@aztec/foundation/json-rpc/server'; import type { LogFn } from '@aztec/foundation/log'; import { @@ -13,6 +14,7 @@ import type { ProvingJobBroker } from '@aztec/stdlib/interfaces/server'; import { getConfigEnvVars as getTelemetryClientConfig, initTelemetryClient } from '@aztec/telemetry-client'; import { extractRelevantOptions } from '../util.js'; +import { computeExpectedGenesisRoot, waitForCompatibleRollup } from './standby.js'; export async function startProverBroker( options: any, @@ -34,6 +36,10 @@ export async function startProverBroker( throw new Error('L1 registry address is required to start Aztec Node without --deploy-aztec-contracts option'); } + const genesisConfig = getGenesisStateConfigEnvVars(); + const { genesisArchiveRoot } = await computeExpectedGenesisRoot(genesisConfig, userLog); + await waitForCompatibleRollup(config, genesisArchiveRoot, options.port, userLog); + const { addresses, config: rollupConfig } = await getL1Config( config.l1Contracts.registryAddress, config.l1RpcUrls, diff --git a/yarn-project/aztec/src/examples/token.ts b/yarn-project/aztec/src/examples/token.ts index 2a99d350c314..1e7e2077910d 100644 --- a/yarn-project/aztec/src/examples/token.ts +++ b/yarn-project/aztec/src/examples/token.ts @@ -32,7 +32,9 @@ async function main() { logger.info(`Fetched Alice and Bob accounts: ${alice.toString()}, ${bob.toString()}`); logger.info('Deploying Token...'); - const token = await TokenContract.deploy(wallet, alice, 'TokenName', 'TokenSymbol', 18).send({ from: alice }); + const { contract: token } = await TokenContract.deploy(wallet, alice, 'TokenName', 'TokenSymbol', 18).send({ + from: alice, + }); logger.info('Token deployed'); // Mint tokens to Alice @@ -41,7 +43,7 @@ async function main() { logger.info(`${ALICE_MINT_BALANCE} tokens were successfully minted by Alice and transferred to private`); - const balanceAfterMint = await token.methods.balance_of_private(alice).simulate({ from: alice }); + const { result: balanceAfterMint } = await token.methods.balance_of_private(alice).simulate({ from: alice }); logger.info(`Tokens successfully minted. New Alice's balance: ${balanceAfterMint}`); // We will now transfer tokens from Alice to Bob @@ -49,10 +51,10 @@ async function main() { await token.methods.transfer(bob, TRANSFER_AMOUNT).send({ from: alice }); // Check the new balances - const aliceBalance = await token.methods.balance_of_private(alice).simulate({ from: alice }); + const { result: aliceBalance } = await token.methods.balance_of_private(alice).simulate({ from: alice }); logger.info(`Alice's balance ${aliceBalance}`); - const bobBalance = await token.methods.balance_of_private(bob).simulate({ from: bob }); + const { result: bobBalance } = await token.methods.balance_of_private(bob).simulate({ from: bob }); logger.info(`Bob's balance ${bobBalance}`); } diff --git a/yarn-project/aztec/src/local-network/banana_fpc.ts b/yarn-project/aztec/src/local-network/banana_fpc.ts index 5ccb73d8746f..e5363e0eda73 100644 --- a/yarn-project/aztec/src/local-network/banana_fpc.ts +++ b/yarn-project/aztec/src/local-network/banana_fpc.ts @@ -48,7 +48,7 @@ export async function getBananaFPCAddress(initialAccounts: InitialAccountData[]) export async function setupBananaFPC(initialAccounts: InitialAccountData[], wallet: Wallet, log: LogFn) { const bananaCoinAddress = await getBananaCoinAddress(initialAccounts); const admin = getBananaAdmin(initialAccounts); - const [bananaCoin, fpc] = await Promise.all([ + const [{ contract: bananaCoin }, { contract: fpc }] = await Promise.all([ TokenContract.deploy(wallet, admin, bananaCoinArgs.name, bananaCoinArgs.symbol, bananaCoinArgs.decimal).send({ from: admin, contractAddressSalt: BANANA_COIN_SALT, diff --git a/yarn-project/aztec/src/local-network/local-network.ts b/yarn-project/aztec/src/local-network/local-network.ts index a2d04e8b22ab..4f62ea214738 100644 --- a/yarn-project/aztec/src/local-network/local-network.ts +++ b/yarn-project/aztec/src/local-network/local-network.ts @@ -16,11 +16,15 @@ import { SecretValue } from '@aztec/foundation/config'; import { EthAddress } from '@aztec/foundation/eth-address'; import type { LogFn } from '@aztec/foundation/log'; import { DateProvider, TestDateProvider } from '@aztec/foundation/timer'; +import { TokenContractArtifact } from '@aztec/noir-contracts.js/Token'; import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree'; import { protocolContractsHash } from '@aztec/protocol-contracts'; import { SequencerState } from '@aztec/sequencer-client'; +import { FunctionSelector, countArgumentsSize } from '@aztec/stdlib/abi'; +import type { FunctionAbi } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { ProvingJobBroker } from '@aztec/stdlib/interfaces/server'; +import { getContractClassFromArtifact } from '@aztec/stdlib/contract'; +import type { AllowedElement, ProvingJobBroker } from '@aztec/stdlib/interfaces/server'; import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees'; import { type TelemetryClient, @@ -44,6 +48,38 @@ import { getSponsoredFPCAddress } from './sponsored_fpc.js'; const logger = createLogger('local-network'); +/** + * Returns Token-specific allowlist entries for FPC-based fee payments. + * The local network deploys a banana FPC and Token contracts, so the node must allow Token setup functions. + */ +async function getTokenAllowedSetupFunctions(): Promise { + const tokenClassId = (await getContractClassFromArtifact(TokenContractArtifact)).id; + const allFunctions: FunctionAbi[] = (TokenContractArtifact.functions as FunctionAbi[]).concat( + TokenContractArtifact.nonDispatchPublicFunctions || [], + ); + const getCalldataLength = (name: string) => { + const fn = allFunctions.find(f => f.name === name)!; + return 1 + countArgumentsSize(fn); + }; + const increaseBalanceSelector = await FunctionSelector.fromSignature('_increase_public_balance((Field),u128)'); + const transferInPublicSelector = await FunctionSelector.fromSignature( + 'transfer_in_public((Field),(Field),u128,Field)', + ); + return [ + { + classId: tokenClassId, + selector: increaseBalanceSelector, + calldataLength: getCalldataLength('_increase_public_balance'), + onlySelf: true, + }, + { + classId: tokenClassId, + selector: transferInPublicSelector, + calldataLength: getCalldataLength('transfer_in_public'), + }, + ]; +} + const localAnvil = foundry; /** @@ -102,9 +138,14 @@ export async function createLocalNetwork(config: Partial = { logger.warn(`Multiple L1 RPC URLs provided. Local networks will only use the first one: ${l1RpcUrl}`); } + // The local network deploys a banana FPC with Token contracts, so include Token entries + // in the setup allowlist so FPC-based fee payments work out of the box. + const tokenAllowList = await getTokenAllowedSetupFunctions(); + const aztecNodeConfig: AztecNodeConfig = { ...getConfigEnvVars(), ...config, + txPublicSetupAllowListExtend: [...tokenAllowList, ...(config.txPublicSetupAllowListExtend ?? [])], }; const hdAccount = mnemonicToAccount(config.l1Mnemonic || DefaultMnemonic); if ( diff --git a/yarn-project/bot/src/amm_bot.ts b/yarn-project/bot/src/amm_bot.ts index 362470479518..21461c775d90 100644 --- a/yarn-project/bot/src/amm_bot.ts +++ b/yarn-project/bot/src/amm_bot.ts @@ -71,12 +71,14 @@ export class AmmBot extends BaseBot { .getFunctionCall(), }); - const amountOutMin = await amm.methods - .get_amount_out_for_exact_in( - await tokenIn.methods.balance_of_public(amm.address).simulate({ from: this.defaultAccountAddress }), - await tokenOut.methods.balance_of_public(amm.address).simulate({ from: this.defaultAccountAddress }), - amountIn, - ) + const { result: tokenInBalance } = await tokenIn.methods + .balance_of_public(amm.address) + .simulate({ from: this.defaultAccountAddress }); + const { result: tokenOutBalance } = await tokenOut.methods + .balance_of_public(amm.address) + .simulate({ from: this.defaultAccountAddress }); + const { result: amountOutMin } = await amm.methods + .get_amount_out_for_exact_in(tokenInBalance, tokenOutBalance, amountIn) .simulate({ from: this.defaultAccountAddress }); const swapExactTokensInteraction = amm.methods @@ -89,7 +91,8 @@ export class AmmBot extends BaseBot { this.log.verbose(`Sending transaction`, logCtx); this.log.info(`Tx. Balances: ${jsonStringify(balances)}`, { ...logCtx, balances }); - return swapExactTokensInteraction.send({ ...opts, wait: NO_WAIT }); + const { txHash } = await swapExactTokensInteraction.send({ ...opts, wait: NO_WAIT }); + return txHash; } protected override async onTxMined(receipt: TxReceipt, logCtx: object): Promise { @@ -110,15 +113,17 @@ export class AmmBot extends BaseBot { } private async getPublicBalanceFor(address: AztecAddress, from?: AztecAddress): Promise { - return { - token0: await this.token0.methods.balance_of_public(address).simulate({ from: from ?? address }), - token1: await this.token1.methods.balance_of_public(address).simulate({ from: from ?? address }), - }; + const { result: token0 } = await this.token0.methods.balance_of_public(address).simulate({ from: from ?? address }); + const { result: token1 } = await this.token1.methods.balance_of_public(address).simulate({ from: from ?? address }); + return { token0, token1 }; } private async getPrivateBalanceFor(address: AztecAddress, from?: AztecAddress): Promise { - return { - token0: await this.token0.methods.balance_of_private(address).simulate({ from: from ?? address }), - token1: await this.token1.methods.balance_of_private(address).simulate({ from: from ?? address }), - }; + const { result: token0 } = await this.token0.methods + .balance_of_private(address) + .simulate({ from: from ?? address }); + const { result: token1 } = await this.token1.methods + .balance_of_private(address) + .simulate({ from: from ?? address }); + return { token0, token1 }; } } diff --git a/yarn-project/bot/src/bot.ts b/yarn-project/bot/src/bot.ts index dce40d2194ad..c2128b2a219e 100644 --- a/yarn-project/bot/src/bot.ts +++ b/yarn-project/bot/src/bot.ts @@ -76,7 +76,8 @@ export class Bot extends BaseBot { await batch.simulate({ from: this.defaultAccountAddress }); this.log.verbose(`Sending transaction`, logCtx); - return batch.send({ ...opts, wait: NO_WAIT }); + const { txHash } = await batch.send({ ...opts, wait: NO_WAIT }); + return txHash; } public async getBalances() { diff --git a/yarn-project/bot/src/cross_chain_bot.ts b/yarn-project/bot/src/cross_chain_bot.ts index 0165b5a778a8..2a3ab8848ddb 100644 --- a/yarn-project/bot/src/cross_chain_bot.ts +++ b/yarn-project/bot/src/cross_chain_bot.ts @@ -140,7 +140,8 @@ export class CrossChainBot extends BaseBot { const opts = await this.getSendMethodOpts(batch); this.log.verbose(`Sending cross-chain batch with ${calls.length} calls`, logCtx); - return batch.send({ ...opts, wait: NO_WAIT }); + const { txHash } = await batch.send({ ...opts, wait: NO_WAIT }); + return txHash; } protected override async onTxMined(receipt: TxReceipt, logCtx: object): Promise { @@ -174,14 +175,7 @@ export class CrossChainBot extends BaseBot { ): Promise { const now = Date.now(); for (const msg of pendingMessages) { - const ready = await isL1ToL2MessageReady(this.node, Fr.fromHexString(msg.msgHash), { - // Use forPublicConsumption: false so we wait until blockNumber >= messageBlockNumber. - // With forPublicConsumption: true, the check returns true one block early (the sequencer - // includes L1→L2 messages before executing the block's txs), but gas estimation simulates - // against the current world state which doesn't yet have the message. - // See https://linear.app/aztec-labs/issue/A-548 for details. - forPublicConsumption: false, - }); + const ready = await isL1ToL2MessageReady(this.node, Fr.fromHexString(msg.msgHash)); if (ready) { return msg; } diff --git a/yarn-project/bot/src/factory.ts b/yarn-project/bot/src/factory.ts index 77b474c559b0..f37bffd1031c 100644 --- a/yarn-project/bot/src/factory.ts +++ b/yarn-project/bot/src/factory.ts @@ -158,11 +158,6 @@ export class BotFactory { const firstMsg = allMessages[0]; await waitForL1ToL2MessageReady(this.aztecNode, Fr.fromHexString(firstMsg.msgHash), { timeoutSeconds: this.config.l1ToL2MessageTimeoutSeconds, - // Use forPublicConsumption: false so we wait until the message is in the current world - // state. With true, it returns one block early which causes gas estimation simulation to - // fail since it runs against the current state. - // See https://linear.app/aztec-labs/issue/A-548 for details. - forPublicConsumption: false, }); this.log.info(`First L1→L2 message is ready`); } @@ -227,7 +222,7 @@ export class BotFactory { const gasSettings = GasSettings.default({ maxFeesPerGas }); await this.withNoMinTxsPerBlock(async () => { - const txHash = await deployMethod.send({ + const { txHash } = await deployMethod.send({ from: AztecAddress.ZERO, fee: { gasSettings, paymentMethod }, wait: NO_WAIT, @@ -285,6 +280,8 @@ export class BotFactory { tokenInstance = await deploy.getInstance(deployOpts); token = PrivateTokenContract.at(tokenInstance.address, this.wallet); await this.wallet.registerContract(tokenInstance, PrivateTokenContract.artifact, tokenSecretKey); + // The contract constructor initializes private storage vars that need the contract's own nullifier key. + deployOpts.additionalScopes = [tokenInstance.address]; } else { throw new Error(`Unsupported token contract type: ${this.config.contract}`); } @@ -296,7 +293,7 @@ export class BotFactory { await deploy.register(); } else { this.log.info(`Deploying token contract at ${address.toString()}`); - const txHash = await deploy.send({ ...deployOpts, wait: NO_WAIT }); + const { txHash } = await deploy.send({ ...deployOpts, wait: NO_WAIT }); this.log.info(`Sent tx for token setup with hash ${txHash.toString()}`); await this.withNoMinTxsPerBlock(async () => { await waitForTx(this.aztecNode, txHash, { timeout: this.config.txMinedWaitSeconds }); @@ -337,7 +334,7 @@ export class BotFactory { const amm = AMMContract.at(instance.address, this.wallet); this.log.info(`AMM deployed at ${amm.address}`); - const minterReceipt = await lpToken.methods + const { receipt: minterReceipt } = await lpToken.methods .set_minter(amm.address, true) .send({ from: deployer, wait: { timeout: this.config.txMinedWaitSeconds } }); this.log.info(`Set LP token minter to AMM txHash=${minterReceipt.txHash.toString()}`); @@ -356,9 +353,18 @@ export class BotFactory { ): Promise { const getPrivateBalances = () => Promise.all([ - token0.methods.balance_of_private(liquidityProvider).simulate({ from: liquidityProvider }), - token1.methods.balance_of_private(liquidityProvider).simulate({ from: liquidityProvider }), - lpToken.methods.balance_of_private(liquidityProvider).simulate({ from: liquidityProvider }), + token0.methods + .balance_of_private(liquidityProvider) + .simulate({ from: liquidityProvider }) + .then(r => r.result), + token1.methods + .balance_of_private(liquidityProvider) + .simulate({ from: liquidityProvider }) + .then(r => r.result), + lpToken.methods + .balance_of_private(liquidityProvider) + .simulate({ from: liquidityProvider }) + .then(r => r.result), ]); const authwitNonce = Fr.random(); @@ -399,14 +405,14 @@ export class BotFactory { .getFunctionCall(), }); - const mintReceipt = await new BatchCall(this.wallet, [ + const { receipt: mintReceipt } = await new BatchCall(this.wallet, [ token0.methods.mint_to_private(liquidityProvider, MINT_BALANCE), token1.methods.mint_to_private(liquidityProvider, MINT_BALANCE), ]).send({ from: liquidityProvider, wait: { timeout: this.config.txMinedWaitSeconds } }); this.log.info(`Sent mint tx: ${mintReceipt.txHash.toString()}`); - const addLiquidityReceipt = await amm.methods + const { receipt: addLiquidityReceipt } = await amm.methods .add_liquidity(amount0Max, amount1Max, amount0Min, amount1Min, authwitNonce) .send({ from: liquidityProvider, @@ -437,7 +443,7 @@ export class BotFactory { } else { this.log.info(`Deploying contract ${name} at ${address.toString()}`); await this.withNoMinTxsPerBlock(async () => { - const txHash = await deploy.send({ ...deployOpts, wait: NO_WAIT }); + const { txHash } = await deploy.send({ ...deployOpts, wait: NO_WAIT }); this.log.info(`Sent contract ${name} setup tx with hash ${txHash.toString()}`); return waitForTx(this.aztecNode, txHash, { timeout: this.config.txMinedWaitSeconds }); }); @@ -479,8 +485,14 @@ export class BotFactory { return; } + // PrivateToken's mint accesses contract-level private storage vars (admin, total_supply). + const additionalScopes = isStandardToken ? undefined : [token.address]; await this.withNoMinTxsPerBlock(async () => { - const txHash = await new BatchCall(token.wallet, calls).send({ from: minter, wait: NO_WAIT }); + const { txHash } = await new BatchCall(token.wallet, calls).send({ + from: minter, + additionalScopes, + wait: NO_WAIT, + }); this.log.info(`Sent token mint tx with hash ${txHash.toString()}`); return waitForTx(this.aztecNode, txHash, { timeout: this.config.txMinedWaitSeconds }); }); @@ -503,7 +515,6 @@ export class BotFactory { await this.withNoMinTxsPerBlock(() => waitForL1ToL2MessageReady(this.aztecNode, messageHash, { timeoutSeconds: this.config.l1ToL2MessageTimeoutSeconds, - forPublicConsumption: false, }), ); return existingClaim.claim; @@ -542,7 +553,6 @@ export class BotFactory { await this.withNoMinTxsPerBlock(() => waitForL1ToL2MessageReady(this.aztecNode, Fr.fromHexString(claim.messageHash), { timeoutSeconds: this.config.l1ToL2MessageTimeoutSeconds, - forPublicConsumption: false, }), ); diff --git a/yarn-project/bot/src/utils.ts b/yarn-project/bot/src/utils.ts index 1bd0b7f6b743..807d34be95ab 100644 --- a/yarn-project/bot/src/utils.ts +++ b/yarn-project/bot/src/utils.ts @@ -15,8 +15,8 @@ export async function getBalances( who: AztecAddress, from?: AztecAddress, ): Promise<{ privateBalance: bigint; publicBalance: bigint }> { - const privateBalance = await token.methods.balance_of_private(who).simulate({ from: from ?? who }); - const publicBalance = await token.methods.balance_of_public(who).simulate({ from: from ?? who }); + const { result: privateBalance } = await token.methods.balance_of_private(who).simulate({ from: from ?? who }); + const { result: publicBalance } = await token.methods.balance_of_public(who).simulate({ from: from ?? who }); return { privateBalance, publicBalance }; } @@ -25,7 +25,7 @@ export async function getPrivateBalance( who: AztecAddress, from?: AztecAddress, ): Promise { - const privateBalance = await token.methods.get_balance(who).simulate({ from: from ?? who }); + const { result: privateBalance } = await token.methods.get_balance(who).simulate({ from: from ?? who }); return privateBalance; } diff --git a/yarn-project/cli-wallet/src/cmds/create_account.ts b/yarn-project/cli-wallet/src/cmds/create_account.ts index 64b730b95aa1..2e317c6a7799 100644 --- a/yarn-project/cli-wallet/src/cmds/create_account.ts +++ b/yarn-project/cli-wallet/src/cmds/create_account.ts @@ -83,10 +83,13 @@ export async function createAccount( }; const deployMethod = await account.getDeployMethod(); - const { estimatedGas, stats } = await deployMethod.simulate({ + const sim = await deployMethod.simulate({ ...deployAccountOpts, fee: { ...deployAccountOpts.fee, estimateGas: true }, }); + // estimateGas: true guarantees these fields are present + const estimatedGas = sim.estimatedGas!; + const stats = sim.stats!; if (feeOpts.estimateOnly) { if (json) { @@ -109,7 +112,7 @@ export async function createAccount( if (!json) { log(`\nWaiting for account contract deployment...`); } - const result = await deployMethod.send({ + const sendOpts = { ...deployAccountOpts, fee: deployAccountOpts.fee ? { @@ -117,18 +120,20 @@ export async function createAccount( gasSettings: estimatedGas, } : undefined, - wait: wait ? { timeout: DEFAULT_TX_TIMEOUT_S, returnReceipt: true } : NO_WAIT, - }); - const isReceipt = (data: TxReceipt | TxHash): data is TxReceipt => 'txHash' in data; - if (isReceipt(result)) { - txReceipt = result; - txHash = result.txHash; + }; + if (wait) { + const { receipt } = await deployMethod.send({ + ...sendOpts, + wait: { timeout: DEFAULT_TX_TIMEOUT_S, returnReceipt: true }, + }); + txReceipt = receipt; + txHash = receipt.txHash; out.txReceipt = { status: txReceipt.status, transactionFee: txReceipt.transactionFee, }; } else { - txHash = result; + ({ txHash } = await deployMethod.send({ ...sendOpts, wait: NO_WAIT })); } debugLogger.debug(`Account contract tx sent with hash ${txHash.toString()}`); out.txHash = txHash; diff --git a/yarn-project/cli-wallet/src/cmds/deploy.ts b/yarn-project/cli-wallet/src/cmds/deploy.ts index 0de9615aa276..edc7e5db29a9 100644 --- a/yarn-project/cli-wallet/src/cmds/deploy.ts +++ b/yarn-project/cli-wallet/src/cmds/deploy.ts @@ -71,10 +71,13 @@ export async function deploy( skipInstancePublication, }; - const { estimatedGas, stats } = await deploy.simulate({ + const sim = await deploy.simulate({ ...deployOpts, fee: { ...deployOpts.fee, estimateGas: true }, }); + // estimateGas: true guarantees these fields are present + const estimatedGas = sim.estimatedGas!; + const stats = sim.stats!; if (feeOpts.estimateOnly) { if (json) { @@ -98,7 +101,7 @@ export async function deploy( const instance = await deploy.getInstance(); if (wait) { - const receipt = await deploy.send({ ...deployOpts, wait: { timeout, returnReceipt: true } }); + const { receipt } = await deploy.send({ ...deployOpts, wait: { timeout, returnReceipt: true } }); const txHash = receipt.txHash; debugLogger.debug(`Deploy tx sent with hash ${txHash.toString()}`); out.hash = txHash; @@ -121,7 +124,7 @@ export async function deploy( }; } } else { - const txHash = await deploy.send({ ...deployOpts, wait: NO_WAIT }); + const { txHash } = await deploy.send({ ...deployOpts, wait: NO_WAIT }); debugLogger.debug(`Deploy tx sent with hash ${txHash.toString()}`); out.hash = txHash; diff --git a/yarn-project/cli-wallet/src/cmds/deploy_account.ts b/yarn-project/cli-wallet/src/cmds/deploy_account.ts index f18037e7cbb6..4667fa6f12c6 100644 --- a/yarn-project/cli-wallet/src/cmds/deploy_account.ts +++ b/yarn-project/cli-wallet/src/cmds/deploy_account.ts @@ -63,10 +63,13 @@ export async function deployAccount( }; const deployMethod = await account.getDeployMethod(); - const { estimatedGas, stats } = await deployMethod.simulate({ + const sim = await deployMethod.simulate({ ...deployAccountOpts, fee: { ...deployAccountOpts.fee, estimateGas: true }, }); + // estimateGas: true guarantees these fields are present + const estimatedGas = sim.estimatedGas!; + const stats = sim.stats!; if (feeOpts.estimateOnly) { if (json) { @@ -89,7 +92,7 @@ export async function deployAccount( if (!json) { log(`\nWaiting for account contract deployment...`); } - const result = await deployMethod.send({ + const sendOpts = { ...deployAccountOpts, fee: deployAccountOpts.fee ? { @@ -97,18 +100,20 @@ export async function deployAccount( gasSettings: estimatedGas, } : undefined, - wait: wait ? { timeout: DEFAULT_TX_TIMEOUT_S, returnReceipt: true } : NO_WAIT, - }); - const isReceipt = (data: TxReceipt | TxHash): data is TxReceipt => 'txHash' in data; - if (isReceipt(result)) { - txReceipt = result; - txHash = result.txHash; + }; + if (wait) { + const { receipt } = await deployMethod.send({ + ...sendOpts, + wait: { timeout: DEFAULT_TX_TIMEOUT_S, returnReceipt: true }, + }); + txReceipt = receipt; + txHash = receipt.txHash; out.txReceipt = { status: txReceipt.status, transactionFee: txReceipt.transactionFee, }; } else { - txHash = result; + ({ txHash } = await deployMethod.send({ ...sendOpts, wait: NO_WAIT })); } debugLogger.debug(`Account contract tx sent with hash ${txHash.toString()}`); out.txHash = txHash; diff --git a/yarn-project/cli-wallet/src/cmds/send.ts b/yarn-project/cli-wallet/src/cmds/send.ts index 4059db13a93b..4cc3c69b4505 100644 --- a/yarn-project/cli-wallet/src/cmds/send.ts +++ b/yarn-project/cli-wallet/src/cmds/send.ts @@ -37,10 +37,13 @@ export async function send( authWitnesses, }; - const { estimatedGas, stats } = await call.simulate({ + const sim = await call.simulate({ ...sendOptions, fee: { ...sendOptions.fee, estimateGas: true }, }); + // estimateGas: true guarantees these fields are present + const estimatedGas = sim.estimatedGas!; + const stats = sim.stats!; if (feeOpts.estimateOnly) { return; @@ -52,7 +55,7 @@ export async function send( if (wait) { try { - const receipt = await call.send({ + const { receipt } = await call.send({ ...sendOptions, fee: { ...sendOptions.fee, gasSettings: estimatedGas }, wait: { timeout: DEFAULT_TX_TIMEOUT_S }, @@ -74,7 +77,7 @@ export async function send( throw err; } } else { - const txHash = await call.send({ + const { txHash } = await call.send({ ...sendOptions, fee: { ...sendOptions.fee, gasSettings: estimatedGas }, wait: NO_WAIT, diff --git a/yarn-project/cli-wallet/src/cmds/simulate.ts b/yarn-project/cli-wallet/src/cmds/simulate.ts index 2443428df708..aec161b4ab33 100644 --- a/yarn-project/cli-wallet/src/cmds/simulate.ts +++ b/yarn-project/cli-wallet/src/cmds/simulate.ts @@ -38,7 +38,7 @@ export async function simulate( }); if (verbose) { await printAuthorizations( - simulationResult.offchainEffects!, + simulationResult.offchainEffects, async (address: AztecAddress) => { const metadata = await wallet.getContractMetadata(address); if (!metadata.instance) { diff --git a/yarn-project/cli-wallet/src/utils/options/fees.ts b/yarn-project/cli-wallet/src/utils/options/fees.ts index 79e89ff8e60c..908de366f549 100644 --- a/yarn-project/cli-wallet/src/utils/options/fees.ts +++ b/yarn-project/cli-wallet/src/utils/options/fees.ts @@ -171,6 +171,9 @@ export function parsePaymentMethod( case 'fpc-public': { const fpc = getFpc(); const asset = getAsset(); + log( + `WARNING: fpc-public is deprecated and will not work on mainnet alpha. Use fee_juice or fpc-sponsored instead.`, + ); log(`Using public fee payment with asset ${asset} via paymaster ${fpc}`); const { PublicFeePaymentMethod } = await import('@aztec/aztec.js/fee'); return new PublicFeePaymentMethod(fpc, from, wallet, gasSettings); @@ -178,6 +181,9 @@ export function parsePaymentMethod( case 'fpc-private': { const fpc = getFpc(); const asset = getAsset(); + log( + `WARNING: fpc-private is deprecated and will not work on mainnet alpha. Use fee_juice or fpc-sponsored instead.`, + ); log(`Using private fee payment with asset ${asset} via paymaster ${fpc}`); const { PrivateFeePaymentMethod } = await import('@aztec/aztec.js/fee'); return new PrivateFeePaymentMethod(fpc, from, wallet, gasSettings); diff --git a/yarn-project/cli/src/config/network_config.ts b/yarn-project/cli/src/config/network_config.ts index 998acadae315..4e835919014a 100644 --- a/yarn-project/cli/src/config/network_config.ts +++ b/yarn-project/cli/src/config/network_config.ts @@ -142,4 +142,7 @@ export async function enrichEnvironmentWithNetworkConfig(networkName: NetworkNam if (networkConfig.blockDurationMs !== undefined) { enrichVar('SEQ_BLOCK_DURATION_MS', String(networkConfig.blockDurationMs)); } + if (networkConfig.txPublicSetupAllowListExtend) { + enrichVar('TX_PUBLIC_SETUP_ALLOWLIST', networkConfig.txPublicSetupAllowListExtend); + } } diff --git a/yarn-project/end-to-end/src/bench/client_flows/account_deployments.test.ts b/yarn-project/end-to-end/src/bench/client_flows/account_deployments.test.ts index db57a7b52d9b..e38eb09f7c9f 100644 --- a/yarn-project/end-to-end/src/bench/client_flows/account_deployments.test.ts +++ b/yarn-project/end-to-end/src/bench/client_flows/account_deployments.test.ts @@ -91,8 +91,8 @@ describe('Deployment benchmark', () => { if (process.env.SANITY_CHECKS) { // Ensure we paid a fee - const tx = await deploymentInteraction.send({ ...options, wait: { returnReceipt: true } }); - expect(tx.transactionFee!).toBeGreaterThan(0n); + const { receipt } = await deploymentInteraction.send({ ...options, wait: { returnReceipt: true } }); + expect(receipt.transactionFee!).toBeGreaterThan(0n); } }); } diff --git a/yarn-project/end-to-end/src/bench/client_flows/amm.test.ts b/yarn-project/end-to-end/src/bench/client_flows/amm.test.ts index 22ca27be3eee..44a5652b484f 100644 --- a/yarn-project/end-to-end/src/bench/client_flows/amm.test.ts +++ b/yarn-project/end-to-end/src/bench/client_flows/amm.test.ts @@ -199,7 +199,7 @@ describe('AMM benchmark', () => { ); if (process.env.SANITY_CHECKS) { - const tx = await addLiquidityInteraction.send({ from: benchysAddress }); + const { receipt: tx } = await addLiquidityInteraction.send({ from: benchysAddress }); expect(tx.transactionFee!).toBeGreaterThan(0n); } diff --git a/yarn-project/end-to-end/src/bench/client_flows/bridging.test.ts b/yarn-project/end-to-end/src/bench/client_flows/bridging.test.ts index 0800c709ea82..27aa73ef8e39 100644 --- a/yarn-project/end-to-end/src/bench/client_flows/bridging.test.ts +++ b/yarn-project/end-to-end/src/bench/client_flows/bridging.test.ts @@ -106,7 +106,7 @@ describe('Bridging benchmark', () => { if (process.env.SANITY_CHECKS) { // Ensure we paid a fee - const tx = await claimInteraction.send(options); + const { receipt: tx } = await claimInteraction.send(options); expect(tx.transactionFee!).toBeGreaterThan(0n); // 4. Check the balance diff --git a/yarn-project/end-to-end/src/bench/client_flows/client_flows_benchmark.ts b/yarn-project/end-to-end/src/bench/client_flows/client_flows_benchmark.ts index 6281e6f7f4cd..7b997404b6e1 100644 --- a/yarn-project/end-to-end/src/bench/client_flows/client_flows_benchmark.ts +++ b/yarn-project/end-to-end/src/bench/client_flows/client_flows_benchmark.ts @@ -19,12 +19,16 @@ import { AMMContract } from '@aztec/noir-contracts.js/AMM'; import { FPCContract } from '@aztec/noir-contracts.js/FPC'; import { FeeJuiceContract } from '@aztec/noir-contracts.js/FeeJuice'; import { SponsoredFPCContract } from '@aztec/noir-contracts.js/SponsoredFPC'; -import { TokenContract as BananaCoin, TokenContract } from '@aztec/noir-contracts.js/Token'; +import { TokenContract as BananaCoin, TokenContract, TokenContractArtifact } from '@aztec/noir-contracts.js/Token'; import { ProtocolContractAddress } from '@aztec/protocol-contracts'; import { getCanonicalFeeJuice } from '@aztec/protocol-contracts/fee-juice'; import { type PXEConfig, getPXEConfig } from '@aztec/pxe/server'; +import { FunctionSelector, countArgumentsSize } from '@aztec/stdlib/abi'; +import type { FunctionAbi } from '@aztec/stdlib/abi'; +import { getContractClassFromArtifact } from '@aztec/stdlib/contract'; import type { ContractInstanceWithAddress } from '@aztec/stdlib/contract'; import { GasSettings } from '@aztec/stdlib/gas'; +import type { AllowedElement } from '@aztec/stdlib/interfaces/server'; import { deriveSigningKey } from '@aztec/stdlib/keys'; import { MNEMONIC } from '../../fixtures/fixtures.js'; @@ -42,6 +46,35 @@ import { type ClientFlowsConfig, FULL_FLOWS_CONFIG, KEY_FLOWS_CONFIG } from './c const { BENCHMARK_CONFIG } = process.env; +/** Returns Token-specific allowlist entries for FPC-based fee payments (test-only). */ +async function getTokenAllowedSetupFunctions(): Promise { + const tokenClassId = (await getContractClassFromArtifact(TokenContractArtifact)).id; + const allFunctions: FunctionAbi[] = (TokenContractArtifact.functions as FunctionAbi[]).concat( + TokenContractArtifact.nonDispatchPublicFunctions || [], + ); + const getCalldataLength = (name: string) => { + const fn = allFunctions.find(f => f.name === name)!; + return 1 + countArgumentsSize(fn); + }; + const increaseBalanceSelector = await FunctionSelector.fromSignature('_increase_public_balance((Field),u128)'); + const transferInPublicSelector = await FunctionSelector.fromSignature( + 'transfer_in_public((Field),(Field),u128,Field)', + ); + return [ + { + classId: tokenClassId, + selector: increaseBalanceSelector, + calldataLength: getCalldataLength('_increase_public_balance'), + onlySelf: true, + }, + { + classId: tokenClassId, + selector: transferInPublicSelector, + calldataLength: getCalldataLength('transfer_in_public'), + }, + ]; +} + export type AccountType = 'ecdsar1' | 'schnorr'; export type FeePaymentMethodGetter = (wallet: Wallet, sender: AztecAddress) => Promise; export type BenchmarkingFeePaymentMethod = 'bridged_fee_juice' | 'private_fpc' | 'sponsored_fpc' | 'fee_juice'; @@ -130,11 +163,14 @@ export class ClientFlowsBenchmark { async setup() { this.logger.info('Setting up subsystems from fresh'); + // Token allowlist entries are test-only: FPC-based fee payment with custom tokens won't work on mainnet alpha. + const tokenAllowList = await getTokenAllowedSetupFunctions(); this.context = await setup(0, { ...this.setupOptions, fundSponsoredFPC: true, skipAccountDeployment: true, l1ContractsArgs: this.setupOptions, + txPublicSetupAllowListExtend: [...(this.setupOptions.txPublicSetupAllowListExtend ?? []), ...tokenAllowList], }); await this.applyBaseSetup(); @@ -161,11 +197,15 @@ export class ClientFlowsBenchmark { /** Admin mints bananaCoin tokens privately to the target address and redeems them. */ async mintPrivateBananas(amount: bigint, address: AztecAddress) { - const balanceBefore = await this.bananaCoin.methods.balance_of_private(address).simulate({ from: address }); + const { result: balanceBefore } = await this.bananaCoin.methods + .balance_of_private(address) + .simulate({ from: address }); await mintTokensToPrivate(this.bananaCoin, this.adminAddress, address, amount); - const balanceAfter = await this.bananaCoin.methods.balance_of_private(address).simulate({ from: address }); + const { result: balanceAfter } = await this.bananaCoin.methods + .balance_of_private(address) + .simulate({ from: address }); expect(balanceAfter).toEqual(balanceBefore + amount); } @@ -241,13 +281,12 @@ export class ClientFlowsBenchmark { async applyDeployBananaToken() { this.logger.info('Applying banana token deployment'); - const { contract: bananaCoin, instance: bananaCoinInstance } = await BananaCoin.deploy( - this.adminWallet, - this.adminAddress, - 'BC', - 'BC', - 18n, - ).send({ from: this.adminAddress, wait: { returnReceipt: true } }); + const { + receipt: { contract: bananaCoin, instance: bananaCoinInstance }, + } = await BananaCoin.deploy(this.adminWallet, this.adminAddress, 'BC', 'BC', 18n).send({ + from: this.adminAddress, + wait: { returnReceipt: true }, + }); this.logger.info(`BananaCoin deployed at ${bananaCoin.address}`); this.bananaCoin = bananaCoin; this.bananaCoinInstance = bananaCoinInstance; @@ -255,13 +294,12 @@ export class ClientFlowsBenchmark { async applyDeployCandyBarToken() { this.logger.info('Applying candy bar token deployment'); - const { contract: candyBarCoin, instance: candyBarCoinInstance } = await TokenContract.deploy( - this.adminWallet, - this.adminAddress, - 'CBC', - 'CBC', - 18n, - ).send({ from: this.adminAddress, wait: { returnReceipt: true } }); + const { + receipt: { contract: candyBarCoin, instance: candyBarCoinInstance }, + } = await TokenContract.deploy(this.adminWallet, this.adminAddress, 'CBC', 'CBC', 18n).send({ + from: this.adminAddress, + wait: { returnReceipt: true }, + }); this.logger.info(`CandyBarCoin deployed at ${candyBarCoin.address}`); this.candyBarCoin = candyBarCoin; this.candyBarCoinInstance = candyBarCoinInstance; @@ -273,11 +311,12 @@ export class ClientFlowsBenchmark { expect((await this.context.wallet.getContractMetadata(feeJuiceContract.address)).isContractPublished).toBe(true); const bananaCoin = this.bananaCoin; - const { contract: bananaFPC, instance: bananaFPCInstance } = await FPCContract.deploy( - this.adminWallet, - bananaCoin.address, - this.adminAddress, - ).send({ from: this.adminAddress, wait: { returnReceipt: true } }); + const { + receipt: { contract: bananaFPC, instance: bananaFPCInstance }, + } = await FPCContract.deploy(this.adminWallet, bananaCoin.address, this.adminAddress).send({ + from: this.adminAddress, + wait: { returnReceipt: true }, + }); this.logger.info(`BananaPay deployed at ${bananaFPC.address}`); @@ -340,14 +379,15 @@ export class ClientFlowsBenchmark { public async applyDeployAmm() { this.logger.info('Applying AMM deployment'); - const { contract: liquidityToken, instance: liquidityTokenInstance } = await TokenContract.deploy( - this.adminWallet, - this.adminAddress, - 'LPT', - 'LPT', - 18n, - ).send({ from: this.adminAddress, wait: { returnReceipt: true } }); - const { contract: amm, instance: ammInstance } = await AMMContract.deploy( + const { + receipt: { contract: liquidityToken, instance: liquidityTokenInstance }, + } = await TokenContract.deploy(this.adminWallet, this.adminAddress, 'LPT', 'LPT', 18n).send({ + from: this.adminAddress, + wait: { returnReceipt: true }, + }); + const { + receipt: { contract: amm, instance: ammInstance }, + } = await AMMContract.deploy( this.adminWallet, this.bananaCoin.address, this.candyBarCoin.address, diff --git a/yarn-project/end-to-end/src/bench/client_flows/deployments.test.ts b/yarn-project/end-to-end/src/bench/client_flows/deployments.test.ts index 124e0b0cbbae..f75f4d011751 100644 --- a/yarn-project/end-to-end/src/bench/client_flows/deployments.test.ts +++ b/yarn-project/end-to-end/src/bench/client_flows/deployments.test.ts @@ -86,8 +86,8 @@ describe('Deployment benchmark', () => { if (process.env.SANITY_CHECKS) { // Ensure we paid a fee - const tx = await deploymentInteraction.send({ ...options, wait: { returnReceipt: true } }); - expect(tx.transactionFee!).toBeGreaterThan(0n); + const { receipt } = await deploymentInteraction.send({ ...options, wait: { returnReceipt: true } }); + expect(receipt.transactionFee!).toBeGreaterThan(0n); } }); }); diff --git a/yarn-project/end-to-end/src/bench/client_flows/storage_proof.test.ts b/yarn-project/end-to-end/src/bench/client_flows/storage_proof.test.ts index 00d431a4bc40..c46f17ccf444 100644 --- a/yarn-project/end-to-end/src/bench/client_flows/storage_proof.test.ts +++ b/yarn-project/end-to-end/src/bench/client_flows/storage_proof.test.ts @@ -34,7 +34,7 @@ describe('Storage proof benchmark', () => { await t.applyFPCSetup(); await t.applyDeploySponsoredFPC(); - const deployed = await StorageProofTestContract.deploy(t.adminWallet).send({ + const { receipt: deployed } = await StorageProofTestContract.deploy(t.adminWallet).send({ from: t.adminAddress, wait: { returnReceipt: true }, }); @@ -106,7 +106,7 @@ describe('Storage proof benchmark', () => { ); if (process.env.SANITY_CHECKS) { - const tx = await interaction.send(options); + const { receipt: tx } = await interaction.send(options); expect(tx.transactionFee!).toBeGreaterThan(0n); expect(tx.hasExecutionSucceeded()).toBe(true); } diff --git a/yarn-project/end-to-end/src/bench/client_flows/transfers.test.ts b/yarn-project/end-to-end/src/bench/client_flows/transfers.test.ts index 77c8149072b7..df7222e067db 100644 --- a/yarn-project/end-to-end/src/bench/client_flows/transfers.test.ts +++ b/yarn-project/end-to-end/src/bench/client_flows/transfers.test.ts @@ -151,7 +151,7 @@ describe('Transfer benchmark', () => { if (process.env.SANITY_CHECKS) { // Ensure we paid a fee - const tx = await transferInteraction.send(options); + const { receipt: tx } = await transferInteraction.send(options); expect(tx.transactionFee!).toBeGreaterThan(0n); // Sanity checks @@ -179,7 +179,7 @@ describe('Transfer benchmark', () => { */ expect(txEffects!.data.noteHashes.length).toBe(2 + (benchmarkingPaymentMethod === 'private_fpc' ? 2 : 0)); - const senderBalance = await asset.methods + const { result: senderBalance } = await asset.methods .balance_of_private(benchysAddress) .simulate({ from: benchysAddress }); expect(senderBalance).toEqual(expectedChange); diff --git a/yarn-project/end-to-end/src/bench/node_rpc_perf.test.ts b/yarn-project/end-to-end/src/bench/node_rpc_perf.test.ts index a0362bf60e56..7149ec20637c 100644 --- a/yarn-project/end-to-end/src/bench/node_rpc_perf.test.ts +++ b/yarn-project/end-to-end/src/bench/node_rpc_perf.test.ts @@ -152,9 +152,9 @@ describe('e2e_node_rpc_perf', () => { })); logger.info('Deploying token contract...'); - tokenContract = await TokenContract.deploy(wallet, ownerAddress, 'TestToken', 'TST', 18n).send({ + ({ contract: tokenContract } = await TokenContract.deploy(wallet, ownerAddress, 'TestToken', 'TST', 18n).send({ from: ownerAddress, - }); + })); contractAddress = tokenContract.address; logger.info(`Token contract deployed at ${contractAddress}`); @@ -280,6 +280,12 @@ describe('e2e_node_rpc_perf', () => { expect(stats.avg).toBeLessThan(1000); }); + it('benchmarks getCheckpointNumber', async () => { + const { stats } = await benchmark('getCheckpointNumber', () => aztecNode.getCheckpointNumber()); + addResult('getCheckpointNumber', stats); + expect(stats.avg).toBeLessThan(1000); + }); + it('benchmarks getProvenBlockNumber', async () => { const { stats } = await benchmark('getProvenBlockNumber', () => aztecNode.getProvenBlockNumber()); addResult('getProvenBlockNumber', stats); @@ -414,10 +420,12 @@ describe('e2e_node_rpc_perf', () => { }); describe('message APIs', () => { - it('benchmarks getL1ToL2MessageBlock', async () => { + it('benchmarks getL1ToL2MessageCheckpoint', async () => { const l1ToL2Message = Fr.random(); - const { stats } = await benchmark('getL1ToL2MessageBlock', () => aztecNode.getL1ToL2MessageBlock(l1ToL2Message)); - addResult('getL1ToL2MessageBlock', stats); + const { stats } = await benchmark('getL1ToL2MessageCheckpoint', () => + aztecNode.getL1ToL2MessageCheckpoint(l1ToL2Message), + ); + addResult('getL1ToL2MessageCheckpoint', stats); expect(stats.avg).toBeLessThan(2000); }); diff --git a/yarn-project/end-to-end/src/bench/tx_stats_bench.test.ts b/yarn-project/end-to-end/src/bench/tx_stats_bench.test.ts index 54d51c3ce6da..e69ffed1efc2 100644 --- a/yarn-project/end-to-end/src/bench/tx_stats_bench.test.ts +++ b/yarn-project/end-to-end/src/bench/tx_stats_bench.test.ts @@ -65,12 +65,12 @@ describe('transaction benchmarks', () => { } = t); // Create the two transactions - const privateBalance = await provenAsset.methods.balance_of_private(sender).simulate({ from: sender }); + const { result: privateBalance } = await provenAsset.methods.balance_of_private(sender).simulate({ from: sender }); const privateSendAmount = privateBalance / 10n; expect(privateSendAmount).toBeGreaterThan(0n); const privateInteraction = provenAsset.methods.transfer(recipient, privateSendAmount); - const publicBalance = await provenAsset.methods.balance_of_public(sender).simulate({ from: sender }); + const { result: publicBalance } = await provenAsset.methods.balance_of_public(sender).simulate({ from: sender }); const publicSendAmount = publicBalance / 10n; expect(publicSendAmount).toBeGreaterThan(0n); const publicInteraction = provenAsset.methods.transfer_in_public(sender, recipient, publicSendAmount, 0); diff --git a/yarn-project/end-to-end/src/bench/utils.ts b/yarn-project/end-to-end/src/bench/utils.ts index 370baefc8b6c..7daa924782df 100644 --- a/yarn-project/end-to-end/src/bench/utils.ts +++ b/yarn-project/end-to-end/src/bench/utils.ts @@ -25,7 +25,7 @@ export async function benchmarkSetup( ) { const context = await setup(1, { ...opts, telemetryConfig: { benchmark: true } }); const defaultAccountAddress = context.accounts[0]; - const contract = await BenchmarkingContract.deploy(context.wallet).send({ from: defaultAccountAddress }); + const { contract } = await BenchmarkingContract.deploy(context.wallet).send({ from: defaultAccountAddress }); context.logger.info(`Deployed benchmarking contract at ${contract.address}`); const sequencer = (context.aztecNode as AztecNodeService).getSequencer()!; const telemetry = context.telemetryClient as BenchmarkTelemetryClient; @@ -149,7 +149,12 @@ export async function sendTxs( context.logger.info(`Creating ${txCount} txs`); const [from] = context.accounts; context.logger.info(`Sending ${txCount} txs`); - return Promise.all(calls.map(call => call.send({ from, wait: NO_WAIT }))); + return Promise.all( + calls.map(async call => { + const { txHash } = await call.send({ from, wait: NO_WAIT }); + return txHash; + }), + ); } export async function waitTxs(txs: TxHash[], context: EndToEndContext, txWaitOpts?: WaitOpts) { diff --git a/yarn-project/end-to-end/src/composed/docs_examples.test.ts b/yarn-project/end-to-end/src/composed/docs_examples.test.ts index fe843da8ff56..45611db7c898 100644 --- a/yarn-project/end-to-end/src/composed/docs_examples.test.ts +++ b/yarn-project/end-to-end/src/composed/docs_examples.test.ts @@ -34,7 +34,7 @@ describe('docs_examples', () => { const newAccountAddress = newAccountManager.address; const defaultAccountAddress = prefundedAccount.address; - const deployedContract = await TokenContract.deploy( + const { contract: deployedContract } = await TokenContract.deploy( wallet, // wallet instance defaultAccountAddress, // account 'TokenName', // constructor arg1 @@ -47,7 +47,9 @@ describe('docs_examples', () => { await contract.methods.mint_to_public(newAccountAddress, 1).send({ from: defaultAccountAddress }); // docs:start:simulate_function - const balance = await contract.methods.balance_of_public(newAccountAddress).simulate({ from: newAccountAddress }); + const { result: balance } = await contract.methods + .balance_of_public(newAccountAddress) + .simulate({ from: newAccountAddress }); expect(balance).toEqual(1n); // docs:end:simulate_function }); diff --git a/yarn-project/end-to-end/src/composed/e2e_local_network_example.test.ts b/yarn-project/end-to-end/src/composed/e2e_local_network_example.test.ts index 951c3e0152c4..7bd8d64e0fad 100644 --- a/yarn-project/end-to-end/src/composed/e2e_local_network_example.test.ts +++ b/yarn-project/end-to-end/src/composed/e2e_local_network_example.test.ts @@ -71,10 +71,10 @@ describe('e2e_local_network_example', () => { ////////////// QUERYING THE TOKEN BALANCE FOR EACH ACCOUNT ////////////// - let aliceBalance = await tokenContract.methods.balance_of_private(alice).simulate({ from: alice }); + let { result: aliceBalance } = await tokenContract.methods.balance_of_private(alice).simulate({ from: alice }); logger.info(`Alice's balance ${aliceBalance}`); - let bobBalance = await tokenContract.methods.balance_of_private(bob).simulate({ from: bob }); + let { result: bobBalance } = await tokenContract.methods.balance_of_private(bob).simulate({ from: bob }); logger.info(`Bob's balance ${bobBalance}`); expect(aliceBalance).toBe(initialSupply); @@ -88,10 +88,10 @@ describe('e2e_local_network_example', () => { await tokenContract.methods.transfer(bob, transferQuantity).send({ from: alice }); // Check the new balances - aliceBalance = await tokenContract.methods.balance_of_private(alice).simulate({ from: alice }); + ({ result: aliceBalance } = await tokenContract.methods.balance_of_private(alice).simulate({ from: alice })); logger.info(`Alice's balance ${aliceBalance}`); - bobBalance = await tokenContract.methods.balance_of_private(bob).simulate({ from: bob }); + ({ result: bobBalance } = await tokenContract.methods.balance_of_private(bob).simulate({ from: bob })); logger.info(`Bob's balance ${bobBalance}`); expect(aliceBalance).toBe(initialSupply - transferQuantity); @@ -108,10 +108,10 @@ describe('e2e_local_network_example', () => { await mintTokensToPrivate(tokenContract, bob, bob, mintQuantity); // Check the new balances - aliceBalance = await tokenContract.methods.balance_of_private(alice).simulate({ from: alice }); + ({ result: aliceBalance } = await tokenContract.methods.balance_of_private(alice).simulate({ from: alice })); logger.info(`Alice's balance ${aliceBalance}`); - bobBalance = await tokenContract.methods.balance_of_private(bob).simulate({ from: bob }); + ({ result: bobBalance } = await tokenContract.methods.balance_of_private(bob).simulate({ from: bob })); logger.info(`Bob's balance ${bobBalance}`); expect(aliceBalance).toBe(initialSupply - transferQuantity); @@ -188,7 +188,7 @@ describe('e2e_local_network_example', () => { const maxFeesPerGas = (await node.getCurrentMinFees()).mul(1.5); const gasSettings = GasSettings.default({ maxFeesPerGas }); const paymentMethod = new PrivateFeePaymentMethod(bananaFPCAddress, alice, wallet, gasSettings); - const receiptForAlice = await bananaCoin.methods + const { receipt: receiptForAlice } = await bananaCoin.methods .transfer(bob, amountTransferToBob) .send({ from: alice, fee: { paymentMethod } }); // docs:end:private_fpc_payment @@ -196,11 +196,11 @@ describe('e2e_local_network_example', () => { logger.info(`Transaction fee: ${transactionFee}`); // Check the balances - const aliceBalance = await bananaCoin.methods.balance_of_private(alice).simulate({ from: alice }); + const { result: aliceBalance } = await bananaCoin.methods.balance_of_private(alice).simulate({ from: alice }); logger.info(`Alice's balance: ${aliceBalance}`); expect(aliceBalance).toEqual(mintAmount - transactionFee - amountTransferToBob); - const bobBalance = await bananaCoin.methods.balance_of_private(bob).simulate({ from: bob }); + const { result: bobBalance } = await bananaCoin.methods.balance_of_private(bob).simulate({ from: bob }); logger.info(`Bob's balance: ${bobBalance}`); expect(bobBalance).toEqual(amountTransferToBob); @@ -214,16 +214,16 @@ describe('e2e_local_network_example', () => { // const sponsoredPaymentMethod = await SponsoredFeePaymentMethod.new(pxe); const initialFPCFeeJuice = await getFeeJuiceBalance(sponsoredFPC, node); - const receiptForBob = await bananaCoin.methods + const { receipt: receiptForBob } = await bananaCoin.methods .transfer(alice, amountTransferToAlice) .send({ from: bob, fee: { paymentMethod: sponsoredPaymentMethod } }); // docs:end:sponsored_fpc_payment // Check the balances - const aliceNewBalance = await bananaCoin.methods.balance_of_private(alice).simulate({ from: alice }); + const { result: aliceNewBalance } = await bananaCoin.methods.balance_of_private(alice).simulate({ from: alice }); logger.info(`Alice's new balance: ${aliceNewBalance}`); expect(aliceNewBalance).toEqual(aliceBalance + amountTransferToAlice); - const bobNewBalance = await bananaCoin.methods.balance_of_private(bob).simulate({ from: bob }); + const { result: bobNewBalance } = await bananaCoin.methods.balance_of_private(bob).simulate({ from: bob }); logger.info(`Bob's new balance: ${bobNewBalance}`); expect(bobNewBalance).toEqual(bobBalance - amountTransferToAlice); diff --git a/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts b/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts index 40a53ad5f254..3f60809d3f8f 100644 --- a/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts +++ b/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts @@ -68,7 +68,9 @@ describe('Aztec persistence', () => { owner = initialFundedAccounts[0]; ownerAddress = owner.address; - const { contract, instance } = await TokenBlacklistContract.deploy(wallet, ownerAddress).send({ + const { + receipt: { contract, instance }, + } = await TokenBlacklistContract.deploy(wallet, ownerAddress).send({ from: ownerAddress, wait: { returnReceipt: true }, }); @@ -84,7 +86,7 @@ describe('Aztec persistence', () => { const secret = Fr.random(); - const mintTxReceipt = await contract.methods + const { receipt: mintTxReceipt } = await contract.methods .mint_private(1000n, await computeSecretHash(secret)) .send({ from: ownerAddress }); @@ -139,19 +141,29 @@ describe('Aztec persistence', () => { it('correctly restores private notes', async () => { // test for >0 instead of exact value so test isn't dependent on run order await expect( - contract.methods.balance_of_private(ownerAddress).simulate({ from: ownerAddress }), + contract.methods + .balance_of_private(ownerAddress) + .simulate({ from: ownerAddress }) + .then(r => r.result), ).resolves.toBeGreaterThan(0n); }); it('correctly restores public storage', async () => { - await expect(contract.methods.total_supply().simulate({ from: ownerAddress })).resolves.toBeGreaterThan(0n); + await expect( + contract.methods + .total_supply() + .simulate({ from: ownerAddress }) + .then(r => r.result), + ).resolves.toBeGreaterThan(0n); }); it('tracks new notes for the owner', async () => { - const balance = await contract.methods.balance_of_private(ownerAddress).simulate({ from: ownerAddress }); + const { result: balance } = await contract.methods + .balance_of_private(ownerAddress) + .simulate({ from: ownerAddress }); const secret = Fr.random(); - const mintTxReceipt = await contract.methods + const { receipt: mintTxReceipt } = await contract.methods .mint_private(1000n, await computeSecretHash(secret)) .send({ from: ownerAddress }); await addPendingShieldNoteToPXE( @@ -165,9 +177,12 @@ describe('Aztec persistence', () => { await contract.methods.redeem_shield(ownerAddress, 1000n, secret).send({ from: ownerAddress }); - await expect(contract.methods.balance_of_private(ownerAddress).simulate({ from: ownerAddress })).resolves.toEqual( - balance + 1000n, - ); + await expect( + contract.methods + .balance_of_private(ownerAddress) + .simulate({ from: ownerAddress }) + .then(r => r.result), + ).resolves.toEqual(balance + 1000n); }); it('allows spending of private notes', async () => { @@ -175,13 +190,13 @@ describe('Aztec persistence', () => { const otherAccount = await context.wallet.createSchnorrAccount(account.secret, account.salt); const otherAddress = otherAccount.address; - const initialOwnerBalance = await contract.methods + const { result: initialOwnerBalance } = await contract.methods .balance_of_private(ownerAddress) .simulate({ from: ownerAddress }); await contract.methods.transfer(ownerAddress, otherAddress, 500n, 0).send({ from: ownerAddress }); - const [ownerBalance, targetBalance] = await Promise.all([ + const [{ result: ownerBalance }, { result: targetBalance }] = await Promise.all([ contract.methods.balance_of_private(ownerAddress).simulate({ from: ownerAddress }), contract.methods.balance_of_private(otherAddress).simulate({ from: otherAddress }), ]); @@ -224,16 +239,24 @@ describe('Aztec persistence', () => { it("pxe does not have owner's private notes", async () => { await context.wallet.registerContract(contractInstance, TokenBlacklistContract.artifact); const contract = TokenBlacklistContract.at(contractAddress, wallet); - await expect(contract.methods.balance_of_private(ownerAddress).simulate({ from: ownerAddress })).resolves.toEqual( - 0n, - ); + await expect( + contract.methods + .balance_of_private(ownerAddress) + .simulate({ from: ownerAddress }) + .then(r => r.result), + ).resolves.toEqual(0n); }); it('has access to public storage', async () => { await context.wallet.registerContract(contractInstance, TokenBlacklistContract.artifact); const contract = TokenBlacklistContract.at(contractAddress, wallet); - await expect(contract.methods.total_supply().simulate({ from: ownerAddress })).resolves.toBeGreaterThan(0n); + await expect( + contract.methods + .total_supply() + .simulate({ from: ownerAddress }) + .then(r => r.result), + ).resolves.toBeGreaterThan(0n); }); it('pxe restores notes after registering the owner', async () => { @@ -245,7 +268,10 @@ describe('Aztec persistence', () => { // check that notes total more than 0 so that this test isn't dependent on run order await expect( - contract.methods.balance_of_private(ownerAddress).simulate({ from: ownerAddress }), + contract.methods + .balance_of_private(ownerAddress) + .simulate({ from: ownerAddress }) + .then(r => r.result), ).resolves.toBeGreaterThan(0n); }); }); @@ -274,7 +300,7 @@ describe('Aztec persistence', () => { // mint some tokens with a secret we know and redeem later on a separate PXE secret = Fr.random(); mintAmount = 1000n; - const mintTxReceipt = await contract.methods + const { receipt: mintTxReceipt } = await contract.methods .mint_private(mintAmount, await computeSecretHash(secret)) .send({ from: ownerAddress }); mintTxHash = mintTxReceipt.txHash; @@ -301,9 +327,12 @@ describe('Aztec persistence', () => { }); it("restores owner's public balance", async () => { - await expect(contract.methods.balance_of_public(ownerAddress).simulate({ from: ownerAddress })).resolves.toEqual( - revealedAmount, - ); + await expect( + contract.methods + .balance_of_public(ownerAddress) + .simulate({ from: ownerAddress }) + .then(r => r.result), + ).resolves.toEqual(revealedAmount); }); it('allows consuming transparent note created on another PXE', async () => { @@ -317,12 +346,12 @@ describe('Aztec persistence', () => { aztecNode, ); - const balanceBeforeRedeem = await contract.methods + const { result: balanceBeforeRedeem } = await contract.methods .balance_of_private(ownerAddress) .simulate({ from: ownerAddress }); await contract.methods.redeem_shield(ownerAddress, mintAmount, secret).send({ from: ownerAddress }); - const balanceAfterRedeem = await contract.methods + const { result: balanceAfterRedeem } = await contract.methods .balance_of_private(ownerAddress) .simulate({ from: ownerAddress }); diff --git a/yarn-project/end-to-end/src/composed/e2e_token_bridge_tutorial_test.test.ts b/yarn-project/end-to-end/src/composed/e2e_token_bridge_tutorial_test.test.ts index 923b14ea06f8..3a4b9932e270 100644 --- a/yarn-project/end-to-end/src/composed/e2e_token_bridge_tutorial_test.test.ts +++ b/yarn-project/end-to-end/src/composed/e2e_token_bridge_tutorial_test.test.ts @@ -100,7 +100,13 @@ describe('e2e_cross_chain_messaging token_bridge_tutorial_test', () => { // Deploy L2 token contract // docs:start:deploy-l2-token - const l2TokenContract = await TokenContract.deploy(wallet, ownerAztecAddress, 'L2 Token', 'L2', 18).send({ + const { contract: l2TokenContract } = await TokenContract.deploy( + wallet, + ownerAztecAddress, + 'L2 Token', + 'L2', + 18, + ).send({ from: ownerAztecAddress, }); logger.info(`L2 token contract deployed at ${l2TokenContract.address}`); @@ -130,7 +136,7 @@ describe('e2e_cross_chain_messaging token_bridge_tutorial_test', () => { // docs:end:deploy-portal // Deploy L2 bridge contract // docs:start:deploy-l2-bridge - const l2BridgeContract = await TokenBridgeContract.deploy( + const { contract: l2BridgeContract } = await TokenBridgeContract.deploy( wallet, l2TokenContract.address, l1PortalContractAddress, @@ -175,7 +181,7 @@ describe('e2e_cross_chain_messaging token_bridge_tutorial_test', () => { await l2BridgeContract.methods .claim_public(ownerAztecAddress, MINT_AMOUNT, claim.claimSecret, claim.messageLeafIndex) .send({ from: ownerAztecAddress }); - const balance = await l2TokenContract.methods + const { result: balance } = await l2TokenContract.methods .balance_of_public(ownerAztecAddress) .simulate({ from: ownerAztecAddress }); logger.info(`Public L2 balance of ${ownerAztecAddress} is ${balance}`); @@ -206,12 +212,12 @@ describe('e2e_cross_chain_messaging token_bridge_tutorial_test', () => { l2BridgeContract.address, EthAddress.ZERO, ); - const l2TxReceipt = await l2BridgeContract.methods + const { receipt: l2TxReceipt } = await l2BridgeContract.methods .exit_to_l1_public(EthAddress.fromString(ownerEthAddress), withdrawAmount, EthAddress.ZERO, authwitNonce) .send({ from: ownerAztecAddress }); await waitForProven(node, l2TxReceipt, { provenTimeout: 500 }); - const newL2Balance = await l2TokenContract.methods + const { result: newL2Balance } = await l2TokenContract.methods .balance_of_public(ownerAztecAddress) .simulate({ from: ownerAztecAddress }); logger.info(`New L2 balance of ${ownerAztecAddress} is ${newL2Balance}`); diff --git a/yarn-project/end-to-end/src/composed/ha/e2e_ha_full.test.ts b/yarn-project/end-to-end/src/composed/ha/e2e_ha_full.test.ts index 78db818d3e80..279b42a40de6 100644 --- a/yarn-project/end-to-end/src/composed/ha/e2e_ha_full.test.ts +++ b/yarn-project/end-to-end/src/composed/ha/e2e_ha_full.test.ts @@ -279,7 +279,7 @@ describe('HA Full Setup', () => { const sender = ownerAddress; logger.info(`Deploying contract from ${sender}`); - const receipt = await deployer.deploy(ownerAddress, sender, 1).send({ + const { receipt } = await deployer.deploy(ownerAddress, sender, 1).send({ from: ownerAddress, contractAddressSalt: new Fr(BigInt(1)), skipClassPublication: true, @@ -400,7 +400,7 @@ describe('HA Full Setup', () => { // Send a transaction to trigger block building which will also trigger voting logger.info('Sending transaction to trigger block building...'); const deployer = new ContractDeployer(StatefulTestContractArtifact, wallet); - const receipt = await deployer.deploy(ownerAddress, ownerAddress, 42).send({ + const { receipt } = await deployer.deploy(ownerAddress, ownerAddress, 42).send({ from: ownerAddress, contractAddressSalt: Fr.random(), wait: { returnReceipt: true }, @@ -516,7 +516,7 @@ describe('HA Full Setup', () => { logger.info(`Active nodes: ${haNodeServices.length - killedNodes.length}/${NODE_COUNT}`); const deployer = new ContractDeployer(StatefulTestContractArtifact, wallet); - const receipt = await deployer.deploy(ownerAddress, ownerAddress, i + 100).send({ + const { receipt } = await deployer.deploy(ownerAddress, ownerAddress, i + 100).send({ from: ownerAddress, contractAddressSalt: new Fr(BigInt(i + 100)), skipClassPublication: true, diff --git a/yarn-project/end-to-end/src/composed/web3signer/e2e_multi_validator_node_key_store.test.ts b/yarn-project/end-to-end/src/composed/web3signer/e2e_multi_validator_node_key_store.test.ts index ce44c4ecd9c4..ff0bf8f94e9b 100644 --- a/yarn-project/end-to-end/src/composed/web3signer/e2e_multi_validator_node_key_store.test.ts +++ b/yarn-project/end-to-end/src/composed/web3signer/e2e_multi_validator_node_key_store.test.ts @@ -400,7 +400,10 @@ describe('e2e_multi_validator_node', () => { }); const settledTransactions = await Promise.all( - sentTransactionPromises.map(async sentTransactionPromise => waitForTx(aztecNode, await sentTransactionPromise)), + sentTransactionPromises.map(async sentTransactionPromise => { + const { txHash } = await sentTransactionPromise; + return waitForTx(aztecNode, txHash); + }), ); await Promise.all( diff --git a/yarn-project/end-to-end/src/devnet/e2e_smoke.test.ts b/yarn-project/end-to-end/src/devnet/e2e_smoke.test.ts index 2ce5cf1fef9d..6b985b3a0242 100644 --- a/yarn-project/end-to-end/src/devnet/e2e_smoke.test.ts +++ b/yarn-project/end-to-end/src/devnet/e2e_smoke.test.ts @@ -138,7 +138,7 @@ describe('End-to-end tests for devnet', () => { const l2AccountDeployMethod = await l2AccountManager.getDeployMethod(); - const txReceipt = await l2AccountDeployMethod.send({ + const { receipt: txReceipt } = await l2AccountDeployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod: new FeeJuicePaymentMethodWithClaim(l2AccountAddress, { @@ -172,7 +172,9 @@ describe('End-to-end tests for devnet', () => { expect(txReceipt.isMined() && txReceipt.hasExecutionSucceeded()).toBe(true); const feeJuice = FeeJuiceContract.at((await node.getNodeInfo()).protocolContractAddresses.feeJuice, wallet); - const balance = await feeJuice.methods.balance_of_public(l2AccountAddress).simulate({ from: l2AccountAddress }); + const { result: balance } = await feeJuice.methods + .balance_of_public(l2AccountAddress) + .simulate({ from: l2AccountAddress }); expect(balance).toEqual(amount - txReceipt.transactionFee!); }); @@ -253,7 +255,7 @@ describe('End-to-end tests for devnet', () => { async function advanceChainWithEmptyBlocks(wallet: TestWallet) { const [fundedAccountAddress] = await registerInitialLocalNetworkAccountsInWallet(wallet); - const test = await TestContract.deploy(wallet).send({ + const { contract: test } = await TestContract.deploy(wallet).send({ from: fundedAccountAddress, universalDeploy: true, skipClassPublication: true, 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 e7c6b83ebe8b..fb18c816774d 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 @@ -97,7 +97,9 @@ describe('e2e_2_pxes', () => { const deployChildContractViaServerA = async () => { logger.info(`Deploying Child contract...`); - const { instance } = await ChildContract.deploy(walletA).send({ + const { + receipt: { instance }, + } = await ChildContract.deploy(walletA).send({ from: accountAAddress, wait: { returnReceipt: true }, }); diff --git a/yarn-project/end-to-end/src/e2e_abi_types.test.ts b/yarn-project/end-to-end/src/e2e_abi_types.test.ts index d970d9c105b2..a7702f78c720 100644 --- a/yarn-project/end-to-end/src/e2e_abi_types.test.ts +++ b/yarn-project/end-to-end/src/e2e_abi_types.test.ts @@ -29,19 +29,19 @@ describe('AbiTypes', () => { wallet, accounts: [defaultAccountAddress], } = await setup(1)); - abiTypesContract = await AbiTypesContract.deploy(wallet).send({ from: defaultAccountAddress }); + ({ contract: abiTypesContract } = await AbiTypesContract.deploy(wallet).send({ from: defaultAccountAddress })); }); afterAll(() => teardown()); it('passes public parameters', async () => { - const minResult = await abiTypesContract.methods + const { result: minResult } = await abiTypesContract.methods .return_public_parameters(false, 0n, 0n, I64_MIN, { w: 0n, x: false, y: 0n, z: I64_MIN }) .simulate({ from: defaultAccountAddress }); expect(minResult).toEqual([false, 0n, 0n, I64_MIN, { w: 0n, x: false, y: 0n, z: I64_MIN }]); - const maxResult = await abiTypesContract.methods + const { result: maxResult } = await abiTypesContract.methods .return_public_parameters(true, MAX_FIELD_VALUE, U64_MAX, I64_MAX, { w: MAX_FIELD_VALUE, x: true, @@ -60,13 +60,13 @@ describe('AbiTypes', () => { }); it('passes private parameters', async () => { - const minResult = await abiTypesContract.methods + const { result: minResult } = await abiTypesContract.methods .return_private_parameters(false, 0n, 0n, I64_MIN, { w: 0n, x: false, y: 0n, z: I64_MIN }) .simulate({ from: defaultAccountAddress }); expect(minResult).toEqual([false, 0n, 0n, I64_MIN, { w: 0n, x: false, y: 0n, z: I64_MIN }]); - const maxResult = await abiTypesContract.methods + const { result: maxResult } = await abiTypesContract.methods .return_private_parameters(true, MAX_FIELD_VALUE, U64_MAX, I64_MAX, { w: MAX_FIELD_VALUE, x: true, @@ -85,13 +85,13 @@ describe('AbiTypes', () => { }); it('passes utility parameters', async () => { - const minResult = await abiTypesContract.methods + const { result: minResult } = await abiTypesContract.methods .return_utility_parameters(false, 0n, 0n, I64_MIN, { w: 0n, x: false, y: 0n, z: I64_MIN }) .simulate({ from: defaultAccountAddress }); expect(minResult).toEqual([false, 0n, 0n, I64_MIN, { w: 0n, x: false, y: 0n, z: I64_MIN }]); - const maxResult = await abiTypesContract.methods + const { result: maxResult } = await abiTypesContract.methods .return_utility_parameters(true, MAX_FIELD_VALUE, U64_MAX, I64_MAX, { w: MAX_FIELD_VALUE, x: true, diff --git a/yarn-project/end-to-end/src/e2e_account_contracts.test.ts b/yarn-project/end-to-end/src/e2e_account_contracts.test.ts index 85285d0a4fb1..a4e0338f4cfa 100644 --- a/yarn-project/end-to-end/src/e2e_account_contracts.test.ts +++ b/yarn-project/end-to-end/src/e2e_account_contracts.test.ts @@ -65,7 +65,7 @@ const itShouldBehaveLikeAnAccountContract = ( await deployMethod.send({ from: AztecAddress.ZERO }); } - child = await ChildContract.deploy(wallet).send({ from: address }); + ({ contract: child } = await ChildContract.deploy(wallet).send({ from: address })); }); afterAll(() => teardown()); diff --git a/yarn-project/end-to-end/src/e2e_amm.test.ts b/yarn-project/end-to-end/src/e2e_amm.test.ts index 79f640236665..b92c5e9dc683 100644 --- a/yarn-project/end-to-end/src/e2e_amm.test.ts +++ b/yarn-project/end-to-end/src/e2e_amm.test.ts @@ -49,9 +49,9 @@ describe('AMM', () => { ({ contract: token1 } = await deployToken(wallet, adminAddress, 0n, logger)); ({ contract: liquidityToken } = await deployToken(wallet, adminAddress, 0n, logger)); - amm = await AMMContract.deploy(wallet, token0.address, token1.address, liquidityToken.address).send({ + ({ contract: amm } = await AMMContract.deploy(wallet, token0.address, token1.address, liquidityToken.address).send({ from: adminAddress, - }); + })); // TODO(#9480): consider deploying the token by some factory when the AMM is deployed, and making the AMM be the // minter there. @@ -81,15 +81,15 @@ describe('AMM', () => { async function getAmmBalances(): Promise { return { - token0: await token0.methods.balance_of_public(amm.address).simulate({ from: adminAddress }), - token1: await token1.methods.balance_of_public(amm.address).simulate({ from: adminAddress }), + token0: (await token0.methods.balance_of_public(amm.address).simulate({ from: adminAddress })).result, + token1: (await token1.methods.balance_of_public(amm.address).simulate({ from: adminAddress })).result, }; } async function getWalletBalances(lp: AztecAddress): Promise { return { - token0: await token0.methods.balance_of_private(lp).simulate({ from: lp }), - token1: await token1.methods.balance_of_private(lp).simulate({ from: lp }), + token0: (await token0.methods.balance_of_private(lp).simulate({ from: lp })).result, + token1: (await token1.methods.balance_of_private(lp).simulate({ from: lp })).result, }; } @@ -146,11 +146,13 @@ describe('AMM', () => { // Liquidity tokens should also be minted for the liquidity provider, as well as locked at the zero address. const expectedLiquidityTokens = (INITIAL_AMM_TOTAL_SUPPLY * 99n) / 100n; expect( - await liquidityToken.methods - .balance_of_private(liquidityProviderAddress) - .simulate({ from: liquidityProviderAddress }), + ( + await liquidityToken.methods + .balance_of_private(liquidityProviderAddress) + .simulate({ from: liquidityProviderAddress }) + ).result, ).toEqual(expectedLiquidityTokens); - expect(await liquidityToken.methods.total_supply().simulate({ from: adminAddress })).toEqual( + expect((await liquidityToken.methods.total_supply().simulate({ from: adminAddress })).result).toEqual( INITIAL_AMM_TOTAL_SUPPLY, ); }); @@ -162,7 +164,8 @@ describe('AMM', () => { const ammBalancesBefore = await getAmmBalances(); const lpBalancesBefore = await getWalletBalances(otherLiquidityProviderAddress); - const liquidityTokenSupplyBefore = await liquidityToken.methods.total_supply().simulate({ from: adminAddress }); + const liquidityTokenSupplyBefore = (await liquidityToken.methods.total_supply().simulate({ from: adminAddress })) + .result; // The pool currently has the same number of tokens for token0 and token1, since that is the ratio the first // liquidity provider used. Our maximum values have a different ratio (6:5 instead of 1:1), so we will end up @@ -214,11 +217,15 @@ describe('AMM', () => { (liquidityTokenSupplyBefore * (ammBalancesBefore.token0 + expectedAmount0)) / ammBalancesBefore.token0; const expectedLiquidityTokens = expectedTotalSupply - INITIAL_AMM_TOTAL_SUPPLY; - expect(await liquidityToken.methods.total_supply().simulate({ from: adminAddress })).toEqual(expectedTotalSupply); + expect((await liquidityToken.methods.total_supply().simulate({ from: adminAddress })).result).toEqual( + expectedTotalSupply, + ); expect( - await liquidityToken.methods - .balance_of_private(otherLiquidityProviderAddress) - .simulate({ from: otherLiquidityProviderAddress }), + ( + await liquidityToken.methods + .balance_of_private(otherLiquidityProviderAddress) + .simulate({ from: otherLiquidityProviderAddress }) + ).result, ).toEqual(expectedLiquidityTokens); }); @@ -239,9 +246,11 @@ describe('AMM', () => { // We compute the expected amount out and set it as the minimum. In a real-life scenario we'd choose a slightly // lower value to account for slippage, but since we're the only actor interacting with the AMM we can afford to // just pass the exact value. Of course any lower value would also suffice. - const amountOutMin = await amm.methods - .get_amount_out_for_exact_in(ammBalancesBefore.token0, ammBalancesBefore.token1, amountIn) - .simulate({ from: swapperAddress }); + const amountOutMin = ( + await amm.methods + .get_amount_out_for_exact_in(ammBalancesBefore.token0, ammBalancesBefore.token1, amountIn) + .simulate({ from: swapperAddress }) + ).result; const swapExactTokensInteraction = amm.methods .swap_exact_tokens_for_tokens(token0.address, token1.address, amountIn, amountOutMin, nonceForAuthwits) @@ -264,9 +273,11 @@ describe('AMM', () => { // query the contract for how much token0 we'd get if we sent our entire token1 balance, and then request exactly // that amount. This would fail in a real-life scenario since we'd need to account for slippage, but we can do it // in this test environment since there's nobody else interacting with the AMM. - const amountOut = await amm.methods - .get_amount_out_for_exact_in(ammBalancesBefore.token1, ammBalancesBefore.token0, swapperBalancesBefore.token1) - .simulate({ from: swapperAddress }); + const amountOut = ( + await amm.methods + .get_amount_out_for_exact_in(ammBalancesBefore.token1, ammBalancesBefore.token0, swapperBalancesBefore.token1) + .simulate({ from: swapperAddress }) + ).result; const amountInMax = swapperBalancesBefore.token1; // Swaps also transfer tokens into the AMM, so we provide an authwit for the full amount in (any change will be @@ -299,9 +310,11 @@ describe('AMM', () => { it('remove liquidity', async () => { // We now withdraw all of the tokens of one of the liquidity providers by burning their entire liquidity token // balance. - const liquidityTokenBalance = await liquidityToken.methods - .balance_of_private(otherLiquidityProviderAddress) - .simulate({ from: otherLiquidityProviderAddress }); + const liquidityTokenBalance = ( + await liquidityToken.methods + .balance_of_private(otherLiquidityProviderAddress) + .simulate({ from: otherLiquidityProviderAddress }) + ).result; // Because private burning requires first transferring the tokens into the AMM, we again need to provide an // authwit. @@ -328,9 +341,11 @@ describe('AMM', () => { // The liquidity provider should have no remaining liquidity tokens, and should have recovered the value they // originally deposited. expect( - await liquidityToken.methods - .balance_of_private(otherLiquidityProviderAddress) - .simulate({ from: otherLiquidityProviderAddress }), + ( + await liquidityToken.methods + .balance_of_private(otherLiquidityProviderAddress) + .simulate({ from: otherLiquidityProviderAddress }) + ).result, ).toEqual(0n); // We now assert that the liquidity provider ended up with more tokens than they began with. These extra tokens diff --git a/yarn-project/end-to-end/src/e2e_authwit.test.ts b/yarn-project/end-to-end/src/e2e_authwit.test.ts index adccc438291a..e978ab49e233 100644 --- a/yarn-project/end-to-end/src/e2e_authwit.test.ts +++ b/yarn-project/end-to-end/src/e2e_authwit.test.ts @@ -32,8 +32,8 @@ describe('e2e_authwit_tests', () => { } = await setup(2)); await ensureAccountContractsPublished(wallet, [account1Address, account2Address]); - auth = await AuthWitTestContract.deploy(wallet).send({ from: account1Address }); - authwitProxy = await GenericProxyContract.deploy(wallet).send({ from: account1Address }); + ({ contract: auth } = await AuthWitTestContract.deploy(wallet).send({ from: account1Address })); + ({ contract: authwitProxy } = await GenericProxyContract.deploy(wallet).send({ from: account1Address })); }); describe('Private', () => { diff --git a/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts index 07782ca8dd71..b40af1162298 100644 --- a/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts +++ b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts @@ -36,11 +36,13 @@ describe('e2e_avm_simulator', () => { let secondAvmContract: AvmTestContract; beforeEach(async () => { - ({ contract: avmContract, instance: avmContractInstance } = await AvmTestContract.deploy(wallet).send({ + ({ + receipt: { contract: avmContract, instance: avmContractInstance }, + } = await AvmTestContract.deploy(wallet).send({ from: defaultAccountAddress, wait: { returnReceipt: true }, })); - secondAvmContract = await AvmTestContract.deploy(wallet).send({ from: defaultAccountAddress }); + ({ contract: secondAvmContract } = await AvmTestContract.deploy(wallet).send({ from: defaultAccountAddress })); }); describe('Assertions & error enriching', () => { @@ -117,16 +119,18 @@ describe('e2e_avm_simulator', () => { describe('Storage', () => { it('Modifies storage (Field)', async () => { await avmContract.methods.set_storage_single(20n).send({ from: defaultAccountAddress }); - expect(await avmContract.methods.read_storage_single().simulate({ from: defaultAccountAddress })).toEqual(20n); + expect( + (await avmContract.methods.read_storage_single().simulate({ from: defaultAccountAddress })).result, + ).toEqual(20n); }); it('Modifies storage (Map)', async () => { const address = AztecAddress.fromBigInt(9090n); await avmContract.methods.set_storage_map(address, 100).send({ from: defaultAccountAddress }); await avmContract.methods.add_storage_map(address, 100).send({ from: defaultAccountAddress }); - expect(await avmContract.methods.read_storage_map(address).simulate({ from: defaultAccountAddress })).toEqual( - 200n, - ); + expect( + (await avmContract.methods.read_storage_map(address).simulate({ from: defaultAccountAddress })).result, + ).toEqual(200n); }); it('Preserves storage across enqueued public calls', async () => { @@ -137,15 +141,15 @@ describe('e2e_avm_simulator', () => { avmContract.methods.add_storage_map(address, 100), ]).send({ from: defaultAccountAddress }); // On a separate tx, we check the result. - expect(await avmContract.methods.read_storage_map(address).simulate({ from: defaultAccountAddress })).toEqual( - 200n, - ); + expect( + (await avmContract.methods.read_storage_map(address).simulate({ from: defaultAccountAddress })).result, + ).toEqual(200n); }); }); describe('Contract instance', () => { it('Works', async () => { - const tx = await avmContract.methods + const { receipt: tx } = await avmContract.methods .test_get_contract_instance_matches( avmContract.address, avmContractInstance.deployer, @@ -160,17 +164,21 @@ describe('e2e_avm_simulator', () => { describe('Nullifiers', () => { // Nullifier will not yet be siloed by the kernel. it('Emit and check in the same tx', async () => { - const tx = await avmContract.methods.emit_nullifier_and_check(123456).send({ from: defaultAccountAddress }); + const { receipt: tx } = await avmContract.methods + .emit_nullifier_and_check(123456) + .send({ from: defaultAccountAddress }); expect(tx.executionResult).toEqual(TxExecutionResult.SUCCESS); }); // Nullifier will have been siloed by the kernel, but we check against the unsiloed one. it('Emit and check in separate tx', async () => { const nullifier = new Fr(123456); - let tx = await avmContract.methods.new_nullifier(nullifier).send({ from: defaultAccountAddress }); + let { receipt: tx } = await avmContract.methods.new_nullifier(nullifier).send({ from: defaultAccountAddress }); expect(tx.executionResult).toEqual(TxExecutionResult.SUCCESS); - tx = await avmContract.methods.assert_nullifier_exists(nullifier).send({ from: defaultAccountAddress }); + ({ receipt: tx } = await avmContract.methods + .assert_nullifier_exists(nullifier) + .send({ from: defaultAccountAddress })); expect(tx.executionResult).toEqual(TxExecutionResult.SUCCESS); }); @@ -204,7 +212,9 @@ describe('e2e_avm_simulator', () => { it('Nested CALL instruction to non-existent contract returns failure, but caller can recover', async () => { // The nested call reverts (returns failure), but the caller doesn't HAVE to rethrow. - const tx = await avmContract.methods.nested_call_to_nothing_recovers().send({ from: defaultAccountAddress }); + const { receipt: tx } = await avmContract.methods + .nested_call_to_nothing_recovers() + .send({ from: defaultAccountAddress }); expect(tx.executionResult).toEqual(TxExecutionResult.SUCCESS); }); it('Should NOT be able to emit the same unsiloed nullifier from the same contract', async () => { @@ -218,7 +228,7 @@ describe('e2e_avm_simulator', () => { it('Should be able to emit different unsiloed nullifiers from the same contract', async () => { const nullifier = new Fr(1); - const tx = await avmContract.methods + const { receipt: tx } = await avmContract.methods .create_different_nullifier_in_nested_call(avmContract.address, nullifier) .send({ from: defaultAccountAddress }); expect(tx.executionResult).toEqual(TxExecutionResult.SUCCESS); @@ -226,7 +236,7 @@ describe('e2e_avm_simulator', () => { it('Should be able to emit the same unsiloed nullifier from two different contracts', async () => { const nullifier = new Fr(1); - const tx = await avmContract.methods + const { receipt: tx } = await avmContract.methods .create_same_nullifier_in_nested_call(secondAvmContract.address, nullifier) .send({ from: defaultAccountAddress }); expect(tx.executionResult).toEqual(TxExecutionResult.SUCCESS); @@ -234,7 +244,7 @@ describe('e2e_avm_simulator', () => { it('Should be able to emit different unsiloed nullifiers from two different contracts', async () => { const nullifier = new Fr(1); - const tx = await avmContract.methods + const { receipt: tx } = await avmContract.methods .create_different_nullifier_in_nested_call(secondAvmContract.address, nullifier) .send({ from: defaultAccountAddress }); expect(tx.executionResult).toEqual(TxExecutionResult.SUCCESS); @@ -246,14 +256,16 @@ describe('e2e_avm_simulator', () => { let avmContract: AvmInitializerTestContract; beforeEach(async () => { - avmContract = await AvmInitializerTestContract.deploy(wallet).send({ from: defaultAccountAddress }); + ({ contract: avmContract } = await AvmInitializerTestContract.deploy(wallet).send({ + from: defaultAccountAddress, + })); }); describe('Storage', () => { it('Read immutable (initialized) storage (Field)', async () => { - expect(await avmContract.methods.read_storage_immutable().simulate({ from: defaultAccountAddress })).toEqual( - 42n, - ); + expect( + (await avmContract.methods.read_storage_immutable().simulate({ from: defaultAccountAddress })).result, + ).toEqual(42n); }); }); }); diff --git a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/access_control.test.ts b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/access_control.test.ts index 97e8bbb19c23..76f015066d44 100644 --- a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/access_control.test.ts +++ b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/access_control.test.ts @@ -23,7 +23,7 @@ describe('e2e_blacklist_token_contract access control', () => { await t.crossTimestampOfChange(); - expect(await t.asset.methods.get_roles(t.adminAddress).simulate({ from: t.adminAddress })).toEqual( + expect((await t.asset.methods.get_roles(t.adminAddress).simulate({ from: t.adminAddress })).result).toEqual( adminMinterRole.toNoirStruct(), ); }); @@ -34,7 +34,7 @@ describe('e2e_blacklist_token_contract access control', () => { await t.crossTimestampOfChange(); - expect(await t.asset.methods.get_roles(t.otherAddress).simulate({ from: t.adminAddress })).toEqual( + expect((await t.asset.methods.get_roles(t.otherAddress).simulate({ from: t.adminAddress })).result).toEqual( adminRole.toNoirStruct(), ); }); @@ -45,7 +45,7 @@ describe('e2e_blacklist_token_contract access control', () => { await t.crossTimestampOfChange(); - expect(await t.asset.methods.get_roles(t.otherAddress).simulate({ from: t.adminAddress })).toEqual( + expect((await t.asset.methods.get_roles(t.otherAddress).simulate({ from: t.adminAddress })).result).toEqual( noRole.toNoirStruct(), ); }); @@ -58,7 +58,7 @@ describe('e2e_blacklist_token_contract access control', () => { await t.crossTimestampOfChange(); - expect(await t.asset.methods.get_roles(t.blacklistedAddress).simulate({ from: t.adminAddress })).toEqual( + expect((await t.asset.methods.get_roles(t.blacklistedAddress).simulate({ from: t.adminAddress })).result).toEqual( blacklistRole.toNoirStruct(), ); }); diff --git a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts index 22cad7213ca5..5fc0640a8079 100644 --- a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts +++ b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts @@ -107,20 +107,24 @@ export class BlacklistTokenContractTest { await publicDeployAccounts(this.wallet, [this.adminAddress, this.otherAddress, this.blacklistedAddress]); this.logger.verbose(`Deploying TokenContract...`); - this.asset = await TokenBlacklistContract.deploy(this.wallet, this.adminAddress).send({ + ({ contract: this.asset } = await TokenBlacklistContract.deploy(this.wallet, this.adminAddress).send({ from: this.adminAddress, - }); + })); this.logger.verbose(`Token deployed to ${this.asset.address}`); this.logger.verbose(`Deploying bad account...`); - this.badAccount = await InvalidAccountContract.deploy(this.wallet).send({ from: this.adminAddress }); + ({ contract: this.badAccount } = await InvalidAccountContract.deploy(this.wallet).send({ + from: this.adminAddress, + })); this.logger.verbose(`Deployed to ${this.badAccount.address}.`); // Deploy a proxy contract for "on behalf of other" tests. The note owner must be the tx sender // (so their notes are in scope), but msg_sender in the target must differ from the note owner // to trigger authwit validation. The proxy forwards calls so that msg_sender != tx sender. this.logger.verbose(`Deploying generic proxy...`); - this.authwitProxy = await GenericProxyContract.deploy(this.wallet).send({ from: this.adminAddress }); + ({ contract: this.authwitProxy } = await GenericProxyContract.deploy(this.wallet).send({ + from: this.adminAddress, + })); this.logger.verbose(`Deployed to ${this.authwitProxy.address}.`); await this.crossTimestampOfChange(); @@ -133,9 +137,9 @@ export class BlacklistTokenContractTest { [this.adminAddress, this.otherAddress, this.blacklistedAddress], ); - expect(await this.asset.methods.get_roles(this.adminAddress).simulate({ from: this.adminAddress })).toEqual( - new Role().withAdmin().toNoirStruct(), - ); + expect( + (await this.asset.methods.get_roles(this.adminAddress).simulate({ from: this.adminAddress })).result, + ).toEqual(new Role().withAdmin().toNoirStruct()); } async setup() { @@ -189,9 +193,9 @@ export class BlacklistTokenContractTest { await this.crossTimestampOfChange(); - expect(await this.asset.methods.get_roles(this.adminAddress).simulate({ from: this.adminAddress })).toEqual( - adminMinterRole.toNoirStruct(), - ); + expect( + (await this.asset.methods.get_roles(this.adminAddress).simulate({ from: this.adminAddress })).result, + ).toEqual(adminMinterRole.toNoirStruct()); this.logger.verbose(`Minting ${amount} publicly...`); await asset.methods.mint_public(this.adminAddress, amount).send({ from: this.adminAddress }); @@ -199,7 +203,7 @@ export class BlacklistTokenContractTest { this.logger.verbose(`Minting ${amount} privately...`); const secret = Fr.random(); const secretHash = await computeSecretHash(secret); - const receipt = await asset.methods.mint_private(amount, secretHash).send({ from: this.adminAddress }); + const { receipt } = await asset.methods.mint_private(amount, secretHash).send({ from: this.adminAddress }); await this.addPendingShieldNoteToPXE(asset, this.adminAddress, amount, secretHash, receipt.txHash); await asset.methods.redeem_shield(this.adminAddress, amount, secret).send({ from: this.adminAddress }); @@ -207,20 +211,20 @@ export class BlacklistTokenContractTest { tokenSim.mintPublic(this.adminAddress, amount); - const publicBalance = await asset.methods + const { result: publicBalance } = await asset.methods .balance_of_public(this.adminAddress) .simulate({ from: this.adminAddress }); this.logger.verbose(`Public balance of wallet 0: ${publicBalance}`); expect(publicBalance).toEqual(this.tokenSim.balanceOfPublic(this.adminAddress)); tokenSim.mintPrivate(this.adminAddress, amount); - const privateBalance = await asset.methods + const { result: privateBalance } = await asset.methods .balance_of_private(this.adminAddress) .simulate({ from: this.adminAddress }); this.logger.verbose(`Private balance of wallet 0: ${privateBalance}`); expect(privateBalance).toEqual(tokenSim.balanceOfPrivate(this.adminAddress)); - const totalSupply = await asset.methods.total_supply().simulate({ from: this.adminAddress }); + const { result: totalSupply } = await asset.methods.total_supply().simulate({ from: this.adminAddress }); this.logger.verbose(`Total supply: ${totalSupply}`); expect(totalSupply).toEqual(tokenSim.totalSupply); } diff --git a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/burn.test.ts b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/burn.test.ts index 6028b6173922..9b8b28204e15 100644 --- a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/burn.test.ts +++ b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/burn.test.ts @@ -27,7 +27,10 @@ describe('e2e_blacklist_token_contract burn', () => { describe('public', () => { it('burn less than balance', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 / 2n; expect(amount).toBeGreaterThan(0n); await asset.methods.burn_public(adminAddress, amount, 0).send({ from: adminAddress }); @@ -36,7 +39,10 @@ describe('e2e_blacklist_token_contract burn', () => { }); it('burn on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 / 2n; expect(amount).toBeGreaterThan(0n); const authwitNonce = Fr.random(); @@ -61,7 +67,10 @@ describe('e2e_blacklist_token_contract burn', () => { describe('failure cases', () => { it('burn more than balance', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 + 1n; const authwitNonce = 0; await expect( @@ -70,7 +79,10 @@ describe('e2e_blacklist_token_contract burn', () => { }); it('burn on behalf of self with non-zero nonce', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 - 1n; expect(amount).toBeGreaterThan(0n); const authwitNonce = 1; @@ -82,7 +94,10 @@ describe('e2e_blacklist_token_contract burn', () => { }); it('burn on behalf of other without "approval"', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 + 1n; const authwitNonce = Fr.random(); await expect( @@ -91,7 +106,10 @@ describe('e2e_blacklist_token_contract burn', () => { }); it('burn more than balance on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 + 1n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -109,7 +127,10 @@ describe('e2e_blacklist_token_contract burn', () => { }); it('burn on behalf of other, wrong designated caller', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 + 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -138,7 +159,10 @@ describe('e2e_blacklist_token_contract burn', () => { describe('private', () => { it('burn less than balance', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 / 2n; expect(amount).toBeGreaterThan(0n); await asset.methods.burn(adminAddress, amount, 0).send({ from: adminAddress }); @@ -146,7 +170,10 @@ describe('e2e_blacklist_token_contract burn', () => { }); it('burn on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 / 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -166,7 +193,10 @@ describe('e2e_blacklist_token_contract burn', () => { describe('failure cases', () => { it('burn more than balance', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 + 1n; expect(amount).toBeGreaterThan(0n); await expect(asset.methods.burn(adminAddress, amount, 0).simulate({ from: adminAddress })).rejects.toThrow( @@ -175,7 +205,10 @@ describe('e2e_blacklist_token_contract burn', () => { }); it('burn on behalf of self with non-zero nonce', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 - 1n; expect(amount).toBeGreaterThan(0n); await expect(asset.methods.burn(adminAddress, amount, 1).simulate({ from: adminAddress })).rejects.toThrow( @@ -184,7 +217,10 @@ describe('e2e_blacklist_token_contract burn', () => { }); it('burn more than balance on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 + 1n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -199,7 +235,10 @@ describe('e2e_blacklist_token_contract burn', () => { }); it('burn on behalf of other without approval', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 / 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -218,7 +257,10 @@ describe('e2e_blacklist_token_contract burn', () => { }); it('on behalf of other (invalid designated caller)', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 + 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); diff --git a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/minting.test.ts b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/minting.test.ts index f1386cfa812a..3e704d342355 100644 --- a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/minting.test.ts +++ b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/minting.test.ts @@ -86,9 +86,11 @@ describe('e2e_blacklist_token_contract mint', () => { describe('Mint flow', () => { it('mint_private as minter and redeem as recipient', async () => { - const balanceBefore = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balanceBefore } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); - const receipt = await asset.methods.mint_private(amount, secretHash).send({ from: adminAddress }); + const { receipt } = await asset.methods.mint_private(amount, secretHash).send({ from: adminAddress }); txHash = receipt.txHash; await t.addPendingShieldNoteToPXE(asset, adminAddress, amount, secretHash, txHash); @@ -96,7 +98,9 @@ describe('e2e_blacklist_token_contract mint', () => { await asset.methods.redeem_shield(adminAddress, amount, secret).send({ from: adminAddress }); tokenSim.mintPrivate(adminAddress, amount); - const balanceAfter = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balanceAfter } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); expect(balanceAfter).toBe(balanceBefore + amount); }); }); diff --git a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/shielding.test.ts b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/shielding.test.ts index b09cb1d9b9c2..7ac49c276345 100644 --- a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/shielding.test.ts +++ b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/shielding.test.ts @@ -31,11 +31,14 @@ describe('e2e_blacklist_token_contract shield + redeem_shield', () => { }); it('on behalf of self', async () => { - const balancePub = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const balancePub = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balancePub / 2n; expect(amount).toBeGreaterThan(0n); - const receipt = await asset.methods.shield(adminAddress, amount, secretHash, 0).send({ from: adminAddress }); + const { receipt } = await asset.methods.shield(adminAddress, amount, secretHash, 0).send({ from: adminAddress }); // Redeem it await t.addPendingShieldNoteToPXE(asset, adminAddress, amount, secretHash, receipt.txHash); @@ -47,7 +50,10 @@ describe('e2e_blacklist_token_contract shield + redeem_shield', () => { }); it('on behalf of other', async () => { - const balancePub = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const balancePub = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balancePub / 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -61,7 +67,7 @@ describe('e2e_blacklist_token_contract shield + redeem_shield', () => { ); await validateActionInteraction.send(); - const receipt = await action.send({ from: otherAddress }); + const { receipt } = await action.send({ from: otherAddress }); // Check that replaying the shield should fail! await expect( @@ -79,7 +85,10 @@ describe('e2e_blacklist_token_contract shield + redeem_shield', () => { describe('failure cases', () => { it('on behalf of self (more than balance)', async () => { - const balancePub = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const balancePub = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balancePub + 1n; expect(amount).toBeGreaterThan(0n); @@ -89,7 +98,10 @@ describe('e2e_blacklist_token_contract shield + redeem_shield', () => { }); it('on behalf of self (invalid authwit nonce)', async () => { - const balancePub = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const balancePub = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balancePub + 1n; expect(amount).toBeGreaterThan(0n); @@ -101,7 +113,10 @@ describe('e2e_blacklist_token_contract shield + redeem_shield', () => { }); it('on behalf of other (more than balance)', async () => { - const balancePub = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const balancePub = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balancePub + 1n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -119,7 +134,10 @@ describe('e2e_blacklist_token_contract shield + redeem_shield', () => { }); it('on behalf of other (wrong designated caller)', async () => { - const balancePub = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const balancePub = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balancePub + 1n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -137,7 +155,10 @@ describe('e2e_blacklist_token_contract shield + redeem_shield', () => { }); it('on behalf of other (without approval)', async () => { - const balance = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const balance = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance / 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); diff --git a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/transfer_private.test.ts b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/transfer_private.test.ts index 87fc616cd1e3..88bbbf428fd5 100644 --- a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/transfer_private.test.ts +++ b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/transfer_private.test.ts @@ -26,7 +26,10 @@ describe('e2e_blacklist_token_contract transfer private', () => { }); it('transfer less than balance', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 / 2n; expect(amount).toBeGreaterThan(0n); const tokenTransferInteraction = asset.methods.transfer(adminAddress, otherAddress, amount, 0); @@ -35,7 +38,10 @@ describe('e2e_blacklist_token_contract transfer private', () => { }); it('transfer to self', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 / 2n; expect(amount).toBeGreaterThan(0n); @@ -44,7 +50,10 @@ describe('e2e_blacklist_token_contract transfer private', () => { }); it('transfer on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 / 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -64,7 +73,10 @@ describe('e2e_blacklist_token_contract transfer private', () => { describe('failure cases', () => { it('transfer more than balance', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 + 1n; expect(amount).toBeGreaterThan(0n); @@ -74,7 +86,10 @@ describe('e2e_blacklist_token_contract transfer private', () => { }); it('transfer on behalf of self with non-zero nonce', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 - 1n; expect(amount).toBeGreaterThan(0n); @@ -86,8 +101,14 @@ describe('e2e_blacklist_token_contract transfer private', () => { }); it('transfer more than balance on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); - const balance1 = await asset.methods.balance_of_private(otherAddress).simulate({ from: otherAddress }); + const balance0 = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); + const balance1 = await asset.methods + .balance_of_private(otherAddress) + .simulate({ from: otherAddress }) + .then(r => r.result); const amount = balance0 + 1n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -99,8 +120,18 @@ describe('e2e_blacklist_token_contract transfer private', () => { await expect( simulateThroughAuthwitProxy(t.authwitProxy, action, { from: adminAddress, authWitnesses: [witness] }), ).rejects.toThrow('Assertion failed: Balance too low'); - expect(await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress })).toEqual(balance0); - expect(await asset.methods.balance_of_private(otherAddress).simulate({ from: otherAddress })).toEqual(balance1); + expect( + await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result), + ).toEqual(balance0); + expect( + await asset.methods + .balance_of_private(otherAddress) + .simulate({ from: otherAddress }) + .then(r => r.result), + ).toEqual(balance1); }); it.skip('transfer into account to overflow', () => { @@ -111,7 +142,10 @@ describe('e2e_blacklist_token_contract transfer private', () => { }); it('transfer on behalf of other without approval', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 / 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -130,7 +164,10 @@ describe('e2e_blacklist_token_contract transfer private', () => { }); it('transfer on behalf of other, wrong designated caller', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 / 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -148,7 +185,12 @@ describe('e2e_blacklist_token_contract transfer private', () => { await expect( simulateThroughAuthwitProxy(t.authwitProxy, action, { from: adminAddress, authWitnesses: [witness] }), ).rejects.toThrow(`Unknown auth witness for message hash ${expectedMessageHash.toString()}`); - expect(await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress })).toEqual(balance0); + expect( + await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result), + ).toEqual(balance0); }); it('transfer from a blacklisted account', async () => { diff --git a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/transfer_public.test.ts b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/transfer_public.test.ts index c5d23f421bbd..2862a3c735e7 100644 --- a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/transfer_public.test.ts +++ b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/transfer_public.test.ts @@ -24,7 +24,10 @@ describe('e2e_blacklist_token_contract transfer public', () => { }); it('transfer less than balance', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 / 2n; expect(amount).toBeGreaterThan(0n); await asset.methods.transfer_public(adminAddress, otherAddress, amount, 0).send({ from: adminAddress }); @@ -33,7 +36,10 @@ describe('e2e_blacklist_token_contract transfer public', () => { }); it('transfer to self', async () => { - const balance = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const balance = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance / 2n; expect(amount).toBeGreaterThan(0n); await asset.methods.transfer_public(adminAddress, adminAddress, amount, 0).send({ from: adminAddress }); @@ -42,7 +48,10 @@ describe('e2e_blacklist_token_contract transfer public', () => { }); it('transfer on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 / 2n; expect(amount).toBeGreaterThan(0n); const authwitNonce = Fr.random(); @@ -68,7 +77,10 @@ describe('e2e_blacklist_token_contract transfer public', () => { describe('failure cases', () => { it('transfer more than balance', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 + 1n; const authwitNonce = 0; await expect( @@ -79,7 +91,10 @@ describe('e2e_blacklist_token_contract transfer public', () => { }); it('transfer on behalf of self with non-zero nonce', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 - 1n; const authwitNonce = 1; await expect( @@ -92,7 +107,10 @@ describe('e2e_blacklist_token_contract transfer public', () => { }); it('transfer on behalf of other without "approval"', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const balance0 = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balance0 + 1n; const authwitNonce = Fr.random(); await expect( @@ -103,8 +121,14 @@ describe('e2e_blacklist_token_contract transfer public', () => { }); it('transfer more than balance on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); - const balance1 = await asset.methods.balance_of_public(otherAddress).simulate({ from: otherAddress }); + const balance0 = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); + const balance1 = await asset.methods + .balance_of_public(otherAddress) + .simulate({ from: otherAddress }) + .then(r => r.result); const amount = balance0 + 1n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -121,13 +145,29 @@ describe('e2e_blacklist_token_contract transfer public', () => { // Perform the transfer await expect(action.simulate({ from: otherAddress })).rejects.toThrow(U128_UNDERFLOW_ERROR); - expect(await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress })).toEqual(balance0); - expect(await asset.methods.balance_of_public(otherAddress).simulate({ from: otherAddress })).toEqual(balance1); + expect( + await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result), + ).toEqual(balance0); + expect( + await asset.methods + .balance_of_public(otherAddress) + .simulate({ from: otherAddress }) + .then(r => r.result), + ).toEqual(balance1); }); it('transfer on behalf of other, wrong designated caller', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); - const balance1 = await asset.methods.balance_of_public(otherAddress).simulate({ from: otherAddress }); + const balance0 = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); + const balance1 = await asset.methods + .balance_of_public(otherAddress) + .simulate({ from: otherAddress }) + .then(r => r.result); const amount = balance0 + 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -145,8 +185,18 @@ describe('e2e_blacklist_token_contract transfer public', () => { // Perform the transfer await expect(action.simulate({ from: otherAddress })).rejects.toThrow(/unauthorized/); - expect(await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress })).toEqual(balance0); - expect(await asset.methods.balance_of_public(otherAddress).simulate({ from: otherAddress })).toEqual(balance1); + expect( + await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result), + ).toEqual(balance0); + expect( + await asset.methods + .balance_of_public(otherAddress) + .simulate({ from: otherAddress }) + .then(r => r.result), + ).toEqual(balance1); }); it.skip('transfer into account to overflow', () => { diff --git a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/unshielding.test.ts b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/unshielding.test.ts index ae10ada9661d..9547b5b992dd 100644 --- a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/unshielding.test.ts +++ b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/unshielding.test.ts @@ -26,7 +26,10 @@ describe('e2e_blacklist_token_contract unshielding', () => { }); it('on behalf of self', async () => { - const balancePriv = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const balancePriv = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balancePriv / 2n; expect(amount).toBeGreaterThan(0n); @@ -36,7 +39,10 @@ describe('e2e_blacklist_token_contract unshielding', () => { }); it('on behalf of other', async () => { - const balancePriv0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const balancePriv0 = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balancePriv0 / 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -56,7 +62,10 @@ describe('e2e_blacklist_token_contract unshielding', () => { describe('failure cases', () => { it('on behalf of self (more than balance)', async () => { - const balancePriv = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const balancePriv = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balancePriv + 1n; expect(amount).toBeGreaterThan(0n); @@ -66,7 +75,10 @@ describe('e2e_blacklist_token_contract unshielding', () => { }); it('on behalf of self (invalid authwit nonce)', async () => { - const balancePriv = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const balancePriv = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balancePriv + 1n; expect(amount).toBeGreaterThan(0n); @@ -78,7 +90,10 @@ describe('e2e_blacklist_token_contract unshielding', () => { }); it('on behalf of other (more than balance)', async () => { - const balancePriv0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const balancePriv0 = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balancePriv0 + 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -93,7 +108,10 @@ describe('e2e_blacklist_token_contract unshielding', () => { }); it('on behalf of other (invalid designated caller)', async () => { - const balancePriv0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const balancePriv0 = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }) + .then(r => r.result); const amount = balancePriv0 + 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); 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 21aa382720ac..23b17b5d98f6 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 @@ -16,6 +16,7 @@ import { TestContract } from '@aztec/noir-test-contracts.js/Test'; import type { SequencerClient } from '@aztec/sequencer-client'; import type { TestSequencerClient } from '@aztec/sequencer-client/test'; import { getProofSubmissionDeadlineEpoch } from '@aztec/stdlib/epoch-helpers'; +import { GasFees } from '@aztec/stdlib/gas'; import { computeSiloedPrivateLogFirstField } from '@aztec/stdlib/hash'; import type { AztecNodeAdmin } from '@aztec/stdlib/interfaces/client'; import { TX_ERROR_EXISTING_NULLIFIER } from '@aztec/stdlib/tx'; @@ -84,7 +85,7 @@ describe('e2e_block_building', () => { // so many so that we don't end up hitting a reorg or timing out the tx wait(). const TX_COUNT = 16; - const contract = await StatefulTestContract.deploy(wallet, ownerAddress, 1).send({ from: ownerAddress }); + const { contract } = await StatefulTestContract.deploy(wallet, ownerAddress, 1).send({ from: ownerAddress }); logger.info(`Deployed stateful test contract at ${contract.address}`); // We add a delay to every public tx processing @@ -165,7 +166,7 @@ describe('e2e_block_building', () => { it('assembles a block with multiple txs with public fns', async () => { // First deploy the contract - const contract = await StatefulTestContract.deploy(wallet, ownerAddress, 1).send({ from: ownerAddress }); + const { contract } = await StatefulTestContract.deploy(wallet, ownerAddress, 1).send({ from: ownerAddress }); // Assemble N contract deployment txs // We need to create them sequentially since we cannot have parallel calls to a circuit @@ -194,8 +195,8 @@ describe('e2e_block_building', () => { // Skipped since we only use it to manually test number of invocations to world-state. it.skip('builds blocks with multiple public fns after multiple nullifier insertions', async () => { // First deploy the contracts - const contract = await StatefulTestContract.deploy(wallet, ownerAddress, 1).send({ from: ownerAddress }); - const another = await TestContract.deploy(wallet).send({ from: ownerAddress }); + const { contract } = await StatefulTestContract.deploy(wallet, ownerAddress, 1).send({ from: ownerAddress }); + const { contract: another } = await TestContract.deploy(wallet).send({ from: ownerAddress }); await aztecNodeAdmin.setConfig({ minTxsPerBlock: 16, maxTxsPerBlock: 16 }); @@ -243,12 +244,23 @@ describe('e2e_block_building', () => { [minterAddress, true], ); - const [deployTxReceipt, callTxReceipt] = await Promise.all([ - deployMethod.send({ from: ownerAddress, wait: { returnReceipt: true } }), - callInteraction.send({ from: ownerAddress }), + // Use priority fees to guarantee ordering: deploy tx gets higher priority so the + // sequencer places it before the call tx in the block. + const highPriority = new GasFees(100, 100); + const lowPriority = new GasFees(1, 1); + + const [deployResult, callResult] = await Promise.all([ + deployMethod.send({ + from: ownerAddress, + fee: { gasSettings: { maxPriorityFeesPerGas: highPriority } }, + }), + callInteraction.send({ + from: ownerAddress, + fee: { gasSettings: { maxPriorityFeesPerGas: lowPriority } }, + }), ]); - expect(deployTxReceipt.blockNumber).toEqual(callTxReceipt.blockNumber); + expect(deployResult.receipt.blockNumber).toEqual(callResult.receipt.blockNumber); }); }); @@ -263,7 +275,7 @@ describe('e2e_block_building', () => { wallet, accounts: [ownerAddress], } = await setup(1)); - contract = await TestContract.deploy(wallet).send({ from: ownerAddress }); + ({ contract } = await TestContract.deploy(wallet).send({ from: ownerAddress })); logger.info(`Test contract deployed at ${contract.address}`); }); @@ -391,7 +403,7 @@ describe('e2e_block_building', () => { } = await setup(1)); logger.info(`Deploying test contract`); - testContract = await TestContract.deploy(wallet).send({ from: ownerAddress }); + ({ contract: testContract } = await TestContract.deploy(wallet).send({ from: ownerAddress })); }, 60_000); afterAll(() => teardown()); @@ -495,9 +507,11 @@ describe('e2e_block_building', () => { })); logger.info('Deploying token contract'); - const token = await TokenContract.deploy(wallet, ownerAddress, 'TokenName', 'TokenSymbol', 18).send({ - from: ownerAddress, - }); + const { contract: token } = await TokenContract.deploy(wallet, ownerAddress, 'TokenName', 'TokenSymbol', 18).send( + { + from: ownerAddress, + }, + ); logger.info('Updating txs per block to 4'); await aztecNodeAdmin.setConfig({ minTxsPerBlock: 4, maxTxsPerBlock: 4 }); @@ -530,7 +544,7 @@ describe('e2e_block_building', () => { accounts: [ownerAddress], } = context); - const testContract = await TestContract.deploy(wallet).send({ from: ownerAddress }); + const { contract: testContract } = await TestContract.deploy(wallet).send({ from: ownerAddress }); logger.warn(`Test contract deployed at ${testContract.address}`); // We want the sequencer to wait until both txs have arrived (so minTxsPerBlock=2), but agree to build @@ -549,7 +563,8 @@ describe('e2e_block_building', () => { ]); const batches = times(2, makeBatch); - const txHashes = await Promise.all(batches.map(batch => batch.send({ from: ownerAddress, wait: NO_WAIT }))); + const txHashResults = await Promise.all(batches.map(batch => batch.send({ from: ownerAddress, wait: NO_WAIT }))); + const txHashes = txHashResults.map(({ txHash }) => txHash); logger.warn(`Sent two txs to test contract`, { txs: txHashes.map(hash => hash.toString()) }); await Promise.race(txHashes.map(txHash => waitForTx(aztecNode, txHash, { timeout: 60 }))); @@ -581,7 +596,7 @@ describe('e2e_block_building', () => { accounts: [ownerAddress], } = await setup(1)); - contract = await StatefulTestContract.deploy(wallet, ownerAddress, 1).send({ from: ownerAddress }); + ({ contract } = await StatefulTestContract.deploy(wallet, ownerAddress, 1).send({ from: ownerAddress })); initialBlockNumber = await aztecNode.getBlockNumber(); logger.info(`Stateful test contract deployed at ${contract.address}`); @@ -605,15 +620,15 @@ describe('e2e_block_building', () => { // Send a tx to the contract that creates a note. This tx will be reorgd but re-included, // since it is being built against a proven block number. logger.info('Sending initial tx'); - const tx1 = await contract.methods.create_note(ownerAddress, 20).send({ from: ownerAddress }); + const { receipt: tx1 } = await contract.methods.create_note(ownerAddress, 20).send({ from: ownerAddress }); expect(tx1.blockNumber).toEqual(initialBlockNumber + 1); - expect(await contract.methods.summed_values(ownerAddress).simulate({ from: ownerAddress })).toEqual(21n); + expect((await contract.methods.summed_values(ownerAddress).simulate({ from: ownerAddress })).result).toEqual(21n); // And send a second one, which won't be re-included. logger.info('Sending second tx'); - const tx2 = await contract.methods.create_note(ownerAddress, 30).send({ from: ownerAddress }); + const { receipt: tx2 } = await contract.methods.create_note(ownerAddress, 30).send({ from: ownerAddress }); expect(tx2.blockNumber).toEqual(initialBlockNumber + 2); - expect(await contract.methods.summed_values(ownerAddress).simulate({ from: ownerAddress })).toEqual(51n); + expect((await contract.methods.summed_values(ownerAddress).simulate({ from: ownerAddress })).result).toEqual(51n); logger.info('Advancing past the proof submission window'); @@ -648,12 +663,12 @@ describe('e2e_block_building', () => { expect(newTx1Receipt.blockHash).not.toEqual(tx1.blockHash); // PXE should have cleared out the 30-note from tx2, but reapplied the 20-note from tx1 - expect(await contract.methods.summed_values(ownerAddress).simulate({ from: ownerAddress })).toEqual(21n); + expect((await contract.methods.summed_values(ownerAddress).simulate({ from: ownerAddress })).result).toEqual(21n); // And we should be able to send a new tx on the new chain logger.info('Sending new tx on reorgd chain'); - const tx3 = await contract.methods.create_note(ownerAddress, 10).send({ from: ownerAddress }); - expect(await contract.methods.summed_values(ownerAddress).simulate({ from: ownerAddress })).toEqual(31n); + const { receipt: tx3 } = await contract.methods.create_note(ownerAddress, 10).send({ from: ownerAddress }); + expect((await contract.methods.summed_values(ownerAddress).simulate({ from: ownerAddress })).result).toEqual(31n); expect(tx3.blockNumber).toBeGreaterThanOrEqual(newTx1Receipt.blockNumber! + 1); }); }); diff --git a/yarn-project/end-to-end/src/e2e_card_game.test.ts b/yarn-project/end-to-end/src/e2e_card_game.test.ts index 56e05017370b..6f387d27e51c 100644 --- a/yarn-project/end-to-end/src/e2e_card_game.test.ts +++ b/yarn-project/end-to-end/src/e2e_card_game.test.ts @@ -104,7 +104,7 @@ describe('e2e_card_game', () => { const deployContract = async () => { logger.debug(`Deploying L2 contract...`); - contract = await CardGameContract.deploy(wallet).send({ from: firstPlayer }); + ({ contract } = await CardGameContract.deploy(wallet).send({ from: firstPlayer })); logger.info(`L2 contract deployed at ${contract.address}`); }; @@ -113,7 +113,9 @@ describe('e2e_card_game', () => { // docs:start:send_tx await contract.methods.buy_pack(seed).send({ from: firstPlayer }); // docs:end:send_tx - const collection = await contract.methods.view_collection_cards(firstPlayer, 0).simulate({ from: firstPlayer }); + const { result: collection } = await contract.methods + .view_collection_cards(firstPlayer, 0) + .simulate({ from: firstPlayer }); const expected = await getPackedCards(0, seed); expect(boundedVecToArray(collection)).toMatchObject(expected); }); @@ -128,7 +130,7 @@ describe('e2e_card_game', () => { contract.methods.buy_pack(seed).send({ from: secondPlayer }), ]); firstPlayerCollection = boundedVecToArray( - await contract.methods.view_collection_cards(firstPlayer, 0).simulate({ from: firstPlayer }), + (await contract.methods.view_collection_cards(firstPlayer, 0).simulate({ from: firstPlayer })).result, ); }); @@ -143,11 +145,13 @@ describe('e2e_card_game', () => { .send({ from: secondPlayer }), ).rejects.toThrow(`Not all cards were removed`); - const collection = await contract.methods.view_collection_cards(firstPlayer, 0).simulate({ from: firstPlayer }); + const { result: collection } = await contract.methods + .view_collection_cards(firstPlayer, 0) + .simulate({ from: firstPlayer }); expect(boundedVecToArray(collection)).toHaveLength(1); expect(boundedVecToArray(collection)).toMatchObject([firstPlayerCollection[1]]); - expect((await contract.methods.view_game(GAME_ID).simulate({ from: firstPlayer })) as Game).toMatchObject({ + expect((await contract.methods.view_game(GAME_ID).simulate({ from: firstPlayer })).result as Game).toMatchObject({ players: [ { address: firstPlayer, @@ -169,9 +173,8 @@ describe('e2e_card_game', () => { it('should start games', async () => { const secondPlayerCollection = boundedVecToArray( - (await contract.methods - .view_collection_cards(secondPlayer, 0) - .simulate({ from: secondPlayer })) as NoirBoundedVec, + (await contract.methods.view_collection_cards(secondPlayer, 0).simulate({ from: secondPlayer })) + .result as NoirBoundedVec, ); await Promise.all([ @@ -185,7 +188,7 @@ describe('e2e_card_game', () => { await contract.methods.start_game(GAME_ID).send({ from: firstPlayer }); - expect((await contract.methods.view_game(GAME_ID).simulate({ from: firstPlayer })) as Game).toMatchObject({ + expect((await contract.methods.view_game(GAME_ID).simulate({ from: firstPlayer })).result as Game).toMatchObject({ players: expect.arrayContaining([ { address: firstPlayer, @@ -220,15 +223,15 @@ describe('e2e_card_game', () => { ]); firstPlayerCollection = boundedVecToArray( - await contract.methods.view_collection_cards(firstPlayer, 0).simulate({ from: firstPlayer }), + (await contract.methods.view_collection_cards(firstPlayer, 0).simulate({ from: firstPlayer })).result, ); secondPlayerCollection = boundedVecToArray( - await contract.methods.view_collection_cards(secondPlayer, 0).simulate({ from: secondPlayer }), + (await contract.methods.view_collection_cards(secondPlayer, 0).simulate({ from: secondPlayer })).result, ); thirdPlayerCOllection = boundedVecToArray( - await contract.methods.view_collection_cards(thirdPlayer, 0).simulate({ from: thirdPlayer }), + (await contract.methods.view_collection_cards(thirdPlayer, 0).simulate({ from: thirdPlayer })).result, ); }); @@ -240,7 +243,7 @@ describe('e2e_card_game', () => { } async function playGame(playerDecks: { address: AztecAddress; deck: Card[] }[], id = GAME_ID) { - const initialGameState = (await contract.methods.view_game(id).simulate({ from: firstPlayer })) as Game; + const initialGameState = (await contract.methods.view_game(id).simulate({ from: firstPlayer })).result as Game; const players = initialGameState.players.map(player => player.address); const cards = players.map( player => playerDecks.find(playerDeckEntry => playerDeckEntry.address.equals(player))!.deck, @@ -254,7 +257,7 @@ describe('e2e_card_game', () => { } } - const finalGameState = (await contract.methods.view_game(id).simulate({ from: firstPlayer })) as Game; + const finalGameState = (await contract.methods.view_game(id).simulate({ from: firstPlayer })).result as Game; expect(finalGameState.finished).toBe(true); return finalGameState; @@ -285,7 +288,8 @@ describe('e2e_card_game', () => { await contract.methods.claim_cards(GAME_ID, game.rounds_cards.map(cardToField)).send({ from: winner }); const winnerCollection = boundedVecToArray( - (await contract.methods.view_collection_cards(winner, 0).simulate({ from: winner })) as NoirBoundedVec, + (await contract.methods.view_collection_cards(winner, 0).simulate({ from: winner })) + .result as NoirBoundedVec, ); const winnerGameDeck = [winnerCollection[0], winnerCollection[3]]; diff --git a/yarn-project/end-to-end/src/e2e_contract_updates.test.ts b/yarn-project/end-to-end/src/e2e_contract_updates.test.ts index 10bf16f3e987..e34cedae2e97 100644 --- a/yarn-project/end-to-end/src/e2e_contract_updates.test.ts +++ b/yarn-project/end-to-end/src/e2e_contract_updates.test.ts @@ -110,7 +110,9 @@ describe('e2e_contract_updates', () => { } sequencer = maybeSequencer; - ({ contract, instance } = await UpdatableContract.deploy(wallet, constructorArgs[0]).send({ + ({ + receipt: { contract, instance }, + } = await UpdatableContract.deploy(wallet, constructorArgs[0]).send({ from: defaultAccountAddress, contractAddressSalt: salt, wait: { returnReceipt: true }, @@ -126,9 +128,10 @@ describe('e2e_contract_updates', () => { it('should update the contract', async () => { expect( - await contract.methods.get_private_value(defaultAccountAddress).simulate({ from: defaultAccountAddress }), + (await contract.methods.get_private_value(defaultAccountAddress).simulate({ from: defaultAccountAddress })) + .result, ).toEqual(INITIAL_UPDATABLE_CONTRACT_VALUE); - expect(await contract.methods.get_public_value().simulate({ from: defaultAccountAddress })).toEqual( + expect((await contract.methods.get_public_value().simulate({ from: defaultAccountAddress })).result).toEqual( INITIAL_UPDATABLE_CONTRACT_VALUE, ); await contract.methods.update_to(updatedContractClassId).send({ from: defaultAccountAddress }); @@ -141,18 +144,19 @@ describe('e2e_contract_updates', () => { await updatedContract.methods.set_private_value().send({ from: defaultAccountAddress }); // Read state that was changed by the previous tx expect( - await updatedContract.methods.get_private_value(defaultAccountAddress).simulate({ from: defaultAccountAddress }), + (await updatedContract.methods.get_private_value(defaultAccountAddress).simulate({ from: defaultAccountAddress })) + .result, ).toEqual(UPDATED_CONTRACT_PUBLIC_VALUE); // Call a public method with a new implementation await updatedContract.methods.set_public_value().send({ from: defaultAccountAddress }); - expect(await updatedContract.methods.get_public_value().simulate({ from: defaultAccountAddress })).toEqual( + expect((await updatedContract.methods.get_public_value().simulate({ from: defaultAccountAddress })).result).toEqual( UPDATED_CONTRACT_PUBLIC_VALUE, ); }); it('should change the update delay and then update the contract', async () => { - expect(await contract.methods.get_update_delay().simulate({ from: defaultAccountAddress })).toEqual( + expect((await contract.methods.get_update_delay().simulate({ from: defaultAccountAddress })).result).toEqual( BigInt(DEFAULT_TEST_UPDATE_DELAY), ); @@ -161,7 +165,7 @@ describe('e2e_contract_updates', () => { .set_update_delay(BigInt(DEFAULT_TEST_UPDATE_DELAY) + 1n) .send({ from: defaultAccountAddress }); - expect(await contract.methods.get_update_delay().simulate({ from: defaultAccountAddress })).toEqual( + expect((await contract.methods.get_update_delay().simulate({ from: defaultAccountAddress })).result).toEqual( BigInt(DEFAULT_TEST_UPDATE_DELAY) + 1n, ); diff --git a/yarn-project/end-to-end/src/e2e_cross_chain_messaging/l1_to_l2.test.ts b/yarn-project/end-to-end/src/e2e_cross_chain_messaging/l1_to_l2.test.ts index e83b195c5f84..3f12389109ac 100644 --- a/yarn-project/end-to-end/src/e2e_cross_chain_messaging/l1_to_l2.test.ts +++ b/yarn-project/end-to-end/src/e2e_cross_chain_messaging/l1_to_l2.test.ts @@ -6,7 +6,7 @@ import { isL1ToL2MessageReady } from '@aztec/aztec.js/messaging'; import type { AztecNode } from '@aztec/aztec.js/node'; import { TxExecutionResult } from '@aztec/aztec.js/tx'; import type { Wallet } from '@aztec/aztec.js/wallet'; -import { BlockNumber } from '@aztec/foundation/branded-types'; +import { BlockNumber, IndexWithinCheckpoint } from '@aztec/foundation/branded-types'; import { timesAsync } from '@aztec/foundation/collection'; import { retryUntil } from '@aztec/foundation/retry'; import { TestContract } from '@aztec/noir-test-contracts.js/Test'; @@ -34,7 +34,7 @@ describe('e2e_cross_chain_messaging l1_to_l2', () => { await t.setup(); ({ logger: log, crossChainTestHarness, wallet, user1Address, aztecNode } = t); - testContract = await TestContract.deploy(wallet).send({ from: user1Address }); + ({ contract: testContract } = await TestContract.deploy(wallet).send({ from: user1Address })); }, 300_000); afterEach(async () => { @@ -56,7 +56,34 @@ describe('e2e_cross_chain_messaging l1_to_l2', () => { if (newBlock === block) { throw new Error(`Failed to advance block ${block}`); } - return undefined; + return newBlock; + }; + + const waitForBlockToCheckpoint = async (blockNumber: BlockNumber) => { + return await retryUntil( + async () => { + const checkpointedBlockNumber = await aztecNode.getCheckpointedBlockNumber(); + const isCheckpointed = checkpointedBlockNumber >= blockNumber; + if (!isCheckpointed) { + return undefined; + } + const [checkpointedBlock] = await aztecNode.getCheckpointedBlocks(blockNumber, 1); + return checkpointedBlock.checkpointNumber; + }, + 'wait for block to checkpoint', + 60, + ); + }; + + const advanceCheckpoint = async () => { + let checkpoint = await aztecNode.getCheckpointNumber(); + const originalCheckpoint = checkpoint; + log.warn(`Original checkpoint ${originalCheckpoint}`); + do { + const newBlock = await advanceBlock(); + checkpoint = await waitForBlockToCheckpoint(newBlock); + } while (checkpoint <= originalCheckpoint); + log.warn(`At checkpoint ${checkpoint}`); }; // Same as above but ignores errors. Useful if we expect a prune. @@ -68,12 +95,19 @@ describe('e2e_cross_chain_messaging l1_to_l2', () => { } }; - // Waits until the message is fetched by the archiver of the node and returns the msg target block + // Waits until the message is fetched by the archiver of the node and returns the msg target checkpoint const waitForMessageFetched = async (msgHash: Fr) => { log.warn(`Waiting until the message is fetched by the node`); return await retryUntil( - async () => (await aztecNode.getL1ToL2MessageBlock(msgHash)) ?? (await advanceBlock()), - 'get msg block', + async () => { + const checkpoint = await aztecNode.getL1ToL2MessageCheckpoint(msgHash); + if (checkpoint !== undefined) { + return checkpoint; + } + await advanceBlock(); + return undefined; + }, + 'get msg checkpoint', 60, ); }; @@ -84,20 +118,27 @@ describe('e2e_cross_chain_messaging l1_to_l2', () => { scope: 'private' | 'public', onNotReady?: (blockNumber: BlockNumber) => Promise, ) => { - const msgBlock = await waitForMessageFetched(msgHash); - log.warn(`Waiting until L2 reaches msg block ${msgBlock} (current is ${await aztecNode.getBlockNumber()})`); + const msgCheckpoint = await waitForMessageFetched(msgHash); + log.warn( + `Waiting until L2 reaches the first block of msg checkpoint ${msgCheckpoint} (current is ${await aztecNode.getCheckpointNumber()})`, + ); await retryUntil( async () => { - const blockNumber = await aztecNode.getBlockNumber(); + const [blockNumber, checkpointNumber] = await Promise.all([ + aztecNode.getBlockNumber(), + aztecNode.getCheckpointNumber(), + ]); const witness = await aztecNode.getL1ToL2MessageMembershipWitness('latest', msgHash); - const isReady = await isL1ToL2MessageReady(aztecNode, msgHash, { forPublicConsumption: scope === 'public' }); - log.info(`Block is ${blockNumber}. Message block is ${msgBlock}. Witness ${!!witness}. Ready ${isReady}.`); + const isReady = await isL1ToL2MessageReady(aztecNode, msgHash); + log.info( + `Block is ${blockNumber}, checkpoint is ${checkpointNumber}. Message checkpoint is ${msgCheckpoint}. Witness ${!!witness}. Ready ${isReady}.`, + ); if (!isReady) { await (onNotReady ? onNotReady(blockNumber) : advanceBlock()); } return isReady; }, - `wait for rollup to reach msg block ${msgBlock}`, + `wait for rollup to reach msg checkpoint ${msgCheckpoint}`, 120, ); }; @@ -118,12 +159,8 @@ describe('e2e_cross_chain_messaging l1_to_l2', () => { await waitForMessageReady(message1Hash, scope); - // The waitForMessageReady returns true earlier for public-land, so we can only check the membership - // witness for private-land here. - if (scope === 'private') { - const [message1Index] = (await aztecNode.getL1ToL2MessageMembershipWitness('latest', message1Hash))!; - expect(actualMessage1Index.toBigInt()).toBe(message1Index); - } + const [message1Index] = (await aztecNode.getL1ToL2MessageMembershipWitness('latest', message1Hash))!; + expect(actualMessage1Index.toBigInt()).toBe(message1Index); // We consume the L1 to L2 message using the test contract either from private or public await getConsumeMethod(scope)( @@ -143,12 +180,10 @@ describe('e2e_cross_chain_messaging l1_to_l2', () => { // We check that the duplicate message was correctly inserted by checking that its message index is defined await waitForMessageReady(message2Hash, scope); - if (scope === 'private') { - const [message2Index] = (await aztecNode.getL1ToL2MessageMembershipWitness('latest', message2Hash))!; - expect(message2Index).toBeDefined(); - expect(message2Index).toBeGreaterThan(actualMessage1Index.toBigInt()); - expect(actualMessage2Index.toBigInt()).toBe(message2Index); - } + const [message2Index] = (await aztecNode.getL1ToL2MessageMembershipWitness('latest', message2Hash))!; + expect(message2Index).toBeDefined(); + expect(message2Index).toBeGreaterThan(actualMessage1Index.toBigInt()); + expect(actualMessage2Index.toBigInt()).toBe(message2Index); // Now we consume the message again. Everything should pass because oracle should return the duplicate message // which is not nullified @@ -162,21 +197,22 @@ describe('e2e_cross_chain_messaging l1_to_l2', () => { 120_000, ); - // Inbox block number can drift on two scenarios: if the rollup reorgs and rolls back its own - // block number, or if the inbox receives too many messages and they are inserted faster than - // they are consumed. In this test, we mine several blocks without marking them as proven until + // Inbox checkpoint number can drift on two scenarios: if the rollup reorgs and rolls back its own + // checkpoint number, or if the inbox receives too many messages and they are inserted faster than + // they are consumed. In this test, we mine several checkpoints without marking them as proven until // we can trigger a reorg, and then wait until the message can be processed to consume it. it.each(['private', 'public'] as const)( 'can consume L1 to L2 message in %s after inbox drifts away from the rollup', async (scope: 'private' | 'public') => { // Stop proving const lastProven = await aztecNode.getBlockNumber(); - log.warn(`Stopping proof submission at block ${lastProven} to allow drift`); + const [checkpointedProvenBlock] = await aztecNode.getCheckpointedBlocks(lastProven, 1); + log.warn(`Stopping proof submission at checkpoint ${checkpointedProvenBlock.checkpointNumber} to allow drift`); t.context.watcher.setIsMarkingAsProven(false); - // Mine several blocks to ensure drift + // Mine several checkpoints to ensure drift log.warn(`Mining blocks to allow drift`); - await timesAsync(4, advanceBlock); + await timesAsync(4, advanceCheckpoint); // Generate and send the message to the L1 contract log.warn(`Sending L1 to L2 message`); @@ -185,9 +221,9 @@ describe('e2e_cross_chain_messaging l1_to_l2', () => { const { msgHash, globalLeafIndex } = await sendL1ToL2Message(message, crossChainTestHarness); // Wait until the Aztec node has synced it - const msgBlockNumber = await waitForMessageFetched(msgHash); - log.warn(`Message synced for block ${msgBlockNumber}`); - expect(lastProven + 4).toBeLessThan(msgBlockNumber); + const msgCheckpointNumber = await waitForMessageFetched(msgHash); + log.warn(`Message synced for checkpoint ${msgCheckpointNumber}`); + expect(checkpointedProvenBlock.checkpointNumber + 4).toBeLessThan(msgCheckpointNumber); // And keep mining until we prune back to the original block number. Now the "waiting for two blocks" // strategy for the message to be ready to use shouldn't work, since the lastProven block is more than @@ -214,25 +250,33 @@ describe('e2e_cross_chain_messaging l1_to_l2', () => { // On private, we simulate the tx locally and check that we get a missing message error, then we advance to the next block await expect(() => consume().simulate({ from: user1Address })).rejects.toThrow(/No L1 to L2 message found/); await tryAdvanceBlock(); - await t.context.watcher.markAsProven(); } else { - // On public, we actually send the tx and check that it reverts due to the missing message. - // This advances the block too as a side-effect. Note that we do not rely on a simulation since the cross chain messages - // do not get added at the beginning of the block during node_simulatePublicCalls (maybe they should?). - const receipt = await consume().send({ from: user1Address, wait: { dontThrowOnRevert: true } }); - expect(receipt.executionResult).toEqual(TxExecutionResult.APP_LOGIC_REVERTED); - await t.context.watcher.markAsProven(); + // In public it is harder to determine when a message becomes consumable. + // We send a transaction, this advances the chain and the message MIGHT be consumed in the new block. + // If it does get consumed then we check that the block contains the message. + // If it fails we check that the block doesn't contain the message + const { receipt } = await consume().send({ from: user1Address, wait: { dontThrowOnRevert: true } }); + if (receipt.executionResult === TxExecutionResult.SUCCESS) { + // The block the transaction included should be for the message checkpoint number + // and be the first block in the checkpoint + const block = await aztecNode.getBlock(receipt.blockNumber!); + expect(block).toBeDefined(); + expect(block!.checkpointNumber).toEqual(msgCheckpointNumber); + expect(block!.indexWithinCheckpoint).toEqual(IndexWithinCheckpoint.ZERO); + } else { + expect(receipt.executionResult).toEqual(TxExecutionResult.APP_LOGIC_REVERTED); + } } + await t.context.watcher.markAsProven(); }); // Verify the membership witness is available for creating the tx (private-land only) if (scope === 'private') { const [messageIndex] = (await aztecNode.getL1ToL2MessageMembershipWitness('latest', msgHash))!; expect(messageIndex).toEqual(globalLeafIndex.toBigInt()); + // And consume the message for private, public was already consumed. + await consume().send({ from: user1Address }); } - - // And consume the message - await consume().send({ from: user1Address }); }, ); }); diff --git a/yarn-project/end-to-end/src/e2e_cross_chain_messaging/l2_to_l1.test.ts b/yarn-project/end-to-end/src/e2e_cross_chain_messaging/l2_to_l1.test.ts index 7432bd33d4f3..d747d3ef6666 100644 --- a/yarn-project/end-to-end/src/e2e_cross_chain_messaging/l2_to_l1.test.ts +++ b/yarn-project/end-to-end/src/e2e_cross_chain_messaging/l2_to_l1.test.ts @@ -43,7 +43,7 @@ describe('e2e_cross_chain_messaging l2_to_l1', () => { version = BigInt(await rollup.getVersion()); - contract = await TestContract.deploy(wallet).send({ from: user1Address }); + ({ contract } = await TestContract.deploy(wallet).send({ from: user1Address })); }); afterAll(async () => { @@ -60,7 +60,7 @@ describe('e2e_cross_chain_messaging l2_to_l1', () => { // Configure the node to be able to rollup only 1 tx. await aztecNodeAdmin.setConfig({ minTxsPerBlock: 1 }); - const txReceipt = await new BatchCall(wallet, [ + const { receipt: txReceipt } = await new BatchCall(wallet, [ contract.methods.create_l2_to_l1_message_arbitrary_recipient_private(contents[0], recipient), contract.methods.create_l2_to_l1_message_arbitrary_recipient_public(contents[1], recipient), ]).send({ from: user1Address }); @@ -92,7 +92,7 @@ describe('e2e_cross_chain_messaging l2_to_l1', () => { await aztecNodeAdmin.setConfig({ minTxsPerBlock: 2 }); // Send the 2 txs. - const [noMessageReceipt, withMessageReceipt] = await Promise.all([ + const [{ receipt: noMessageReceipt }, { receipt: withMessageReceipt }] = await Promise.all([ contract.methods.emit_nullifier(Fr.random()).send({ from: user1Address }), contract.methods .create_l2_to_l1_message_arbitrary_recipient_private(content, recipient) @@ -119,7 +119,7 @@ describe('e2e_cross_chain_messaging l2_to_l1', () => { const call0 = createBatchCall(wallet, tx0.recipients, tx0.contents); const call1 = createBatchCall(wallet, tx1.recipients, tx1.contents); - const [l2TxReceipt0, l2TxReceipt1] = await Promise.all([ + const [{ receipt: l2TxReceipt0 }, { receipt: l2TxReceipt1 }] = await Promise.all([ call0.send({ from: user1Address }), call1.send({ from: user1Address }), ]); @@ -173,7 +173,7 @@ describe('e2e_cross_chain_messaging l2_to_l1', () => { const call1 = createBatchCall(wallet, tx1.recipients, tx1.contents); const call2 = createBatchCall(wallet, tx2.recipients, tx2.contents); - const [l2TxReceipt0, l2TxReceipt1, l2TxReceipt2] = await Promise.all([ + const [{ receipt: l2TxReceipt0 }, { receipt: l2TxReceipt1 }, { receipt: l2TxReceipt2 }] = await Promise.all([ call0.send({ from: user1Address }), call1.send({ from: user1Address }), call2.send({ from: user1Address }), diff --git a/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts b/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts index e82734a14b15..7da34cf384a2 100644 --- a/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts +++ b/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts @@ -43,7 +43,7 @@ describe('e2e_crowdfunding_and_claim', () => { let crowdfundingContract: CrowdfundingContract; let claimContract: ClaimContract; - let crowdfundingSecretKey; + let crowdfundingSecretKey: Fr; let crowdfundingPublicKeys: PublicKeys; let cheatCodes: CheatCodes; let deadline: number; // end of crowdfunding period @@ -62,22 +62,22 @@ describe('e2e_crowdfunding_and_claim', () => { // We set the deadline to a week from now deadline = (await cheatCodes.eth.timestamp()) + 7 * 24 * 60 * 60; - donationToken = await TokenContract.deploy( + ({ contract: donationToken } = await TokenContract.deploy( wallet, operatorAddress, donationTokenMetadata.name, donationTokenMetadata.symbol, donationTokenMetadata.decimals, - ).send({ from: operatorAddress }); + ).send({ from: operatorAddress })); logger.info(`Donation Token deployed to ${donationToken.address}`); - rewardToken = await TokenContract.deploy( + ({ contract: rewardToken } = await TokenContract.deploy( wallet, operatorAddress, rewardTokenMetadata.name, rewardTokenMetadata.symbol, rewardTokenMetadata.decimals, - ).send({ from: operatorAddress }); + ).send({ from: operatorAddress })); logger.info(`Reward Token deployed to ${rewardToken.address}`); // We deploy the Crowdfunding contract as an escrow contract (i.e. with populated public keys that make it @@ -94,12 +94,20 @@ describe('e2e_crowdfunding_and_claim', () => { ); const crowdfundingInstance = await crowdfundingDeployment.getInstance(); await wallet.registerContract(crowdfundingInstance, CrowdfundingContract.artifact, crowdfundingSecretKey); - crowdfundingContract = await crowdfundingDeployment.send({ from: operatorAddress }); + ({ contract: crowdfundingContract } = await crowdfundingDeployment.send({ + from: operatorAddress, + // The contract constructor initializes private storage vars that need the contract's own nullifier key. + additionalScopes: [crowdfundingInstance.address], + })); logger.info(`Crowdfunding contract deployed at ${crowdfundingContract.address}`); - claimContract = await ClaimContract.deploy(wallet, crowdfundingContract.address, rewardToken.address).send({ + ({ contract: claimContract } = await ClaimContract.deploy( + wallet, + crowdfundingContract.address, + rewardToken.address, + ).send({ from: operatorAddress, - }); + })); logger.info(`Claim contract deployed at ${claimContract.address}`); await rewardToken.methods.set_minter(claimContract.address, true).send({ from: operatorAddress }); @@ -129,7 +137,7 @@ describe('e2e_crowdfunding_and_claim', () => { // The donor should have exactly one note const pageIndex = 0; - const notes = await crowdfundingContract.methods + const { result: notes } = await crowdfundingContract.methods .get_donation_notes(donor1Address, pageIndex) .simulate({ from: donor1Address }); expect(notes.len).toEqual(1n); @@ -142,10 +150,12 @@ describe('e2e_crowdfunding_and_claim', () => { } // Since the RWT is minted 1:1 with the DNT, the balance of the reward token should be equal to the donation amount - const balanceRWT = await rewardToken.methods.balance_of_public(donor1Address).simulate({ from: operatorAddress }); + const { result: balanceRWT } = await rewardToken.methods + .balance_of_public(donor1Address) + .simulate({ from: operatorAddress }); expect(balanceRWT).toEqual(donationAmount); - const balanceDNTBeforeWithdrawal = await donationToken.methods + const { result: balanceDNTBeforeWithdrawal } = await donationToken.methods .balance_of_private(operatorAddress) .simulate({ from: operatorAddress }); expect(balanceDNTBeforeWithdrawal).toEqual(0n); @@ -153,7 +163,7 @@ describe('e2e_crowdfunding_and_claim', () => { // 3) At last, we withdraw the raised funds from the crowdfunding contract to the operator's address await crowdfundingContract.methods.withdraw(donationAmount).send({ from: operatorAddress }); - const balanceDNTAfterWithdrawal = await donationToken.methods + const { result: balanceDNTAfterWithdrawal } = await donationToken.methods .balance_of_private(operatorAddress) .simulate({ from: operatorAddress }); @@ -184,7 +194,7 @@ describe('e2e_crowdfunding_and_claim', () => { // The donor should have exactly one note const pageIndex = 0; - const notes = await crowdfundingContract.methods + const { result: notes } = await crowdfundingContract.methods .get_donation_notes(donorAddress, pageIndex) .simulate({ from: donorAddress }); expect(notes.len).toEqual(1n); @@ -221,7 +231,13 @@ describe('e2e_crowdfunding_and_claim', () => { deadline, ); - otherCrowdfundingContract = await otherCrowdfundingDeployment.send({ from: operatorAddress }); + const otherCrowdfundingInstance = await otherCrowdfundingDeployment.getInstance(); + await wallet.registerContract(otherCrowdfundingInstance, CrowdfundingContract.artifact, crowdfundingSecretKey); + ({ contract: otherCrowdfundingContract } = await otherCrowdfundingDeployment.send({ + from: operatorAddress, + // The contract constructor initializes private storage vars that need the contract's own nullifier key. + additionalScopes: [otherCrowdfundingInstance.address], + })); logger.info(`Crowdfunding contract deployed at ${otherCrowdfundingContract.address}`); } @@ -241,11 +257,11 @@ describe('e2e_crowdfunding_and_claim', () => { // 3) Get the donation note const pageIndex = 0; - const notes = await otherCrowdfundingContract.methods + const { result: notes2 } = await otherCrowdfundingContract.methods .get_donation_notes(donor1Address, pageIndex) .simulate({ from: donor1Address }); - expect(notes.len).toEqual(1n); - const otherContractNote = notes.storage[0]; + expect(notes2.len).toEqual(1n); + const otherContractNote = notes2.storage[0]; // 4) Try to claim rewards using note from other contract await expect( diff --git a/yarn-project/end-to-end/src/e2e_deploy_contract/contract_class_registration.test.ts b/yarn-project/end-to-end/src/e2e_deploy_contract/contract_class_registration.test.ts index fc5a57f84fe6..37da34dd7f3c 100644 --- a/yarn-project/end-to-end/src/e2e_deploy_contract/contract_class_registration.test.ts +++ b/yarn-project/end-to-end/src/e2e_deploy_contract/contract_class_registration.test.ts @@ -37,7 +37,7 @@ describe('e2e_deploy_contract contract class registration', () => { ({ logger, wallet, aztecNode, defaultAccountAddress } = await t.setup()); artifact = StatefulTestContract.artifact; publicationTxReceipt = await publishContractClass(wallet, artifact).then(c => - c.send({ from: defaultAccountAddress }), + c.send({ from: defaultAccountAddress }).then(({ receipt }) => receipt), ); contractClass = await getContractClassFromArtifact(artifact); expect(await aztecNode.getContractClass(contractClass.id)).toBeDefined(); @@ -47,7 +47,7 @@ describe('e2e_deploy_contract contract class registration', () => { describe('publishing a contract class', () => { it('emits public bytecode', async () => { - const publicationTxReceipt = await publishContractClass(wallet, TestContract.artifact).then(c => + const { receipt: publicationTxReceipt } = await publishContractClass(wallet, TestContract.artifact).then(c => c.send({ from: defaultAccountAddress }), ); const logs = await aztecNode.getContractClassLogs({ txHash: publicationTxReceipt.txHash }); @@ -149,19 +149,23 @@ describe('e2e_deploy_contract contract class registration', () => { it('calls a public function with no init check on the deployed instance', async () => { const whom = await AztecAddress.random(); await contract.methods.increment_public_value_no_init_check(whom, 10).send({ from: defaultAccountAddress }); - const stored = await contract.methods.get_public_value(whom).simulate({ from: defaultAccountAddress }); + const { result: stored } = await contract.methods + .get_public_value(whom) + .simulate({ from: defaultAccountAddress }); expect(stored).toEqual(10n); }); it('refuses to call a public function with init check if the instance is not initialized', async () => { const whom = await AztecAddress.random(); - const receipt = await contract.methods + const { receipt } = await contract.methods .increment_public_value(whom, 10) .send({ from: defaultAccountAddress, wait: { dontThrowOnRevert: true } }); expect(receipt.executionResult).toEqual(TxExecutionResult.APP_LOGIC_REVERTED); // Meanwhile we check we didn't increment the value - expect(await contract.methods.get_public_value(whom).simulate({ from: defaultAccountAddress })).toEqual(0n); + expect( + (await contract.methods.get_public_value(whom).simulate({ from: defaultAccountAddress })).result, + ).toEqual(0n); }); it('refuses to initialize the instance with wrong args via a private function', async () => { @@ -174,7 +178,9 @@ describe('e2e_deploy_contract contract class registration', () => { await contract.methods.constructor(...initArgs).send({ from: defaultAccountAddress }); const whom = await AztecAddress.random(); await contract.methods.increment_public_value(whom, 10).send({ from: defaultAccountAddress }); - const stored = await contract.methods.get_public_value(whom).simulate({ from: defaultAccountAddress }); + const { result: stored } = await contract.methods + .get_public_value(whom) + .simulate({ from: defaultAccountAddress }); expect(stored).toEqual(10n); }); @@ -195,18 +201,22 @@ describe('e2e_deploy_contract contract class registration', () => { it('refuses to initialize the instance with wrong args via a public function', async () => { const whom = await AztecAddress.random(); - const receipt = await contract.methods + const { receipt } = await contract.methods .public_constructor(whom, 43) .send({ from: defaultAccountAddress, wait: { dontThrowOnRevert: true } }); expect(receipt.executionResult).toEqual(TxExecutionResult.APP_LOGIC_REVERTED); - expect(await contract.methods.get_public_value(whom).simulate({ from: defaultAccountAddress })).toEqual(0n); + expect( + (await contract.methods.get_public_value(whom).simulate({ from: defaultAccountAddress })).result, + ).toEqual(0n); }); it('initializes the contract and calls a public function', async () => { await contract.methods.public_constructor(...initArgs).send({ from: defaultAccountAddress }); const whom = await AztecAddress.random(); await contract.methods.increment_public_value(whom, 10).send({ from: defaultAccountAddress }); - const stored = await contract.methods.get_public_value(whom).simulate({ from: defaultAccountAddress }); + const { result: stored } = await contract.methods + .get_public_value(whom) + .simulate({ from: defaultAccountAddress }); expect(stored).toEqual(10n); }); @@ -228,7 +238,7 @@ describe('e2e_deploy_contract contract class registration', () => { // Register the instance to be deployed in the pxe await wallet.registerContract(instance, artifact); // Set up the contract that calls the deployer (which happens to be the TestContract) and call it - const deployer = await TestContract.deploy(wallet).send({ from: defaultAccountAddress }); + const { contract: deployer } = await TestContract.deploy(wallet).send({ from: defaultAccountAddress }); await deployer.methods.publish_contract_instance(instance.address).send({ from: defaultAccountAddress }); }); @@ -243,7 +253,7 @@ describe('e2e_deploy_contract contract class registration', () => { ).rejects.toThrow(/not deployed/); // This time, don't throw on revert and confirm that the tx is included // despite reverting in app logic because of the call to a non-existent contract - const tx = await instance.methods + const { receipt: tx } = await instance.methods .increment_public_value_no_init_check(whom, 10) .send({ from: defaultAccountAddress, wait: { dontThrowOnRevert: true } }); expect(tx.executionResult).toEqual(TxExecutionResult.APP_LOGIC_REVERTED); diff --git a/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_method.test.ts b/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_method.test.ts index 3d81fda867be..ed7cfba703fb 100644 --- a/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_method.test.ts +++ b/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_method.test.ts @@ -40,12 +40,14 @@ describe('e2e_deploy_contract deploy method', () => { const owner = defaultAccountAddress; logger.debug(`Deploying stateful test contract`); // docs:start:deploy_basic - const contract = await StatefulTestContract.deploy(wallet, owner, 42).send({ from: defaultAccountAddress }); + const { contract } = await StatefulTestContract.deploy(wallet, owner, 42).send({ from: defaultAccountAddress }); // docs:end:deploy_basic - expect(await contract.methods.summed_values(owner).simulate({ from: defaultAccountAddress })).toEqual(42n); + expect((await contract.methods.summed_values(owner).simulate({ from: defaultAccountAddress })).result).toEqual(42n); logger.debug(`Calling public method on stateful test contract at ${contract.address.toString()}`); await contract.methods.increment_public_value(owner, 84).send({ from: defaultAccountAddress }); - expect(await contract.methods.get_public_value(owner).simulate({ from: defaultAccountAddress })).toEqual(84n); + expect((await contract.methods.get_public_value(owner).simulate({ from: defaultAccountAddress })).result).toEqual( + 84n, + ); // docs:start:verify_deployment const metadata = await wallet.getContractMetadata(contract.address); const classMetadata = await wallet.getContractClassMetadata(metadata.instance!.currentContractClassId); @@ -58,28 +60,30 @@ describe('e2e_deploy_contract deploy method', () => { const owner = defaultAccountAddress; // docs:start:deploy_universal const opts = { universalDeploy: true, from: defaultAccountAddress }; - const contract = await StatefulTestContract.deploy(wallet, owner, 42).send(opts); + const { contract } = await StatefulTestContract.deploy(wallet, owner, 42).send(opts); // docs:end:deploy_universal - expect(await contract.methods.summed_values(owner).simulate({ from: defaultAccountAddress })).toEqual(42n); + expect((await contract.methods.summed_values(owner).simulate({ from: defaultAccountAddress })).result).toEqual(42n); await contract.methods.increment_public_value(owner, 84).send({ from: defaultAccountAddress }); - expect(await contract.methods.get_public_value(owner).simulate({ from: defaultAccountAddress })).toEqual(84n); + expect((await contract.methods.get_public_value(owner).simulate({ from: defaultAccountAddress })).result).toEqual( + 84n, + ); }); it('publicly deploys and calls a public function from the constructor', async () => { const owner = defaultAccountAddress; // docs:start:deploy_token - const token = await TokenContract.deploy(wallet, owner, 'TOKEN', 'TKN', 18).send({ + const { contract: token } = await TokenContract.deploy(wallet, owner, 'TOKEN', 'TKN', 18).send({ from: defaultAccountAddress, }); // docs:end:deploy_token - expect(await token.methods.is_minter(owner).simulate({ from: defaultAccountAddress })).toEqual(true); + expect((await token.methods.is_minter(owner).simulate({ from: defaultAccountAddress })).result).toEqual(true); }); it('publicly deploys and initializes via a public function', async () => { const owner = defaultAccountAddress; logger.debug(`Deploying contract via a public constructor`); // docs:start:deploy_with_opts - const contract = await StatefulTestContract.deployWithOpts( + const { contract } = await StatefulTestContract.deployWithOpts( { wallet, method: 'public_constructor' }, owner, 42, @@ -87,29 +91,31 @@ describe('e2e_deploy_contract deploy method', () => { from: defaultAccountAddress, }); // docs:end:deploy_with_opts - expect(await contract.methods.get_public_value(owner).simulate({ from: defaultAccountAddress })).toEqual(42n); + expect((await contract.methods.get_public_value(owner).simulate({ from: defaultAccountAddress })).result).toEqual( + 42n, + ); logger.debug(`Calling a private function to ensure the contract was properly initialized`); await contract.methods.create_note(owner, 30).send({ from: defaultAccountAddress }); - expect(await contract.methods.summed_values(owner).simulate({ from: defaultAccountAddress })).toEqual(30n); + expect((await contract.methods.summed_values(owner).simulate({ from: defaultAccountAddress })).result).toEqual(30n); }); it('deploys a contract with a default initializer not named constructor', async () => { logger.debug(`Deploying contract with a default initializer named initialize`); const opts = { skipClassPublication: true, skipInstancePublication: true, from: defaultAccountAddress }; - const contract = await CounterContract.deploy(wallet, 10, defaultAccountAddress).send(opts); + const { contract } = await CounterContract.deploy(wallet, 10, defaultAccountAddress).send(opts); logger.debug(`Calling a function to ensure the contract was properly initialized`); await contract.methods.increment_twice(defaultAccountAddress).send({ from: defaultAccountAddress }); - expect(await contract.methods.get_counter(defaultAccountAddress).simulate({ from: defaultAccountAddress })).toEqual( - 12n, - ); + expect( + (await contract.methods.get_counter(defaultAccountAddress).simulate({ from: defaultAccountAddress })).result, + ).toEqual(12n); }); it('publicly deploys a contract with no constructor', async () => { logger.debug(`Deploying contract with no constructor`); - const contract = await NoConstructorContract.deploy(wallet).send({ from: defaultAccountAddress }); + const { contract } = await NoConstructorContract.deploy(wallet).send({ from: defaultAccountAddress }); const arbitraryValue = 42; logger.debug(`Call a public function to check that it was publicly deployed`); - const receipt = await contract.methods.emit_public(arbitraryValue).send({ from: defaultAccountAddress }); + const { receipt } = await contract.methods.emit_public(arbitraryValue).send({ from: defaultAccountAddress }); const logs = await aztecNode.getPublicLogs({ txHash: receipt.txHash }); expect(logs.logs[0].log.getEmittedFields()).toEqual([new Fr(arbitraryValue)]); }); @@ -162,7 +168,10 @@ describe('e2e_deploy_contract deploy method', () => { const publicCallTxPromise = publicCall.send({ from: defaultAccountAddress, wait: { timeout: 600 } }); logger.debug('Deploying a contract and calling a public function in the same block'); - const [deployTxReceipt, publicCallTxReceipt] = await Promise.all([deployTxPromise, publicCallTxPromise]); + const [{ receipt: deployTxReceipt }, { receipt: publicCallTxReceipt }] = await Promise.all([ + deployTxPromise, + publicCallTxPromise, + ]); expect(deployTxReceipt.blockNumber).toEqual(publicCallTxReceipt.blockNumber); }, 300_000); diff --git a/yarn-project/end-to-end/src/e2e_deploy_contract/legacy.test.ts b/yarn-project/end-to-end/src/e2e_deploy_contract/legacy.test.ts index 55f638be25f6..9d16aeb08bb0 100644 --- a/yarn-project/end-to-end/src/e2e_deploy_contract/legacy.test.ts +++ b/yarn-project/end-to-end/src/e2e_deploy_contract/legacy.test.ts @@ -36,7 +36,7 @@ describe('e2e_deploy_contract legacy', () => { deployer: defaultAccountAddress, }); const deployer = new ContractDeployer(TestContractArtifact, wallet); - const receipt = await deployer + const { receipt } = await deployer .deploy() .send({ from: defaultAccountAddress, contractAddressSalt: salt, wait: { returnReceipt: true } }); expect(receipt.contract.address).toEqual(deploymentData.address); @@ -65,7 +65,7 @@ describe('e2e_deploy_contract legacy', () => { for (let index = 0; index < 2; index++) { logger.info(`Deploying contract ${index + 1}...`); - const receipt = await deployer + const { receipt } = await deployer .deploy() .send({ from: defaultAccountAddress, contractAddressSalt: Fr.random(), wait: { returnReceipt: true } }); logger.info(`Sending TX to contract ${index + 1}...`); @@ -113,8 +113,8 @@ describe('e2e_deploy_contract legacy', () => { expect(goodTxPromiseResult.status).toBe('fulfilled'); expect(badTxReceiptResult.status).toBe('fulfilled'); // but reverted - const goodTxReceipt = goodTxPromiseResult.status === 'fulfilled' ? goodTxPromiseResult.value : null; - const badTxReceipt = badTxReceiptResult.status === 'fulfilled' ? badTxReceiptResult.value : null; + const goodTxReceipt = goodTxPromiseResult.status === 'fulfilled' ? goodTxPromiseResult.value.receipt : null; + const badTxReceipt = badTxReceiptResult.status === 'fulfilled' ? badTxReceiptResult.value.receipt : null; // Both the good and bad transactions are included expect(goodTxReceipt).toBeDefined(); diff --git a/yarn-project/end-to-end/src/e2e_deploy_contract/private_initialization.test.ts b/yarn-project/end-to-end/src/e2e_deploy_contract/private_initialization.test.ts index f1af8d9c9adf..a6814602e130 100644 --- a/yarn-project/end-to-end/src/e2e_deploy_contract/private_initialization.test.ts +++ b/yarn-project/end-to-end/src/e2e_deploy_contract/private_initialization.test.ts @@ -31,7 +31,7 @@ describe('e2e_deploy_contract private initialization', () => { // The function has a noinitcheck flag so it can be called without initialization. it('executes a noinitcheck function in an uninitialized contract', async () => { const contract = await t.registerContract(wallet, TestContract); - const receipt = await contract.methods.emit_nullifier(10).send({ from: defaultAccountAddress }); + const { receipt } = await contract.methods.emit_nullifier(10).send({ from: defaultAccountAddress }); const txEffects = await aztecNode.getTxEffect(receipt.txHash); const expected = await siloNullifier(contract.address, new Fr(10)); @@ -45,11 +45,11 @@ describe('e2e_deploy_contract private initialization', () => { const contract = await t.registerContract(wallet, NoConstructorContract); await expect( contract.methods.is_private_mutable_initialized(defaultAccountAddress).simulate({ from: defaultAccountAddress }), - ).resolves.toEqual(false); + ).resolves.toEqual(expect.objectContaining({ result: false })); await contract.methods.initialize_private_mutable(42).send({ from: defaultAccountAddress }); await expect( contract.methods.is_private_mutable_initialized(defaultAccountAddress).simulate({ from: defaultAccountAddress }), - ).resolves.toEqual(true); + ).resolves.toEqual(expect.objectContaining({ result: true })); }); // Tests privately initializing an undeployed contract. Also requires pxe registration in advance. @@ -60,10 +60,10 @@ describe('e2e_deploy_contract private initialization', () => { logger.info(`Calling the constructor for ${contract.address}`); await contract.methods.constructor(...initArgs).send({ from: defaultAccountAddress }); logger.info(`Checking if the constructor was run for ${contract.address}`); - expect(await contract.methods.summed_values(owner).simulate({ from: owner })).toEqual(42n); + expect((await contract.methods.summed_values(owner).simulate({ from: owner })).result).toEqual(42n); logger.info(`Calling a private function that requires initialization on ${contract.address}`); await contract.methods.create_note(owner, 10).send({ from: defaultAccountAddress }); - expect(await contract.methods.summed_values(owner).simulate({ from: owner })).toEqual(52n); + expect((await contract.methods.summed_values(owner).simulate({ from: owner })).result).toEqual(52n); }); // Tests privately initializing multiple undeployed contracts on the same tx through an account contract. @@ -75,8 +75,8 @@ describe('e2e_deploy_contract private initialization', () => { ); const calls = contracts.map((c, i) => c.methods.constructor(...initArgs[i])); await new BatchCall(wallet, calls).send({ from: defaultAccountAddress }); - expect(await contracts[0].methods.summed_values(owner).simulate({ from: owner })).toEqual(42n); - expect(await contracts[1].methods.summed_values(owner).simulate({ from: owner })).toEqual(52n); + expect((await contracts[0].methods.summed_values(owner).simulate({ from: owner })).result).toEqual(42n); + expect((await contracts[1].methods.summed_values(owner).simulate({ from: owner })).result).toEqual(52n); }); it('initializes and calls a private function in a single tx', async () => { @@ -89,7 +89,7 @@ describe('e2e_deploy_contract private initialization', () => { ]); logger.info(`Executing constructor and private function in batch at ${contract.address}`); await batch.send({ from: defaultAccountAddress }); - expect(await contract.methods.summed_values(owner).simulate({ from: owner })).toEqual(52n); + expect((await contract.methods.summed_values(owner).simulate({ from: owner })).result).toEqual(52n); }); it('refuses to initialize a contract twice', async () => { diff --git a/yarn-project/end-to-end/src/e2e_double_spend.test.ts b/yarn-project/end-to-end/src/e2e_double_spend.test.ts index 6b4e2053b380..3cc69dec717d 100644 --- a/yarn-project/end-to-end/src/e2e_double_spend.test.ts +++ b/yarn-project/end-to-end/src/e2e_double_spend.test.ts @@ -25,7 +25,7 @@ describe('e2e_double_spend', () => { logger, } = await setup(1)); - contract = await TestContract.deploy(wallet).send({ from: defaultAccountAddress }); + ({ contract } = await TestContract.deploy(wallet).send({ from: defaultAccountAddress })); logger.info(`Test contract deployed at ${contract.address}`); }); diff --git a/yarn-project/end-to-end/src/e2e_epochs/epochs_invalidate_block.parallel.test.ts b/yarn-project/end-to-end/src/e2e_epochs/epochs_invalidate_block.parallel.test.ts index e4740dde721d..220face468aa 100644 --- a/yarn-project/end-to-end/src/e2e_epochs/epochs_invalidate_block.parallel.test.ts +++ b/yarn-project/end-to-end/src/e2e_epochs/epochs_invalidate_block.parallel.test.ts @@ -238,7 +238,7 @@ describe('e2e_epochs/epochs_invalidate_block', () => { // Send a few transactions so the sequencer builds multiple blocks in the checkpoint // We'll later check that the first tx at least was picked up and mined logger.warn('Sending multiple transactions to trigger block building'); - const [sentTx] = await timesAsync(8, i => + const [{ txHash: sentTx }] = await timesAsync(8, i => testContract.methods.emit_nullifier(BigInt(i + 1)).send({ from: context.accounts[0], wait: NO_WAIT }), ); diff --git a/yarn-project/end-to-end/src/e2e_epochs/epochs_mbps.parallel.test.ts b/yarn-project/end-to-end/src/e2e_epochs/epochs_mbps.parallel.test.ts index 1917f419e9f4..3c6e274138d7 100644 --- a/yarn-project/end-to-end/src/e2e_epochs/epochs_mbps.parallel.test.ts +++ b/yarn-project/end-to-end/src/e2e_epochs/epochs_mbps.parallel.test.ts @@ -117,8 +117,8 @@ describe('e2e_epochs/epochs_mbps', () => { // Unlike emit_nullifier (which has #[noinitcheck]), cross-chain methods require a deployed contract. if (deployCrossChainContract) { logger.warn(`Deploying cross-chain test contract before stopping initial sequencer`); - crossChainContract = await TestContract.deploy(wallet).send({ from }); - logger.warn(`Cross-chain test contract deployed at ${crossChainContract.address}`); + ({ contract: crossChainContract } = await TestContract.deploy(wallet).send({ from })); + logger.warn(`Cross-chain test contract deployed at ${crossChainContract!.address}`); } // Halt block building in initial aztec node, which was not set up as a validator. @@ -356,7 +356,7 @@ describe('e2e_epochs/epochs_mbps', () => { l1ToL2Messages.map(async ({ msgHash }, i) => { logger.warn(`Waiting for L1→L2 message ${i + 1} to be ready`); await retryUntil( - () => isL1ToL2MessageReady(context.aztecNode, msgHash, { forPublicConsumption: true }), + () => isL1ToL2MessageReady(context.aztecNode, msgHash), `L1→L2 message ${i + 1} ready`, test.L2_SLOT_DURATION_IN_S * 5, ); diff --git a/yarn-project/end-to-end/src/e2e_epochs/epochs_proof_public_cross_chain.test.ts b/yarn-project/end-to-end/src/e2e_epochs/epochs_proof_public_cross_chain.test.ts index 846cd5f82b96..59a2788286b0 100644 --- a/yarn-project/end-to-end/src/e2e_epochs/epochs_proof_public_cross_chain.test.ts +++ b/yarn-project/end-to-end/src/e2e_epochs/epochs_proof_public_cross_chain.test.ts @@ -46,7 +46,7 @@ describe('e2e_epochs/epochs_proof_public_cross_chain', () => { // Deploy a contract that consumes L1 to L2 messages await context.aztecNodeAdmin.setConfig({ minTxsPerBlock: 0 }); logger.warn(`Deploying test contract`); - const testContract = await TestContract.deploy(context.wallet).send({ from: context.accounts[0] }); + const { contract: testContract } = await TestContract.deploy(context.wallet).send({ from: context.accounts[0] }); logger.warn(`Test contract deployed at ${testContract.address}`); // Send an l1 to l2 message to be consumed from the contract @@ -57,14 +57,13 @@ describe('e2e_epochs/epochs_proof_public_cross_chain', () => { logger.warn(`Waiting for message ${msgHash} with index ${globalLeafIndex} to be synced`); await waitForL1ToL2MessageReady(context.aztecNode, msgHash, { - forPublicConsumption: true, timeoutSeconds: test.L2_SLOT_DURATION_IN_S * 6, }); // And we consume the message using the test contract. It's important that we don't wait for the membership witness // to be available, since we want to test the scenario where the message becomes available on the same block the tx lands. logger.warn(`Consuming message ${message.content.toString()} from the contract at ${testContract.address}`); - const txReceipt = await testContract.methods + const { receipt: txReceipt } = await testContract.methods .consume_message_from_arbitrary_sender_public( message.content, secret, @@ -90,7 +89,7 @@ describe('e2e_epochs/epochs_proof_public_cross_chain', () => { expect(provenBlockNumber).toBeGreaterThanOrEqual(txReceipt.blockNumber!); // Should not be able to consume the message again. - const failedReceipt = await testContract.methods + const { receipt: failedReceipt } = await testContract.methods .consume_message_from_arbitrary_sender_public( message.content, secret, 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 d6efb8165374..c3051b449b5a 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 @@ -41,11 +41,17 @@ describe('e2e_escrow_contract', () => { const escrowDeployment = EscrowContract.deployWithPublicKeys(escrowPublicKeys, wallet, owner); const escrowInstance = await escrowDeployment.getInstance(); await wallet.registerContract(escrowInstance, EscrowContract.artifact, escrowSecretKey); - escrowContract = await escrowDeployment.send({ from: owner }); + // The contract constructor initializes private storage vars that need the contract's own nullifier key. + ({ contract: escrowContract } = await escrowDeployment.send({ + from: owner, + additionalScopes: [escrowInstance.address], + })); logger.info(`Escrow contract deployed at ${escrowContract.address}`); // Deploy Token contract and mint funds for the escrow contract - token = await TokenContract.deploy(wallet, owner, 'TokenName', 'TokenSymbol', 18).send({ from: owner }); + ({ contract: token } = await TokenContract.deploy(wallet, owner, 'TokenName', 'TokenSymbol', 18).send({ + from: owner, + })); await mintTokensToPrivate(token, owner, escrowContract.address, 100n); diff --git a/yarn-project/end-to-end/src/e2e_event_logs.test.ts b/yarn-project/end-to-end/src/e2e_event_logs.test.ts index 4645fc1dcc77..64d89526709f 100644 --- a/yarn-project/end-to-end/src/e2e_event_logs.test.ts +++ b/yarn-project/end-to-end/src/e2e_event_logs.test.ts @@ -47,7 +47,7 @@ describe('Logs', () => { await ensureAccountContractsPublished(wallet, [account1Address, account2Address]); log.warn(`Deploying test contract`); - testLogContract = await TestLogContract.deploy(wallet).send({ from: account1Address }); + ({ contract: testLogContract } = await TestLogContract.deploy(wallet).send({ from: account1Address })); }); afterAll(() => teardown()); @@ -57,9 +57,12 @@ describe('Logs', () => { const preimages = makeTuple(5, makeTuple.bind(undefined, 4, Fr.random)) as Tuple, 5>; const txs = await Promise.all( - preimages.map(preimage => - testLogContract.methods.emit_encrypted_events(account2Address, preimage).send({ from: account1Address }), - ), + preimages.map(async preimage => { + const { receipt } = await testLogContract.methods + .emit_encrypted_events(account2Address, preimage) + .send({ from: account1Address }); + return receipt; + }), ); const firstBlockNumber = Math.min(...txs.map(tx => tx.blockNumber!)); @@ -124,13 +127,13 @@ describe('Logs', () => { const preimage = makeTuple(5, makeTuple.bind(undefined, 4, Fr.random)) as Tuple, 5>; let i = 0; - const firstTx = await testLogContract.methods + const { receipt: firstTx } = await testLogContract.methods .emit_unencrypted_events(preimage[i]) .send({ from: account1Address }); await timesParallel(3, () => testLogContract.methods.emit_unencrypted_events(preimage[++i]).send({ from: account1Address }), ); - const lastTx = await testLogContract.methods + const { receipt: lastTx } = await testLogContract.methods .emit_unencrypted_events(preimage[++i]) .send({ from: account1Address }); @@ -181,7 +184,9 @@ describe('Logs', () => { const c = await AztecAddress.random(); const extra = Fr.random(); - const tx = await testLogContract.methods.emit_nested_event(a, b, c, extra).send({ from: account1Address }); + const { receipt: tx } = await testLogContract.methods + .emit_nested_event(a, b, c, extra) + .send({ from: account1Address }); const collectedEvents = await getPublicEvents( aztecNode, @@ -212,7 +217,7 @@ describe('Logs', () => { const tx1NumLogs = 10; { // Call the private function that emits two encrypted logs per call and recursively nests 4 times - const tx = await testLogContract.methods + const { receipt: tx } = await testLogContract.methods .emit_encrypted_events_nested(account2Address, 4) .send({ from: account1Address }); @@ -231,7 +236,7 @@ describe('Logs', () => { const tx2NumLogs = 6; { // Call the private function that emits two encrypted logs per call and recursively nests 2 times - const tx = await testLogContract.methods + const { receipt: tx } = await testLogContract.methods .emit_encrypted_events_nested(account2Address, 2) .send({ from: account1Address }); diff --git a/yarn-project/end-to-end/src/e2e_event_only.test.ts b/yarn-project/end-to-end/src/e2e_event_only.test.ts index 7dd0c3859451..d2b036f601a0 100644 --- a/yarn-project/end-to-end/src/e2e_event_only.test.ts +++ b/yarn-project/end-to-end/src/e2e_event_only.test.ts @@ -26,14 +26,16 @@ describe('EventOnly', () => { accounts: [defaultAccountAddress], } = await setup(1)); await ensureAccountContractsPublished(wallet, [defaultAccountAddress]); - eventOnlyContract = await EventOnlyContract.deploy(wallet).send({ from: defaultAccountAddress }); + ({ contract: eventOnlyContract } = await EventOnlyContract.deploy(wallet).send({ from: defaultAccountAddress })); }); afterAll(() => teardown()); it('emits and retrieves a private event for a contract with no notes', async () => { const value = Fr.random(); - const tx = await eventOnlyContract.methods.emit_event_for_msg_sender(value).send({ from: defaultAccountAddress }); + const { receipt: tx } = await eventOnlyContract.methods + .emit_event_for_msg_sender(value) + .send({ from: defaultAccountAddress }); const events = await wallet.getPrivateEvents(EventOnlyContract.events.TestEvent, { contractAddress: eventOnlyContract.address, diff --git a/yarn-project/end-to-end/src/e2e_expiration_timestamp.test.ts b/yarn-project/end-to-end/src/e2e_expiration_timestamp.test.ts index 78e3be77ee02..18240f671298 100644 --- a/yarn-project/end-to-end/src/e2e_expiration_timestamp.test.ts +++ b/yarn-project/end-to-end/src/e2e_expiration_timestamp.test.ts @@ -25,7 +25,7 @@ describe('e2e_expiration_timestamp', () => { aztecNode, accounts: [defaultAccountAddress], } = await setup()); - contract = await TestContract.deploy(wallet).send({ from: defaultAccountAddress }); + ({ contract } = await TestContract.deploy(wallet).send({ from: defaultAccountAddress })); }); afterAll(() => teardown()); diff --git a/yarn-project/end-to-end/src/e2e_fees/account_init.test.ts b/yarn-project/end-to-end/src/e2e_fees/account_init.test.ts index b93a429b2265..1323bd1ba00a 100644 --- a/yarn-project/end-to-end/src/e2e_fees/account_init.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/account_init.test.ts @@ -89,7 +89,7 @@ describe('e2e_fees account_init', () => { const [bobsInitialGas] = await t.getGasBalanceFn(bobsAddress); expect(bobsInitialGas).toEqual(mintAmount); - const tx = await bobsDeployMethod.send({ from: AztecAddress.ZERO, wait: { returnReceipt: true } }); + const { receipt: tx } = await bobsDeployMethod.send({ from: AztecAddress.ZERO, wait: { returnReceipt: true } }); expect(tx.transactionFee!).toBeGreaterThan(0n); await expect(t.getGasBalanceFn(bobsAddress)).resolves.toEqual([bobsInitialGas - tx.transactionFee!]); @@ -98,7 +98,7 @@ describe('e2e_fees account_init', () => { it('pays natively in the Fee Juice by bridging funds themselves', async () => { const claim = await t.feeJuiceBridgeTestHarness.prepareTokensOnL1(bobsAddress); const paymentMethod = new FeeJuicePaymentMethodWithClaim(bobsAddress, claim); - const tx = await bobsDeployMethod.send({ + const { receipt: tx } = await bobsDeployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod }, wait: { returnReceipt: true }, @@ -118,7 +118,7 @@ describe('e2e_fees account_init', () => { const maxFeesPerGas = (await aztecNode.getCurrentMinFees()).mul(1.5); const gasSettings = GasSettings.default({ maxFeesPerGas }); const paymentMethod = new PrivateFeePaymentMethod(bananaFPC.address, bobsAddress, wallet, gasSettings); - const tx = await bobsDeployMethod.send({ + const { receipt: tx } = await bobsDeployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod }, wait: { returnReceipt: true }, @@ -147,7 +147,7 @@ describe('e2e_fees account_init', () => { const maxFeesPerGas = (await aztecNode.getCurrentMinFees()).mul(1.5); const gasSettings = GasSettings.default({ maxFeesPerGas }); const paymentMethod = new PublicFeePaymentMethod(bananaFPC.address, bobsAddress, wallet, gasSettings); - const tx = await bobsDeployMethod.send({ + const { receipt: tx } = await bobsDeployMethod.send({ from: AztecAddress.ZERO, skipInstancePublication: false, fee: { paymentMethod }, @@ -180,7 +180,7 @@ describe('e2e_fees account_init', () => { await t.mintPrivateBananas(mintedBananas, bobsAddress); const [aliceBalanceBefore] = await t.getGasBalanceFn(aliceAddress); - const tx = await SchnorrAccountContractInterface.deployWithPublicKeys( + const { receipt: tx } = await SchnorrAccountContractInterface.deployWithPublicKeys( bobsPublicKeys, wallet, bobsSigningPubKey.x, diff --git a/yarn-project/end-to-end/src/e2e_fees/failures.test.ts b/yarn-project/end-to-end/src/e2e_fees/failures.test.ts index 0c4c54c5032c..440f1949acf8 100644 --- a/yarn-project/end-to-end/src/e2e_fees/failures.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/failures.test.ts @@ -84,7 +84,7 @@ describe('e2e_fees failures', () => { const currentSequencerRewards = await t.getCoinbaseSequencerRewards(); - const txReceipt = await bananaCoin.methods + const { receipt: txReceipt } = await bananaCoin.methods .transfer_in_public(aliceAddress, sequencerAddress, outrageousPublicAmountAliceDoesNotHave, 0) .send({ from: aliceAddress, @@ -187,7 +187,7 @@ describe('e2e_fees failures', () => { ); // if we skip simulation, it includes the failed TX - const txReceipt = await bananaCoin.methods + const { receipt: txReceipt } = await bananaCoin.methods .transfer_in_public(aliceAddress, sequencerAddress, outrageousPublicAmountAliceDoesNotHave, 0) .send({ from: aliceAddress, @@ -285,7 +285,7 @@ describe('e2e_fees failures', () => { }), ).rejects.toThrow(); - const receipt = await bananaCoin.methods + const { receipt } = await bananaCoin.methods .mint_to_public(aliceAddress, 1n) // random operation .send({ from: aliceAddress, diff --git a/yarn-project/end-to-end/src/e2e_fees/fee_juice_payments.test.ts b/yarn-project/end-to-end/src/e2e_fees/fee_juice_payments.test.ts index f90dfadf2e7c..16889aef501f 100644 --- a/yarn-project/end-to-end/src/e2e_fees/fee_juice_payments.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/fee_juice_payments.test.ts @@ -43,7 +43,9 @@ describe('e2e_fees Fee Juice payments', () => { describe('without initial funds', () => { beforeAll(async () => { - expect(await feeJuiceContract.methods.balance_of_public(bobAddress).simulate({ from: bobAddress })).toEqual(0n); + expect( + (await feeJuiceContract.methods.balance_of_public(bobAddress).simulate({ from: bobAddress })).result, + ).toEqual(0n); }); it('fails to simulate a tx', async () => { @@ -63,10 +65,12 @@ describe('e2e_fees Fee Juice payments', () => { it('claims bridged funds and pays with them on the same tx', async () => { const claim = await t.feeJuiceBridgeTestHarness.prepareTokensOnL1(bobAddress); const paymentMethod = new FeeJuicePaymentMethodWithClaim(bobAddress, claim); - const receipt = await feeJuiceContract.methods + const { receipt } = await feeJuiceContract.methods .check_balance(0n) .send({ from: bobAddress, fee: { gasSettings, paymentMethod } }); - const endBalance = await feeJuiceContract.methods.balance_of_public(bobAddress).simulate({ from: bobAddress }); + const { result: endBalance } = await feeJuiceContract.methods + .balance_of_public(bobAddress) + .simulate({ from: bobAddress }); expect(endBalance).toBeGreaterThan(0n); expect(endBalance).toBeLessThan(claim.claimAmount); @@ -76,28 +80,30 @@ describe('e2e_fees Fee Juice payments', () => { describe('with initial funds', () => { it('sends tx with payment in Fee Juice with public calls', async () => { - const initialBalance = await feeJuiceContract.methods + const { result: initialBalance } = await feeJuiceContract.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); - const { transactionFee } = await bananaCoin.methods + const { + receipt: { transactionFee }, + } = await bananaCoin.methods .transfer_in_public(aliceAddress, bobAddress, 1n, 0n) .send({ fee: { gasSettings }, from: aliceAddress }); expect(transactionFee).toBeGreaterThan(0n); - const endBalance = await feeJuiceContract.methods + const { result: endBalance } = await feeJuiceContract.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); expect(endBalance).toBeLessThan(initialBalance); }); it('sends tx fee payment in Fee Juice with no public calls', async () => { - const initialBalance = await feeJuiceContract.methods + const { result: initialBalance } = await feeJuiceContract.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); - const { transactionFee } = await bananaCoin.methods - .transfer(bobAddress, 1n) - .send({ fee: { gasSettings }, from: aliceAddress }); + const { + receipt: { transactionFee }, + } = await bananaCoin.methods.transfer(bobAddress, 1n).send({ fee: { gasSettings }, from: aliceAddress }); expect(transactionFee).toBeGreaterThan(0n); - const endBalance = await feeJuiceContract.methods + const { result: endBalance } = await feeJuiceContract.methods .balance_of_public(aliceAddress) .simulate({ from: aliceAddress }); expect(endBalance).toBeLessThan(initialBalance); diff --git a/yarn-project/end-to-end/src/e2e_fees/fee_settings.test.ts b/yarn-project/end-to-end/src/e2e_fees/fee_settings.test.ts index 5ac0ec444727..83fe2d1b837b 100644 --- a/yarn-project/end-to-end/src/e2e_fees/fee_settings.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/fee_settings.test.ts @@ -26,7 +26,7 @@ describe('e2e_fees fee settings', () => { await t.setup(); ({ aliceAddress, wallet, gasSettings, cheatCodes, aztecNode } = t); - testContract = await TestContract.deploy(wallet).send({ from: aliceAddress }); + ({ contract: testContract } = await TestContract.deploy(wallet).send({ from: aliceAddress })); gasSettings = { ...gasSettings, maxFeesPerGas: undefined }; }); diff --git a/yarn-project/end-to-end/src/e2e_fees/fees_test.ts b/yarn-project/end-to-end/src/e2e_fees/fees_test.ts index d810c896ffc1..a4019b3b1c45 100644 --- a/yarn-project/end-to-end/src/e2e_fees/fees_test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/fees_test.ts @@ -14,12 +14,16 @@ import { AppSubscriptionContract } from '@aztec/noir-contracts.js/AppSubscriptio import { FPCContract } from '@aztec/noir-contracts.js/FPC'; import { FeeJuiceContract } from '@aztec/noir-contracts.js/FeeJuice'; import { SponsoredFPCContract } from '@aztec/noir-contracts.js/SponsoredFPC'; -import { TokenContract as BananaCoin } from '@aztec/noir-contracts.js/Token'; +import { TokenContract as BananaCoin, TokenContractArtifact } from '@aztec/noir-contracts.js/Token'; import { CounterContract } from '@aztec/noir-test-contracts.js/Counter'; import { ProtocolContractAddress } from '@aztec/protocol-contracts'; import { getCanonicalFeeJuice } from '@aztec/protocol-contracts/fee-juice'; +import { FunctionSelector, countArgumentsSize } from '@aztec/stdlib/abi'; +import type { FunctionAbi } from '@aztec/stdlib/abi'; +import { getContractClassFromArtifact } from '@aztec/stdlib/contract'; import { GasSettings } from '@aztec/stdlib/gas'; import type { AztecNodeAdmin } from '@aztec/stdlib/interfaces/client'; +import type { AllowedElement } from '@aztec/stdlib/interfaces/server'; import { getContract } from 'viem'; @@ -37,6 +41,46 @@ import { type BalancesFn, getBalancesFn, setupSponsoredFPC } from '../fixtures/u import { FeeJuicePortalTestingHarnessFactory, type GasBridgingTestHarness } from '../shared/gas_portal_test_harness.js'; import { TestWallet } from '../test-wallet/test_wallet.js'; +/** Returns the calldata length for a function: 1 (selector) + arguments size. */ +function getCalldataLength(functionName: string): number { + const allFunctions: FunctionAbi[] = (TokenContractArtifact.functions as FunctionAbi[]).concat( + TokenContractArtifact.nonDispatchPublicFunctions || [], + ); + const fn = allFunctions.find(f => f.name === functionName); + if (!fn) { + throw new Error(`Unknown function ${functionName} in Token artifact`); + } + return 1 + countArgumentsSize(fn); +} + +/** + * Returns Token-specific allowlist entries needed for FPC-based fee payments. + * These are test-only — FPC-based fee payment with custom tokens won't work on mainnet alpha. + */ +async function getTokenAllowedSetupFunctions(): Promise { + const tokenClassId = (await getContractClassFromArtifact(TokenContractArtifact)).id; + const increaseBalanceSelector = await FunctionSelector.fromSignature('_increase_public_balance((Field),u128)'); + const transferInPublicSelector = await FunctionSelector.fromSignature( + 'transfer_in_public((Field),(Field),u128,Field)', + ); + + return [ + // Token: needed for private transfers via FPC (transfer_to_public enqueues this) + { + classId: tokenClassId, + selector: increaseBalanceSelector, + calldataLength: getCalldataLength('_increase_public_balance'), + onlySelf: true, + }, + // Token: needed for public transfers via FPC (fee_entrypoint_public enqueues this) + { + classId: tokenClassId, + selector: transferInPublicSelector, + calldataLength: getCalldataLength('transfer_in_public'), + }, + ]; +} + /** * Test fixture for testing fees. Provides the following setup steps: * InitialAccounts: Initializes 3 Schnorr account contracts. @@ -104,12 +148,15 @@ export class FeesTest { async setup() { this.logger.verbose('Setting up fresh context...'); + // Token allowlist entries are test-only: FPC-based fee payment with custom tokens won't work on mainnet alpha. + const tokenAllowList = await getTokenAllowedSetupFunctions(); this.context = await setup(0, { startProverNode: true, ...this.setupOptions, fundSponsoredFPC: true, skipAccountDeployment: true, l1ContractsArgs: { ...this.setupOptions }, + txPublicSetupAllowListExtend: [...(this.setupOptions.txPublicSetupAllowListExtend ?? []), ...tokenAllowList], }); this.rollupContract = RollupContract.getFromConfig(this.context.config); @@ -157,11 +204,15 @@ export class FeesTest { /** Alice mints bananaCoin tokens privately to the target address and redeems them. */ async mintPrivateBananas(amount: bigint, address: AztecAddress) { - const balanceBefore = await this.bananaCoin.methods.balance_of_private(address).simulate({ from: address }); + const { result: balanceBefore } = await this.bananaCoin.methods + .balance_of_private(address) + .simulate({ from: address }); await mintTokensToPrivate(this.bananaCoin, this.aliceAddress, address, amount); - const balanceAfter = await this.bananaCoin.methods.balance_of_private(address).simulate({ from: address }); + const { result: balanceAfter } = await this.bananaCoin.methods + .balance_of_private(address) + .simulate({ from: address }); expect(balanceAfter).toEqual(balanceBefore + amount); } @@ -223,7 +274,7 @@ export class FeesTest { async applyDeployBananaToken() { this.logger.info('Applying deploy banana token setup'); - const bananaCoin = await BananaCoin.deploy(this.wallet, this.aliceAddress, 'BC', 'BC', 18n).send({ + const { contract: bananaCoin } = await BananaCoin.deploy(this.wallet, this.aliceAddress, 'BC', 'BC', 18n).send({ from: this.aliceAddress, }); this.logger.info(`BananaCoin deployed at ${bananaCoin.address}`); @@ -244,7 +295,7 @@ export class FeesTest { expect((await this.wallet.getContractMetadata(feeJuiceContract.address)).isContractPublished).toBe(true); const bananaCoin = this.bananaCoin; - const bananaFPC = await FPCContract.deploy(this.wallet, bananaCoin.address, this.fpcAdmin).send({ + const { contract: bananaFPC } = await FPCContract.deploy(this.wallet, bananaCoin.address, this.fpcAdmin).send({ from: this.aliceAddress, }); diff --git a/yarn-project/end-to-end/src/e2e_fees/gas_estimation.test.ts b/yarn-project/end-to-end/src/e2e_fees/gas_estimation.test.ts index 417cdb495cd0..b456cd167784 100644 --- a/yarn-project/end-to-end/src/e2e_fees/gas_estimation.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/gas_estimation.test.ts @@ -88,9 +88,13 @@ describe('e2e_fees gas_estimation', () => { paymentMethod?: FeePaymentMethod, ) => Promise.all( - [GasSettings.from({ ...gasSettings, ...limits }), gasSettings].map(gasSettings => - makeTransferRequest().send({ from: aliceAddress, fee: { gasSettings, paymentMethod } }), - ), + [GasSettings.from({ ...gasSettings, ...limits }), gasSettings].map(async gasSettings => { + const { receipt } = await makeTransferRequest().send({ + from: aliceAddress, + fee: { gasSettings, paymentMethod }, + }); + return receipt; + }), ); const logGasEstimate = (estimatedGas: Pick) => @@ -100,10 +104,11 @@ describe('e2e_fees gas_estimation', () => { }); it('estimates gas with Fee Juice payment method', async () => { - const { estimatedGas } = await makeTransferRequest().simulate({ + const sim = await makeTransferRequest().simulate({ from: aliceAddress, fee: { gasSettings, estimateGas: true, estimatedGasPadding: 0 }, }); + const estimatedGas = sim.estimatedGas!; logGasEstimate(estimatedGas); const sequencer = t.context.sequencer!.getSequencer(); @@ -143,10 +148,11 @@ describe('e2e_fees gas_estimation', () => { ); const paymentMethod = new PublicFeePaymentMethod(bananaFPC.address, aliceAddress, wallet, gasSettingsForEstimation); - const { estimatedGas } = await makeTransferRequest().simulate({ + const sim2 = await makeTransferRequest().simulate({ from: aliceAddress, fee: { paymentMethod, estimatedGasPadding: 0, estimateGas: true }, }); + const estimatedGas = sim2.estimatedGas!; logGasEstimate(estimatedGas); const [withEstimate, withoutEstimate] = await sendTransfers(estimatedGas, paymentMethod); @@ -184,7 +190,7 @@ describe('e2e_fees gas_estimation', () => { }; }; - const { estimatedGas } = await deployMethod().simulate({ + const sim3 = await deployMethod().simulate({ from: aliceAddress, skipClassPublication: true, fee: { @@ -192,12 +198,13 @@ describe('e2e_fees gas_estimation', () => { estimatedGasPadding: 0, }, }); + const estimatedGas = sim3.estimatedGas!; logGasEstimate(estimatedGas); - const [withEstimate, withoutEstimate] = (await Promise.all([ + const [{ receipt: withEstimate }, { receipt: withoutEstimate }] = (await Promise.all([ deployMethod().send(deployOpts(estimatedGas)), deployMethod().send(deployOpts()), - ])) as unknown as DeployTxReceipt[]; + ])) as unknown as { receipt: DeployTxReceipt }[]; // Estimation should yield that teardown has no cost, so should send the tx with zero for teardown expect(withEstimate.transactionFee!).toEqual(withoutEstimate.transactionFee!); diff --git a/yarn-project/end-to-end/src/e2e_fees/private_payments.test.ts b/yarn-project/end-to-end/src/e2e_fees/private_payments.test.ts index ebb72b1f9b3e..9563d02815da 100644 --- a/yarn-project/end-to-end/src/e2e_fees/private_payments.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/private_payments.test.ts @@ -155,7 +155,7 @@ describe('e2e_fees private_payment', () => { * increase Alice's private banana balance by feeAmount by finalizing partial note */ const newlyMintedBananas = 10n; - const tx = await bananaCoin.methods.mint_to_private(aliceAddress, newlyMintedBananas).send({ + const { receipt: tx } = await bananaCoin.methods.mint_to_private(aliceAddress, newlyMintedBananas).send({ from: aliceAddress, fee: { paymentMethod: new PrivateFeePaymentMethod(bananaFPC.address, aliceAddress, wallet, gasSettings), @@ -200,12 +200,14 @@ describe('e2e_fees private_payment', () => { * increase Alice's private banana balance by feeAmount by finalizing partial note */ const amountTransferredToPrivate = 1n; - const tx = await bananaCoin.methods.transfer_to_private(aliceAddress, amountTransferredToPrivate).send({ - from: aliceAddress, - fee: { - paymentMethod: new PrivateFeePaymentMethod(bananaFPC.address, aliceAddress, wallet, gasSettings), - }, - }); + const { receipt: tx } = await bananaCoin.methods + .transfer_to_private(aliceAddress, amountTransferredToPrivate) + .send({ + from: aliceAddress, + fee: { + paymentMethod: new PrivateFeePaymentMethod(bananaFPC.address, aliceAddress, wallet, gasSettings), + }, + }); const feeAmount = tx.transactionFee!; @@ -249,7 +251,7 @@ describe('e2e_fees private_payment', () => { * increase sequencer/fee recipient/FPC admin private banana balance by feeAmount by finalizing partial note * increase Alice's private banana balance by feeAmount by finalizing partial note */ - const tx = await new BatchCall(wallet, [ + const { receipt: tx } = await new BatchCall(wallet, [ bananaCoin.methods.transfer(bobAddress, amountTransferredInPrivate), bananaCoin.methods.transfer_to_private(aliceAddress, amountTransferredToPrivate), ]).send({ @@ -283,7 +285,7 @@ describe('e2e_fees private_payment', () => { it('rejects txs that dont have enough balance to cover gas costs', async () => { // deploy a copy of bananaFPC but don't fund it! - const bankruptFPC = await FPCContract.deploy(wallet, bananaCoin.address, aliceAddress).send({ + const { contract: bankruptFPC } = await FPCContract.deploy(wallet, bananaCoin.address, aliceAddress).send({ from: aliceAddress, }); diff --git a/yarn-project/end-to-end/src/e2e_fees/public_payments.test.ts b/yarn-project/end-to-end/src/e2e_fees/public_payments.test.ts index 51e9109a90dc..b059938777db 100644 --- a/yarn-project/end-to-end/src/e2e_fees/public_payments.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/public_payments.test.ts @@ -59,12 +59,14 @@ describe('e2e_fees public_payment', () => { it('pays fees for tx that make public transfer', async () => { const bananasToSendToBob = 10n; - const tx = await bananaCoin.methods.transfer_in_public(aliceAddress, bobAddress, bananasToSendToBob, 0).send({ - from: aliceAddress, - fee: { - paymentMethod: new PublicFeePaymentMethod(bananaFPC.address, aliceAddress, wallet, gasSettings), - }, - }); + const { receipt: tx } = await bananaCoin.methods + .transfer_in_public(aliceAddress, bobAddress, bananasToSendToBob, 0) + .send({ + from: aliceAddress, + fee: { + paymentMethod: new PublicFeePaymentMethod(bananaFPC.address, aliceAddress, wallet, gasSettings), + }, + }); const feeAmount = tx.transactionFee!; diff --git a/yarn-project/end-to-end/src/e2e_fees/sponsored_payments.test.ts b/yarn-project/end-to-end/src/e2e_fees/sponsored_payments.test.ts index aac9aaa557ee..ec9726d9d129 100644 --- a/yarn-project/end-to-end/src/e2e_fees/sponsored_payments.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/sponsored_payments.test.ts @@ -56,13 +56,15 @@ describe('e2e_fees sponsored_public_payment', () => { it('pays fees for tx that makes a public transfer', async () => { // docs:start:sponsored_fpc_simple const bananasToSendToBob = 10n; - const tx = await bananaCoin.methods.transfer_in_public(aliceAddress, bobAddress, bananasToSendToBob, 0).send({ - from: aliceAddress, - fee: { - gasSettings, - paymentMethod: new SponsoredFeePaymentMethod(sponsoredFPC.address), - }, - }); + const { receipt: tx } = await bananaCoin.methods + .transfer_in_public(aliceAddress, bobAddress, bananasToSendToBob, 0) + .send({ + from: aliceAddress, + fee: { + gasSettings, + paymentMethod: new SponsoredFeePaymentMethod(sponsoredFPC.address), + }, + }); // docs:end:sponsored_fpc_simple const feeAmount = tx.transactionFee!; diff --git a/yarn-project/end-to-end/src/e2e_kernelless_simulation.test.ts b/yarn-project/end-to-end/src/e2e_kernelless_simulation.test.ts index d424b78be5f1..4cdd919dacad 100644 --- a/yarn-project/end-to-end/src/e2e_kernelless_simulation.test.ts +++ b/yarn-project/end-to-end/src/e2e_kernelless_simulation.test.ts @@ -51,9 +51,9 @@ describe('Kernelless simulation', () => { ({ contract: token1 } = await deployToken(wallet, adminAddress, 0n, logger)); ({ contract: liquidityToken } = await deployToken(wallet, adminAddress, 0n, logger)); - amm = await AMMContract.deploy(wallet, token0.address, token1.address, liquidityToken.address).send({ + ({ contract: amm } = await AMMContract.deploy(wallet, token0.address, token1.address, liquidityToken.address).send({ from: adminAddress, - }); + })); await liquidityToken.methods.set_minter(amm.address, true).send({ from: adminAddress }); @@ -75,15 +75,15 @@ describe('Kernelless simulation', () => { async function getWalletBalances(lpAddress: AztecAddress): Promise { return { - token0: await token0.methods.balance_of_private(lpAddress).simulate({ from: lpAddress }), - token1: await token1.methods.balance_of_private(lpAddress).simulate({ from: lpAddress }), + token0: (await token0.methods.balance_of_private(lpAddress).simulate({ from: lpAddress })).result, + token1: (await token1.methods.balance_of_private(lpAddress).simulate({ from: lpAddress })).result, }; } async function getAmmBalances(): Promise { return { - token0: await token0.methods.balance_of_public(amm.address).simulate({ from: adminAddress }), - token1: await token1.methods.balance_of_public(amm.address).simulate({ from: adminAddress }), + token0: (await token0.methods.balance_of_public(amm.address).simulate({ from: adminAddress })).result, + token1: (await token1.methods.balance_of_public(amm.address).simulate({ from: adminAddress })).result, }; } @@ -224,7 +224,7 @@ describe('Kernelless simulation', () => { const nonceForAuthwits = Fr.random(); - const amountOutMin = await amm.methods + const { result: amountOutMin } = await amm.methods .get_amount_out_for_exact_in(ammBalancesBefore.token0, ammBalancesBefore.token1, amountIn) .simulate({ from: swapperAddress }); @@ -237,10 +237,12 @@ describe('Kernelless simulation', () => { ); wallet.enableSimulatedSimulations(); - const { estimatedGas: swapKernellessGas } = await swapExactTokensInteraction.simulate({ - from: swapperAddress, - includeMetadata: true, - }); + const swapKernellessGas = ( + await swapExactTokensInteraction.simulate({ + from: swapperAddress, + includeMetadata: true, + }) + ).estimatedGas!; const swapAuthwit = await wallet.createAuthWit(swapperAddress, { caller: amm.address, @@ -248,11 +250,13 @@ describe('Kernelless simulation', () => { }); wallet.disableSimulatedSimulations(); - const { estimatedGas: swapWithKernelsGas } = await swapExactTokensInteraction.simulate({ - from: swapperAddress, - includeMetadata: true, - authWitnesses: [swapAuthwit], - }); + const swapWithKernelsGas = ( + await swapExactTokensInteraction.simulate({ + from: swapperAddress, + includeMetadata: true, + authWitnesses: [swapAuthwit], + }) + ).estimatedGas!; logger.info(`Kernelless gas: L2=${swapKernellessGas.gasLimits.l2Gas} DA=${swapKernellessGas.gasLimits.daGas}`); logger.info( @@ -268,7 +272,9 @@ describe('Kernelless simulation', () => { let pendingNoteHashesContract: PendingNoteHashesContract; beforeAll(async () => { - pendingNoteHashesContract = await PendingNoteHashesContract.deploy(wallet).send({ from: adminAddress }); + ({ contract: pendingNoteHashesContract } = await PendingNoteHashesContract.deploy(wallet).send({ + from: adminAddress, + })); }); it('squashing produces same gas estimates as with-kernels path', async () => { @@ -283,16 +289,20 @@ describe('Kernelless simulation', () => { ); wallet.enableSimulatedSimulations(); - const { estimatedGas: kernellessGas } = await interaction.simulate({ - from: adminAddress, - includeMetadata: true, - }); + const kernellessGas = ( + await interaction.simulate({ + from: adminAddress, + includeMetadata: true, + }) + ).estimatedGas!; wallet.disableSimulatedSimulations(); - const { estimatedGas: withKernelsGas } = await interaction.simulate({ - from: adminAddress, - includeMetadata: true, - }); + const withKernelsGas = ( + await interaction.simulate({ + from: adminAddress, + includeMetadata: true, + }) + ).estimatedGas!; logger.info(`Kernelless gas: L2=${kernellessGas.gasLimits.l2Gas} DA=${kernellessGas.gasLimits.daGas}`); logger.info(`With kernels gas: L2=${withKernelsGas.gasLimits.l2Gas} DA=${withKernelsGas.gasLimits.daGas}`); @@ -306,7 +316,9 @@ describe('Kernelless simulation', () => { let pendingNoteHashesContract: PendingNoteHashesContract; beforeAll(async () => { - pendingNoteHashesContract = await PendingNoteHashesContract.deploy(wallet).send({ from: adminAddress }); + ({ contract: pendingNoteHashesContract } = await PendingNoteHashesContract.deploy(wallet).send({ + from: adminAddress, + })); }); it('verifies settled read requests against the note hash tree', async () => { diff --git a/yarn-project/end-to-end/src/e2e_keys.test.ts b/yarn-project/end-to-end/src/e2e_keys.test.ts index 82ecf79ca366..9e56bb423b87 100644 --- a/yarn-project/end-to-end/src/e2e_keys.test.ts +++ b/yarn-project/end-to-end/src/e2e_keys.test.ts @@ -45,7 +45,7 @@ describe('Keys', () => { initialFundedAccounts, } = await setup(1)); - testContract = await TestContract.deploy(wallet).send({ from: defaultAccountAddress }); + ({ contract: testContract } = await TestContract.deploy(wallet).send({ from: defaultAccountAddress })); secret = initialFundedAccounts[0].secret; }); @@ -125,7 +125,7 @@ describe('Keys', () => { const expectedOvskApp = await computeAppSecretKey(ovskM, testContract.address, 'ov'); // Get the ovsk_app via the test contract - const ovskAppBigInt = await testContract.methods + const { result: ovskAppBigInt } = await testContract.methods .get_ovsk_app(ovpkMHash) .simulate({ from: defaultAccountAddress }); const ovskApp = new Fr(ovskAppBigInt); 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 5c143383ebb1..22c1964fe1f8 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 @@ -39,11 +39,13 @@ describe('e2e_lending_contract', () => { const deployContracts = async () => { logger.info(`Deploying price feed contract...`); - const priceFeedContract = await PriceFeedContract.deploy(wallet).send({ from: defaultAccountAddress }); + const { contract: priceFeedContract } = await PriceFeedContract.deploy(wallet).send({ + from: defaultAccountAddress, + }); logger.info(`Price feed deployed to ${priceFeedContract.address}`); logger.info(`Deploying collateral asset feed contract...`); - const collateralAsset = await TokenContract.deploy( + const { contract: collateralAsset } = await TokenContract.deploy( wallet, defaultAccountAddress, 'TokenName', @@ -53,13 +55,19 @@ describe('e2e_lending_contract', () => { logger.info(`Collateral asset deployed to ${collateralAsset.address}`); logger.info(`Deploying stable coin contract...`); - const stableCoin = await TokenContract.deploy(wallet, defaultAccountAddress, 'TokenName', 'TokenSymbol', 18).send({ + const { contract: stableCoin } = await TokenContract.deploy( + wallet, + defaultAccountAddress, + 'TokenName', + 'TokenSymbol', + 18, + ).send({ from: defaultAccountAddress, }); logger.info(`Stable coin asset deployed to ${stableCoin.address}`); logger.info(`Deploying L2 public contract...`); - const lendingContract = await LendingContract.deploy(wallet).send({ from: defaultAccountAddress }); + const { contract: lendingContract } = await LendingContract.deploy(wallet).send({ from: defaultAccountAddress }); logger.info(`CDP deployed at ${lendingContract.address}`); await collateralAsset.methods.set_minter(lendingContract.address, true).send({ from: defaultAccountAddress }); diff --git a/yarn-project/end-to-end/src/e2e_mempool_limit.test.ts b/yarn-project/end-to-end/src/e2e_mempool_limit.test.ts index a158c05540f7..bb9df36ca251 100644 --- a/yarn-project/end-to-end/src/e2e_mempool_limit.test.ts +++ b/yarn-project/end-to-end/src/e2e_mempool_limit.test.ts @@ -29,9 +29,9 @@ describe('e2e_mempool_limit', () => { throw new Error('Aztec node admin API must be available for this test'); } - token = await TokenContract.deploy(wallet, defaultAccountAddress, 'TEST', 'T', 18).send({ + ({ contract: token } = await TokenContract.deploy(wallet, defaultAccountAddress, 'TEST', 'T', 18).send({ from: defaultAccountAddress, - }); + })); await token.methods.mint_to_public(defaultAccountAddress, 10n ** 18n).send({ from: defaultAccountAddress }); }); diff --git a/yarn-project/end-to-end/src/e2e_multi_validator/e2e_multi_validator_node.test.ts b/yarn-project/end-to-end/src/e2e_multi_validator/e2e_multi_validator_node.test.ts index 35dccd29e152..47ec290401f2 100644 --- a/yarn-project/end-to-end/src/e2e_multi_validator/e2e_multi_validator_node.test.ts +++ b/yarn-project/end-to-end/src/e2e_multi_validator/e2e_multi_validator_node.test.ts @@ -114,7 +114,7 @@ describe('e2e_multi_validator_node', () => { const sender = ownerAddress; logger.info(`Deploying contract from ${sender}`); - const tx = await deployer.deploy(ownerAddress, sender, 1).send({ + const { receipt: tx } = await deployer.deploy(ownerAddress, sender, 1).send({ from: ownerAddress, contractAddressSalt: new Fr(BigInt(1)), skipClassPublication: true, @@ -177,7 +177,7 @@ describe('e2e_multi_validator_node', () => { logger.info(`Deploying contract from ${sender}`); const deployer = new ContractDeployer(artifact, wallet); - const tx = await deployer.deploy(ownerAddress, sender, 1).send({ + const { receipt: tx } = await deployer.deploy(ownerAddress, sender, 1).send({ from: ownerAddress, contractAddressSalt: new Fr(BigInt(1)), skipClassPublication: true, diff --git a/yarn-project/end-to-end/src/e2e_multiple_blobs.test.ts b/yarn-project/end-to-end/src/e2e_multiple_blobs.test.ts index e31f66cfb61d..38d8ce4ee3f4 100644 --- a/yarn-project/end-to-end/src/e2e_multiple_blobs.test.ts +++ b/yarn-project/end-to-end/src/e2e_multiple_blobs.test.ts @@ -38,7 +38,7 @@ describe('e2e_multiple_blobs', () => { } = await setup(1)); aztecNodeAdmin = maybeAztecNodeAdmin!; - contract = await TestContract.deploy(wallet).send({ from: defaultAccountAddress }); + ({ contract } = await TestContract.deploy(wallet).send({ from: defaultAccountAddress })); }); afterAll(() => teardown()); @@ -71,10 +71,10 @@ describe('e2e_multiple_blobs', () => { expect(provenTxs.length).toBe(TX_COUNT); // Send them simultaneously to be picked up by the sequencer - const txHashes = await Promise.all(provenTxs.map(tx => tx.send({ from: defaultAccountAddress, wait: NO_WAIT }))); + const sendResults = await Promise.all(provenTxs.map(tx => tx.send({ from: defaultAccountAddress, wait: NO_WAIT }))); // Wait for all to be mined const receipts = await Promise.all( - txHashes.map(txHash => { + sendResults.map(({ txHash }) => { return waitForTx(aztecNode, txHash); }), ); diff --git a/yarn-project/end-to-end/src/e2e_nested_contract/importer.test.ts b/yarn-project/end-to-end/src/e2e_nested_contract/importer.test.ts index 27449075dfe9..aab6d6fb59c4 100644 --- a/yarn-project/end-to-end/src/e2e_nested_contract/importer.test.ts +++ b/yarn-project/end-to-end/src/e2e_nested_contract/importer.test.ts @@ -15,8 +15,8 @@ describe('e2e_nested_contract manual', () => { }); beforeEach(async () => { - importerContract = await ImportTestContract.deploy(wallet).send({ from: defaultAccountAddress }); - testContract = await TestContract.deploy(wallet).send({ from: defaultAccountAddress }); + ({ contract: importerContract } = await ImportTestContract.deploy(wallet).send({ from: defaultAccountAddress })); + ({ contract: testContract } = await TestContract.deploy(wallet).send({ from: defaultAccountAddress })); }); afterAll(async () => { diff --git a/yarn-project/end-to-end/src/e2e_nested_contract/manual_private_enqueue.test.ts b/yarn-project/end-to-end/src/e2e_nested_contract/manual_private_enqueue.test.ts index ac9512f43421..0e19664a5662 100644 --- a/yarn-project/end-to-end/src/e2e_nested_contract/manual_private_enqueue.test.ts +++ b/yarn-project/end-to-end/src/e2e_nested_contract/manual_private_enqueue.test.ts @@ -19,8 +19,8 @@ describe('e2e_nested_contract manual_enqueue', () => { }); beforeEach(async () => { - parentContract = await ParentContract.deploy(wallet).send({ from: defaultAccountAddress }); - childContract = await ChildContract.deploy(wallet).send({ from: defaultAccountAddress }); + ({ contract: parentContract } = await ParentContract.deploy(wallet).send({ from: defaultAccountAddress })); + ({ contract: childContract } = await ChildContract.deploy(wallet).send({ from: defaultAccountAddress })); }); afterAll(async () => { diff --git a/yarn-project/end-to-end/src/e2e_nested_contract/manual_public.test.ts b/yarn-project/end-to-end/src/e2e_nested_contract/manual_public.test.ts index 3b47116fcb96..a699ea8f1764 100644 --- a/yarn-project/end-to-end/src/e2e_nested_contract/manual_public.test.ts +++ b/yarn-project/end-to-end/src/e2e_nested_contract/manual_public.test.ts @@ -48,7 +48,7 @@ describe('e2e_nested_contract manual', () => { parentContract.methods.enqueue_call_to_child(childContract.address, pubSetValueSelector, 40n), ]; - const tx = await new BatchCall(wallet, actions).send({ from: defaultAccountAddress }); + const { receipt: tx } = await new BatchCall(wallet, actions).send({ from: defaultAccountAddress }); const extendedLogs = ( await aztecNode.getPublicLogs({ fromBlock: tx.blockNumber!, diff --git a/yarn-project/end-to-end/src/e2e_nested_contract/nested_contract_test.ts b/yarn-project/end-to-end/src/e2e_nested_contract/nested_contract_test.ts index 5632daf6c133..769db81c1ba0 100644 --- a/yarn-project/end-to-end/src/e2e_nested_contract/nested_contract_test.ts +++ b/yarn-project/end-to-end/src/e2e_nested_contract/nested_contract_test.ts @@ -65,9 +65,11 @@ export class NestedContractTest { async applyManual() { this.logger.info('Deploying parent and child contracts'); - const parentContract = await ParentContract.deploy(this.wallet).send({ from: this.defaultAccountAddress }); - const childContract = await ChildContract.deploy(this.wallet).send({ from: this.defaultAccountAddress }); - this.parentContract = parentContract; - this.childContract = childContract; + ({ contract: this.parentContract } = await ParentContract.deploy(this.wallet).send({ + from: this.defaultAccountAddress, + })); + ({ contract: this.childContract } = await ChildContract.deploy(this.wallet).send({ + from: this.defaultAccountAddress, + })); } } diff --git a/yarn-project/end-to-end/src/e2e_nft.test.ts b/yarn-project/end-to-end/src/e2e_nft.test.ts index 35a2e8c7310d..7e7f8da1079a 100644 --- a/yarn-project/end-to-end/src/e2e_nft.test.ts +++ b/yarn-project/end-to-end/src/e2e_nft.test.ts @@ -33,7 +33,9 @@ describe('NFT', () => { ({ teardown, wallet, accounts } = await setup(4)); [adminAddress, minterAddress, user1Address, user2Address] = accounts; - nftContract = await NFTContract.deploy(wallet, adminAddress, 'FROG', 'FRG').send({ from: adminAddress }); + ({ contract: nftContract } = await NFTContract.deploy(wallet, adminAddress, 'FROG', 'FRG').send({ + from: adminAddress, + })); }); afterAll(() => teardown()); @@ -41,13 +43,15 @@ describe('NFT', () => { // NOTE: This test is sequential and each test case depends on the previous one it('sets minter', async () => { await nftContract.methods.set_minter(minterAddress, true).send({ from: adminAddress }); - const isMinterAMinter = await nftContract.methods.is_minter(minterAddress).simulate({ from: minterAddress }); + const { result: isMinterAMinter } = await nftContract.methods + .is_minter(minterAddress) + .simulate({ from: minterAddress }); expect(isMinterAMinter).toBe(true); }); it('minter mints to a user', async () => { await nftContract.methods.mint(user1Address, TOKEN_ID).send({ from: minterAddress }); - const ownerAfterMint = await nftContract.methods.owner_of(TOKEN_ID).simulate({ from: user1Address }); + const { result: ownerAfterMint } = await nftContract.methods.owner_of(TOKEN_ID).simulate({ from: user1Address }); expect(ownerAfterMint).toEqual(user1Address); }); @@ -57,7 +61,7 @@ describe('NFT', () => { const recipient = user2Address; await nftContract.methods.transfer_to_private(recipient, TOKEN_ID).send({ from: user1Address }); - const publicOwnerAfter = await nftContract.methods.owner_of(TOKEN_ID).simulate({ from: user1Address }); + const { result: publicOwnerAfter } = await nftContract.methods.owner_of(TOKEN_ID).simulate({ from: user1Address }); expect(publicOwnerAfter).toEqual(AztecAddress.ZERO); }); @@ -74,19 +78,21 @@ describe('NFT', () => { it('transfers to public', async () => { await nftContract.methods.transfer_to_public(user1Address, user2Address, TOKEN_ID, 0).send({ from: user1Address }); - const publicOwnerAfter = await nftContract.methods.owner_of(TOKEN_ID).simulate({ from: user1Address }); + const { result: publicOwnerAfter } = await nftContract.methods.owner_of(TOKEN_ID).simulate({ from: user1Address }); expect(publicOwnerAfter).toEqual(user2Address); }); it('transfers in public', async () => { await nftContract.methods.transfer_in_public(user2Address, user1Address, TOKEN_ID, 0).send({ from: user2Address }); - const publicOwnerAfter = await nftContract.methods.owner_of(TOKEN_ID).simulate({ from: user2Address }); + const { result: publicOwnerAfter } = await nftContract.methods.owner_of(TOKEN_ID).simulate({ from: user2Address }); expect(publicOwnerAfter).toEqual(user1Address); }); const getPrivateNfts = async (owner: AztecAddress) => { - const [nfts, pageLimitReached] = await nftContract.methods.get_private_nfts(owner, 0).simulate({ from: owner }); + const { + result: [nfts, pageLimitReached], + } = await nftContract.methods.get_private_nfts(owner, 0).simulate({ from: owner }); if (pageLimitReached) { throw new Error('Page limit reached and pagination not implemented in test'); } diff --git a/yarn-project/end-to-end/src/e2e_note_getter.test.ts b/yarn-project/end-to-end/src/e2e_note_getter.test.ts index d8192b71827c..c48c9f291a53 100644 --- a/yarn-project/end-to-end/src/e2e_note_getter.test.ts +++ b/yarn-project/end-to-end/src/e2e_note_getter.test.ts @@ -34,7 +34,7 @@ describe('e2e_note_getter', () => { let contract: NoteGetterContract; beforeAll(async () => { - contract = await NoteGetterContract.deploy(wallet).send({ from: defaultAddress }); + ({ contract } = await NoteGetterContract.deploy(wallet).send({ from: defaultAddress })); }); it('inserts notes from 0-9, then makes multiple queries specifying the total suite of comparators', async () => { @@ -47,7 +47,14 @@ describe('e2e_note_getter', () => { // We insert a note with value 5 twice to better test the comparators await contract.methods.insert_note(5).send({ from: defaultAddress }); - const [returnEq, returnNeq, returnLt, returnGt, returnLte, returnGte] = await Promise.all([ + const [ + { result: returnEq }, + { result: returnNeq }, + { result: returnLt }, + { result: returnGt }, + { result: returnLte }, + { result: returnGte }, + ] = await Promise.all([ contract.methods.read_note_values(defaultAddress, Comparator.EQ, 5).simulate({ from: defaultAddress }), contract.methods.read_note_values(defaultAddress, Comparator.NEQ, 5).simulate({ from: defaultAddress }), contract.methods.read_note_values(defaultAddress, Comparator.LT, 5).simulate({ from: defaultAddress }), @@ -78,7 +85,7 @@ describe('e2e_note_getter', () => { const makeTxHybrid = false; beforeAll(async () => { - contract = await TestContract.deploy(wallet).send({ from: defaultAddress }); + ({ contract } = await TestContract.deploy(wallet).send({ from: defaultAddress })); owner = defaultAddress; }); @@ -93,10 +100,10 @@ describe('e2e_note_getter', () => { }); async function assertNoteIsReturned(storageSlot: number, expectedValue: number, activeOrNullified: boolean) { - const viewNotesResult = await contract.methods + const { result: viewNotesResult } = await contract.methods .call_view_notes(owner, storageSlot, activeOrNullified) .simulate({ from: defaultAddress }); - const getNotesResult = await contract.methods + const { result: getNotesResult } = await contract.methods .call_get_notes(owner, storageSlot, activeOrNullified) .simulate({ from: defaultAddress }); @@ -155,10 +162,10 @@ describe('e2e_note_getter', () => { await contract.methods.call_destroy_note(owner, storageSlot).send({ from: defaultAddress }); // We now fetch multiple notes, and get both the active and the nullified one. - const viewNotesManyResult = await contract.methods + const { result: viewNotesManyResult } = await contract.methods .call_view_notes_many(owner, storageSlot, activeOrNullified) .simulate({ from: defaultAddress }); - const getNotesManyResult = await contract.methods + const { result: getNotesManyResult } = await contract.methods .call_get_notes_many(owner, storageSlot, activeOrNullified) .simulate({ from: defaultAddress }); diff --git a/yarn-project/end-to-end/src/e2e_offchain_effect.test.ts b/yarn-project/end-to-end/src/e2e_offchain_effect.test.ts index 624433699dd6..9968050c18b7 100644 --- a/yarn-project/end-to-end/src/e2e_offchain_effect.test.ts +++ b/yarn-project/end-to-end/src/e2e_offchain_effect.test.ts @@ -33,12 +33,34 @@ describe('e2e_offchain_effect', () => { accounts: [defaultAccountAddress], aztecNode, } = await setup(1)); - contract1 = await OffchainEffectContract.deploy(wallet).send({ from: defaultAccountAddress }); - contract2 = await OffchainEffectContract.deploy(wallet).send({ from: defaultAccountAddress }); + ({ contract: contract1 } = await OffchainEffectContract.deploy(wallet).send({ from: defaultAccountAddress })); + ({ contract: contract2 } = await OffchainEffectContract.deploy(wallet).send({ from: defaultAccountAddress })); }); afterAll(() => teardown()); + it('should return offchain effects from send()', async () => { + const effects = Array(2) + .fill(null) + .map(() => ({ + data: [Fr.random(), Fr.random(), Fr.random(), Fr.random(), Fr.random()], + // eslint-disable-next-line camelcase + next_contract: contract1.address, + })); + + const { receipt, offchainEffects } = await contract1.methods + .emit_offchain_effects(effects) + .send({ from: defaultAccountAddress }); + + expect(receipt.hasExecutionSucceeded()).toBe(true); + // Effects are popped from the end of the BoundedVec, so they come out reversed + expect(offchainEffects).toHaveLength(2); + expect(offchainEffects[0].contractAddress).toEqual(contract1.address); + expect(offchainEffects[0].data).toEqual(effects[1].data); + expect(offchainEffects[1].contractAddress).toEqual(contract1.address); + expect(offchainEffects[1].data).toEqual(effects[0].data); + }); + it('should emit offchain effects', async () => { const effects = Array(3) .fill(null) @@ -164,7 +186,9 @@ describe('e2e_offchain_effect', () => { .simulate({ from: defaultAccountAddress }); // Get the note value - const noteValue = await contract1.methods.get_note_value(owner).simulate({ from: defaultAccountAddress }); + const { result: noteValue } = await contract1.methods + .get_note_value(owner) + .simulate({ from: defaultAccountAddress }); expect(noteValue).toBe(value); }); }); diff --git a/yarn-project/end-to-end/src/e2e_orderbook.test.ts b/yarn-project/end-to-end/src/e2e_orderbook.test.ts index 1ac98d5ae223..22efa02edd17 100644 --- a/yarn-project/end-to-end/src/e2e_orderbook.test.ts +++ b/yarn-project/end-to-end/src/e2e_orderbook.test.ts @@ -52,9 +52,9 @@ describe('Orderbook', () => { ({ contract: token0 } = await deployToken(wallet, adminAddress, 0n, logger)); ({ contract: token1 } = await deployToken(wallet, adminAddress, 0n, logger)); - orderbook = await OrderbookContract.deploy(wallet, token0.address, token1.address).send({ + ({ contract: orderbook } = await OrderbookContract.deploy(wallet, token0.address, token1.address).send({ from: adminAddress, - }); + })); // Mint tokens to maker and taker await mintTokensToPrivate(token0, adminAddress, makerAddress, bidAmount); @@ -95,7 +95,9 @@ describe('Orderbook', () => { orderId = orderCreatedEvents[0].event.order_id; // Get order from orderbook and verify details - const [order, isFulfilled] = await orderbook.methods.get_order(orderId).simulate({ from: adminAddress }); + const { + result: [order, isFulfilled], + } = await orderbook.methods.get_order(orderId).simulate({ from: adminAddress }); expect(order.bid_amount).toEqual(bidAmount); expect(order.ask_amount).toEqual(askAmount); expect(order.bid_token_is_zero).toBeTrue(); @@ -103,10 +105,12 @@ describe('Orderbook', () => { // At this point, bidAmount of token0 should be transferred to the public balance of the orderbook and maker // should have 0. - const orderbookBalances0 = await token0.methods + const { result: orderbookBalances0 } = await token0.methods .balance_of_public(orderbook.address) .simulate({ from: makerAddress }); - const makerBalances0 = await token0.methods.balance_of_private(makerAddress).simulate({ from: makerAddress }); + const { result: makerBalances0 } = await token0.methods + .balance_of_private(makerAddress) + .simulate({ from: makerAddress }); expect(orderbookBalances0).toEqual(bidAmount); expect(makerBalances0).toEqual(0n); }); @@ -142,10 +146,18 @@ describe('Orderbook', () => { expect(orderFulfilledEvents[0].event.order_id).toEqual(orderId); // Verify balances after order fulfillment - const makerBalances0 = await token0.methods.balance_of_private(makerAddress).simulate({ from: makerAddress }); - const makerBalances1 = await token1.methods.balance_of_private(makerAddress).simulate({ from: makerAddress }); - const takerBalances0 = await token0.methods.balance_of_private(takerAddress).simulate({ from: takerAddress }); - const takerBalances1 = await token1.methods.balance_of_private(takerAddress).simulate({ from: takerAddress }); + const { result: makerBalances0 } = await token0.methods + .balance_of_private(makerAddress) + .simulate({ from: makerAddress }); + const { result: makerBalances1 } = await token1.methods + .balance_of_private(makerAddress) + .simulate({ from: makerAddress }); + const { result: takerBalances0 } = await token0.methods + .balance_of_private(takerAddress) + .simulate({ from: takerAddress }); + const { result: takerBalances1 } = await token1.methods + .balance_of_private(takerAddress) + .simulate({ from: takerAddress }); // Full maker token 0 balance should be transferred to taker and hence maker should have 0 expect(makerBalances0).toEqual(0n); @@ -157,7 +169,9 @@ describe('Orderbook', () => { expect(takerBalances1).toEqual(0n); // Verify that the order is fulfilled - const [_, isFulfilled] = await orderbook.methods.get_order(orderId).simulate({ from: adminAddress }); + const { + result: [_, isFulfilled], + } = await orderbook.methods.get_order(orderId).simulate({ from: adminAddress }); expect(isFulfilled).toBeTrue(); }); }); diff --git a/yarn-project/end-to-end/src/e2e_ordering.test.ts b/yarn-project/end-to-end/src/e2e_ordering.test.ts index fb310765f04e..56f8df05058a 100644 --- a/yarn-project/end-to-end/src/e2e_ordering.test.ts +++ b/yarn-project/end-to-end/src/e2e_ordering.test.ts @@ -56,8 +56,8 @@ describe('e2e_ordering', () => { let pubSetValueSelector: FunctionSelector; beforeEach(async () => { - parent = await ParentContract.deploy(wallet).send({ from: defaultAccountAddress }); - child = await ChildContract.deploy(wallet).send({ from: defaultAccountAddress }); + ({ contract: parent } = await ParentContract.deploy(wallet).send({ from: defaultAccountAddress })); + ({ contract: child } = await ChildContract.deploy(wallet).send({ from: defaultAccountAddress })); pubSetValueSelector = await child.methods.pub_set_value.selector(); }, TIMEOUT); diff --git a/yarn-project/end-to-end/src/e2e_p2p/add_rollup.test.ts b/yarn-project/end-to-end/src/e2e_p2p/add_rollup.test.ts index a1964ef8f74b..786b2c9aa7a5 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/add_rollup.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/add_rollup.test.ts @@ -285,7 +285,7 @@ describe('e2e_p2p_add_rollup', () => { const aliceAddress = aliceAccountManager.address; - const testContract = await TestContract.deploy(wallet).send({ from: aliceAddress }); + const { contract: testContract } = await TestContract.deploy(wallet).send({ from: aliceAddress }); const [secret, secretHash] = await generateClaimSecret(); @@ -304,7 +304,7 @@ describe('e2e_p2p_add_rollup', () => { // We poll isL1ToL2MessageSynced endpoint until the message is available await retryUntil(async () => await node.isL1ToL2MessageSynced(msgHash), 'message sync', 10); - const receipt = await testContract.methods + const { receipt } = await testContract.methods .create_l2_to_l1_message_arbitrary_recipient_private(contentOutFromRollup, ethRecipient) .send({ from: aliceAddress }); diff --git a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts index bdb752da9f00..1f2120f28177 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts @@ -323,8 +323,9 @@ export class P2PNetworkTest { throw new Error('Call setupAccount before deploying spam contract'); } - const spamContract = await SpamContract.deploy(this.wallet).send({ from: this.defaultAccountAddress! }); - this.spamContract = spamContract; + ({ contract: this.spamContract } = await SpamContract.deploy(this.wallet).send({ + from: this.defaultAccountAddress!, + })); } async removeInitialNode() { diff --git a/yarn-project/end-to-end/src/e2e_p2p/shared.ts b/yarn-project/end-to-end/src/e2e_p2p/shared.ts index 5b3450673e24..a74488fef1c0 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/shared.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/shared.ts @@ -41,7 +41,7 @@ export const submitComplexTxsTo = async ( const spamCount = 15; for (let i = 0; i < numTxs; i++) { const method = spamContract.methods.spam(seed + BigInt(i * spamCount), spamCount, !!opts.callPublic); - const txHash = await method.send({ from, wait: NO_WAIT }); + const { txHash } = await method.send({ from, wait: NO_WAIT }); logger.info(`Tx sent with hash ${txHash.toString()}`); txs.push(txHash); } diff --git a/yarn-project/end-to-end/src/e2e_partial_notes.test.ts b/yarn-project/end-to-end/src/e2e_partial_notes.test.ts index 104d7ef3b215..8742a20129a0 100644 --- a/yarn-project/end-to-end/src/e2e_partial_notes.test.ts +++ b/yarn-project/end-to-end/src/e2e_partial_notes.test.ts @@ -43,7 +43,8 @@ describe('partial notes', () => { it('mint to private', async () => { await mintTokensToPrivate(token0, adminAddress, liquidityProviderAddress, INITIAL_TOKEN_BALANCE); expect( - await token0.methods.balance_of_private(liquidityProviderAddress).simulate({ from: liquidityProviderAddress }), + (await token0.methods.balance_of_private(liquidityProviderAddress).simulate({ from: liquidityProviderAddress })) + .result, ).toEqual(INITIAL_TOKEN_BALANCE); }); }); diff --git a/yarn-project/end-to-end/src/e2e_pending_note_hashes_contract.test.ts b/yarn-project/end-to-end/src/e2e_pending_note_hashes_contract.test.ts index 3b01932f8edf..9982087e83dd 100644 --- a/yarn-project/end-to-end/src/e2e_pending_note_hashes_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_pending_note_hashes_contract.test.ts @@ -75,7 +75,7 @@ describe('e2e_pending_note_hashes_contract', () => { const deployContract = async () => { logger.debug(`Deploying L2 contract...`); - contract = await PendingNoteHashesContract.deploy(wallet).send({ from: owner }); + ({ contract } = await PendingNoteHashesContract.deploy(wallet).send({ from: owner })); logger.info(`L2 contract deployed at ${contract.address}`); return contract; }; diff --git a/yarn-project/end-to-end/src/e2e_phase_check.test.ts b/yarn-project/end-to-end/src/e2e_phase_check.test.ts index 03ba19047c61..581611cbbd71 100644 --- a/yarn-project/end-to-end/src/e2e_phase_check.test.ts +++ b/yarn-project/end-to-end/src/e2e_phase_check.test.ts @@ -35,7 +35,7 @@ describe('Phase check', () => { accounts: [defaultAccountAddress], } = await setup(1, { genesisPublicData: [genesisBalanceEntry] })); - contract = await TestContract.deploy(wallet).send({ from: defaultAccountAddress }); + ({ contract } = await TestContract.deploy(wallet).send({ from: defaultAccountAddress })); sponsoredFPC = await SponsoredFPCNoEndSetupContract.deploy(wallet).register({ contractAddressSalt: new Fr(SPONSORED_FPC_SALT), }); diff --git a/yarn-project/end-to-end/src/e2e_private_voting_contract.test.ts b/yarn-project/end-to-end/src/e2e_private_voting_contract.test.ts index 86809063a08c..6a60739c2d36 100644 --- a/yarn-project/end-to-end/src/e2e_private_voting_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_private_voting_contract.test.ts @@ -25,7 +25,7 @@ describe('e2e_voting_contract', () => { accounts: [owner], } = await setup(1)); - votingContract = await PrivateVotingContract.deploy(wallet, owner).send({ from: owner }); + ({ contract: votingContract } = await PrivateVotingContract.deploy(wallet, owner).send({ from: owner })); logger.info(`Counter contract deployed at ${votingContract.address}`); }); @@ -40,7 +40,7 @@ describe('e2e_voting_contract', () => { await votingContract.methods.start_vote(electionId).send({ from: owner }); await votingContract.methods.cast_vote(electionId, candidate).send({ from: owner }); - expect(await votingContract.methods.get_tally(electionId, candidate).simulate({ from: owner })).toBe(1n); + expect((await votingContract.methods.get_tally(electionId, candidate).simulate({ from: owner })).result).toBe(1n); // We try voting again, but our TX is dropped due to trying to emit duplicate nullifiers // first confirm that it fails simulation diff --git a/yarn-project/end-to-end/src/e2e_prover/client.test.ts b/yarn-project/end-to-end/src/e2e_prover/client.test.ts index 7ff197d01a71..aef6fcd86af6 100644 --- a/yarn-project/end-to-end/src/e2e_prover/client.test.ts +++ b/yarn-project/end-to-end/src/e2e_prover/client.test.ts @@ -71,12 +71,14 @@ describe('client_prover', () => { ); // Create the two transactions - const privateBalance = await provenAsset.methods.balance_of_private(sender).simulate({ from: sender }); + const { result: privateBalance } = await provenAsset.methods + .balance_of_private(sender) + .simulate({ from: sender }); const privateSendAmount = privateBalance / 10n; expect(privateSendAmount).toBeGreaterThan(0n); const privateInteraction = provenAsset.methods.transfer(recipient, privateSendAmount); - const publicBalance = await provenAsset.methods.balance_of_public(sender).simulate({ from: sender }); + const { result: publicBalance } = await provenAsset.methods.balance_of_public(sender).simulate({ from: sender }); const publicSendAmount = publicBalance / 10n; expect(publicSendAmount).toBeGreaterThan(0n); const publicInteraction = provenAsset.methods.transfer_in_public(sender, recipient, publicSendAmount, 0); diff --git a/yarn-project/end-to-end/src/e2e_prover/full.test.ts b/yarn-project/end-to-end/src/e2e_prover/full.test.ts index eb3dc3e82529..7d70a5b443d2 100644 --- a/yarn-project/end-to-end/src/e2e_prover/full.test.ts +++ b/yarn-project/end-to-end/src/e2e_prover/full.test.ts @@ -87,12 +87,14 @@ describe('full_prover', () => { ); // Create the two transactions - const privateBalance = await provenAsset.methods.balance_of_private(sender).simulate({ from: sender }); + const { result: privateBalance } = await provenAsset.methods + .balance_of_private(sender) + .simulate({ from: sender }); const privateSendAmount = privateBalance / 10n; expect(privateSendAmount).toBeGreaterThan(0n); const privateInteraction = provenAsset.methods.transfer(recipient, privateSendAmount); - const publicBalance = await provenAsset.methods.balance_of_public(sender).simulate({ from: sender }); + const { result: publicBalance } = await provenAsset.methods.balance_of_public(sender).simulate({ from: sender }); const publicSendAmount = publicBalance / 10n; expect(publicSendAmount).toBeGreaterThan(0n); const publicInteraction = provenAsset.methods.transfer_in_public(sender, recipient, publicSendAmount, 0); @@ -180,12 +182,12 @@ describe('full_prover', () => { return; } // Create the two transactions - const privateBalance = await provenAsset.methods.balance_of_private(sender).simulate({ from: sender }); + const { result: privateBalance } = await provenAsset.methods.balance_of_private(sender).simulate({ from: sender }); const privateSendAmount = privateBalance / 20n; expect(privateSendAmount).toBeGreaterThan(0n); const firstPrivateInteraction = provenAsset.methods.transfer(recipient, privateSendAmount); - const publicBalance = await provenAsset.methods.balance_of_public(sender).simulate({ from: sender }); + const { result: publicBalance } = await provenAsset.methods.balance_of_public(sender).simulate({ from: sender }); const publicSendAmount = publicBalance / 10n; expect(publicSendAmount).toBeGreaterThan(0n); const publicInteraction = provenAsset.methods.transfer_in_public(sender, recipient, publicSendAmount, 0); diff --git a/yarn-project/end-to-end/src/e2e_pruned_blocks.test.ts b/yarn-project/end-to-end/src/e2e_pruned_blocks.test.ts index 50a8db649dd6..02db37673052 100644 --- a/yarn-project/end-to-end/src/e2e_pruned_blocks.test.ts +++ b/yarn-project/end-to-end/src/e2e_pruned_blocks.test.ts @@ -53,7 +53,7 @@ describe('e2e_pruned_blocks', () => { aztecProofSubmissionEpochs: 1024, // effectively do not reorg })); - token = await TokenContract.deploy(wallet, admin, 'TEST', '$TST', 18).send({ from: admin }); + ({ contract: token } = await TokenContract.deploy(wallet, admin, 'TEST', '$TST', 18).send({ from: admin })); logger.info(`L2 token contract deployed at ${token.address}`); }); @@ -76,7 +76,9 @@ describe('e2e_pruned_blocks', () => { // mint transaction that the node will drop the block corresponding to the first mint, resulting in errors if PXE // tried to access any historical information related to it (which it shouldn't). - const firstMintReceipt = await token.methods.mint_to_private(sender, MINT_AMOUNT / 2n).send({ from: admin }); + const { receipt: firstMintReceipt } = await token.methods + .mint_to_private(sender, MINT_AMOUNT / 2n) + .send({ from: admin }); const firstMintTxEffect = await aztecNode.getTxEffect(firstMintReceipt.txHash); // mint_to_private should create just one new note with the minted amount @@ -122,7 +124,9 @@ describe('e2e_pruned_blocks', () => { await token.methods.transfer(recipient, MINT_AMOUNT).send({ from: sender }); - expect(await token.methods.balance_of_private(recipient).simulate({ from: recipient })).toEqual(MINT_AMOUNT); - expect(await token.methods.balance_of_private(sender).simulate({ from: sender })).toEqual(0n); + expect((await token.methods.balance_of_private(recipient).simulate({ from: recipient })).result).toEqual( + MINT_AMOUNT, + ); + expect((await token.methods.balance_of_private(sender).simulate({ from: sender })).result).toEqual(0n); }); }); diff --git a/yarn-project/end-to-end/src/e2e_public_testnet/e2e_public_testnet_transfer.test.ts b/yarn-project/end-to-end/src/e2e_public_testnet/e2e_public_testnet_transfer.test.ts index a5ed696875eb..daed07568294 100644 --- a/yarn-project/end-to-end/src/e2e_public_testnet/e2e_public_testnet_transfer.test.ts +++ b/yarn-project/end-to-end/src/e2e_public_testnet/e2e_public_testnet_transfer.test.ts @@ -60,7 +60,7 @@ describe(`deploys and transfers a private only token`, () => { ); const tokenInstance = await tokenDeployment.getInstance(); await wallet.registerContract(tokenInstance, PrivateTokenContract.artifact, tokenSecretKey); - const token = await tokenDeployment.send({ + const { contract: token } = await tokenDeployment.send({ from: deployerAddress, universalDeploy: true, skipInstancePublication: true, @@ -76,8 +76,12 @@ describe(`deploys and transfers a private only token`, () => { logger.info(`Transfer completed`); - const balanceDeployer = await token.methods.get_balance(deployerAddress).simulate({ from: deployerAddress }); - const balanceRecipient = await token.methods.get_balance(recipientAddress).simulate({ from: recipientAddress }); + const { result: balanceDeployer } = await token.methods + .get_balance(deployerAddress) + .simulate({ from: deployerAddress }); + const { result: balanceRecipient } = await token.methods + .get_balance(recipientAddress) + .simulate({ from: recipientAddress }); logger.info(`Deployer balance: ${balanceDeployer}, Recipient balance: ${balanceRecipient}`); diff --git a/yarn-project/end-to-end/src/e2e_scope_isolation.test.ts b/yarn-project/end-to-end/src/e2e_scope_isolation.test.ts index c84eeadc62b0..5c8e4e2fdfc2 100644 --- a/yarn-project/end-to-end/src/e2e_scope_isolation.test.ts +++ b/yarn-project/end-to-end/src/e2e_scope_isolation.test.ts @@ -21,7 +21,7 @@ describe('e2e scope isolation', () => { ({ teardown, wallet, accounts } = await setup(3)); [alice, bob, charlie] = accounts; - contract = await ScopeTestContract.deploy(wallet).send({ from: alice }); + ({ contract } = await ScopeTestContract.deploy(wallet).send({ from: alice })); // Alice and bob create a note for themselves (used by multiple tests below) await contract.methods.create_note(alice, Number(ALICE_NOTE_VALUE)).send({ from: alice }); @@ -32,7 +32,7 @@ describe('e2e scope isolation', () => { describe('external private', () => { it('owner can read own notes', async () => { - const value = await contract.methods.read_note(alice).simulate({ from: alice }); + const { result: value } = await contract.methods.read_note(alice).simulate({ from: alice }); expect(value).toEqual(ALICE_NOTE_VALUE); }); @@ -47,8 +47,8 @@ describe('e2e scope isolation', () => { }); it('each account can access their isolated state on a shared wallet', async () => { - const aliceValue = await contract.methods.read_note(alice).simulate({ from: alice }); - const bobValue = await contract.methods.read_note(bob).simulate({ from: bob }); + const { result: aliceValue } = await contract.methods.read_note(alice).simulate({ from: alice }); + const { result: bobValue } = await contract.methods.read_note(bob).simulate({ from: bob }); expect(aliceValue).toEqual(ALICE_NOTE_VALUE); expect(bobValue).toEqual(BOB_NOTE_VALUE); @@ -57,7 +57,7 @@ describe('e2e scope isolation', () => { describe('external utility', () => { it('owner can read own notes', async () => { - const value = await contract.methods.read_note_utility(alice).simulate({ from: alice }); + const { result: value } = await contract.methods.read_note_utility(alice).simulate({ from: alice }); expect(value).toEqual(ALICE_NOTE_VALUE); }); @@ -74,8 +74,8 @@ describe('e2e scope isolation', () => { }); it('each account can access their isolated state on a shared wallet', async () => { - const aliceValue = await contract.methods.read_note_utility(alice).simulate({ from: alice }); - const bobValue = await contract.methods.read_note_utility(bob).simulate({ from: bob }); + const { result: aliceValue } = await contract.methods.read_note_utility(alice).simulate({ from: alice }); + const { result: bobValue } = await contract.methods.read_note_utility(bob).simulate({ from: bob }); expect(aliceValue).toEqual(ALICE_NOTE_VALUE); expect(bobValue).toEqual(BOB_NOTE_VALUE); diff --git a/yarn-project/end-to-end/src/e2e_sequencer/gov_proposal.parallel.test.ts b/yarn-project/end-to-end/src/e2e_sequencer/gov_proposal.parallel.test.ts index b223bc086f1f..8795dc257a6f 100644 --- a/yarn-project/end-to-end/src/e2e_sequencer/gov_proposal.parallel.test.ts +++ b/yarn-project/end-to-end/src/e2e_sequencer/gov_proposal.parallel.test.ts @@ -112,7 +112,7 @@ describe('e2e_gov_proposal', () => { // Deploy a test contract to send msgs via the outbox, since this increases // gas cost of a proposal, which has triggered oog errors in the past. - testContract = await TestContract.deploy(wallet).send({ from: defaultAccountAddress }); + ({ contract: testContract } = await TestContract.deploy(wallet).send({ from: defaultAccountAddress })); logger.warn(`Deployed test contract at ${testContract.address}`); await cheatCodes.rollup.advanceToEpoch(EpochNumber(4)); @@ -169,11 +169,12 @@ describe('e2e_gov_proposal', () => { // since we wait for the txs to be mined, and do so `roundDuration` times. // Simultaneously, we should be voting for the proposal in every slot. for (let i = 0; i < roundDuration; i++) { - const txHashes = await timesAsync(TXS_PER_BLOCK, () => - testContract.methods + const txHashes = await timesAsync(TXS_PER_BLOCK, async () => { + const { txHash } = await testContract.methods .create_l2_to_l1_message_arbitrary_recipient_private(Fr.random(), EthAddress.random()) - .send({ from: defaultAccountAddress, wait: NO_WAIT }), - ); + .send({ from: defaultAccountAddress, wait: NO_WAIT }); + return txHash; + }); await Promise.all( txHashes.map((hash, j) => { logger.info(`Waiting for tx ${i}-${j}: ${hash} to be mined`); diff --git a/yarn-project/end-to-end/src/e2e_sequencer/reload_keystore.test.ts b/yarn-project/end-to-end/src/e2e_sequencer/reload_keystore.test.ts index 09deed9db69d..667728c4056b 100644 --- a/yarn-project/end-to-end/src/e2e_sequencer/reload_keystore.test.ts +++ b/yarn-project/end-to-end/src/e2e_sequencer/reload_keystore.test.ts @@ -130,7 +130,7 @@ describe('e2e_reload_keystore', () => { // Send a tx and verify the block uses the initial coinbase const deployer = new ContractDeployer(artifact, wallet); - const sentTx1 = await deployer.deploy(ownerAddress, ownerAddress, 1).send({ + const { txHash: sentTx1 } = await deployer.deploy(ownerAddress, ownerAddress, 1).send({ from: ownerAddress, contractAddressSalt: new Fr(1), skipClassPublication: true, @@ -197,7 +197,7 @@ describe('e2e_reload_keystore', () => { // Whichever validator is the proposer, its coinbase must be from the reloaded keystore. const allNewCoinbasesLower = newCoinbases.map(c => c.toString().toLowerCase()); - const sentTx2 = await deployer.deploy(ownerAddress, ownerAddress, 2).send({ + const { txHash: sentTx2 } = await deployer.deploy(ownerAddress, ownerAddress, 2).send({ from: ownerAddress, contractAddressSalt: new Fr(2), skipClassPublication: true, diff --git a/yarn-project/end-to-end/src/e2e_simple.test.ts b/yarn-project/end-to-end/src/e2e_simple.test.ts index 0fcedb023202..1dbfe5861776 100644 --- a/yarn-project/end-to-end/src/e2e_simple.test.ts +++ b/yarn-project/end-to-end/src/e2e_simple.test.ts @@ -72,7 +72,7 @@ describe('e2e_simple', () => { const deployer = new ContractDeployer(artifact, wallet); const sender = ownerAddress; - const txReceipt = await deployer.deploy(ownerAddress, sender, 1).send({ + const { receipt: txReceipt } = await deployer.deploy(ownerAddress, sender, 1).send({ from: ownerAddress, contractAddressSalt: new Fr(BigInt(1)), skipClassPublication: true, diff --git a/yarn-project/end-to-end/src/e2e_state_vars.test.ts b/yarn-project/end-to-end/src/e2e_state_vars.test.ts index c5b8d6f0635b..6d2f58a7f37d 100644 --- a/yarn-project/end-to-end/src/e2e_state_vars.test.ts +++ b/yarn-project/end-to-end/src/e2e_state_vars.test.ts @@ -33,7 +33,7 @@ describe('e2e_state_vars', () => { wallet, accounts: [defaultAccountAddress], } = await setup(1)); - contract = await StateVarsContract.deploy(wallet).send({ from: defaultAccountAddress }); + ({ contract } = await StateVarsContract.deploy(wallet).send({ from: defaultAccountAddress })); }); afterAll(() => teardown()); @@ -51,7 +51,7 @@ describe('e2e_state_vars', () => { await contract.methods.initialize_public_immutable(1).send({ from: defaultAccountAddress }); - const read = await contract.methods.get_public_immutable().simulate({ from: defaultAccountAddress }); + const { result: read } = await contract.methods.get_public_immutable().simulate({ from: defaultAccountAddress }); expect(read).toEqual({ account: defaultAccountAddress, value: read.value }); }); @@ -62,11 +62,13 @@ describe('e2e_state_vars', () => { // 2. A constrained private function that calls another private function that reads. // The indirect, adds 1 to the point to ensure that we are returning the correct value. - const [a, b, c] = await new BatchCall(wallet, [ - contract.methods.get_public_immutable_constrained_private(), - contract.methods.get_public_immutable_constrained_private_indirect(), - contract.methods.get_public_immutable(), - ]).simulate({ from: defaultAccountAddress }); + const [a, b, c] = ( + await new BatchCall(wallet, [ + contract.methods.get_public_immutable_constrained_private(), + contract.methods.get_public_immutable_constrained_private_indirect(), + contract.methods.get_public_immutable(), + ]).simulate({ from: defaultAccountAddress }) + ).map((r: any) => r.result); expect(a).toEqual(c); expect(b).toEqual({ account: c.account, value: c.value + 1n }); @@ -79,11 +81,13 @@ describe('e2e_state_vars', () => { // 2. A constrained public function that calls another public function that reads. // The indirect, adds 1 to the point to ensure that we are returning the correct value. - const [a, b, c] = await new BatchCall(wallet, [ - contract.methods.get_public_immutable_constrained_public(), - contract.methods.get_public_immutable_constrained_public_indirect(), - contract.methods.get_public_immutable(), - ]).simulate({ from: defaultAccountAddress }); + const [a, b, c] = ( + await new BatchCall(wallet, [ + contract.methods.get_public_immutable_constrained_public(), + contract.methods.get_public_immutable_constrained_public_indirect(), + contract.methods.get_public_immutable(), + ]).simulate({ from: defaultAccountAddress }) + ).map((r: any) => r.result); expect(a).toEqual(c); expect(b).toEqual({ account: c.account, value: c.value + 1n }); @@ -95,10 +99,10 @@ describe('e2e_state_vars', () => { // Reads the value using a utility function checking the return values with: // 1. A constrained public function that reads 5 times directly (going beyond the previous 4 Field return value) - const a = await contract.methods + const { result: a } = await contract.methods .get_public_immutable_constrained_public_multiple() .simulate({ from: defaultAccountAddress }); - const c = await contract.methods.get_public_immutable().simulate({ from: defaultAccountAddress }); + const { result: c } = await contract.methods.get_public_immutable().simulate({ from: defaultAccountAddress }); expect(a).toEqual([c, c, c, c, c]); }); @@ -115,9 +119,11 @@ describe('e2e_state_vars', () => { describe('PrivateMutable', () => { it('fail to read uninitialized PrivateMutable', async () => { expect( - await contract.methods - .is_private_mutable_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }), + ( + await contract.methods + .is_private_mutable_initialized(defaultAccountAddress) + .simulate({ from: defaultAccountAddress }) + ).result, ).toEqual(false); await expect( contract.methods.get_private_mutable(defaultAccountAddress).simulate({ from: defaultAccountAddress }), @@ -126,12 +132,14 @@ describe('e2e_state_vars', () => { it('initialize PrivateMutable', async () => { expect( - await contract.methods - .is_private_mutable_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }), + ( + await contract.methods + .is_private_mutable_initialized(defaultAccountAddress) + .simulate({ from: defaultAccountAddress }) + ).result, ).toEqual(false); // Send the transaction and wait for it to be mined (wait function throws if the tx is not mined) - const txReceipt = await contract.methods + const { receipt: txReceipt } = await contract.methods .initialize_private(RANDOMNESS, VALUE) .send({ from: defaultAccountAddress }); @@ -140,50 +148,60 @@ describe('e2e_state_vars', () => { // 1 for the tx, another for the initializer expect(txEffects?.data.nullifiers.length).toEqual(2); expect( - await contract.methods - .is_private_mutable_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }), + ( + await contract.methods + .is_private_mutable_initialized(defaultAccountAddress) + .simulate({ from: defaultAccountAddress }) + ).result, ).toEqual(true); }); it('fail to reinitialize', async () => { expect( - await contract.methods - .is_private_mutable_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }), + ( + await contract.methods + .is_private_mutable_initialized(defaultAccountAddress) + .simulate({ from: defaultAccountAddress }) + ).result, ).toEqual(true); await expect( contract.methods.initialize_private(RANDOMNESS, VALUE).send({ from: defaultAccountAddress }), ).rejects.toThrow(); expect( - await contract.methods - .is_private_mutable_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }), + ( + await contract.methods + .is_private_mutable_initialized(defaultAccountAddress) + .simulate({ from: defaultAccountAddress }) + ).result, ).toEqual(true); }); it('read initialized PrivateMutable', async () => { expect( - await contract.methods - .is_private_mutable_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }), + ( + await contract.methods + .is_private_mutable_initialized(defaultAccountAddress) + .simulate({ from: defaultAccountAddress }) + ).result, ).toEqual(true); - const { value } = await contract.methods - .get_private_mutable(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }); + const { + result: { value }, + } = await contract.methods.get_private_mutable(defaultAccountAddress).simulate({ from: defaultAccountAddress }); expect(value).toEqual(VALUE); }); it('replace with same value', async () => { expect( - await contract.methods - .is_private_mutable_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }), + ( + await contract.methods + .is_private_mutable_initialized(defaultAccountAddress) + .simulate({ from: defaultAccountAddress }) + ).result, ).toEqual(true); - const noteBefore = await contract.methods + const { result: noteBefore } = await contract.methods .get_private_mutable(defaultAccountAddress) .simulate({ from: defaultAccountAddress }); - const txReceipt = await contract.methods + const { receipt: txReceipt } = await contract.methods .update_private_mutable(RANDOMNESS, VALUE) .send({ from: defaultAccountAddress }); @@ -193,7 +211,7 @@ describe('e2e_state_vars', () => { // 1 for the tx, another for the nullifier of the previous note expect(txEffects?.data.nullifiers.length).toEqual(2); - const noteAfter = await contract.methods + const { result: noteAfter } = await contract.methods .get_private_mutable(defaultAccountAddress) .simulate({ from: defaultAccountAddress }); @@ -202,11 +220,13 @@ describe('e2e_state_vars', () => { it('replace PrivateMutable with other values', async () => { expect( - await contract.methods - .is_private_mutable_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }), + ( + await contract.methods + .is_private_mutable_initialized(defaultAccountAddress) + .simulate({ from: defaultAccountAddress }) + ).result, ).toEqual(true); - const txReceipt = await contract.methods + const { receipt: txReceipt } = await contract.methods .update_private_mutable(RANDOMNESS + 2n, VALUE + 1n) .send({ from: defaultAccountAddress }); @@ -216,22 +236,26 @@ describe('e2e_state_vars', () => { // 1 for the tx, another for the nullifier of the previous note expect(txEffects?.data.nullifiers.length).toEqual(2); - const { value } = await contract.methods - .get_private_mutable(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }); + const { + result: { value }, + } = await contract.methods.get_private_mutable(defaultAccountAddress).simulate({ from: defaultAccountAddress }); expect(value).toEqual(VALUE + 1n); }); it('replace PrivateMutable dependent on prior value', async () => { expect( - await contract.methods - .is_private_mutable_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }), + ( + await contract.methods + .is_private_mutable_initialized(defaultAccountAddress) + .simulate({ from: defaultAccountAddress }) + ).result, ).toEqual(true); - const noteBefore = await contract.methods + const { result: noteBefore } = await contract.methods .get_private_mutable(defaultAccountAddress) .simulate({ from: defaultAccountAddress }); - const txReceipt = await contract.methods.increase_private_value().send({ from: defaultAccountAddress }); + const { receipt: txReceipt } = await contract.methods + .increase_private_value() + .send({ from: defaultAccountAddress }); const txEffects = await aztecNode.getTxEffect(txReceipt.txHash); @@ -239,9 +263,9 @@ describe('e2e_state_vars', () => { // 1 for the tx, another for the nullifier of the previous note expect(txEffects?.data.nullifiers.length).toEqual(2); - const { value } = await contract.methods - .get_private_mutable(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }); + const { + result: { value }, + } = await contract.methods.get_private_mutable(defaultAccountAddress).simulate({ from: defaultAccountAddress }); expect(value).toEqual(noteBefore.value + 1n); }); }); @@ -249,7 +273,11 @@ describe('e2e_state_vars', () => { describe('PrivateImmutable', () => { it('fail to read uninitialized PrivateImmutable', async () => { expect( - await contract.methods.is_priv_imm_initialized(defaultAccountAddress).simulate({ from: defaultAccountAddress }), + ( + await contract.methods + .is_priv_imm_initialized(defaultAccountAddress) + .simulate({ from: defaultAccountAddress }) + ).result, ).toEqual(false); await expect( contract.methods.view_private_immutable(defaultAccountAddress).simulate({ from: defaultAccountAddress }), @@ -258,9 +286,13 @@ describe('e2e_state_vars', () => { it('initialize PrivateImmutable', async () => { expect( - await contract.methods.is_priv_imm_initialized(defaultAccountAddress).simulate({ from: defaultAccountAddress }), + ( + await contract.methods + .is_priv_imm_initialized(defaultAccountAddress) + .simulate({ from: defaultAccountAddress }) + ).result, ).toEqual(false); - const txReceipt = await contract.methods + const { receipt: txReceipt } = await contract.methods .initialize_private_immutable(RANDOMNESS, VALUE) .send({ from: defaultAccountAddress }); @@ -270,27 +302,45 @@ describe('e2e_state_vars', () => { // 1 for the tx, another for the initializer expect(txEffects?.data.nullifiers.length).toEqual(2); expect( - await contract.methods.is_priv_imm_initialized(defaultAccountAddress).simulate({ from: defaultAccountAddress }), + ( + await contract.methods + .is_priv_imm_initialized(defaultAccountAddress) + .simulate({ from: defaultAccountAddress }) + ).result, ).toEqual(true); }); it('fail to reinitialize', async () => { expect( - await contract.methods.is_priv_imm_initialized(defaultAccountAddress).simulate({ from: defaultAccountAddress }), + ( + await contract.methods + .is_priv_imm_initialized(defaultAccountAddress) + .simulate({ from: defaultAccountAddress }) + ).result, ).toEqual(true); await expect( contract.methods.initialize_private_immutable(RANDOMNESS, VALUE).send({ from: defaultAccountAddress }), ).rejects.toThrow(); expect( - await contract.methods.is_priv_imm_initialized(defaultAccountAddress).simulate({ from: defaultAccountAddress }), + ( + await contract.methods + .is_priv_imm_initialized(defaultAccountAddress) + .simulate({ from: defaultAccountAddress }) + ).result, ).toEqual(true); }); it('read initialized PrivateImmutable', async () => { expect( - await contract.methods.is_priv_imm_initialized(defaultAccountAddress).simulate({ from: defaultAccountAddress }), + ( + await contract.methods + .is_priv_imm_initialized(defaultAccountAddress) + .simulate({ from: defaultAccountAddress }) + ).result, ).toEqual(true); - const { value } = await contract.methods + const { + result: { value }, + } = await contract.methods .view_private_immutable(defaultAccountAddress) .simulate({ from: defaultAccountAddress }); expect(value).toEqual(VALUE); @@ -310,9 +360,9 @@ describe('e2e_state_vars', () => { beforeAll(async () => { // We use the auth contract here because has a nice, clear, simple implementation of Delayed Public Mutable - authContract = await AuthContract.deploy(wallet, defaultAccountAddress).send({ + ({ contract: authContract } = await AuthContract.deploy(wallet, defaultAccountAddress).send({ from: defaultAccountAddress, - }); + })); if (aztecSlotDuration !== 72) { throw new Error( diff --git a/yarn-project/end-to-end/src/e2e_static_calls.test.ts b/yarn-project/end-to-end/src/e2e_static_calls.test.ts index 9576d39f7845..1e2cc3117f1b 100644 --- a/yarn-project/end-to-end/src/e2e_static_calls.test.ts +++ b/yarn-project/end-to-end/src/e2e_static_calls.test.ts @@ -21,8 +21,8 @@ describe('e2e_static_calls', () => { accounts: [owner], } = await setup()); sender = owner; - parentContract = await StaticParentContract.deploy(wallet).send({ from: owner }); - childContract = await StaticChildContract.deploy(wallet).send({ from: owner }); + ({ contract: parentContract } = await StaticParentContract.deploy(wallet).send({ from: owner })); + ({ contract: childContract } = await StaticChildContract.deploy(wallet).send({ from: owner })); // We create a note in the set, such that later reads doesn't fail due to get_notes returning 0 notes await childContract.methods.private_set_value(42n, owner, sender).send({ from: owner }); diff --git a/yarn-project/end-to-end/src/e2e_storage_proof/e2e_storage_proof.test.ts b/yarn-project/end-to-end/src/e2e_storage_proof/e2e_storage_proof.test.ts index cdee4c492b29..70c6ce8b0b8f 100644 --- a/yarn-project/end-to-end/src/e2e_storage_proof/e2e_storage_proof.test.ts +++ b/yarn-project/end-to-end/src/e2e_storage_proof/e2e_storage_proof.test.ts @@ -13,7 +13,7 @@ describe('Storage proof', () => { beforeAll(async () => { ctx = await setup(1); - contract = await StorageProofTestContract.deploy(ctx.wallet).send({ from: ctx.accounts[0] }); + ({ contract } = await StorageProofTestContract.deploy(ctx.wallet).send({ from: ctx.accounts[0] })); }); afterAll(async () => { @@ -26,7 +26,7 @@ describe('Storage proof', () => { ctx.logger.info('Sending storage proof TX...'); - const receipt = await contract.methods + const { receipt } = await contract.methods .storage_proof(ethAddress, slotKey, slotContents, root) .with({ capsules }) .send({ from: ctx.accounts[0] }); diff --git a/yarn-project/end-to-end/src/e2e_synching.test.ts b/yarn-project/end-to-end/src/e2e_synching.test.ts index 76a54cd05832..c93560f43064 100644 --- a/yarn-project/end-to-end/src/e2e_synching.test.ts +++ b/yarn-project/end-to-end/src/e2e_synching.test.ts @@ -203,7 +203,7 @@ class TestVariant { ); this.contractAddresses.push(accountManager.address); const deployMethod = await accountManager.getDeployMethod(); - const txHash = await deployMethod.send({ + const { txHash } = await deployMethod.send({ from: deployAccount, skipClassPublication: true, skipInstancePublication: true, @@ -218,7 +218,9 @@ class TestVariant { for (let i = 0; i < this.txCount; i++) { const recipient = this.accounts[(i + 1) % this.txCount]; const tk = TokenContract.at(this.token.address, this.wallet); - txHashes.push(await tk.methods.transfer(recipient, 1n).send({ from: this.accounts[i], wait: NO_WAIT })); + txHashes.push( + (await tk.methods.transfer(recipient, 1n).send({ from: this.accounts[i], wait: NO_WAIT })).txHash, + ); } return txHashes; } else if (this.txComplexity == TxComplexity.PublicTransfer) { @@ -229,7 +231,7 @@ class TestVariant { const recipient = this.accounts[(i + 1) % this.txCount]; const tk = TokenContract.at(this.token.address, this.wallet); txHashes.push( - await tk.methods.transfer_in_public(sender, recipient, 1n, 0).send({ from: sender, wait: NO_WAIT }), + (await tk.methods.transfer_in_public(sender, recipient, 1n, 0).send({ from: sender, wait: NO_WAIT })).txHash, ); } return txHashes; @@ -247,7 +249,7 @@ class TestVariant { ]); this.seed += 100n; - txHashes.push(await batch.send({ from: this.accounts[0], wait: NO_WAIT })); + txHashes.push((await batch.send({ from: this.accounts[0], wait: NO_WAIT })).txHash); } return txHashes; } else { @@ -340,10 +342,16 @@ describe('e2e_synching', () => { variant.setWallet(wallet); // Deploy a token, such that we could use it - const token = await TokenContract.deploy(wallet, defaultAccountAddress, 'TestToken', 'TST', 18n).send({ + const { contract: token } = await TokenContract.deploy( + wallet, + defaultAccountAddress, + 'TestToken', + 'TST', + 18n, + ).send({ from: defaultAccountAddress, }); - const spam = await SpamContract.deploy(wallet).send({ from: defaultAccountAddress }); + const { contract: spam } = await SpamContract.deploy(wallet).send({ from: defaultAccountAddress }); variant.setToken(token); variant.setSpam(spam); @@ -542,15 +550,21 @@ describe('e2e_synching', () => { const defaultAccountAddress = (await variant.deployAccounts(opts.initialFundedAccounts!.slice(0, 1)))[0]; contracts.push( - await TokenContract.deploy(wallet, defaultAccountAddress, 'TestToken', 'TST', 18n).send({ - from: defaultAccountAddress, - }), + ( + await TokenContract.deploy(wallet, defaultAccountAddress, 'TestToken', 'TST', 18n).send({ + from: defaultAccountAddress, + }) + ).contract, + ); + contracts.push( + (await SchnorrHardcodedAccountContract.deploy(wallet).send({ from: defaultAccountAddress })).contract, ); - contracts.push(await SchnorrHardcodedAccountContract.deploy(wallet).send({ from: defaultAccountAddress })); contracts.push( - await TokenContract.deploy(wallet, defaultAccountAddress, 'TestToken', 'TST', 18n).send({ - from: defaultAccountAddress, - }), + ( + await TokenContract.deploy(wallet, defaultAccountAddress, 'TestToken', 'TST', 18n).send({ + from: defaultAccountAddress, + }) + ).contract, ); await watcher.stop(); diff --git a/yarn-project/end-to-end/src/e2e_token_contract/access_control.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/access_control.test.ts index cab2d394475f..50ac4a7f36be 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/access_control.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/access_control.test.ts @@ -18,17 +18,19 @@ describe('e2e_token_contract access control', () => { it('Set admin', async () => { await t.asset.methods.set_admin(t.account1Address).send({ from: t.adminAddress }); - expect(await t.asset.methods.get_admin().simulate({ from: t.adminAddress })).toBe(t.account1Address.toBigInt()); + expect((await t.asset.methods.get_admin().simulate({ from: t.adminAddress })).result).toBe( + t.account1Address.toBigInt(), + ); }); it('Add minter as admin', async () => { await t.asset.methods.set_minter(t.account1Address, true).send({ from: t.account1Address }); - expect(await t.asset.methods.is_minter(t.account1Address).simulate({ from: t.adminAddress })).toBe(true); + expect((await t.asset.methods.is_minter(t.account1Address).simulate({ from: t.adminAddress })).result).toBe(true); }); it('Revoke minter as admin', async () => { await t.asset.methods.set_minter(t.account1Address, false).send({ from: t.account1Address }); - expect(await t.asset.methods.is_minter(t.account1Address).simulate({ from: t.adminAddress })).toBe(false); + expect((await t.asset.methods.is_minter(t.account1Address).simulate({ from: t.adminAddress })).result).toBe(false); }); describe('failure cases', () => { diff --git a/yarn-project/end-to-end/src/e2e_token_contract/burn.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/burn.test.ts index 1c9b670771ca..b9760a983627 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/burn.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/burn.test.ts @@ -27,7 +27,7 @@ describe('e2e_token_contract burn', () => { describe('public', () => { it('burn less than balance', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); const amount = balance0 / 2n; expect(amount).toBeGreaterThan(0n); await asset.methods.burn_public(adminAddress, amount, 0).send({ from: adminAddress }); @@ -36,7 +36,7 @@ describe('e2e_token_contract burn', () => { }); it('burn on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); const amount = balance0 / 2n; expect(amount).toBeGreaterThan(0n); const authwitNonce = Fr.random(); @@ -61,7 +61,9 @@ describe('e2e_token_contract burn', () => { describe('failure cases', () => { it('burn more than balance', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }); const amount = balance0 + 1n; const authwitNonce = 0; await expect( @@ -70,7 +72,9 @@ describe('e2e_token_contract burn', () => { }); it('burn on behalf of self with non-zero nonce', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }); const amount = balance0 - 1n; expect(amount).toBeGreaterThan(0n); const authwitNonce = 1; @@ -82,7 +86,9 @@ describe('e2e_token_contract burn', () => { }); it('burn on behalf of other without "approval"', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }); const amount = balance0 + 1n; const authwitNonce = Fr.random(); await expect( @@ -91,7 +97,9 @@ describe('e2e_token_contract burn', () => { }); it('burn more than balance on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }); const amount = balance0 + 1n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -109,7 +117,9 @@ describe('e2e_token_contract burn', () => { }); it('burn on behalf of other, wrong designated caller', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }); const amount = balance0 + 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -132,7 +142,9 @@ describe('e2e_token_contract burn', () => { describe('private', () => { it('burn less than balance', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); const amount = balance0 / 2n; expect(amount).toBeGreaterThan(0n); await asset.methods.burn_private(adminAddress, amount, 0).send({ from: adminAddress }); @@ -140,7 +152,9 @@ describe('e2e_token_contract burn', () => { }); it('burn on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); const amount = balance0 / 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -160,7 +174,9 @@ describe('e2e_token_contract burn', () => { describe('failure cases', () => { it('burn more than balance', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); const amount = balance0 + 1n; expect(amount).toBeGreaterThan(0n); await expect( @@ -169,7 +185,9 @@ describe('e2e_token_contract burn', () => { }); it('burn on behalf of self with non-zero nonce', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); const amount = balance0 - 1n; expect(amount).toBeGreaterThan(0n); await expect( @@ -180,7 +198,9 @@ describe('e2e_token_contract burn', () => { }); it('burn more than balance on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); const amount = balance0 + 1n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -195,7 +215,9 @@ describe('e2e_token_contract burn', () => { }); it('burn on behalf of other without approval', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); const amount = balance0 / 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -214,7 +236,9 @@ describe('e2e_token_contract burn', () => { }); it('on behalf of other (invalid designated caller)', async () => { - const balancePriv0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balancePriv0 } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); const amount = balancePriv0 + 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); diff --git a/yarn-project/end-to-end/src/e2e_token_contract/minting.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/minting.test.ts index bc6a1251a833..e7479801a3a6 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/minting.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/minting.test.ts @@ -25,10 +25,12 @@ describe('e2e_token_contract minting', () => { await asset.methods.mint_to_public(adminAddress, amount).send({ from: adminAddress }); tokenSim.mintPublic(adminAddress, amount); - expect(await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress })).toEqual( + expect((await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress })).result).toEqual( tokenSim.balanceOfPublic(adminAddress), ); - expect(await asset.methods.total_supply().simulate({ from: adminAddress })).toEqual(tokenSim.totalSupply); + expect((await asset.methods.total_supply().simulate({ from: adminAddress })).result).toEqual( + tokenSim.totalSupply, + ); }); describe('failure cases', () => { @@ -61,10 +63,12 @@ describe('e2e_token_contract minting', () => { await asset.methods.mint_to_private(adminAddress, amount).send({ from: adminAddress }); tokenSim.mintPrivate(adminAddress, amount); - expect(await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress })).toEqual( + expect((await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress })).result).toEqual( tokenSim.balanceOfPrivate(adminAddress), ); - expect(await asset.methods.total_supply().simulate({ from: adminAddress })).toEqual(tokenSim.totalSupply); + expect((await asset.methods.total_supply().simulate({ from: adminAddress })).result).toEqual( + tokenSim.totalSupply, + ); }); describe('failure cases', () => { diff --git a/yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts index a986ab6799ba..ffa5688df3b5 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts @@ -23,7 +23,9 @@ describe('e2e_token_contract private transfer recursion', () => { // itself to consume them all (since it retrieves 2 notes on the first pass and 8 in each subsequent pass). const totalNotes = 16; const totalBalance = await mintNotes(wallet, adminAddress, adminAddress, asset, Array(totalNotes).fill(10n)); - const txReceipt = await asset.methods.transfer(account1Address, totalBalance).send({ from: adminAddress }); + const { receipt: txReceipt } = await asset.methods + .transfer(account1Address, totalBalance) + .send({ from: adminAddress }); const txEffects = await node.getTxEffect(txReceipt.txHash); // We should have nullified all notes, plus an extra nullifier for the transaction and one for the event commitment. @@ -59,7 +61,7 @@ describe('e2e_token_contract private transfer recursion', () => { const totalBalance = await mintNotes(wallet, adminAddress, adminAddress, asset, noteAmounts); const toSend = totalBalance - expectedChange; - const txReceipt = await asset.methods.transfer(account1Address, toSend).send({ from: adminAddress }); + const { receipt: txReceipt } = await asset.methods.transfer(account1Address, toSend).send({ from: adminAddress }); const txEffects = await node.getTxEffect(txReceipt.txHash); // We should have nullified all notes, plus an extra nullifier for the transaction and one for the event commitment. @@ -67,7 +69,9 @@ describe('e2e_token_contract private transfer recursion', () => { // We should have created two new notes, one for the recipient and one for the sender (with the change) expect(txEffects!.data.noteHashes.length).toBe(2); - const senderBalance = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: senderBalance } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); expect(senderBalance).toEqual(expectedChange); const events = await wallet.getPrivateEvents(TokenContract.events.Transfer, { @@ -93,7 +97,9 @@ describe('e2e_token_contract private transfer recursion', () => { describe('failure cases', () => { it('transfer more than balance', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); const amount = balance0 + 1n; expect(amount).toBeGreaterThan(0n); diff --git a/yarn-project/end-to-end/src/e2e_token_contract/reading_constants.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/reading_constants.test.ts index a564a5610fed..185ac4231e8e 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/reading_constants.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/reading_constants.test.ts @@ -22,34 +22,40 @@ describe('e2e_token_contract reading constants', () => { }); it('check name private', async () => { - const name = readFieldCompressedString(await t.asset.methods.private_get_name().simulate({ from: t.adminAddress })); + const name = readFieldCompressedString( + (await t.asset.methods.private_get_name().simulate({ from: t.adminAddress })).result, + ); expect(name).toBe(TOKEN_NAME); }); it('check name public', async () => { - const name = readFieldCompressedString(await t.asset.methods.public_get_name().simulate({ from: t.adminAddress })); + const name = readFieldCompressedString( + (await t.asset.methods.public_get_name().simulate({ from: t.adminAddress })).result, + ); expect(name).toBe(TOKEN_NAME); }); it('check symbol private', async () => { const sym = readFieldCompressedString( - await t.asset.methods.private_get_symbol().simulate({ from: t.adminAddress }), + (await t.asset.methods.private_get_symbol().simulate({ from: t.adminAddress })).result, ); expect(sym).toBe(TOKEN_SYMBOL); }); it('check symbol public', async () => { - const sym = readFieldCompressedString(await t.asset.methods.public_get_symbol().simulate({ from: t.adminAddress })); + const sym = readFieldCompressedString( + (await t.asset.methods.public_get_symbol().simulate({ from: t.adminAddress })).result, + ); expect(sym).toBe(TOKEN_SYMBOL); }); it('check decimals private', async () => { - const dec = await t.asset.methods.private_get_decimals().simulate({ from: t.adminAddress }); + const { result: dec } = await t.asset.methods.private_get_decimals().simulate({ from: t.adminAddress }); expect(dec).toBe(TOKEN_DECIMALS); }); it('check decimals public', async () => { - const dec = await t.asset.methods.public_get_decimals().simulate({ from: t.adminAddress }); + const { result: dec } = await t.asset.methods.public_get_decimals().simulate({ from: t.adminAddress }); expect(dec).toBe(TOKEN_DECIMALS); }); }); diff --git a/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts b/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts index ebfde8461643..bbc7f024fd19 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts @@ -81,24 +81,28 @@ export class TokenContractTest { await publicDeployAccounts(this.wallet, [this.adminAddress, this.account1Address]); this.logger.verbose(`Deploying TokenContract...`); - this.asset = await TokenContract.deploy( + ({ contract: this.asset } = await TokenContract.deploy( this.wallet, this.adminAddress, TokenContractTest.TOKEN_NAME, TokenContractTest.TOKEN_SYMBOL, TokenContractTest.TOKEN_DECIMALS, - ).send({ from: this.adminAddress }); + ).send({ from: this.adminAddress })); this.logger.verbose(`Token deployed to ${this.asset.address}`); this.logger.verbose(`Deploying bad account...`); - this.badAccount = await InvalidAccountContract.deploy(this.wallet).send({ from: this.adminAddress }); + ({ contract: this.badAccount } = await InvalidAccountContract.deploy(this.wallet).send({ + from: this.adminAddress, + })); this.logger.verbose(`Deployed to ${this.badAccount.address}.`); // Deploy a proxy contract for "on behalf of other" tests. The note owner must be the tx sender // (so their notes are in scope), but msg_sender in the target must differ from the note owner // to trigger authwit validation. The proxy forwards calls so that msg_sender != tx sender. this.logger.verbose(`Deploying generic proxy...`); - this.authwitProxy = await GenericProxyContract.deploy(this.wallet).send({ from: this.adminAddress }); + ({ contract: this.authwitProxy } = await GenericProxyContract.deploy(this.wallet).send({ + from: this.adminAddress, + })); this.logger.verbose(`Deployed to ${this.authwitProxy.address}.`); this.tokenSim = new TokenSimulator(this.asset, this.wallet, this.adminAddress, this.logger, [ @@ -106,7 +110,7 @@ export class TokenContractTest { this.account1Address, ]); - expect(await this.asset.methods.get_admin().simulate({ from: this.adminAddress })).toBe( + expect((await this.asset.methods.get_admin().simulate({ from: this.adminAddress })).result).toBe( this.adminAddress.toBigInt(), ); } @@ -140,7 +144,9 @@ export class TokenContractTest { await asset.methods.mint_to_public(adminAddress, amount).send({ from: adminAddress }); tokenSim.mintPublic(adminAddress, amount); - const publicBalance = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: publicBalance } = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }); this.logger.verbose(`Public balance of wallet 0: ${publicBalance}`); expect(publicBalance).toEqual(this.tokenSim.balanceOfPublic(adminAddress)); @@ -148,11 +154,13 @@ export class TokenContractTest { await mintTokensToPrivate(asset, adminAddress, adminAddress, amount); tokenSim.mintPrivate(adminAddress, amount); - const privateBalance = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: privateBalance } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); this.logger.verbose(`Private balance of wallet 0: ${privateBalance}`); expect(privateBalance).toEqual(tokenSim.balanceOfPrivate(adminAddress)); - const totalSupply = await asset.methods.total_supply().simulate({ from: adminAddress }); + const { result: totalSupply } = await asset.methods.total_supply().simulate({ from: adminAddress }); this.logger.verbose(`Total supply: ${totalSupply}`); expect(totalSupply).toEqual(tokenSim.totalSupply); diff --git a/yarn-project/end-to-end/src/e2e_token_contract/transfer.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/transfer.test.ts index d801532f8478..564707a74c77 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/transfer.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/transfer.test.ts @@ -24,11 +24,11 @@ describe('e2e_token_contract transfer private', () => { }); it('transfer less than balance', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); const amount = balance0 / 2n; expect(amount).toBeGreaterThan(0n); - const txReceipt = await asset.methods.transfer(account1Address, amount).send({ from: adminAddress }); + const { receipt: txReceipt } = await asset.methods.transfer(account1Address, amount).send({ from: adminAddress }); tokenSim.transferPrivate(adminAddress, account1Address, amount); const events = await wallet.getPrivateEvents(TokenContract.events.Transfer, { @@ -53,7 +53,7 @@ describe('e2e_token_contract transfer private', () => { }); it('transfer less than balance to non-deployed account', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); const amount = balance0 / 2n; expect(amount).toBeGreaterThan(0n); @@ -68,7 +68,7 @@ describe('e2e_token_contract transfer private', () => { }); it('transfer to self', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); const amount = balance0 / 2n; expect(amount).toBeGreaterThan(0n); await asset.methods.transfer(adminAddress, amount).send({ from: adminAddress }); @@ -77,7 +77,9 @@ describe('e2e_token_contract transfer private', () => { describe('failure cases', () => { it('transfer more than balance', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); const amount = balance0 + 1n; expect(amount).toBeGreaterThan(0n); await expect(asset.methods.transfer(account1Address, amount).simulate({ from: adminAddress })).rejects.toThrow( diff --git a/yarn-project/end-to-end/src/e2e_token_contract/transfer_in_private.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/transfer_in_private.test.ts index 6177d2845832..385c912cec06 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/transfer_in_private.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/transfer_in_private.test.ts @@ -25,7 +25,7 @@ describe('e2e_token_contract transfer private', () => { }); it('transfer on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); const amount = balance0 / 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -45,7 +45,9 @@ describe('e2e_token_contract transfer private', () => { describe('failure cases', () => { it('transfer on behalf of self with non-zero nonce', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); const amount = balance0 - 1n; expect(amount).toBeGreaterThan(0n); await expect( @@ -61,8 +63,12 @@ describe('e2e_token_contract transfer private', () => { }); it('transfer more than balance on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); - const balance1 = await asset.methods.balance_of_private(account1Address).simulate({ from: account1Address }); + const { result: balance0 } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); + const { result: balance1 } = await asset.methods + .balance_of_private(account1Address) + .simulate({ from: account1Address }); const amount = balance0 + 1n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -74,10 +80,12 @@ describe('e2e_token_contract transfer private', () => { await expect( simulateThroughAuthwitProxy(t.authwitProxy, action, { from: adminAddress, authWitnesses: [witness] }), ).rejects.toThrow('Assertion failed: Balance too low'); - expect(await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress })).toEqual(balance0); - expect(await asset.methods.balance_of_private(account1Address).simulate({ from: account1Address })).toEqual( - balance1, + expect((await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress })).result).toEqual( + balance0, ); + expect( + (await asset.methods.balance_of_private(account1Address).simulate({ from: account1Address })).result, + ).toEqual(balance1); }); it.skip('transfer into account to overflow', () => { @@ -88,7 +96,9 @@ describe('e2e_token_contract transfer private', () => { }); it('transfer on behalf of other without approval', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); const amount = balance0 / 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -107,7 +117,9 @@ describe('e2e_token_contract transfer private', () => { }); it('transfer on behalf of other, wrong designated caller', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); const amount = balance0 / 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -125,11 +137,15 @@ describe('e2e_token_contract transfer private', () => { await expect( simulateThroughAuthwitProxy(t.authwitProxy, action, { from: adminAddress, authWitnesses: [witness] }), ).rejects.toThrow(`Unknown auth witness for message hash ${expectedMessageHash.toString()}`); - expect(await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress })).toEqual(balance0); + expect((await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress })).result).toEqual( + balance0, + ); }); it('transfer on behalf of other, cancelled authwit', async () => { - const balance0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); const amount = balance0 / 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); diff --git a/yarn-project/end-to-end/src/e2e_token_contract/transfer_in_public.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/transfer_in_public.test.ts index 689a99d61bc9..f50c89d80b5a 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/transfer_in_public.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/transfer_in_public.test.ts @@ -43,7 +43,7 @@ describe('e2e_token_contract transfer public', () => { }); it('transfer less than balance', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); const amount = balance0 / 2n; expect(amount).toBeGreaterThan(0n); await asset.methods.transfer_in_public(adminAddress, account1Address, amount, 0).send({ from: adminAddress }); @@ -52,7 +52,7 @@ describe('e2e_token_contract transfer public', () => { }); it('transfer to self', async () => { - const balance = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balance } = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); const amount = balance / 2n; expect(amount).toBeGreaterThan(0n); await asset.methods.transfer_in_public(adminAddress, adminAddress, amount, 0).send({ from: adminAddress }); @@ -61,7 +61,7 @@ describe('e2e_token_contract transfer public', () => { }); it('transfer on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); const amount = balance0 / 2n; expect(amount).toBeGreaterThan(0n); const authwitNonce = Fr.random(); @@ -90,7 +90,7 @@ describe('e2e_token_contract transfer public', () => { describe('failure cases', () => { it('transfer more than balance', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); const amount = balance0 + 1n; const authwitNonce = 0; await expect( @@ -101,7 +101,7 @@ describe('e2e_token_contract transfer public', () => { }); it('transfer on behalf of self with non-zero nonce', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); const amount = balance0 - 1n; const authwitNonce = 1; await expect( @@ -114,7 +114,7 @@ describe('e2e_token_contract transfer public', () => { }); it('transfer on behalf of other without "approval"', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); const amount = balance0 + 1n; const authwitNonce = Fr.random(); await expect( @@ -125,8 +125,10 @@ describe('e2e_token_contract transfer public', () => { }); it('transfer more than balance on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); - const balance1 = await asset.methods.balance_of_public(account1Address).simulate({ from: account1Address }); + const { result: balance0 } = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balance1 } = await asset.methods + .balance_of_public(account1Address) + .simulate({ from: account1Address }); const amount = balance0 + 1n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -145,15 +147,19 @@ describe('e2e_token_contract transfer public', () => { U128_UNDERFLOW_ERROR, ); - expect(await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress })).toEqual(balance0); - expect(await asset.methods.balance_of_public(account1Address).simulate({ from: account1Address })).toEqual( - balance1, + expect((await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress })).result).toEqual( + balance0, ); + expect( + (await asset.methods.balance_of_public(account1Address).simulate({ from: account1Address })).result, + ).toEqual(balance1); }); it('transfer on behalf of other, wrong designated caller', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); - const balance1 = await asset.methods.balance_of_public(account1Address).simulate({ from: account1Address }); + const { result: balance0 } = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balance1 } = await asset.methods + .balance_of_public(account1Address) + .simulate({ from: account1Address }); const amount = balance0 + 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -171,15 +177,19 @@ describe('e2e_token_contract transfer public', () => { // Perform the transfer await expect(action.simulate({ from: account1Address })).rejects.toThrow(/unauthorized/); - expect(await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress })).toEqual(balance0); - expect(await asset.methods.balance_of_public(account1Address).simulate({ from: account1Address })).toEqual( - balance1, + expect((await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress })).result).toEqual( + balance0, ); + expect( + (await asset.methods.balance_of_public(account1Address).simulate({ from: account1Address })).result, + ).toEqual(balance1); }); it('transfer on behalf of other, wrong designated caller', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); - const balance1 = await asset.methods.balance_of_public(account1Address).simulate({ from: account1Address }); + const { result: balance0 } = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balance1 } = await asset.methods + .balance_of_public(account1Address) + .simulate({ from: account1Address }); const amount = balance0 + 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -196,14 +206,16 @@ describe('e2e_token_contract transfer public', () => { // Perform the transfer await expect(action.simulate({ from: account1Address })).rejects.toThrow(/unauthorized/); - expect(await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress })).toEqual(balance0); - expect(await asset.methods.balance_of_public(account1Address).simulate({ from: account1Address })).toEqual( - balance1, + expect((await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress })).result).toEqual( + balance0, ); + expect( + (await asset.methods.balance_of_public(account1Address).simulate({ from: account1Address })).result, + ).toEqual(balance1); }); it('transfer on behalf of other, cancelled authwit', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); const amount = balance0 / 2n; expect(amount).toBeGreaterThan(0n); const authwitNonce = Fr.random(); @@ -232,7 +244,7 @@ describe('e2e_token_contract transfer public', () => { }); it('transfer on behalf of other, cancelled authwit, flow 2', async () => { - const balance0 = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balance0 } = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); const amount = balance0 / 2n; expect(amount).toBeGreaterThan(0n); const authwitNonce = Fr.random(); diff --git a/yarn-project/end-to-end/src/e2e_token_contract/transfer_to_private.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/transfer_to_private.test.ts index 051ba9370fce..2688fea57172 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/transfer_to_private.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/transfer_to_private.test.ts @@ -22,7 +22,7 @@ describe('e2e_token_contract transfer_to_private', () => { }); it('to self', async () => { - const balancePub = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balancePub } = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); const amount = balancePub / 2n; expect(amount).toBeGreaterThan(0n); @@ -34,7 +34,7 @@ describe('e2e_token_contract transfer_to_private', () => { }); it('to someone else', async () => { - const balancePub = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balancePub } = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); const amount = balancePub / 2n; expect(amount).toBeGreaterThan(0n); @@ -47,7 +47,9 @@ describe('e2e_token_contract transfer_to_private', () => { describe('failure cases', () => { it('to self (more than balance)', async () => { - const balancePub = await asset.methods.balance_of_public(adminAddress).simulate({ from: adminAddress }); + const { result: balancePub } = await asset.methods + .balance_of_public(adminAddress) + .simulate({ from: adminAddress }); const amount = balancePub + 1n; expect(amount).toBeGreaterThan(0n); diff --git a/yarn-project/end-to-end/src/e2e_token_contract/transfer_to_public.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/transfer_to_public.test.ts index 5394130c2429..1ff6f31ef53b 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/transfer_to_public.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/transfer_to_public.test.ts @@ -26,7 +26,9 @@ describe('e2e_token_contract transfer_to_public', () => { }); it('on behalf of self', async () => { - const balancePriv = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balancePriv } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); const amount = balancePriv / 2n; expect(amount).toBeGreaterThan(0n); @@ -36,7 +38,9 @@ describe('e2e_token_contract transfer_to_public', () => { }); it('on behalf of other', async () => { - const balancePriv0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balancePriv0 } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); const amount = balancePriv0 / 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -56,7 +60,9 @@ describe('e2e_token_contract transfer_to_public', () => { describe('failure cases', () => { it('on behalf of self (more than balance)', async () => { - const balancePriv = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balancePriv } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); const amount = balancePriv + 1n; expect(amount).toBeGreaterThan(0n); @@ -66,7 +72,9 @@ describe('e2e_token_contract transfer_to_public', () => { }); it('on behalf of self (invalid authwit nonce)', async () => { - const balancePriv = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balancePriv } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); const amount = balancePriv + 1n; expect(amount).toBeGreaterThan(0n); @@ -78,7 +86,9 @@ describe('e2e_token_contract transfer_to_public', () => { }); it('on behalf of other (more than balance)', async () => { - const balancePriv0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balancePriv0 } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); const amount = balancePriv0 + 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); @@ -93,7 +103,9 @@ describe('e2e_token_contract transfer_to_public', () => { }); it('on behalf of other (invalid designated caller)', async () => { - const balancePriv0 = await asset.methods.balance_of_private(adminAddress).simulate({ from: adminAddress }); + const { result: balancePriv0 } = await asset.methods + .balance_of_private(adminAddress) + .simulate({ from: adminAddress }); const amount = balancePriv0 + 2n; const authwitNonce = Fr.random(); expect(amount).toBeGreaterThan(0n); diff --git a/yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts b/yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts index 6dad123c594a..59f38bfeedea 100644 --- a/yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts +++ b/yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts @@ -106,7 +106,9 @@ export class FullProverTest { await publicDeployAccounts(this.wallet, this.accounts.slice(0, 2)); this.logger.info('Applying base setup: deploying token contract'); - const { contract: asset, instance } = await TokenContract.deploy( + const { + receipt: { contract: asset, instance }, + } = await TokenContract.deploy( this.wallet, this.accounts[0], FullProverTest.TOKEN_NAME, @@ -121,7 +123,7 @@ export class FullProverTest { this.tokenSim = new TokenSimulator(this.fakeProofsAsset, this.wallet, this.accounts[0], this.logger, this.accounts); - expect(await this.fakeProofsAsset.methods.get_admin().simulate({ from: this.accounts[0] })).toBe( + expect((await this.fakeProofsAsset.methods.get_admin().simulate({ from: this.accounts[0] })).result).toBe( this.accounts[0].toBigInt(), ); } @@ -310,16 +312,20 @@ export class FullProverTest { } = this; tokenSim.mintPublic(address, publicAmount); - const publicBalance = await fakeProofsAsset.methods.balance_of_public(address).simulate({ from: address }); + const { result: publicBalance } = await fakeProofsAsset.methods + .balance_of_public(address) + .simulate({ from: address }); this.logger.verbose(`Public balance of wallet 0: ${publicBalance}`); expect(publicBalance).toEqual(this.tokenSim.balanceOfPublic(address)); tokenSim.mintPrivate(address, publicAmount); - const privateBalance = await fakeProofsAsset.methods.balance_of_private(address).simulate({ from: address }); + const { result: privateBalance } = await fakeProofsAsset.methods + .balance_of_private(address) + .simulate({ from: address }); this.logger.verbose(`Private balance of wallet 0: ${privateBalance}`); expect(privateBalance).toEqual(tokenSim.balanceOfPrivate(address)); - const totalSupply = await fakeProofsAsset.methods.total_supply().simulate({ from: address }); + const { result: totalSupply } = await fakeProofsAsset.methods.total_supply().simulate({ from: address }); this.logger.verbose(`Total supply: ${totalSupply}`); expect(totalSupply).toEqual(tokenSim.totalSupply); } diff --git a/yarn-project/end-to-end/src/fixtures/setup.ts b/yarn-project/end-to-end/src/fixtures/setup.ts index abfd4da901e0..fb9ab8d9d88f 100644 --- a/yarn-project/end-to-end/src/fixtures/setup.ts +++ b/yarn-project/end-to-end/src/fixtures/setup.ts @@ -753,7 +753,9 @@ export function getBalancesFn( ): (...addresses: (AztecAddress | { address: AztecAddress })[]) => Promise { const balances = async (...addressLikes: (AztecAddress | { address: AztecAddress })[]) => { const addresses = addressLikes.map(addressLike => ('address' in addressLike ? addressLike.address : addressLike)); - const b = await Promise.all(addresses.map(address => method(address).simulate({ from: address }))); + const b = await Promise.all( + addresses.map(async address => (await method(address).simulate({ from: address })).result), + ); const debugString = `${symbol} balances: ${addresses.map((address, i) => `${address}: ${b[i]}`).join(', ')}`; logger.verbose(debugString); return b; @@ -871,7 +873,7 @@ export async function publicDeployAccounts( const batch = new BatchCall(wallet, calls); - const txReceipt = await batch.send({ from: accountsToDeploy[0] }); + const { receipt: txReceipt } = await batch.send({ from: accountsToDeploy[0] }); if (waitUntilProven) { if (!node) { throw new Error('Need to provide an AztecNode to wait for proven.'); diff --git a/yarn-project/end-to-end/src/fixtures/token_utils.ts b/yarn-project/end-to-end/src/fixtures/token_utils.ts index 47e9c67e78ba..ede82e6966c2 100644 --- a/yarn-project/end-to-end/src/fixtures/token_utils.ts +++ b/yarn-project/end-to-end/src/fixtures/token_utils.ts @@ -6,7 +6,9 @@ import { TokenContract } from '@aztec/noir-contracts.js/Token'; export async function deployToken(wallet: Wallet, admin: AztecAddress, initialAdminBalance: bigint, logger: Logger) { logger.info(`Deploying Token contract...`); - const { contract, instance } = await TokenContract.deploy(wallet, admin, 'TokenName', 'TokenSymbol', 18).send({ + const { + receipt: { contract, instance }, + } = await TokenContract.deploy(wallet, admin, 'TokenName', 'TokenSymbol', 18).send({ from: admin, wait: { returnReceipt: true }, }); @@ -38,7 +40,7 @@ export async function expectTokenBalance( ) { // Then check the balance const contractWithWallet = TokenContract.at(token.address, wallet); - const balance = await contractWithWallet.methods.balance_of_private(owner).simulate({ from: owner }); + const { result: balance } = await contractWithWallet.methods.balance_of_private(owner).simulate({ from: owner }); logger.info(`Account ${owner} balance: ${balance}`); expect(balance).toBe(expectedBalance); } 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 9eaf2473b961..b9f1893bf500 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 @@ -73,7 +73,7 @@ describe('guides/writing_an_account_contract', () => { const address = account.address; logger.info(`Deployed account contract at ${address}`); - const token = await TokenContract.deploy(wallet, fundedAccount, 'TokenName', 'TokenSymbol', 18).send({ + const { contract: token } = await TokenContract.deploy(wallet, fundedAccount, 'TokenName', 'TokenSymbol', 18).send({ from: fundedAccount, }); logger.info(`Deployed token contract at ${token.address}`); @@ -81,7 +81,7 @@ describe('guides/writing_an_account_contract', () => { const mintAmount = 50n; await token.methods.mint_to_private(address, mintAmount).send({ from: fundedAccount }); - const balance = await token.methods.balance_of_private(address).simulate({ from: address }); + const { result: balance } = await token.methods.balance_of_private(address).simulate({ from: address }); logger.info(`Balance of wallet is now ${balance}`); expect(balance).toEqual(50n); diff --git a/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts b/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts index be4564b2a67d..c1b2b39780eb 100644 --- a/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts +++ b/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts @@ -73,22 +73,26 @@ export async function deployAndInitializeTokenAndBridgeContracts( }); // deploy l2 token - const token = await TokenContract.deploy(wallet, owner, 'TokenName', 'TokenSymbol', 18).send({ from: owner }); + const { contract: token } = await TokenContract.deploy(wallet, owner, 'TokenName', 'TokenSymbol', 18).send({ + from: owner, + }); // deploy l2 token bridge and attach to the portal - const bridge = await TokenBridgeContract.deploy(wallet, token.address, tokenPortalAddress).send({ from: owner }); + const { contract: bridge } = await TokenBridgeContract.deploy(wallet, token.address, tokenPortalAddress).send({ + from: owner, + }); - if ((await token.methods.get_admin().simulate({ from: owner })) !== owner.toBigInt()) { + if ((await token.methods.get_admin().simulate({ from: owner })).result !== owner.toBigInt()) { throw new Error(`Token admin is not ${owner}`); } - if (!(await bridge.methods.get_config().simulate({ from: owner })).token.equals(token.address)) { + if (!(await bridge.methods.get_config().simulate({ from: owner })).result.token.equals(token.address)) { throw new Error(`Bridge token is not ${token.address}`); } // make the bridge a minter on the token: await token.methods.set_minter(bridge.address, true).send({ from: owner }); - if ((await token.methods.is_minter(bridge.address).simulate({ from: owner })) === 1n) { + if ((await token.methods.is_minter(bridge.address).simulate({ from: owner })).result === 1n) { throw new Error(`Bridge is not a minter`); } @@ -269,7 +273,7 @@ export class CrossChainTestHarness { authwitNonce: Fr = Fr.ZERO, authWitness: AuthWitness, ): Promise { - const withdrawReceipt = await this.l2Bridge.methods + const { receipt: withdrawReceipt } = await this.l2Bridge.methods .exit_to_l1_private(this.l2Token.address, this.ethAccount, withdrawAmount, EthAddress.ZERO, authwitNonce) .send({ authWitnesses: [authWitness], from: this.ownerAddress }); @@ -277,7 +281,7 @@ export class CrossChainTestHarness { } async withdrawPublicFromAztecToL1(withdrawAmount: bigint, authwitNonce: Fr = Fr.ZERO): Promise { - const withdrawReceipt = await this.l2Bridge.methods + const { receipt: withdrawReceipt } = await this.l2Bridge.methods .exit_to_l1_public(this.ethAccount, withdrawAmount, EthAddress.ZERO, authwitNonce) .send({ from: this.ownerAddress }); @@ -285,7 +289,7 @@ export class CrossChainTestHarness { } async getL2PrivateBalanceOf(owner: AztecAddress) { - return await this.l2Token.methods.balance_of_private(owner).simulate({ from: owner }); + return (await this.l2Token.methods.balance_of_private(owner).simulate({ from: owner })).result; } async expectPrivateBalanceOnL2(owner: AztecAddress, expectedBalance: bigint) { @@ -295,7 +299,7 @@ export class CrossChainTestHarness { } async getL2PublicBalanceOf(owner: AztecAddress) { - return await this.l2Token.methods.balance_of_public(owner).simulate({ from: this.ownerAddress }); + return (await this.l2Token.methods.balance_of_public(owner).simulate({ from: this.ownerAddress })).result; } async expectPublicBalanceOnL2(owner: AztecAddress, expectedBalance: bigint) { diff --git a/yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts b/yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts index 4062e7def159..2e8426fa1351 100644 --- a/yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts +++ b/yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts @@ -131,7 +131,7 @@ export class GasBridgingTestHarness implements IGasBridgingTestHarness { } async getL2PublicBalanceOf(owner: AztecAddress) { - return await this.feeJuice.methods.balance_of_public(owner).simulate({ from: owner }); + return (await this.feeJuice.methods.balance_of_public(owner).simulate({ from: owner })).result; } async expectPublicBalanceOnL2(owner: AztecAddress, expectedBalance: bigint) { diff --git a/yarn-project/end-to-end/src/shared/submit-transactions.ts b/yarn-project/end-to-end/src/shared/submit-transactions.ts index 5bb2a7316a8c..b8b6c5c1a11e 100644 --- a/yarn-project/end-to-end/src/shared/submit-transactions.ts +++ b/yarn-project/end-to-end/src/shared/submit-transactions.ts @@ -19,7 +19,7 @@ export const submitTxsTo = async ( times(numTxs, async () => { const accountManager = await wallet.createSchnorrAccount(Fr.random(), Fr.random(), GrumpkinScalar.random()); const deployMethod = await accountManager.getDeployMethod(); - const txHash = await deployMethod.send({ from: submitter, wait: NO_WAIT }); + const { txHash } = await deployMethod.send({ from: submitter, wait: NO_WAIT }); logger.info(`Tx sent with hash ${txHash}`); const receipt: TxReceipt = await wallet.getTxReceipt(txHash); diff --git a/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts b/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts index 7f25e1a2b4eb..6795cc1bf180 100644 --- a/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts +++ b/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts @@ -130,7 +130,9 @@ export const uniswapL1L2TestSuite = ( client: l1Client, }); // deploy l2 uniswap contract and attach to portal - uniswapL2Contract = await UniswapContract.deploy(wallet, uniswapPortalAddress).send({ from: ownerAddress }); + ({ contract: uniswapL2Contract } = await UniswapContract.deploy(wallet, uniswapPortalAddress).send({ + from: ownerAddress, + })); const registryAddress = (await aztecNode.getNodeInfo()).l1ContractAddresses.registryAddress; @@ -195,7 +197,7 @@ export const uniswapL1L2TestSuite = ( logger.info('Withdrawing weth to L1 and sending message to swap to dai'); const [secretForDepositingSwappedDai, secretHashForDepositingSwappedDai] = await generateClaimSecret(); - const l2UniswapInteractionReceipt = await uniswapL2Contract.methods + const { receipt: l2UniswapInteractionReceipt } = await uniswapL2Contract.methods .swap_private( wethCrossChainHarness.l2Token.address, wethCrossChainHarness.l2Bridge.address, @@ -787,7 +789,7 @@ export const uniswapL1L2TestSuite = ( logger.info('Withdrawing weth to L1 and sending message to swap to dai'); const [, secretHashForDepositingSwappedDai] = await generateClaimSecret(); - const withdrawReceipt = await uniswapL2Contract.methods + const { receipt: withdrawReceipt } = await uniswapL2Contract.methods .swap_private( wethCrossChainHarness.l2Token.address, wethCrossChainHarness.l2Bridge.address, @@ -915,7 +917,7 @@ export const uniswapL1L2TestSuite = ( // Call swap_public on L2 const secretHashForDepositingSwappedDai = Fr.random(); - const withdrawReceipt = await uniswapL2Contract.methods + const { receipt: withdrawReceipt } = await uniswapL2Contract.methods .swap_public( ownerAddress, wethCrossChainHarness.l2Bridge.address, diff --git a/yarn-project/end-to-end/src/simulators/lending_simulator.ts b/yarn-project/end-to-end/src/simulators/lending_simulator.ts index fe10d7ed2276..404bb3d5ad8d 100644 --- a/yarn-project/end-to-end/src/simulators/lending_simulator.ts +++ b/yarn-project/end-to-end/src/simulators/lending_simulator.ts @@ -186,14 +186,16 @@ export class LendingSimulator { expect(this.borrowed).toEqual(this.stableCoin.totalSupply - this.mintedOutside); - const asset = await this.lendingContract.methods.get_asset(0).simulate({ from: this.account.address }); + const { result: asset } = await this.lendingContract.methods.get_asset(0).simulate({ from: this.account.address }); const interestAccumulator = asset['interest_accumulator']; expect(interestAccumulator).toEqual(this.accumulator); expect(asset['last_updated_ts']).toEqual(BigInt(this.time)); for (const key of [this.account.address, AztecAddress.fromField(await this.account.key())]) { - const privatePos = await this.lendingContract.methods.get_position(key).simulate({ from: this.account.address }); + const { result: privatePos } = await this.lendingContract.methods + .get_position(key) + .simulate({ from: this.account.address }); expect(new Fr(privatePos['collateral'])).toEqual(this.collateral[key.toString()] ?? Fr.ZERO); expect(new Fr(privatePos['static_debt'])).toEqual(this.staticDebt[key.toString()] ?? Fr.ZERO); expect(privatePos['debt']).toEqual( diff --git a/yarn-project/end-to-end/src/simulators/token_simulator.ts b/yarn-project/end-to-end/src/simulators/token_simulator.ts index f29f6403a4d2..a2065beb4426 100644 --- a/yarn-project/end-to-end/src/simulators/token_simulator.ts +++ b/yarn-project/end-to-end/src/simulators/token_simulator.ts @@ -109,7 +109,9 @@ export class TokenSimulator { await Promise.all( chunk(calls, 5).map(batch => new BatchCall(this.defaultWallet, batch).simulate({ from: this.defaultAddress })), ) - ).flat(); + ) + .flat() + .map(r => r.result); expect(results[0]).toEqual(this.totalSupply); // Check that all our balances match @@ -123,7 +125,9 @@ export class TokenSimulator { const wallet = this.lookupProvider.get(address.toString()); const asset = wallet ? this.token.withWallet(wallet) : this.token; - const actualPrivateBalance = await asset.methods.balance_of_private(address).simulate({ from: address }); + const { result: actualPrivateBalance } = await asset.methods + .balance_of_private(address) + .simulate({ from: address }); expect(actualPrivateBalance).toEqual(this.balanceOfPrivate(address)); } } diff --git a/yarn-project/end-to-end/src/spartan/1tps.test.ts b/yarn-project/end-to-end/src/spartan/1tps.test.ts index b771d4621308..6d939deb5886 100644 --- a/yarn-project/end-to-end/src/spartan/1tps.test.ts +++ b/yarn-project/end-to-end/src/spartan/1tps.test.ts @@ -63,7 +63,8 @@ describe('token transfer test', () => { it('can get info', async () => { const name = readFieldCompressedString( - await testAccounts.tokenContract.methods.private_get_name().simulate({ from: testAccounts.tokenAdminAddress }), + (await testAccounts.tokenContract.methods.private_get_name().simulate({ from: testAccounts.tokenAdminAddress })) + .result, ); expect(name).toBe(testAccounts.tokenName); }); @@ -74,16 +75,20 @@ describe('token transfer test', () => { for (const acc of testAccounts.accounts) { expect(MINT_AMOUNT).toBe( - await testAccounts.tokenContract.methods - .balance_of_public(acc) - .simulate({ from: testAccounts.tokenAdminAddress }), + ( + await testAccounts.tokenContract.methods + .balance_of_public(acc) + .simulate({ from: testAccounts.tokenAdminAddress }) + ).result, ); } expect(0n).toBe( - await testAccounts.tokenContract.methods - .balance_of_public(recipient) - .simulate({ from: testAccounts.tokenAdminAddress }), + ( + await testAccounts.tokenContract.methods + .balance_of_public(recipient) + .simulate({ from: testAccounts.tokenAdminAddress }) + ).result, ); const defaultAccountAddress = testAccounts.accounts[0]; @@ -128,7 +133,7 @@ describe('token transfer test', () => { }), ); - const recipientBalance = await testAccounts.tokenContract.methods + const { result: recipientBalance } = await testAccounts.tokenContract.methods .balance_of_public(recipient) .simulate({ from: testAccounts.tokenAdminAddress }); logger.info(`recipientBalance: ${recipientBalance}`); diff --git a/yarn-project/end-to-end/src/spartan/4epochs.test.ts b/yarn-project/end-to-end/src/spartan/4epochs.test.ts index b2dcdd2ca987..a0d42328fb7a 100644 --- a/yarn-project/end-to-end/src/spartan/4epochs.test.ts +++ b/yarn-project/end-to-end/src/spartan/4epochs.test.ts @@ -68,7 +68,8 @@ describe('token transfer test', () => { it('can get info', async () => { const name = readFieldCompressedString( - await testAccounts.tokenContract.methods.private_get_name().simulate({ from: testAccounts.tokenAdminAddress }), + (await testAccounts.tokenContract.methods.private_get_name().simulate({ from: testAccounts.tokenAdminAddress })) + .result, ); expect(name).toBe(testAccounts.tokenName); logger.info(`Token name verified: ${name}`); @@ -85,18 +86,22 @@ describe('token transfer test', () => { for (const acc of testAccounts.accounts) { expect(MINT_AMOUNT).toBe( - await testAccounts.tokenContract.methods - .balance_of_public(acc) - .simulate({ from: testAccounts.tokenAdminAddress }), + ( + await testAccounts.tokenContract.methods + .balance_of_public(acc) + .simulate({ from: testAccounts.tokenAdminAddress }) + ).result, ); } logger.info('Minted tokens'); expect(0n).toBe( - await testAccounts.tokenContract.methods - .balance_of_public(recipient) - .simulate({ from: testAccounts.tokenAdminAddress }), + ( + await testAccounts.tokenContract.methods + .balance_of_public(recipient) + .simulate({ from: testAccounts.tokenAdminAddress }) + ).result, ); // For each round, make both private and public transfers @@ -132,16 +137,20 @@ describe('token transfer test', () => { for (const acc of testAccounts.accounts) { expect(MINT_AMOUNT - ROUNDS * transferAmount).toBe( - await testAccounts.tokenContract.methods - .balance_of_public(acc) - .simulate({ from: testAccounts.tokenAdminAddress }), + ( + await testAccounts.tokenContract.methods + .balance_of_public(acc) + .simulate({ from: testAccounts.tokenAdminAddress }) + ).result, ); } expect(ROUNDS * transferAmount * BigInt(testAccounts.accounts.length)).toBe( - await testAccounts.tokenContract.methods - .balance_of_public(recipient) - .simulate({ from: testAccounts.tokenAdminAddress }), + ( + await testAccounts.tokenContract.methods + .balance_of_public(recipient) + .simulate({ from: testAccounts.tokenAdminAddress }) + ).result, ); }); }); diff --git a/yarn-project/end-to-end/src/spartan/n_tps.test.ts b/yarn-project/end-to-end/src/spartan/n_tps.test.ts index 95405e1b9e84..5bbdb2a94136 100644 --- a/yarn-project/end-to-end/src/spartan/n_tps.test.ts +++ b/yarn-project/end-to-end/src/spartan/n_tps.test.ts @@ -279,10 +279,10 @@ describe('sustained N TPS test', () => { logger.info('Deploying benchmark contract...'); const sponsor = new SponsoredFeePaymentMethod(await getSponsoredFPCAddress()); - benchmarkContract = await BenchmarkingContract.deploy(localTestAccounts[0].wallet).send({ + ({ contract: benchmarkContract } = await BenchmarkingContract.deploy(localTestAccounts[0].wallet).send({ from: localTestAccounts[0].recipientAddress, fee: { paymentMethod: sponsor }, - }); + })); logger.info('Benchmark contract deployed', { address: benchmarkContract.address.toString() }); logger.info('Installing chaos mesh chart', { diff --git a/yarn-project/end-to-end/src/spartan/n_tps_prove.test.ts b/yarn-project/end-to-end/src/spartan/n_tps_prove.test.ts index 0e20ccefc471..59d7d567a208 100644 --- a/yarn-project/end-to-end/src/spartan/n_tps_prove.test.ts +++ b/yarn-project/end-to-end/src/spartan/n_tps_prove.test.ts @@ -302,10 +302,10 @@ describe(`prove ${TARGET_TPS}TPS test`, () => { ); logger.info('Deploying benchmark contract...'); - benchmarkContract = await AvmGadgetsTestContract.deploy(wallets[0]).send({ + ({ contract: benchmarkContract } = await AvmGadgetsTestContract.deploy(wallets[0]).send({ from: accountAddresses[0], fee: { paymentMethod: sponsor }, - }); + })); logger.info('Test setup complete'); }); diff --git a/yarn-project/end-to-end/src/spartan/reorg.test.ts b/yarn-project/end-to-end/src/spartan/reorg.test.ts index 946f4f4a3b1a..e01d4ee06f20 100644 --- a/yarn-project/end-to-end/src/spartan/reorg.test.ts +++ b/yarn-project/end-to-end/src/spartan/reorg.test.ts @@ -32,16 +32,20 @@ const debugLogger = createLogger('e2e:spartan-test:reorg'); async function checkBalances(testAccounts: TestAccounts, mintAmount: bigint, totalAmountTransferred: bigint) { for (const acc of testAccounts.accounts) { expect( - await testAccounts.tokenContract.methods - .balance_of_public(acc) - .simulate({ from: testAccounts.tokenAdminAddress }), + ( + await testAccounts.tokenContract.methods + .balance_of_public(acc) + .simulate({ from: testAccounts.tokenAdminAddress }) + ).result, ).toBe(mintAmount - totalAmountTransferred); } expect( - await testAccounts.tokenContract.methods - .balance_of_public(testAccounts.recipientAddress) - .simulate({ from: testAccounts.tokenAdminAddress }), + ( + await testAccounts.tokenContract.methods + .balance_of_public(testAccounts.recipientAddress) + .simulate({ from: testAccounts.tokenAdminAddress }) + ).result, ).toBe(totalAmountTransferred * BigInt(testAccounts.accounts.length)); } diff --git a/yarn-project/end-to-end/src/spartan/reqresp_effectiveness.test.ts b/yarn-project/end-to-end/src/spartan/reqresp_effectiveness.test.ts index 11f3e1b220d7..802509ce0a69 100644 --- a/yarn-project/end-to-end/src/spartan/reqresp_effectiveness.test.ts +++ b/yarn-project/end-to-end/src/spartan/reqresp_effectiveness.test.ts @@ -72,7 +72,8 @@ describe('reqresp effectiveness under tx drop', () => { testAccounts = await deploySponsoredTestAccountsWithTokens(wallet, aztecNode, MINT_AMOUNT, logger); recipient = testAccounts.recipientAddress; const name = readFieldCompressedString( - await testAccounts.tokenContract.methods.private_get_name().simulate({ from: testAccounts.tokenAdminAddress }), + (await testAccounts.tokenContract.methods.private_get_name().simulate({ from: testAccounts.tokenAdminAddress })) + .result, ); expect(name).toBe(testAccounts.tokenName); }); diff --git a/yarn-project/end-to-end/src/spartan/setup_test_wallets.ts b/yarn-project/end-to-end/src/spartan/setup_test_wallets.ts index c7567e794750..0f0a63458835 100644 --- a/yarn-project/end-to-end/src/spartan/setup_test_wallets.ts +++ b/yarn-project/end-to-end/src/spartan/setup_test_wallets.ts @@ -130,7 +130,8 @@ async function deployAccountWithDiagnostics( const deployMethod = await account.getDeployMethod(); let txHash; try { - txHash = await deployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod }, wait: NO_WAIT }); + const deployResult = await deployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod }, wait: NO_WAIT }); + txHash = deployResult.txHash; await waitForTx(aztecNode, txHash, { timeout: 2400 }); logger.info(`${accountLabel} deployed at ${account.address}`); } catch (error) { @@ -266,7 +267,7 @@ async function bridgeL1FeeJuice( const claim = await portal.bridgeTokensPublic(recipient, amount, true /* mint */); const isSynced = async () => - (await aztecNode.getL1ToL2MessageBlock(Fr.fromHexString(claim.messageHash))) !== undefined; + (await aztecNode.getL1ToL2MessageCheckpoint(Fr.fromHexString(claim.messageHash))) !== undefined; await retryUntil(isSynced, `message ${claim.messageHash} sync`, 24, 0.5); log.info(`Created a claim for ${amount} L1 fee juice to ${recipient}.`, claim); @@ -298,13 +299,9 @@ async function deployTokenAndMint( logger: Logger, ) { logger.verbose(`Deploying TokenContract...`); - const { contract: tokenContract } = await TokenContract.deploy( - wallet, - admin, - TOKEN_NAME, - TOKEN_SYMBOL, - TOKEN_DECIMALS, - ).send({ + const { + receipt: { contract: tokenContract }, + } = await TokenContract.deploy(wallet, admin, TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS).send({ from: admin, fee: { paymentMethod, diff --git a/yarn-project/end-to-end/src/spartan/transfer.test.ts b/yarn-project/end-to-end/src/spartan/transfer.test.ts index b3fda8fb1186..7db4b2c0e2aa 100644 --- a/yarn-project/end-to-end/src/spartan/transfer.test.ts +++ b/yarn-project/end-to-end/src/spartan/transfer.test.ts @@ -52,7 +52,8 @@ describe('token transfer test', () => { it('can get info', async () => { const name = readFieldCompressedString( - await testAccounts.tokenContract.methods.private_get_name().simulate({ from: testAccounts.tokenAdminAddress }), + (await testAccounts.tokenContract.methods.private_get_name().simulate({ from: testAccounts.tokenAdminAddress })) + .result, ); expect(name).toBe(testAccounts.tokenName); }); @@ -63,16 +64,20 @@ describe('token transfer test', () => { for (const a of testAccounts.accounts) { expect(MINT_AMOUNT).toBe( - await testAccounts.tokenContract.methods - .balance_of_public(a) - .simulate({ from: testAccounts.tokenAdminAddress }), + ( + await testAccounts.tokenContract.methods + .balance_of_public(a) + .simulate({ from: testAccounts.tokenAdminAddress }) + ).result, ); } expect(0n).toBe( - await testAccounts.tokenContract.methods - .balance_of_public(recipient) - .simulate({ from: testAccounts.tokenAdminAddress }), + ( + await testAccounts.tokenContract.methods + .balance_of_public(recipient) + .simulate({ from: testAccounts.tokenAdminAddress }) + ).result, ); // For each round, make both private and public transfers @@ -94,14 +99,16 @@ describe('token transfer test', () => { for (const a of testAccounts.accounts) { expect(MINT_AMOUNT - ROUNDS * transferAmount).toBe( - await testAccounts.tokenContract.methods.balance_of_public(a).simulate({ from: a }), + (await testAccounts.tokenContract.methods.balance_of_public(a).simulate({ from: a })).result, ); } expect(ROUNDS * transferAmount * BigInt(testAccounts.accounts.length)).toBe( - await testAccounts.tokenContract.methods - .balance_of_public(recipient) - .simulate({ from: testAccounts.tokenAdminAddress }), + ( + await testAccounts.tokenContract.methods + .balance_of_public(recipient) + .simulate({ from: testAccounts.tokenAdminAddress }) + ).result, ); }); }); diff --git a/yarn-project/foundation/src/config/network_config.ts b/yarn-project/foundation/src/config/network_config.ts index b4cdc8549533..7cae851259ea 100644 --- a/yarn-project/foundation/src/config/network_config.ts +++ b/yarn-project/foundation/src/config/network_config.ts @@ -10,6 +10,7 @@ export const NetworkConfigSchema = z l1ChainId: z.number(), blockDurationMs: z.number().positive().optional(), nodeVersion: z.string().optional(), + txPublicSetupAllowListExtend: z.string().optional(), }) .passthrough(); // Allow additional unknown fields to pass through diff --git a/yarn-project/ivc-integration/src/chonk_integration.test.ts b/yarn-project/ivc-integration/src/chonk_integration.test.ts index 6e47c3ac5c86..b089de47d01f 100644 --- a/yarn-project/ivc-integration/src/chonk_integration.test.ts +++ b/yarn-project/ivc-integration/src/chonk_integration.test.ts @@ -1,7 +1,8 @@ -import { AztecClientBackend, BackendType, Barretenberg } from '@aztec/bb.js'; +import { AztecClientBackend, BackendType, Barretenberg, toChonkProof } from '@aztec/bb.js'; import { createLogger } from '@aztec/foundation/log'; import { jest } from '@jest/globals'; +import { Decoder } from 'msgpackr'; import { ungzip } from 'pako'; import { @@ -61,6 +62,29 @@ describe.each([BackendType.Wasm, BackendType.NativeUnixSocket])('Client IVC Inte expect(verified).toBe(true); }); + it('Should compress and decompress a client IVC proof via bbapi', async () => { + const [bytecodes, witnessStack, , vks] = await generateTestingIVCStack(1, 0); + const ivcBackend = new AztecClientBackend(bytecodes, barretenberg); + const [, proof, vk] = await ivcBackend.prove(witnessStack, vks); + + // Decode the msgpack-encoded proof back to a ChonkProof object + const chonkProof = toChonkProof(new Decoder({ useRecords: false }).decode(proof)); + + // Compress via bbapi + const compressResult = await barretenberg.chonkCompressProof({ proof: chonkProof }); + expect(compressResult.compressedProof.length).toBeGreaterThan(0); + logger.info(`Compressed proof: ${compressResult.compressedProof.length} bytes`); + + // Decompress via bbapi + const decompressResult = await barretenberg.chonkDecompressProof({ + compressedProof: compressResult.compressedProof, + }); + + // Verify the decompressed proof matches the original + const verified = await barretenberg.chonkVerify({ proof: decompressResult.proof, vk }); + expect(verified.valid).toBe(true); + }); + it('Should generate an array of gate numbers for the stack of programs being proved by ClientIVC', async () => { // Create ACIR bytecodes const bytecodes = [ diff --git a/yarn-project/native/src/native_module.ts b/yarn-project/native/src/native_module.ts index b1995ac61aa0..bcea92100216 100644 --- a/yarn-project/native/src/native_module.ts +++ b/yarn-project/native/src/native_module.ts @@ -128,23 +128,33 @@ export function cancelSimulation(token: CancellationToken): void { } /** - * Concurrency limiting for C++ AVM simulation to prevent libuv thread pool exhaustion. - * - * The C++ simulator uses NAPI BlockingCall to callback to TypeScript for contract data. - * This blocks the libuv thread while waiting for the callback to complete. If all libuv - * threads are blocked waiting for callbacks, no threads remain to service those callbacks, - * causing deadlock. - * - * We limit concurrent simulations to UV_THREADPOOL_SIZE / 2 to ensure threads remain - * available for callback processing. + * Maximum number of concurrent AVM simulations. Each simulation spawns a dedicated OS thread, + * so this controls resource usage. Defaults to 4. Set to 0 for unlimited. */ -const UV_THREADPOOL_SIZE = parseInt(process.env.UV_THREADPOOL_SIZE ?? '4', 10); -const MAX_CONCURRENT_AVM_SIMULATIONS = Math.max(1, Math.floor(UV_THREADPOOL_SIZE / 2)); -const avmSimulationSemaphore = new Semaphore(MAX_CONCURRENT_AVM_SIMULATIONS); +const AVM_MAX_CONCURRENT_SIMULATIONS = parseInt(process.env.AVM_MAX_CONCURRENT_SIMULATIONS ?? '4', 10); +const avmSimulationSemaphore = + AVM_MAX_CONCURRENT_SIMULATIONS > 0 ? new Semaphore(AVM_MAX_CONCURRENT_SIMULATIONS) : null; + +async function withAvmConcurrencyLimit(fn: () => Promise): Promise { + if (!avmSimulationSemaphore) { + return fn(); + } + await avmSimulationSemaphore.acquire(); + try { + return await fn(); + } finally { + avmSimulationSemaphore.release(); + } +} /** * AVM simulation function that takes serialized inputs and a contract provider. * The contract provider enables C++ to callback to TypeScript for contract data during simulation. + * + * Simulations run on dedicated std::threads (not the libuv thread pool), so there is no risk + * of libuv thread pool exhaustion or deadlock from C++ BlockingCall callbacks. + * Concurrency is limited by AVM_MAX_CONCURRENT_SIMULATIONS (default 4, 0 = unlimited). + * * @param inputs - Msgpack-serialized AvmFastSimulationInputs buffer * @param contractProvider - Object with callbacks for fetching contract instances and classes * @param worldStateHandle - Native handle to WorldState instance @@ -153,7 +163,7 @@ const avmSimulationSemaphore = new Semaphore(MAX_CONCURRENT_AVM_SIMULATIONS); * @param cancellationToken - Optional token to enable cancellation support * @returns Promise resolving to msgpack-serialized AvmCircuitPublicInputs buffer */ -export async function avmSimulate( +export function avmSimulate( inputs: Buffer, contractProvider: ContractProvider, worldStateHandle: any, @@ -161,35 +171,30 @@ export async function avmSimulate( logger?: Logger, cancellationToken?: CancellationToken, ): Promise { - await avmSimulationSemaphore.acquire(); - - try { - return await nativeAvmSimulate( + return withAvmConcurrencyLimit(() => + nativeAvmSimulate( inputs, contractProvider, worldStateHandle, LogLevels.indexOf(logLevel), logger ? (level: LogLevel, msg: string) => logger[level](msg) : null, cancellationToken, - ); - } finally { - avmSimulationSemaphore.release(); - } + ), + ); } /** * AVM simulation function that uses pre-collected hints from TypeScript simulation. * All contract data and merkle tree hints are included in the AvmCircuitInputs, so no runtime * callbacks to TS or WS pointer are needed. + * + * Simulations run on dedicated std::threads (not the libuv thread pool). + * Concurrency is limited by AVM_MAX_CONCURRENT_SIMULATIONS (default 4, 0 = unlimited). + * * @param inputs - Msgpack-serialized AvmCircuitInputs (AvmProvingInputs in C++) buffer * @param logLevel - Log level to control C++ verbosity * @returns Promise resolving to msgpack-serialized simulation results buffer */ -export async function avmSimulateWithHintedDbs(inputs: Buffer, logLevel: LogLevel = 'info'): Promise { - await avmSimulationSemaphore.acquire(); - try { - return await nativeAvmSimulateWithHintedDbs(inputs, LogLevels.indexOf(logLevel)); - } finally { - avmSimulationSemaphore.release(); - } +export function avmSimulateWithHintedDbs(inputs: Buffer, logLevel: LogLevel = 'info'): Promise { + return withAvmConcurrencyLimit(() => nativeAvmSimulateWithHintedDbs(inputs, LogLevels.indexOf(logLevel))); } diff --git a/yarn-project/p2p/src/client/p2p_client.ts b/yarn-project/p2p/src/client/p2p_client.ts index 7996594ff9cb..a6be4b34799a 100644 --- a/yarn-project/p2p/src/client/p2p_client.ts +++ b/yarn-project/p2p/src/client/p2p_client.ts @@ -18,7 +18,6 @@ import { type L2TipsStore, } from '@aztec/stdlib/block'; import type { ContractDataSource } from '@aztec/stdlib/contract'; -import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers'; import { type PeerInfo, tryStop } from '@aztec/stdlib/interfaces/server'; import { type BlockProposal, CheckpointAttestation, type CheckpointProposal, type TopicType } from '@aztec/stdlib/p2p'; import type { BlockHeader, Tx, TxHash } from '@aztec/stdlib/tx'; @@ -111,27 +110,6 @@ export class P2PClient extends WithTracer implements P2P { this.telemetry, ); - // Default to collecting all txs when we see a valid proposal - // This can be overridden by the validator client to validate, and it will call getTxsForBlockProposal on its own - // Note: Validators do NOT attest to individual blocks - attestations are only for checkpoint proposals. - // TODO(palla/txs): We should not trigger a request for txs on a proposal before fully validating it. We need to bring - // validator-client code into here so we can validate a proposal is reasonable. - this.registerBlockProposalHandler(async (block, sender) => { - this.log.debug(`Received block proposal from ${sender.toString()}`); - // TODO(palla/txs): Need to subtract validatorReexecuteDeadlineMs from this deadline (see ValidatorClient.getReexecutionDeadline) - const constants = this.txCollection.getConstants(); - const nextSlotTimestampSeconds = Number(getTimestampForSlot(SlotNumber(block.slotNumber + 1), constants)); - const deadline = new Date(nextSlotTimestampSeconds * 1000); - const parentBlock = await this.l2BlockSource.getBlockHeaderByArchive(block.blockHeader.lastArchive.root); - if (!parentBlock) { - this.log.debug(`Cannot collect txs for proposal as parent block not found`); - return false; - } - const blockNumber = BlockNumber(parentBlock.getBlockNumber() + 1); - await this.txProvider.getTxsForBlockProposal(block, blockNumber, { pinnedPeer: sender, deadline }); - return true; - }); - this.l2Tips = new L2TipsKVStore(store, 'p2p_client'); this.synchedLatestSlot = store.openSingleton('p2p_pool_last_l2_slot'); } diff --git a/yarn-project/p2p/src/config.test.ts b/yarn-project/p2p/src/config.test.ts index f537cffab724..02185e3322c2 100644 --- a/yarn-project/p2p/src/config.test.ts +++ b/yarn-project/p2p/src/config.test.ts @@ -5,18 +5,14 @@ import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { getP2PDefaultConfig, parseAllowList } from './config.js'; describe('config', () => { - it('parses allow list', async () => { - const instance = { address: await AztecAddress.random() }; + it('parses allow list with required selectors', async () => { const instanceFunction = { address: await AztecAddress.random(), selector: FunctionSelector.random() }; - const classId = { classId: Fr.random() }; const classFunction = { classId: Fr.random(), selector: FunctionSelector.random() }; - const config = [instance, instanceFunction, classId, classFunction]; + const config = [instanceFunction, classFunction]; const configStrings = [ - `I:${instance.address}`, `I:${instanceFunction.address}:${instanceFunction.selector}`, - `C:${classId.classId}`, `C:${classFunction.classId}:${classFunction.selector}`, ]; const stringifiedAllowList = configStrings.join(','); @@ -25,6 +21,64 @@ describe('config', () => { expect(allowList).toEqual(config); }); + it('parses entries with flags', async () => { + const address = await AztecAddress.random(); + const selector = FunctionSelector.random(); + const classId = Fr.random(); + const classSelector = FunctionSelector.random(); + + const allowList = parseAllowList(`I:${address}:${selector}:os+cl=4,C:${classId}:${classSelector}:rn+os+cl=10`); + + expect(allowList).toEqual([ + { address, selector, onlySelf: true, calldataLength: 4 }, + { classId, selector: classSelector, rejectNullMsgSender: true, onlySelf: true, calldataLength: 10 }, + ]); + }); + + it('parses entries without flags (backward compat)', async () => { + const address = await AztecAddress.random(); + const selector = FunctionSelector.random(); + + const allowList = parseAllowList(`I:${address}:${selector}`); + expect(allowList).toEqual([{ address, selector }]); + }); + + it('rejects entry with unknown flag', async () => { + const address = await AztecAddress.random(); + const selector = FunctionSelector.random(); + expect(() => parseAllowList(`I:${address}:${selector}:unknown`)).toThrow('unknown flag'); + }); + + it('rejects entry with invalid calldataLength', async () => { + const address = await AztecAddress.random(); + const selector = FunctionSelector.random(); + expect(() => parseAllowList(`I:${address}:${selector}:cl=abc`)).toThrow('invalid calldataLength'); + }); + + it('rejects instance entry without selector', async () => { + const address = await AztecAddress.random(); + expect(() => parseAllowList(`I:${address}`)).toThrow('selector is required'); + }); + + it('rejects class entry without selector', () => { + const classId = Fr.random(); + expect(() => parseAllowList(`C:${classId}`)).toThrow('selector is required'); + }); + + it('rejects entry with unknown type', () => { + expect(() => parseAllowList(`X:0x1234:0x12345678`)).toThrow('unknown type'); + }); + + it('parses empty string', () => { + expect(parseAllowList('')).toEqual([]); + }); + + it('handles whitespace in entries', async () => { + const instanceFunction = { address: await AztecAddress.random(), selector: FunctionSelector.random() }; + const allowList = parseAllowList(` I:${instanceFunction.address}:${instanceFunction.selector} `); + expect(allowList).toEqual([instanceFunction]); + }); + it('defaults missing txs collector type to new', () => { const config = getP2PDefaultConfig(); expect(config.txCollectionMissingTxsCollectorType).toBe('new'); diff --git a/yarn-project/p2p/src/config.ts b/yarn-project/p2p/src/config.ts index 2de83dd387cc..73d37be73276 100644 --- a/yarn-project/p2p/src/config.ts +++ b/yarn-project/p2p/src/config.ts @@ -154,8 +154,8 @@ export interface P2PConfig /** The maximum possible size of the P2P DB in KB. Overwrites the general dataStoreMapSizeKb. */ p2pStoreMapSizeKb?: number; - /** Which calls are allowed in the public setup phase of a tx. */ - txPublicSetupAllowList: AllowedElement[]; + /** Additional entries to extend the default setup allow list. */ + txPublicSetupAllowListExtend: AllowedElement[]; /** The maximum number of pending txs before evicting lower priority txs. */ maxPendingTxCount: number; @@ -409,12 +409,13 @@ export const p2pConfigMappings: ConfigMappingsType = { parseEnv: (val: string | undefined) => (val ? +val : undefined), description: 'The maximum possible size of the P2P DB in KB. Overwrites the general dataStoreMapSizeKb.', }, - txPublicSetupAllowList: { + txPublicSetupAllowListExtend: { env: 'TX_PUBLIC_SETUP_ALLOWLIST', parseEnv: (val: string) => parseAllowList(val), - description: 'The list of functions calls allowed to run in setup', + description: + 'Additional entries to extend the default setup allow list. Format: I:address:selector[:flags],C:classId:selector[:flags]. Flags: os (onlySelf), rn (rejectNullMsgSender), cl=N (calldataLength), joined with +.', printDefault: () => - 'AuthRegistry, FeeJuice.increase_public_balance, Token.increase_public_balance, FPC.prepare_fee', + 'Default: AuthRegistry._set_authorized, AuthRegistry.set_authorized, FeeJuice._increase_public_balance', }, maxPendingTxCount: { env: 'P2P_MAX_PENDING_TX_COUNT', @@ -548,13 +549,44 @@ export const bootnodeConfigMappings = pickConfigMappings( bootnodeConfigKeys, ); +/** + * Parses a `+`-separated flags string into validation properties for an allow list entry. + * Supported flags: `os` (onlySelf), `rn` (rejectNullMsgSender), `cl=N` (calldataLength). + */ +function parseFlags( + flags: string, + entry: string, +): { onlySelf?: boolean; rejectNullMsgSender?: boolean; calldataLength?: number } { + const result: { onlySelf?: boolean; rejectNullMsgSender?: boolean; calldataLength?: number } = {}; + for (const flag of flags.split('+')) { + if (flag === 'os') { + result.onlySelf = true; + } else if (flag === 'rn') { + result.rejectNullMsgSender = true; + } else if (flag.startsWith('cl=')) { + const n = parseInt(flag.slice(3), 10); + if (isNaN(n) || n < 0) { + throw new Error( + `Invalid allow list entry "${entry}": invalid calldataLength in flag "${flag}". Expected a non-negative integer.`, + ); + } + result.calldataLength = n; + } else { + throw new Error(`Invalid allow list entry "${entry}": unknown flag "${flag}". Supported flags: os, rn, cl=N.`); + } + } + return result; +} + /** * Parses a string to a list of allowed elements. - * Each encoded is expected to be of one of the following formats - * `I:${address}` - * `I:${address}:${selector}` - * `C:${classId}` - * `C:${classId}:${selector}` + * Each entry is expected to be of one of the following formats: + * `I:${address}:${selector}` — instance (contract address) with function selector + * `C:${classId}:${selector}` — class with function selector + * + * An optional flags segment can be appended after the selector: + * `I:${address}:${selector}:${flags}` or `C:${classId}:${selector}:${flags}` + * where flags is a `+`-separated list of: `os` (onlySelf), `rn` (rejectNullMsgSender), `cl=N` (calldataLength). * * @param value The string to parse * @returns A list of allowed elements @@ -567,31 +599,37 @@ export function parseAllowList(value: string): AllowedElement[] { } for (const val of value.split(',')) { - const [typeString, identifierString, selectorString] = val.split(':'); - const selector = selectorString !== undefined ? FunctionSelector.fromString(selectorString) : undefined; + const trimmed = val.trim(); + if (!trimmed) { + continue; + } + const [typeString, identifierString, selectorString, flagsString] = trimmed.split(':'); + + if (!selectorString) { + throw new Error( + `Invalid allow list entry "${trimmed}": selector is required. Expected format: I:address:selector or C:classId:selector`, + ); + } + + const selector = FunctionSelector.fromString(selectorString); + const flags = flagsString ? parseFlags(flagsString, trimmed) : {}; if (typeString === 'I') { - if (selector) { - entries.push({ - address: AztecAddress.fromString(identifierString), - selector, - }); - } else { - entries.push({ - address: AztecAddress.fromString(identifierString), - }); - } + entries.push({ + address: AztecAddress.fromString(identifierString), + selector, + ...flags, + }); } else if (typeString === 'C') { - if (selector) { - entries.push({ - classId: Fr.fromHexString(identifierString), - selector, - }); - } else { - entries.push({ - classId: Fr.fromHexString(identifierString), - }); - } + entries.push({ + classId: Fr.fromHexString(identifierString), + selector, + ...flags, + }); + } else { + throw new Error( + `Invalid allow list entry "${trimmed}": unknown type "${typeString}". Expected "I" (instance) or "C" (class).`, + ); } } diff --git a/yarn-project/p2p/src/msg_validators/tx_validator/allowed_public_setup.ts b/yarn-project/p2p/src/msg_validators/tx_validator/allowed_public_setup.ts index 64578772ea82..b8709732a192 100644 --- a/yarn-project/p2p/src/msg_validators/tx_validator/allowed_public_setup.ts +++ b/yarn-project/p2p/src/msg_validators/tx_validator/allowed_public_setup.ts @@ -1,33 +1,53 @@ -import { FPCContract } from '@aztec/noir-contracts.js/FPC'; -import { TokenContractArtifact } from '@aztec/noir-contracts.js/Token'; import { ProtocolContractAddress } from '@aztec/protocol-contracts'; -import { getContractClassFromArtifact } from '@aztec/stdlib/contract'; +import { AuthRegistryArtifact } from '@aztec/protocol-contracts/auth-registry'; +import { FeeJuiceArtifact } from '@aztec/protocol-contracts/fee-juice'; +import { FunctionSelector, countArgumentsSize } from '@aztec/stdlib/abi'; +import type { ContractArtifact, FunctionAbi } from '@aztec/stdlib/abi'; import type { AllowedElement } from '@aztec/stdlib/interfaces/server'; -let defaultAllowedSetupFunctions: AllowedElement[] | undefined = undefined; +/** Returns the expected calldata length for a function: 1 (selector) + arguments size. */ +function getCalldataLength(artifact: ContractArtifact, functionName: string): number { + const allFunctions: FunctionAbi[] = (artifact.functions as FunctionAbi[]).concat( + artifact.nonDispatchPublicFunctions || [], + ); + const fn = allFunctions.find(f => f.name === functionName); + if (!fn) { + throw new Error(`Unknown function ${functionName} in artifact ${artifact.name}`); + } + return 1 + countArgumentsSize(fn); +} + +let defaultAllowedSetupFunctions: AllowedElement[] | undefined; + +/** Returns the default list of functions allowed to run in the setup phase of a transaction. */ export async function getDefaultAllowedSetupFunctions(): Promise { if (defaultAllowedSetupFunctions === undefined) { + const setAuthorizedInternalSelector = await FunctionSelector.fromSignature('_set_authorized((Field),Field,bool)'); + const setAuthorizedSelector = await FunctionSelector.fromSignature('set_authorized(Field,bool)'); + const increaseBalanceSelector = await FunctionSelector.fromSignature('_increase_public_balance((Field),u128)'); + defaultAllowedSetupFunctions = [ - // needed for authwit support + // AuthRegistry: needed for authwit support via private path (set_authorized_private enqueues _set_authorized) { address: ProtocolContractAddress.AuthRegistry, + selector: setAuthorizedInternalSelector, + calldataLength: getCalldataLength(AuthRegistryArtifact, '_set_authorized'), + onlySelf: true, + rejectNullMsgSender: true, }, - // needed for claiming on the same tx as a spend + // AuthRegistry: needed for authwit support via public path (PublicFeePaymentMethod calls set_authorized directly) { - address: ProtocolContractAddress.FeeJuice, - // We can't restrict the selector because public functions get routed via dispatch. - // selector: FunctionSelector.fromSignature('_increase_public_balance((Field),u128)'), - }, - // needed for private transfers via FPC - { - classId: (await getContractClassFromArtifact(TokenContractArtifact)).id, - // We can't restrict the selector because public functions get routed via dispatch. - // selector: FunctionSelector.fromSignature('_increase_public_balance((Field),u128)'), + address: ProtocolContractAddress.AuthRegistry, + selector: setAuthorizedSelector, + calldataLength: getCalldataLength(AuthRegistryArtifact, 'set_authorized'), + rejectNullMsgSender: true, }, + // FeeJuice: needed for claiming on the same tx as a spend (claim_and_end_setup enqueues this) { - classId: (await getContractClassFromArtifact(FPCContract.artifact)).id, - // We can't restrict the selector because public functions get routed via dispatch. - // selector: FunctionSelector.fromSignature('prepare_fee((Field),Field,(Field),Field)'), + address: ProtocolContractAddress.FeeJuice, + selector: increaseBalanceSelector, + calldataLength: getCalldataLength(FeeJuiceArtifact, '_increase_public_balance'), + onlySelf: true, }, ]; } diff --git a/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.test.ts b/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.test.ts index 966aaf930f2e..2d94c2f4e65d 100644 --- a/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.test.ts +++ b/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.test.ts @@ -1,9 +1,17 @@ +import { NULL_MSG_SENDER_CONTRACT_ADDRESS } from '@aztec/constants'; import { Fr } from '@aztec/foundation/curves/bn254'; import type { FunctionSelector } from '@aztec/stdlib/abi'; -import type { AztecAddress } from '@aztec/stdlib/aztec-address'; +import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { ContractDataSource } from '@aztec/stdlib/contract'; import { makeAztecAddress, makeSelector, mockTx } from '@aztec/stdlib/testing'; -import { TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED, type Tx } from '@aztec/stdlib/tx'; +import { + TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED, + TX_ERROR_SETUP_FUNCTION_UNKNOWN_CONTRACT, + TX_ERROR_SETUP_NULL_MSG_SENDER, + TX_ERROR_SETUP_ONLY_SELF_WRONG_SENDER, + TX_ERROR_SETUP_WRONG_CALLDATA_LENGTH, + type Tx, +} from '@aztec/stdlib/tx'; import { type MockProxy, mock, mockFn } from 'jest-mock-extended'; @@ -138,4 +146,347 @@ describe('PhasesTxValidator', () => { await expectInvalid(tx, TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED); }); + + it('rejects address match with wrong selector', async () => { + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + const wrongSelector = makeSelector(99); + await patchNonRevertibleFn(tx, 0, { address: allowedContract, selector: wrongSelector }); + + await expectInvalid(tx, TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED); + }); + + it('rejects class match with wrong selector', async () => { + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + const wrongSelector = makeSelector(99); + const address = await patchNonRevertibleFn(tx, 0, { selector: wrongSelector }); + + contractDataSource.getContract.mockImplementationOnce((contractAddress, atTimestamp) => { + if (timestamp !== atTimestamp) { + throw new Error('Unexpected timestamp'); + } + if (address.equals(contractAddress)) { + return Promise.resolve({ + currentContractClassId: allowedContractClass, + originalContractClassId: Fr.random(), + } as any); + } else { + return Promise.resolve(undefined); + } + }); + + await expectInvalid(tx, TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED); + }); + + it('rejects with unknown contract error when contract is not found', async () => { + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + const address = await patchNonRevertibleFn(tx, 0, { selector: allowedSetupSelector1 }); + + contractDataSource.getContract.mockImplementationOnce((contractAddress, atTimestamp) => { + if (timestamp !== atTimestamp) { + throw new Error('Unexpected timestamp'); + } + if (address.equals(contractAddress)) { + return Promise.resolve(undefined); + } + return Promise.resolve(undefined); + }); + + await expectInvalid(tx, TX_ERROR_SETUP_FUNCTION_UNKNOWN_CONTRACT); + }); + + it('does not fetch contract instance when matching by address', async () => { + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + await patchNonRevertibleFn(tx, 0, { address: allowedContract, selector: allowedSetupSelector1 }); + + await expectValid(tx); + + expect(contractDataSource.getContract).not.toHaveBeenCalled(); + }); + + describe('onlySelf validation', () => { + let allowedOnlySelfSelector: FunctionSelector; + let allowedOnlySelfContract: AztecAddress; + let allowedOnlySelfClass: Fr; + + beforeEach(() => { + allowedOnlySelfSelector = makeSelector(10); + allowedOnlySelfContract = makeAztecAddress(); + allowedOnlySelfClass = Fr.random(); + + txValidator = new PhasesTxValidator( + contractDataSource, + [ + { + address: allowedOnlySelfContract, + selector: allowedOnlySelfSelector, + onlySelf: true, + }, + { + classId: allowedOnlySelfClass, + selector: allowedOnlySelfSelector, + onlySelf: true, + }, + { + address: allowedContract, + selector: allowedSetupSelector1, + }, + ], + timestamp, + ); + }); + + it('allows onlySelf address entry when msgSender equals contractAddress', async () => { + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + await patchNonRevertibleFn(tx, 0, { + address: allowedOnlySelfContract, + selector: allowedOnlySelfSelector, + msgSender: allowedOnlySelfContract, + }); + + await expectValid(tx); + }); + + it('rejects onlySelf address entry when msgSender differs from contractAddress', async () => { + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + await patchNonRevertibleFn(tx, 0, { + address: allowedOnlySelfContract, + selector: allowedOnlySelfSelector, + msgSender: makeAztecAddress(999), + }); + + await expectInvalid(tx, TX_ERROR_SETUP_ONLY_SELF_WRONG_SENDER); + }); + + it('allows onlySelf class entry when msgSender equals contractAddress', async () => { + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + const address = await patchNonRevertibleFn(tx, 0, { + selector: allowedOnlySelfSelector, + msgSender: undefined, // will be patched below + }); + + // Patch msgSender to equal contractAddress + tx.data.forPublic!.nonRevertibleAccumulatedData.publicCallRequests[0].msgSender = address; + + contractDataSource.getContract.mockImplementationOnce((contractAddress, atTimestamp) => { + if (timestamp !== atTimestamp) { + throw new Error('Unexpected timestamp'); + } + if (address.equals(contractAddress)) { + return Promise.resolve({ + currentContractClassId: allowedOnlySelfClass, + originalContractClassId: Fr.random(), + } as any); + } + return Promise.resolve(undefined); + }); + + await expectValid(tx); + }); + + it('rejects onlySelf class entry when msgSender differs from contractAddress', async () => { + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + const address = await patchNonRevertibleFn(tx, 0, { + selector: allowedOnlySelfSelector, + msgSender: makeAztecAddress(), + }); + + contractDataSource.getContract.mockImplementationOnce((contractAddress, atTimestamp) => { + if (timestamp !== atTimestamp) { + throw new Error('Unexpected timestamp'); + } + if (address.equals(contractAddress)) { + return Promise.resolve({ + currentContractClassId: allowedOnlySelfClass, + originalContractClassId: Fr.random(), + } as any); + } + return Promise.resolve(undefined); + }); + + await expectInvalid(tx, TX_ERROR_SETUP_ONLY_SELF_WRONG_SENDER); + }); + + it('allows non-onlySelf entry with different msgSender', async () => { + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + await patchNonRevertibleFn(tx, 0, { + address: allowedContract, + selector: allowedSetupSelector1, + msgSender: makeAztecAddress(), + }); + + await expectValid(tx); + }); + }); + + describe('calldataLength validation', () => { + const expectedLength = 4; // 1 selector + 3 args + let calldataContract: AztecAddress; + let calldataSelector: FunctionSelector; + let calldataClassId: Fr; + + beforeEach(() => { + calldataContract = makeAztecAddress(70); + calldataSelector = makeSelector(70); + calldataClassId = Fr.random(); + + txValidator = new PhasesTxValidator( + contractDataSource, + [ + { + address: calldataContract, + selector: calldataSelector, + calldataLength: expectedLength, + }, + { + classId: calldataClassId, + selector: calldataSelector, + calldataLength: expectedLength, + }, + ], + timestamp, + ); + }); + + it('allows address entry with correct calldata length', async () => { + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + await patchNonRevertibleFn(tx, 0, { + address: calldataContract, + selector: calldataSelector, + args: [Fr.random(), Fr.random(), Fr.random()], + }); + + await expectValid(tx); + }); + + it('rejects address entry with too short calldata', async () => { + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + await patchNonRevertibleFn(tx, 0, { + address: calldataContract, + selector: calldataSelector, + args: [Fr.random()], + }); + + await expectInvalid(tx, TX_ERROR_SETUP_WRONG_CALLDATA_LENGTH); + }); + + it('rejects address entry with too long calldata', async () => { + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + await patchNonRevertibleFn(tx, 0, { + address: calldataContract, + selector: calldataSelector, + args: [Fr.random(), Fr.random(), Fr.random(), Fr.random(), Fr.random()], + }); + + await expectInvalid(tx, TX_ERROR_SETUP_WRONG_CALLDATA_LENGTH); + }); + + it('rejects class entry with wrong calldata length', async () => { + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + const address = await patchNonRevertibleFn(tx, 0, { + selector: calldataSelector, + args: [Fr.random()], + }); + + contractDataSource.getContract.mockImplementationOnce((contractAddress, atTimestamp) => { + if (timestamp !== atTimestamp) { + throw new Error('Unexpected timestamp'); + } + if (address.equals(contractAddress)) { + return Promise.resolve({ + currentContractClassId: calldataClassId, + originalContractClassId: Fr.random(), + } as any); + } + return Promise.resolve(undefined); + }); + + await expectInvalid(tx, TX_ERROR_SETUP_WRONG_CALLDATA_LENGTH); + }); + + it('allows any calldata length when calldataLength is not set', async () => { + txValidator = new PhasesTxValidator( + contractDataSource, + [ + { + address: calldataContract, + selector: calldataSelector, + }, + ], + timestamp, + ); + + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + await patchNonRevertibleFn(tx, 0, { + address: calldataContract, + selector: calldataSelector, + args: [Fr.random(), Fr.random(), Fr.random(), Fr.random(), Fr.random(), Fr.random()], + }); + + await expectValid(tx); + }); + }); + + describe('rejectNullMsgSender validation', () => { + const nullMsgSender = AztecAddress.fromBigInt(NULL_MSG_SENDER_CONTRACT_ADDRESS); + let rejectNullContract: AztecAddress; + let rejectNullSelector: FunctionSelector; + let noRejectNullContract: AztecAddress; + let noRejectNullSelector: FunctionSelector; + + beforeEach(() => { + rejectNullContract = makeAztecAddress(50); + rejectNullSelector = makeSelector(50); + noRejectNullContract = makeAztecAddress(51); + noRejectNullSelector = makeSelector(51); + + txValidator = new PhasesTxValidator( + contractDataSource, + [ + { + address: rejectNullContract, + selector: rejectNullSelector, + rejectNullMsgSender: true, + }, + { + address: noRejectNullContract, + selector: noRejectNullSelector, + }, + ], + timestamp, + ); + }); + + it('rejects when msgSender is NULL_MSG_SENDER_CONTRACT_ADDRESS', async () => { + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + await patchNonRevertibleFn(tx, 0, { + address: rejectNullContract, + selector: rejectNullSelector, + msgSender: nullMsgSender, + }); + + await expectInvalid(tx, TX_ERROR_SETUP_NULL_MSG_SENDER); + }); + + it('allows when msgSender is a normal address', async () => { + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + await patchNonRevertibleFn(tx, 0, { + address: rejectNullContract, + selector: rejectNullSelector, + msgSender: makeAztecAddress(100), + }); + + await expectValid(tx); + }); + + it('allows null msgSender on entries without the flag', async () => { + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + await patchNonRevertibleFn(tx, 0, { + address: noRejectNullContract, + selector: noRejectNullSelector, + msgSender: nullMsgSender, + }); + + await expectValid(tx); + }); + }); }); diff --git a/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.ts b/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.ts index 4b6370fa0477..9de8f7ef19e2 100644 --- a/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.ts +++ b/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.ts @@ -1,11 +1,17 @@ +import { NULL_MSG_SENDER_CONTRACT_ADDRESS } from '@aztec/constants'; import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log'; import { PublicContractsDB, getCallRequestsWithCalldataByPhase } from '@aztec/simulator/server'; +import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { ContractDataSource } from '@aztec/stdlib/contract'; import type { AllowedElement } from '@aztec/stdlib/interfaces/server'; import { type PublicCallRequestWithCalldata, TX_ERROR_DURING_VALIDATION, TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED, + TX_ERROR_SETUP_FUNCTION_UNKNOWN_CONTRACT, + TX_ERROR_SETUP_NULL_MSG_SENDER, + TX_ERROR_SETUP_ONLY_SELF_WRONG_SENDER, + TX_ERROR_SETUP_WRONG_CALLDATA_LENGTH, Tx, TxExecutionPhase, type TxValidationResult, @@ -45,7 +51,8 @@ export class PhasesTxValidator implements TxValidator { const setupFns = getCallRequestsWithCalldataByPhase(tx, TxExecutionPhase.SETUP); for (const setupFn of setupFns) { - if (!(await this.isOnAllowList(setupFn, this.setupAllowList))) { + const rejectionReason = await this.checkAllowList(setupFn, this.setupAllowList); + if (rejectionReason) { this.#log.verbose( `Rejecting tx ${tx.getTxHash().toString()} because it calls setup function not on allow list: ${ setupFn.request.contractAddress @@ -53,7 +60,7 @@ export class PhasesTxValidator implements TxValidator { { allowList: this.setupAllowList }, ); - return { result: 'invalid', reason: [TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED] }; + return { result: 'invalid', reason: [rejectionReason] }; } } @@ -66,53 +73,71 @@ export class PhasesTxValidator implements TxValidator { } } - private async isOnAllowList( + /** Returns a rejection reason if the call is not on the allow list, or undefined if it is allowed. */ + private async checkAllowList( publicCall: PublicCallRequestWithCalldata, allowList: AllowedElement[], - ): Promise { + ): Promise { if (publicCall.isEmpty()) { - return true; + return undefined; } const contractAddress = publicCall.request.contractAddress; const functionSelector = publicCall.functionSelector; - // do these checks first since they don't require the contract class + // Check address-based entries first since they don't require the contract class. for (const entry of allowList) { - if ('address' in entry && !('selector' in entry)) { - if (contractAddress.equals(entry.address)) { - return true; - } - } - - if ('address' in entry && 'selector' in entry) { + if ('address' in entry) { if (contractAddress.equals(entry.address) && entry.selector.equals(functionSelector)) { - return true; + if (entry.calldataLength !== undefined && publicCall.calldata.length !== entry.calldataLength) { + return TX_ERROR_SETUP_WRONG_CALLDATA_LENGTH; + } + if (entry.onlySelf && !publicCall.request.msgSender.equals(contractAddress)) { + return TX_ERROR_SETUP_ONLY_SELF_WRONG_SENDER; + } + if ( + entry.rejectNullMsgSender && + publicCall.request.msgSender.equals(AztecAddress.fromBigInt(NULL_MSG_SENDER_CONTRACT_ADDRESS)) + ) { + return TX_ERROR_SETUP_NULL_MSG_SENDER; + } + return undefined; } } + } - const contractClass = await this.contractsDB.getContractInstance(contractAddress, this.timestamp); - - if (!contractClass) { - throw new Error(`Contract not found: ${contractAddress}`); + // Check class-based entries. Fetch the contract instance lazily (only once). + let contractClassId: undefined | { value: string | undefined }; + for (const entry of allowList) { + if (!('classId' in entry)) { + continue; } - if ('classId' in entry && !('selector' in entry)) { - if (contractClass.currentContractClassId.equals(entry.classId)) { - return true; + if (contractClassId === undefined) { + const instance = await this.contractsDB.getContractInstance(contractAddress, this.timestamp); + contractClassId = { value: instance?.currentContractClassId.toString() }; + if (!contractClassId.value) { + return TX_ERROR_SETUP_FUNCTION_UNKNOWN_CONTRACT; } } - if ('classId' in entry && 'selector' in entry) { + if (contractClassId.value === entry.classId.toString() && entry.selector.equals(functionSelector)) { + if (entry.calldataLength !== undefined && publicCall.calldata.length !== entry.calldataLength) { + return TX_ERROR_SETUP_WRONG_CALLDATA_LENGTH; + } + if (entry.onlySelf && !publicCall.request.msgSender.equals(contractAddress)) { + return TX_ERROR_SETUP_ONLY_SELF_WRONG_SENDER; + } if ( - contractClass.currentContractClassId.equals(entry.classId) && - (entry.selector === undefined || entry.selector.equals(functionSelector)) + entry.rejectNullMsgSender && + publicCall.request.msgSender.equals(AztecAddress.fromBigInt(NULL_MSG_SENDER_CONTRACT_ADDRESS)) ) { - return true; + return TX_ERROR_SETUP_NULL_MSG_SENDER; } + return undefined; } } - return false; + return TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED; } } diff --git a/yarn-project/p2p/src/services/libp2p/libp2p_service.ts b/yarn-project/p2p/src/services/libp2p/libp2p_service.ts index 488135e69a70..2a81e7a1350d 100644 --- a/yarn-project/p2p/src/services/libp2p/libp2p_service.ts +++ b/yarn-project/p2p/src/services/libp2p/libp2p_service.ts @@ -1619,7 +1619,10 @@ export class LibP2PService extends WithTracer implements P2PService { nextSlotTimestamp: UInt64, ): Promise> { const gasFees = await this.getGasFees(currentBlockNumber); - const allowedInSetup = this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()); + const allowedInSetup = [ + ...(await getDefaultAllowedSetupFunctions()), + ...(this.config.txPublicSetupAllowListExtend ?? []), + ]; const blockNumber = BlockNumber(currentBlockNumber + 1); return createFirstStageTxValidationsForGossipedTransactions( diff --git a/yarn-project/p2p/src/testbench/p2p_client_testbench_worker.ts b/yarn-project/p2p/src/testbench/p2p_client_testbench_worker.ts index dc2fa88beb89..845ea7190f26 100644 --- a/yarn-project/p2p/src/testbench/p2p_client_testbench_worker.ts +++ b/yarn-project/p2p/src/testbench/p2p_client_testbench_worker.ts @@ -340,6 +340,7 @@ process.on('message', async msg => { const config: P2PConfig = { ...rawConfig, peerIdPrivateKey: rawConfig.peerIdPrivateKey ? new SecretValue(rawConfig.peerIdPrivateKey) : undefined, + priceBumpPercentage: 10n, } as P2PConfig; workerConfig = config; diff --git a/yarn-project/p2p/src/testbench/worker_client_manager.ts b/yarn-project/p2p/src/testbench/worker_client_manager.ts index d6f3b250fa96..2d8e23be6854 100644 --- a/yarn-project/p2p/src/testbench/worker_client_manager.ts +++ b/yarn-project/p2p/src/testbench/worker_client_manager.ts @@ -81,13 +81,15 @@ class WorkerClientManager { * Note: We send the raw peerIdPrivateKey string instead of SecretValue * because SecretValue.toJSON() returns '[Redacted]', losing the value. * The worker must re-wrap it in SecretValue. + * We also omit priceBumpPercentage since it's a bigint and can't be + * serialized over IPC (which uses JSON under the hood). */ private createClientConfig( clientIndex: number, port: number, otherNodes: string[], - ): Omit & { peerIdPrivateKey: string } & Partial { - return { + ): Omit & { peerIdPrivateKey: string } & Partial { + const { priceBumpPercentage: _, ...config } = { ...getP2PDefaultConfig(), p2pEnabled: true, peerIdPrivateKey: this.peerIdPrivateKeys[clientIndex], @@ -96,7 +98,10 @@ class WorkerClientManager { p2pPort: port, bootstrapNodes: [...otherNodes], ...this.p2pConfig, - } as Omit & { peerIdPrivateKey: string } & Partial; + }; + return config as Omit & { + peerIdPrivateKey: string; + } & Partial; } /** @@ -104,7 +109,9 @@ class WorkerClientManager { * Config uses raw string for peerIdPrivateKey (not SecretValue) for IPC serialization. */ private spawnWorkerProcess( - config: Omit & { peerIdPrivateKey: string } & Partial, + config: Omit & { + peerIdPrivateKey: string; + } & Partial, clientIndex: number, ): [ChildProcess, Promise] { const useCompiled = existsSync(workerJsPath); diff --git a/yarn-project/sequencer-client/src/config.ts b/yarn-project/sequencer-client/src/config.ts index 79df54bb17cc..117839911491 100644 --- a/yarn-project/sequencer-client/src/config.ts +++ b/yarn-project/sequencer-client/src/config.ts @@ -67,7 +67,7 @@ export type SequencerClientConfig = SequencerPublisherConfig & SequencerConfig & L1ReaderConfig & ChainConfig & - Pick & + Pick & Pick; export const sequencerConfigMappings: ConfigMappingsType = { @@ -223,7 +223,7 @@ export const sequencerConfigMappings: ConfigMappingsType = { description: 'Percent probability (0 - 100) of sequencer skipping checkpoint publishing (testing only)', ...numberConfigHelper(DefaultSequencerConfig.skipPublishingCheckpointsPercent), }, - ...pickConfigMappings(p2pConfigMappings, ['txPublicSetupAllowList']), + ...pickConfigMappings(p2pConfigMappings, ['txPublicSetupAllowListExtend']), }; export const sequencerClientConfigMappings: ConfigMappingsType = { diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 527f7144bf60..7da938a6c828 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -110,7 +110,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter) { const filteredConfig = pickFromSchema(config, SequencerConfigSchema); - this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowList')); + this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowListExtend')); this.config = merge(this.config, filteredConfig); this.timetable = new SequencerTimetable( { diff --git a/yarn-project/slasher/README.md b/yarn-project/slasher/README.md index dbd4454cf1eb..adb32be23305 100644 --- a/yarn-project/slasher/README.md +++ b/yarn-project/slasher/README.md @@ -185,7 +185,7 @@ These settings are configured locally on each validator node: - `slashProposeInvalidAttestationsPenalty`: Penalty for PROPOSED_INSUFFICIENT_ATTESTATIONS and PROPOSED_INCORRECT_ATTESTATIONS - `slashAttestDescendantOfInvalidPenalty`: Penalty for ATTESTED_DESCENDANT_OF_INVALID - `slashUnknownPenalty`: Default penalty for unknown offense types -- `slashMaxPayloadSize`: Maximum size of slash payloads (empire model) +- `slashMaxPayloadSize`: Maximum size of slash payloads. In the empire model this limits offenses per payload. In the tally model it limits the number of **unique validators** (across all committees and epochs in a round) that receive non-zero votes. When this cap is hit, the lowest-severity validator-epoch pairs are zeroed out first, so the most severe slashes are always preserved. Note that multiple offenses for the same validator in the same epoch are summed and counted as a single validator entry against this limit. - `slashMinPenaltyPercentage`: Agree to slashes if they are at least this percentage of the configured penalty (empire model) - `slashMaxPenaltyPercentage`: Agree to slashes if they are at most this percentage of the configured penalty (empire model) diff --git a/yarn-project/slasher/src/tally_slasher_client.ts b/yarn-project/slasher/src/tally_slasher_client.ts index d95d565746a8..862525addf63 100644 --- a/yarn-project/slasher/src/tally_slasher_client.ts +++ b/yarn-project/slasher/src/tally_slasher_client.ts @@ -46,7 +46,10 @@ export type TallySlasherSettings = Prettify< >; export type TallySlasherClientConfig = SlashOffensesCollectorConfig & - Pick; + Pick< + SlasherConfig, + 'slashValidatorsAlways' | 'slashValidatorsNever' | 'slashExecuteRoundsLookBack' | 'slashMaxPayloadSize' + >; /** * The Tally Slasher client is responsible for managing slashable offenses using @@ -358,11 +361,13 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC const committees = await this.collectCommitteesActiveDuringRound(slashedRound); const epochsForCommittees = getEpochsForRound(slashedRound, this.settings); + const { slashMaxPayloadSize } = this.config; const votes = getSlashConsensusVotesFromOffenses( offensesToSlash, committees, epochsForCommittees.map(e => BigInt(e)), - this.settings, + { ...this.settings, maxSlashedValidators: slashMaxPayloadSize }, + this.log, ); if (votes.every(v => v === 0)) { this.log.warn(`Computed votes for offenses are all zero. Skipping vote.`, { diff --git a/yarn-project/stdlib/src/block/l2_block_source.ts b/yarn-project/stdlib/src/block/l2_block_source.ts index f1daf550d7eb..39368d09ad99 100644 --- a/yarn-project/stdlib/src/block/l2_block_source.ts +++ b/yarn-project/stdlib/src/block/l2_block_source.ts @@ -49,6 +49,12 @@ export interface L2BlockSource { */ getBlockNumber(): Promise; + /** + * Gets the number of the latest L2 checkpoint processed by the block source implementation. + * @returns The number of the latest L2 checkpoint processed by the block source implementation. + */ + getCheckpointNumber(): Promise; + /** * Gets the number of the latest L2 block proven seen by the block source implementation. * @returns The number of the latest L2 block proven seen by the block source implementation. diff --git a/yarn-project/stdlib/src/interfaces/allowed_element.ts b/yarn-project/stdlib/src/interfaces/allowed_element.ts index b8de0fd4e661..efc1a6b467bf 100644 --- a/yarn-project/stdlib/src/interfaces/allowed_element.ts +++ b/yarn-project/stdlib/src/interfaces/allowed_element.ts @@ -6,18 +6,38 @@ import type { FunctionSelector } from '../abi/function_selector.js'; import type { AztecAddress } from '../aztec-address/index.js'; import { schemas, zodFor } from '../schemas/index.js'; -type AllowedInstance = { address: AztecAddress }; -type AllowedInstanceFunction = { address: AztecAddress; selector: FunctionSelector }; -type AllowedClass = { classId: Fr }; -type AllowedClassFunction = { classId: Fr; selector: FunctionSelector }; +type AllowedInstanceFunction = { + address: AztecAddress; + selector: FunctionSelector; + onlySelf?: boolean; + rejectNullMsgSender?: boolean; + calldataLength?: number; +}; +type AllowedClassFunction = { + classId: Fr; + selector: FunctionSelector; + onlySelf?: boolean; + rejectNullMsgSender?: boolean; + calldataLength?: number; +}; -export type AllowedElement = AllowedInstance | AllowedInstanceFunction | AllowedClass | AllowedClassFunction; +export type AllowedElement = AllowedInstanceFunction | AllowedClassFunction; export const AllowedElementSchema = zodFor()( z.union([ - z.object({ address: schemas.AztecAddress, selector: schemas.FunctionSelector }), - z.object({ address: schemas.AztecAddress }), - z.object({ classId: schemas.Fr, selector: schemas.FunctionSelector }), - z.object({ classId: schemas.Fr }), + z.object({ + address: schemas.AztecAddress, + selector: schemas.FunctionSelector, + onlySelf: z.boolean().optional(), + rejectNullMsgSender: z.boolean().optional(), + calldataLength: z.number().optional(), + }), + z.object({ + classId: schemas.Fr, + selector: schemas.FunctionSelector, + onlySelf: z.boolean().optional(), + rejectNullMsgSender: z.boolean().optional(), + calldataLength: z.number().optional(), + }), ]), ); diff --git a/yarn-project/stdlib/src/interfaces/archiver.test.ts b/yarn-project/stdlib/src/interfaces/archiver.test.ts index 2b5cb983325b..710118ceb5e9 100644 --- a/yarn-project/stdlib/src/interfaces/archiver.test.ts +++ b/yarn-project/stdlib/src/interfaces/archiver.test.ts @@ -81,6 +81,11 @@ describe('ArchiverApiSchema', () => { expect(result).toEqual(BlockNumber(1)); }); + it('getCheckpointNumber', async () => { + const result = await context.client.getCheckpointNumber(); + expect(result).toEqual(CheckpointNumber(1)); + }); + it('getProvenBlockNumber', async () => { const result = await context.client.getProvenBlockNumber(); expect(result).toEqual(BlockNumber(1)); @@ -405,6 +410,9 @@ class MockArchiver implements ArchiverApi { getCheckpointedL2BlockNumber(): Promise { return Promise.resolve(BlockNumber(1)); } + getCheckpointNumber(): Promise { + return Promise.resolve(CheckpointNumber(1)); + } getFinalizedL2BlockNumber(): Promise { return Promise.resolve(BlockNumber(0)); } diff --git a/yarn-project/stdlib/src/interfaces/archiver.ts b/yarn-project/stdlib/src/interfaces/archiver.ts index 9af2b49e6fbc..949c66575040 100644 --- a/yarn-project/stdlib/src/interfaces/archiver.ts +++ b/yarn-project/stdlib/src/interfaces/archiver.ts @@ -86,6 +86,7 @@ export const ArchiverApiSchema: ApiSchemaFor = { getBlockNumber: z.function().args().returns(BlockNumberSchema), getProvenBlockNumber: z.function().args().returns(BlockNumberSchema), getCheckpointedL2BlockNumber: z.function().args().returns(BlockNumberSchema), + getCheckpointNumber: z.function().args().returns(CheckpointNumberSchema), getFinalizedL2BlockNumber: z.function().args().returns(BlockNumberSchema), getBlock: z.function().args(BlockNumberSchema).returns(L2Block.schema.optional()), getBlockHeader: z diff --git a/yarn-project/stdlib/src/interfaces/aztec-node.test.ts b/yarn-project/stdlib/src/interfaces/aztec-node.test.ts index 436945dd773e..9afac73c16d5 100644 --- a/yarn-project/stdlib/src/interfaces/aztec-node.test.ts +++ b/yarn-project/stdlib/src/interfaces/aztec-node.test.ts @@ -115,8 +115,8 @@ describe('AztecNodeApiSchema', () => { expect(response).toEqual([1n, expect.any(SiblingPath)]); }); - it('getL1ToL2MessageBlock', async () => { - const response = await context.client.getL1ToL2MessageBlock(Fr.random()); + it('getL1ToL2MessageCheckpoint', async () => { + const response = await context.client.getL1ToL2MessageCheckpoint(Fr.random()); expect(response).toEqual(5); }); @@ -209,6 +209,11 @@ describe('AztecNodeApiSchema', () => { expect(response).toBe(BlockNumber(1)); }); + it('getCheckpointNumber', async () => { + const response = await context.client.getCheckpointNumber(); + expect(response).toBe(CheckpointNumber(1)); + }); + it('isReady', async () => { const response = await context.client.isReady(); expect(response).toBe(true); @@ -578,9 +583,9 @@ class MockAztecNode implements AztecNode { expect(noteHash).toBeInstanceOf(Fr); return Promise.resolve(MembershipWitness.random(NOTE_HASH_TREE_HEIGHT)); } - getL1ToL2MessageBlock(l1ToL2Message: Fr): Promise { + getL1ToL2MessageCheckpoint(l1ToL2Message: Fr): Promise { expect(l1ToL2Message).toBeInstanceOf(Fr); - return Promise.resolve(BlockNumber(5)); + return Promise.resolve(CheckpointNumber(5)); } isL1ToL2MessageSynced(l1ToL2Message: Fr): Promise { expect(l1ToL2Message).toBeInstanceOf(Fr); @@ -658,6 +663,9 @@ class MockAztecNode implements AztecNode { getCheckpointedBlockNumber(): Promise { return Promise.resolve(BlockNumber(1)); } + getCheckpointNumber(): Promise { + return Promise.resolve(CheckpointNumber(1)); + } isReady(): Promise { return Promise.resolve(true); } diff --git a/yarn-project/stdlib/src/interfaces/aztec-node.ts b/yarn-project/stdlib/src/interfaces/aztec-node.ts index 34b8639eee7f..7f613d9e6891 100644 --- a/yarn-project/stdlib/src/interfaces/aztec-node.ts +++ b/yarn-project/stdlib/src/interfaces/aztec-node.ts @@ -4,7 +4,9 @@ import { BlockNumber, BlockNumberPositiveSchema, BlockNumberSchema, + CheckpointNumber, CheckpointNumberPositiveSchema, + CheckpointNumberSchema, EpochNumber, EpochNumberSchema, type SlotNumber, @@ -172,14 +174,14 @@ export interface AztecNode l1ToL2Message: Fr, ): Promise<[bigint, SiblingPath] | undefined>; - /** Returns the L2 block number in which this L1 to L2 message becomes available, or undefined if not found. */ - getL1ToL2MessageBlock(l1ToL2Message: Fr): Promise; + /** Returns the L2 checkpoint number in which this L1 to L2 message becomes available, or undefined if not found. */ + getL1ToL2MessageCheckpoint(l1ToL2Message: Fr): Promise; /** * Returns whether an L1 to L2 message is synced by archiver. * @param l1ToL2Message - The L1 to L2 message to check. * @returns Whether the message is synced. - * @deprecated Use `getL1ToL2MessageBlock` instead. This method may return true even if the message is not ready to use. + * @deprecated Use `getL1ToL2MessageCheckpoint` instead. This method may return true even if the message is not ready to use. */ isL1ToL2MessageSynced(l1ToL2Message: Fr): Promise; @@ -230,6 +232,12 @@ export interface AztecNode */ getCheckpointedBlockNumber(): Promise; + /** + * Method to fetch the latest checkpoint number synchronized by the node. + * @returns The checkpoint number. + */ + getCheckpointNumber(): Promise; + /** * Method to determine if the node is ready to accept transactions. * @returns - Flag indicating the readiness for tx submission. @@ -517,7 +525,7 @@ export const AztecNodeApiSchema: ApiSchemaFor = { .args(BlockParameterSchema, schemas.Fr) .returns(z.tuple([schemas.BigInt, SiblingPath.schemaFor(L1_TO_L2_MSG_TREE_HEIGHT)]).optional()), - getL1ToL2MessageBlock: z.function().args(schemas.Fr).returns(BlockNumberSchema.optional()), + getL1ToL2MessageCheckpoint: z.function().args(schemas.Fr).returns(CheckpointNumberSchema.optional()), isL1ToL2MessageSynced: z.function().args(schemas.Fr).returns(z.boolean()), @@ -534,6 +542,8 @@ export const AztecNodeApiSchema: ApiSchemaFor = { getBlockNumber: z.function().returns(BlockNumberSchema), + getCheckpointNumber: z.function().returns(CheckpointNumberSchema), + getProvenBlockNumber: z.function().returns(BlockNumberSchema), getCheckpointedBlockNumber: z.function().returns(BlockNumberSchema), diff --git a/yarn-project/stdlib/src/interfaces/block-builder.ts b/yarn-project/stdlib/src/interfaces/block-builder.ts index 056d05711378..87ed1444fff5 100644 --- a/yarn-project/stdlib/src/interfaces/block-builder.ts +++ b/yarn-project/stdlib/src/interfaces/block-builder.ts @@ -57,7 +57,7 @@ export type FullNodeBlockBuilderConfig = Pick & Pick< SequencerConfig, - | 'txPublicSetupAllowList' + | 'txPublicSetupAllowListExtend' | 'fakeProcessingDelayPerTxMs' | 'fakeThrowAfterProcessingTxCount' | 'maxTxsPerBlock' @@ -74,7 +74,7 @@ export const FullNodeBlockBuilderConfigKeys: (keyof FullNodeBlockBuilderConfig)[ 'slotDuration', 'l1ChainId', 'rollupVersion', - 'txPublicSetupAllowList', + 'txPublicSetupAllowListExtend', 'fakeProcessingDelayPerTxMs', 'fakeThrowAfterProcessingTxCount', 'maxTxsPerBlock', diff --git a/yarn-project/stdlib/src/interfaces/configs.ts b/yarn-project/stdlib/src/interfaces/configs.ts index 56435ebf3a03..a6009002575f 100644 --- a/yarn-project/stdlib/src/interfaces/configs.ts +++ b/yarn-project/stdlib/src/interfaces/configs.ts @@ -35,8 +35,8 @@ export interface SequencerConfig { acvmWorkingDirectory?: string; /** The path to the ACVM binary */ acvmBinaryPath?: string; - /** The list of functions calls allowed to run in setup */ - txPublicSetupAllowList?: AllowedElement[]; + /** Additional entries to extend the default setup allow list. */ + txPublicSetupAllowListExtend?: AllowedElement[]; /** Payload address to vote for */ governanceProposerPayload?: EthAddress; /** Whether to enforce the time table when building blocks */ @@ -98,7 +98,7 @@ export const SequencerConfigSchema = zodFor()( feeRecipient: schemas.AztecAddress.optional(), acvmWorkingDirectory: z.string().optional(), acvmBinaryPath: z.string().optional(), - txPublicSetupAllowList: z.array(AllowedElementSchema).optional(), + txPublicSetupAllowListExtend: z.array(AllowedElementSchema).optional(), governanceProposerPayload: schemas.EthAddress.optional(), l1PublishingTime: z.number().optional(), enforceTimeTable: z.boolean().optional(), @@ -135,7 +135,7 @@ type SequencerConfigOptionalKeys = | 'fakeProcessingDelayPerTxMs' | 'fakeThrowAfterProcessingTxCount' | 'l1PublishingTime' - | 'txPublicSetupAllowList' + | 'txPublicSetupAllowListExtend' | 'minValidTxsPerBlock' | 'minBlocksForCheckpoint' | 'maxTxsPerBlock' diff --git a/yarn-project/stdlib/src/interfaces/validator.ts b/yarn-project/stdlib/src/interfaces/validator.ts index 4a238e9d84ff..9be9d18faa93 100644 --- a/yarn-project/stdlib/src/interfaces/validator.ts +++ b/yarn-project/stdlib/src/interfaces/validator.ts @@ -74,7 +74,7 @@ export type ValidatorClientConfig = ValidatorHASignerConfig & { }; export type ValidatorClientFullConfig = ValidatorClientConfig & - Pick & + Pick & Pick< SlasherConfig, 'slashBroadcastedInvalidBlockPenalty' | 'slashDuplicateProposalPenalty' | 'slashDuplicateAttestationPenalty' @@ -107,7 +107,7 @@ export const ValidatorClientConfigSchema = zodFor>()( ValidatorClientConfigSchema.extend({ - txPublicSetupAllowList: z.array(AllowedElementSchema).optional(), + txPublicSetupAllowListExtend: z.array(AllowedElementSchema).optional(), broadcastInvalidBlockProposal: z.boolean().optional(), slashBroadcastedInvalidBlockPenalty: schemas.BigInt, slashDuplicateProposalPenalty: schemas.BigInt, diff --git a/yarn-project/stdlib/src/slashing/tally.test.ts b/yarn-project/stdlib/src/slashing/tally.test.ts index d79a9dcb280e..b0682890d6a5 100644 --- a/yarn-project/stdlib/src/slashing/tally.test.ts +++ b/yarn-project/stdlib/src/slashing/tally.test.ts @@ -467,6 +467,92 @@ describe('TallySlashingHelpers', () => { expect(votes.slice(4, 8)).toEqual([0, 0, 0, 0]); // Padded empty committee }); + it('truncates to maxSlashedValidators unique (validator, epoch) pairs', () => { + const offenses: Offense[] = [ + { validator: mockValidator1, amount: 30n, offenseType: OffenseType.INACTIVITY, epochOrSlot: 5n }, + { validator: mockValidator2, amount: 20n, offenseType: OffenseType.INACTIVITY, epochOrSlot: 5n }, + { validator: mockValidator3, amount: 10n, offenseType: OffenseType.INACTIVITY, epochOrSlot: 5n }, + ]; + + const committees = [[mockValidator1, mockValidator2, mockValidator3, mockValidator4]]; + const epochsForCommittees = [5n]; + // Only 2 slashed validators allowed; validator3 should be zeroed out + const votes = getSlashConsensusVotesFromOffenses(offenses, committees, epochsForCommittees, { + ...settings, + maxSlashedValidators: 2, + }); + + expect(votes).toHaveLength(4); + expect(votes[0]).toEqual(3); // validator1: included (1st) + expect(votes[1]).toEqual(2); // validator2: included (2nd) + expect(votes[2]).toEqual(0); // validator3: zeroed out (limit reached) + expect(votes[3]).toEqual(0); // validator4: no offenses + }); + + it('counts the same validator in multiple epochs as separate slashed pairs', () => { + // An always-slash validator appears once per epoch committee, each generating a slash() call + const offenses = [ + { + validator: mockValidator1, + amount: 30n, + offenseType: OffenseType.UNKNOWN, + epochOrSlot: undefined, // always-slash + }, + { validator: mockValidator2, amount: 20n, offenseType: OffenseType.INACTIVITY, epochOrSlot: 5n }, + { validator: mockValidator3, amount: 10n, offenseType: OffenseType.INACTIVITY, epochOrSlot: 6n }, + ]; + + const committees = [ + [mockValidator1, mockValidator2], + [mockValidator1, mockValidator3], + ]; + const epochsForCommittees = [5n, 6n]; + // Limit of 3: validator1@epoch5, validator2@epoch5, validator1@epoch6 are included; + // validator3@epoch6 is zeroed out + const votes = getSlashConsensusVotesFromOffenses(offenses, committees, epochsForCommittees, { + ...settings, + maxSlashedValidators: 3, + }); + + expect(votes).toHaveLength(8); // 2 committees × 4 targetCommitteeSize + expect(votes[0]).toEqual(3); // validator1 @ epoch5: included (1st) + expect(votes[1]).toEqual(2); // validator2 @ epoch5: included (2nd) + expect(votes[2]).toEqual(0); // padded + expect(votes[3]).toEqual(0); // padded + expect(votes[4]).toEqual(3); // validator1 @ epoch6: included (3rd) + expect(votes[5]).toEqual(0); // validator3 @ epoch6: zeroed out (limit reached) + expect(votes[6]).toEqual(0); // padded + expect(votes[7]).toEqual(0); // padded + }); + + it('truncates based on validator count, not offense count', () => { + // 3 offenses for validator1, 2 for validator2, 1 for validator3 — but only 2 validators allowed. + // Truncation must cut one validator (not one offense record). + const offenses: Offense[] = [ + { validator: mockValidator1, amount: 15n, offenseType: OffenseType.INACTIVITY, epochOrSlot: 5n }, + { validator: mockValidator1, amount: 8n, offenseType: OffenseType.DATA_WITHHOLDING, epochOrSlot: 5n }, + { validator: mockValidator1, amount: 5n, offenseType: OffenseType.VALID_EPOCH_PRUNED, epochOrSlot: 5n }, + { validator: mockValidator2, amount: 20n, offenseType: OffenseType.INACTIVITY, epochOrSlot: 5n }, + { validator: mockValidator2, amount: 5n, offenseType: OffenseType.DATA_WITHHOLDING, epochOrSlot: 5n }, + { validator: mockValidator3, amount: 10n, offenseType: OffenseType.INACTIVITY, epochOrSlot: 5n }, + ]; + + const committees = [[mockValidator1, mockValidator2, mockValidator3, mockValidator4]]; + const epochsForCommittees = [5n]; + // validator1: 15n+8n+5n=28n → 2 units, validator2: 20n+5n=25n → 2 units, validator3: 10n → 1 unit + // Limit of 2 validators: validator3 (lowest vote) is zeroed out + const votes = getSlashConsensusVotesFromOffenses(offenses, committees, epochsForCommittees, { + ...settings, + maxSlashedValidators: 2, + }); + + expect(votes).toHaveLength(4); + expect(votes[0]).toEqual(2); // validator1: 28n → 2 units, included + expect(votes[1]).toEqual(2); // validator2: 25n → 2 units, included + expect(votes[2]).toEqual(0); // validator3: 10n → 1 unit, zeroed out (only 2 validators allowed) + expect(votes[3]).toEqual(0); // validator4: no offenses + }); + it('handles multiple consecutive empty committees', () => { const offenses: Offense[] = [ { diff --git a/yarn-project/stdlib/src/slashing/tally.ts b/yarn-project/stdlib/src/slashing/tally.ts index 91bed7e4ff35..c22e76fe992c 100644 --- a/yarn-project/stdlib/src/slashing/tally.ts +++ b/yarn-project/stdlib/src/slashing/tally.ts @@ -1,6 +1,7 @@ import { sumBigint } from '@aztec/foundation/bigint'; import { padArrayEnd } from '@aztec/foundation/collection'; import { EthAddress } from '@aztec/foundation/eth-address'; +import { type Logger, createLogger } from '@aztec/foundation/log'; import type { PartialBy } from '@aztec/foundation/types'; import { getEpochForOffense } from './helpers.js'; @@ -12,6 +13,9 @@ import type { Offense, ValidatorSlashVote } from './types.js'; * @param committees - Array of committees (each containing array of validator addresses) * @param epochsForCommittees - Array of epochs corresponding to each committee * @param settings - Settings including slashingAmounts and optional validator override lists + * @param settings.maxSlashedValidators - If set, limits the total number of [validator, epoch] pairs + * with non-zero votes. The lowest-vote pairs are zeroed out to stay within the limit. + * @param logger - Logger, logs which validators were dropped. * @returns Array of ValidatorSlashVote, where each vote is how many slash units the validator in that position should be slashed */ export function getSlashConsensusVotesFromOffenses( @@ -22,9 +26,11 @@ export function getSlashConsensusVotesFromOffenses( slashingAmounts: [bigint, bigint, bigint]; epochDuration: number; targetCommitteeSize: number; + maxSlashedValidators?: number; }, + logger: Logger = createLogger('slasher:tally'), ): ValidatorSlashVote[] { - const { slashingAmounts, targetCommitteeSize } = settings; + const { slashingAmounts, targetCommitteeSize, maxSlashedValidators } = settings; if (committees.length !== epochsForCommittees.length) { throw new Error('committees and epochsForCommittees must have the same length'); @@ -53,6 +59,33 @@ export function getSlashConsensusVotesFromOffenses( return padArrayEnd(votes, 0, targetCommitteeSize); }); + // if a cap is set, zero out the lowest-vote [validator, epoch] pairs so that the most severe slashes stay. + if (maxSlashedValidators === undefined) { + return votes; + } + + const nonZeroByDescendingVote = [...votes.entries()].filter(([, vote]) => vote > 0).sort(([, a], [, b]) => b - a); + + const toTruncate = nonZeroByDescendingVote.slice(maxSlashedValidators); + for (const [idx] of toTruncate) { + votes[idx] = 0; + } + + if (toTruncate.length > 0) { + const truncated = toTruncate.map(([idx]) => { + const committeeIndex = Math.floor(idx / targetCommitteeSize); + const positionInCommittee = idx % targetCommitteeSize; + return { + validator: committees[committeeIndex][positionInCommittee].toString(), + epoch: epochsForCommittees[committeeIndex], + }; + }); + logger.warn( + `Truncated ${toTruncate.length} validator-epoch pairs to stay within limit of ${maxSlashedValidators}`, + { truncated }, + ); + } + return votes; } diff --git a/yarn-project/stdlib/src/tx/simulated_tx.ts b/yarn-project/stdlib/src/tx/simulated_tx.ts index 3ad4e7b73c4e..5fb1ef4b8bd4 100644 --- a/yarn-project/stdlib/src/tx/simulated_tx.ts +++ b/yarn-project/stdlib/src/tx/simulated_tx.ts @@ -12,9 +12,11 @@ import { Gas } from '../gas/gas.js'; import type { GasUsed } from '../gas/gas_used.js'; import { PrivateKernelTailCircuitPublicInputs } from '../kernel/private_kernel_tail_circuit_public_inputs.js'; import { ChonkProof } from '../proofs/chonk_proof.js'; +import type { OffchainEffect } from './offchain_effect.js'; import { PrivateCallExecutionResult, PrivateExecutionResult, + collectOffchainEffects, collectSortedContractClassLogs, } from './private_execution_result.js'; import { type SimulationStats, SimulationStatsSchema } from './profiling.js'; @@ -84,6 +86,11 @@ export class TxSimulationResult { public stats?: SimulationStats, ) {} + /** Returns offchain effects collected from private execution. */ + get offchainEffects(): OffchainEffect[] { + return collectOffchainEffects(this.privateExecutionResult); + } + get gasUsed(): GasUsed { return ( this.publicOutput?.gasUsed ?? { @@ -106,7 +113,7 @@ export class TxSimulationResult { .transform(TxSimulationResult.from); } - static from(fields: Omit, 'gasUsed'>) { + static from(fields: Omit, 'gasUsed' | 'offchainEffects'>) { return new TxSimulationResult( fields.privateExecutionResult, fields.publicInputs, diff --git a/yarn-project/stdlib/src/tx/validator/error_texts.ts b/yarn-project/stdlib/src/tx/validator/error_texts.ts index 30f4867d209f..6a8326f032a8 100644 --- a/yarn-project/stdlib/src/tx/validator/error_texts.ts +++ b/yarn-project/stdlib/src/tx/validator/error_texts.ts @@ -6,6 +6,10 @@ export const TX_ERROR_GAS_LIMIT_TOO_HIGH = 'Gas limit is higher than the amount // Phases export const TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED = 'Setup function not on allow list'; +export const TX_ERROR_SETUP_FUNCTION_UNKNOWN_CONTRACT = 'Setup function targets unknown contract'; +export const TX_ERROR_SETUP_ONLY_SELF_WRONG_SENDER = 'Setup only_self function called with incorrect msg_sender'; +export const TX_ERROR_SETUP_NULL_MSG_SENDER = 'Setup function called with null msg sender'; +export const TX_ERROR_SETUP_WRONG_CALLDATA_LENGTH = 'Setup function called with wrong calldata length'; // Nullifiers export const TX_ERROR_DUPLICATE_NULLIFIER_IN_TX = 'Duplicate nullifier in tx'; diff --git a/yarn-project/validator-client/src/block_proposal_handler.ts b/yarn-project/validator-client/src/block_proposal_handler.ts index d6b496613e04..2bb4f0d8d555 100644 --- a/yarn-project/validator-client/src/block_proposal_handler.ts +++ b/yarn-project/validator-client/src/block_proposal_handler.ts @@ -1,6 +1,7 @@ import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants'; import type { EpochCache } from '@aztec/epoch-cache'; import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types'; +import { pick } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/curves/bn254'; import { TimeoutError } from '@aztec/foundation/error'; import { createLogger } from '@aztec/foundation/log'; @@ -88,25 +89,28 @@ export class BlockProposalHandler { this.tracer = telemetry.getTracer('BlockProposalHandler'); } - registerForReexecution(p2pClient: P2P): BlockProposalHandler { - // Non-validator handler that re-executes for monitoring but does not attest. + register(p2pClient: P2P, shouldReexecute: boolean): BlockProposalHandler { + // Non-validator handler that processes or re-executes for monitoring but does not attest. // Returns boolean indicating whether the proposal was valid. const handler = async (proposal: BlockProposal, proposalSender: PeerId): Promise => { try { - const result = await this.handleBlockProposal(proposal, proposalSender, true); + const { slotNumber, blockNumber } = proposal; + const result = await this.handleBlockProposal(proposal, proposalSender, shouldReexecute); if (result.isValid) { - this.log.info(`Non-validator reexecution completed for slot ${proposal.slotNumber}`, { + this.log.info(`Non-validator block proposal ${blockNumber} at slot ${slotNumber} handled`, { blockNumber: result.blockNumber, + slotNumber, reexecutionTimeMs: result.reexecutionResult?.reexecutionTimeMs, totalManaUsed: result.reexecutionResult?.totalManaUsed, numTxs: result.reexecutionResult?.block?.body?.txEffects?.length ?? 0, + reexecuted: shouldReexecute, }); return true; } else { - this.log.warn(`Non-validator reexecution failed for slot ${proposal.slotNumber}`, { - blockNumber: result.blockNumber, - reason: result.reason, - }); + this.log.warn( + `Non-validator block proposal ${blockNumber} at slot ${slotNumber} failed processing with ${result.reason}`, + { blockNumber: result.blockNumber, slotNumber, reason: result.reason }, + ); return false; } } catch (error) { @@ -185,6 +189,15 @@ export class BlockProposalHandler { deadline: this.getReexecutionDeadline(slotNumber, config), }); + // If reexecution is disabled, bail. We are just interested in triggering tx collection. + if (!shouldReexecute) { + this.log.info( + `Received valid block ${blockNumber} proposal at index ${proposal.indexWithinCheckpoint} on slot ${slotNumber}`, + proposalInfo, + ); + return { isValid: true, blockNumber }; + } + // Compute the checkpoint number for this block and validate checkpoint consistency const checkpointResult = this.computeCheckpointNumber(proposal, parentBlock, proposalInfo); if (checkpointResult.reason) { @@ -211,30 +224,28 @@ export class BlockProposalHandler { return { isValid: false, blockNumber, reason: 'txs_not_available' }; } + // Collect the out hashes of all the checkpoints before this one in the same epoch + const epoch = getEpochAtSlot(slotNumber, this.epochCache.getL1Constants()); + const previousCheckpointOutHashes = (await this.blockSource.getCheckpointsDataForEpoch(epoch)) + .filter(c => c.checkpointNumber < checkpointNumber) + .map(c => c.checkpointOutHash); + // Try re-executing the transactions in the proposal if needed let reexecutionResult; - if (shouldReexecute) { - // Collect the out hashes of all the checkpoints before this one in the same epoch - const epoch = getEpochAtSlot(slotNumber, this.epochCache.getL1Constants()); - const previousCheckpointOutHashes = (await this.blockSource.getCheckpointsDataForEpoch(epoch)) - .filter(c => c.checkpointNumber < checkpointNumber) - .map(c => c.checkpointOutHash); - - try { - this.log.verbose(`Re-executing transactions in the proposal`, proposalInfo); - reexecutionResult = await this.reexecuteTransactions( - proposal, - blockNumber, - checkpointNumber, - txs, - l1ToL2Messages, - previousCheckpointOutHashes, - ); - } catch (error) { - this.log.error(`Error reexecuting txs while processing block proposal`, error, proposalInfo); - const reason = this.getReexecuteFailureReason(error); - return { isValid: false, blockNumber, reason, reexecutionResult }; - } + try { + this.log.verbose(`Re-executing transactions in the proposal`, proposalInfo); + reexecutionResult = await this.reexecuteTransactions( + proposal, + blockNumber, + checkpointNumber, + txs, + l1ToL2Messages, + previousCheckpointOutHashes, + ); + } catch (error) { + this.log.error(`Error reexecuting txs while processing block proposal`, error, proposalInfo); + const reason = this.getReexecuteFailureReason(error); + return { isValid: false, blockNumber, reason, reexecutionResult }; } // If we succeeded, push this block into the archiver (unless disabled) @@ -243,8 +254,8 @@ export class BlockProposalHandler { } this.log.info( - `Successfully processed block ${blockNumber} proposal at index ${proposal.indexWithinCheckpoint} on slot ${slotNumber}`, - proposalInfo, + `Successfully re-executed block ${blockNumber} proposal at index ${proposal.indexWithinCheckpoint} on slot ${slotNumber}`, + { ...proposalInfo, ...pick(reexecutionResult, 'reexecutionTimeMs', 'totalManaUsed') }, ); return { isValid: true, blockNumber, reexecutionResult }; @@ -495,10 +506,11 @@ export class BlockProposalHandler { const { block, failedTxs } = result; const numFailedTxs = failedTxs.length; - this.log.verbose(`Transaction re-execution complete for slot ${slot}`, { + this.log.verbose(`Block proposal ${blockNumber} at slot ${slot} transaction re-execution complete`, { numFailedTxs, numProposalTxs: txHashes.length, numProcessedTxs: block.body.txEffects.length, + blockNumber, slot, }); diff --git a/yarn-project/validator-client/src/checkpoint_builder.ts b/yarn-project/validator-client/src/checkpoint_builder.ts index 2bd19ff4ab8c..a80b3d2697b1 100644 --- a/yarn-project/validator-client/src/checkpoint_builder.ts +++ b/yarn-project/validator-client/src/checkpoint_builder.ts @@ -207,7 +207,10 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder { } protected async makeBlockBuilderDeps(globalVariables: GlobalVariables, fork: MerkleTreeWriteOperations) { - const txPublicSetupAllowList = this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()); + const txPublicSetupAllowList = [ + ...(await getDefaultAllowedSetupFunctions()), + ...(this.config.txPublicSetupAllowListExtend ?? []), + ]; const contractsDB = new PublicContractsDB(this.contractDataSource, this.log.getBindings()); const guardedFork = new GuardedMerkleTreeOperations(fork); diff --git a/yarn-project/validator-client/src/validator.integration.test.ts b/yarn-project/validator-client/src/validator.integration.test.ts index d1dcfd91478b..10a89a85bc92 100644 --- a/yarn-project/validator-client/src/validator.integration.test.ts +++ b/yarn-project/validator-client/src/validator.integration.test.ts @@ -127,7 +127,7 @@ describe('ValidatorClient Integration', () => { slotDuration: l1Constants.slotDuration, l1ChainId: chainId.toNumber(), rollupVersion: version.toNumber(), - txPublicSetupAllowList: [], + txPublicSetupAllowListExtend: [], rollupManaLimit: 200_000_000, }, synchronizer, diff --git a/yarn-project/validator-client/src/validator.test.ts b/yarn-project/validator-client/src/validator.test.ts index 1477a94b01b2..14799f855c4b 100644 --- a/yarn-project/validator-client/src/validator.test.ts +++ b/yarn-project/validator-client/src/validator.test.ts @@ -616,6 +616,7 @@ describe('ValidatorClient', () => { }); it('should return false if the transactions are not available', async () => { + enableReexecution(); txProvider.getTxsForBlockProposal.mockImplementation(proposal => Promise.resolve({ txs: [], @@ -692,6 +693,7 @@ describe('ValidatorClient', () => { // L1 messages for the checkpoint) will catch it. it('should return false if global variables do not match parent for non-first block in checkpoint', async () => { + enableReexecution(); // Create a proposal with indexWithinCheckpoint > 0 (non-first block in checkpoint) const parentSlotNumber = 100; const parentBlockNumber = 10; diff --git a/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts b/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts index 406d4942e75d..a769d5907f2c 100644 --- a/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts +++ b/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts @@ -1,6 +1,11 @@ import type { Account } from '@aztec/aztec.js/account'; import type { CallIntent, IntentInnerHash } from '@aztec/aztec.js/authorization'; -import { type InteractionWaitOptions, NO_WAIT, type SendReturn } from '@aztec/aztec.js/contracts'; +import { + type InteractionWaitOptions, + NO_WAIT, + type SendReturn, + extractOffchainOutput, +} from '@aztec/aztec.js/contracts'; import type { FeePaymentMethod } from '@aztec/aztec.js/fee'; import { waitForTx } from '@aztec/aztec.js/node'; import type { @@ -37,7 +42,7 @@ import { decodeFromAbi, } from '@aztec/stdlib/abi'; import type { AuthWitness } from '@aztec/stdlib/auth-witness'; -import type { AztecAddress } from '@aztec/stdlib/aztec-address'; +import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { type ContractInstanceWithAddress, computePartialAddress, @@ -95,6 +100,12 @@ export abstract class BaseWallet implements Wallet { return from.isZero() ? [] : [from]; } + protected scopesFrom(from: AztecAddress, additionalScopes: AztecAddress[] = []): AztecAddress[] { + const allScopes = from.isZero() ? additionalScopes : [from, ...additionalScopes]; + const scopeSet = new Set(allScopes.map(address => address.toString())); + return [...scopeSet].map(AztecAddress.fromString); + } + protected abstract getAccountFromAddress(address: AztecAddress): Promise; abstract getAccounts(): Promise[]>; @@ -356,7 +367,7 @@ export abstract class BaseWallet implements Wallet { remainingPayload, opts.from, feeOptions, - this.scopesFor(opts.from), + this.scopesFrom(opts.from, opts.additionalScopes), opts.skipTxValidation, opts.skipFeeEnforcement ?? true, ) @@ -372,7 +383,7 @@ export abstract class BaseWallet implements Wallet { return this.pxe.profileTx(txRequest, { profileMode: opts.profileMode, skipProofGeneration: opts.skipProofGeneration ?? true, - scopes: this.scopesFor(opts.from), + scopes: this.scopesFrom(opts.from, opts.additionalScopes), }); } @@ -382,7 +393,8 @@ export abstract class BaseWallet implements Wallet { ): Promise> { const feeOptions = await this.completeFeeOptions(opts.from, executionPayload.feePayer, opts.fee?.gasSettings); const txRequest = await this.createTxExecutionRequestFromPayloadAndFee(executionPayload, opts.from, feeOptions); - const provenTx = await this.pxe.proveTx(txRequest, this.scopesFor(opts.from)); + const provenTx = await this.pxe.proveTx(txRequest, this.scopesFrom(opts.from, opts.additionalScopes)); + const offchainOutput = extractOffchainOutput(provenTx.getOffchainEffects()); const tx = await provenTx.toTx(); const txHash = tx.getTxHash(); if (await this.aztecNode.getTxEffect(txHash)) { @@ -396,7 +408,7 @@ export abstract class BaseWallet implements Wallet { // If wait is NO_WAIT, return txHash immediately if (opts.wait === NO_WAIT) { - return txHash as SendReturn; + return { txHash, ...offchainOutput } as SendReturn; } // Otherwise, wait for the full receipt (default behavior on wait: undefined) @@ -408,7 +420,7 @@ export abstract class BaseWallet implements Wallet { await displayDebugLogs(receipt.debugLogs, this.getContractName.bind(this)); } - return receipt as SendReturn; + return { receipt, ...offchainOutput } as SendReturn; } /**