Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Tests that the installed Aztec toolchain works end-to-end.
# Exercises the full developer onboarding path: aztec init -> compile -> test -> start -> codegen -> TS end-to-end test.
name: Full Dev Path Test
# Validates that a published Aztec CLI release can be installed and used end-to-end.
# Runs after CI3 completes a tag release (via workflow_run), or manually via workflow_dispatch.
name: Aztec CLI Acceptance Test

on:
workflow_dispatch:
Expand All @@ -9,22 +9,30 @@ on:
description: "Version to install (e.g. latest, nightly, 4.3.0, 4.3.0-nightly.20260420)"
required: true
type: string
push:
tags:
workflow_run:
workflows: ["CI3"]
types:
- completed
branches:
- "v*"

jobs:
full-dev-path:
release-acceptance:
runs-on: ubuntu-latest
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
timeout-minutes: 30
env:
VERSION: ${{ github.event.inputs.version || github.ref_name }}
VERSION: ${{ github.event.inputs.version || github.event.workflow_run.head_branch }}
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
ref: ${{ github.event.workflow_run.head_sha || github.sha }}

- name: Run full dev path test
run: ./aztec-up/test/full-dev-path/run-test.sh
- name: Run Aztec CLI acceptance test
run: ./aztec-up/test/aztec-cli-acceptance-test/run-test.sh

- name: Notify Slack on success
if: success() && github.event_name != 'workflow_dispatch'
Expand All @@ -33,7 +41,7 @@ jobs:
run: |
export CI=1
./ci3/slack_notify "#team-fairies" \
"Full Dev Path Test passed for version ${VERSION} :white_check_mark:"
"Aztec CLI Acceptance Test passed for version ${VERSION} :white_check_mark:"

- name: Notify Slack and dispatch ClaudeBox on failure
if: failure() && github.event_name != 'workflow_dispatch'
Expand All @@ -44,6 +52,6 @@ jobs:
RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
export CI=1
./ci3/slack_notify_with_claudebox_kickoff "#team-fairies" \
"Full Dev Path Test FAILED (version ${VERSION}): <${RUN_URL}|View Run>" \
"Full dev path test failed for version ${VERSION}. CI run: ${RUN_URL}. Investigate the failure and explain the root cause." \
"Aztec CLI Acceptance Test FAILED (version ${VERSION}): <${RUN_URL}|View Run>" \
"Aztec CLI acceptance test failed for version ${VERSION}. CI run: ${RUN_URL}. Investigate the failure and explain the root cause." \
--link "$RUN_URL"
9 changes: 6 additions & 3 deletions aztec-up/bin/0.0.1/install
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,14 @@ function install_foundry {
temp_foundry_dir=$(mktemp -d) || { echo "Error: Failed to create temp directory" >&2; exit 1; }
mkdir -p "$temp_foundry_dir/bin"

# Always install/update foundryup
curl --max-time 30 -L https://foundry.paradigm.xyz | bash
# The foundry installer defaults to $XDG_CONFIG_HOME/.foundry when that var is set, which
# puts foundryup somewhere we don't expect. We'll pin FOUNDRY_DIR so we control the install
# location regardless of the caller's environment.
local foundry_home="$HOME/.foundry"
FOUNDRY_DIR="$foundry_home" curl --max-time 30 -L https://foundry.paradigm.xyz | FOUNDRY_DIR="$foundry_home" bash

# Install foundry to temp location and move to version directory
FOUNDRY_DIR="$temp_foundry_dir" timeout 30 "$HOME/.foundry/bin/foundryup" -i "$foundry_version"
FOUNDRY_DIR="$temp_foundry_dir" timeout 30 "$foundry_home/bin/foundryup" -i "$foundry_version"

# Move the foundry binaries to our version bin directory
for binary in forge cast anvil chisel; do
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Full Dev Path Test
# Aztec CLI Acceptance Test

Tests that the installed Aztec toolchain works end-to-end. Exercises the complete developer onboarding path:
Tests that the installed Aztec CLI toolchain works end-to-end. Exercises the complete developer onboarding path:

1. `aztec init` - scaffold a new workspace with a Counter contract and test crate
2. `aztec compile` - compile the scaffolded contract
Expand Down Expand Up @@ -30,6 +30,6 @@ VERSION=4.3.0 ./run-test.sh

## Architecture

- **`run-test.sh`** - Bash launcher. Runs the aztec installer (unless skipped), sets up PATH, then `exec node full-dev-path.ts`.
- **`full-dev-path.ts`** - Orchestrator. Runs each CLI step against the installed toolchain and, after codegen, copies `counter.test.ts` into the scaffolded workspace and spawns `node --test` on it. Each phase is wrapped in `step(name, fn)` so failures clearly identify which step broke. Always emits a machine-readable result line for CI/Slack integration: `TEST_RESULT=pass version=...` on success, or `TEST_RESULT=fail step=... version=... error="..."` on failure (with a full banner printed above it).
- **`run-test.sh`** - Bash launcher. Runs the aztec installer (unless skipped), sets up PATH, then `exec node aztec-cli-acceptance-test.ts`.
- **`aztec-cli-acceptance-test.ts`** - Orchestrator. Runs each CLI step against the installed toolchain and, after codegen, copies `counter.test.ts` into the scaffolded workspace and spawns `node --test` on it. Each phase is wrapped in `step(name, fn)` so failures clearly identify which step broke. Always emits a machine-readable result line for CI/Slack integration: `TEST_RESULT=pass version=...` on success, or `TEST_RESULT=fail step=... version=... error="..."` on failure (with a full banner printed above it).
- **`counter.test.ts`** - The `node:test` suite that drives the deployed Counter end-to-end through the codegen'd bindings. Lives here as a template; copied into the workspace at test time so it can statically `import { CounterContract } from './artifacts/Counter.js'` with real codegen types and resolve `@aztec/*` via the workspace's `node_modules` symlink to the install.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ if (!existsSync(join(AZTEC_INSTALL_DIR, 'package.json'))) {
process.exit(2);
}

const TMP_DIR = mkdtempSync(join(tmpdir(), 'aztec-full-dev-path-'));
const TMP_DIR = mkdtempSync(join(tmpdir(), 'aztec-cli-acceptance-test-'));
const WORKSPACE_DIR = join(TMP_DIR, 'my_workspace');

// Exit codes follow the Unix 128+signal convention for signal terminations.
Expand Down Expand Up @@ -231,7 +231,7 @@ function reportFailure(stepName: string, aztecVersion: string, err: unknown) {
const banner = '='.repeat(72);

console.error(`\n${banner}`);
console.error('FULL DEV PATH TEST FAILED');
console.error('AZTEC CLI ACCEPTANCE TEST FAILED');
console.error(banner);
console.error(`Step: ${stepName}`);
console.error(`Version: ${aztecVersion}`);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#!/usr/bin/env bash
# Launcher for the full-dev-path test.
# Launcher for the Aztec CLI acceptance test.
#
# Steps:
# 1. Install Node via NVM if not present (skipped with SKIP_INSTALL=1)
# 2. Install the Aztec toolchain via the public installer (skipped with SKIP_INSTALL=1)
# 3. Run full-dev-path.ts which exercises the installed toolchain end-to-end
# 3. Run aztec-cli-acceptance-test.ts which exercises the installed toolchain end-to-end
#
# Env vars:
# SKIP_INSTALL=1 Skip steps 1-2 and use the already-installed toolchain (dev-box inner loop).
Expand Down Expand Up @@ -37,4 +37,4 @@ export PATH="$HOME/.aztec/current/bin:$HOME/.aztec/bin:$PATH"
export AZTEC_INSTALL_DIR="${AZTEC_INSTALL_DIR:-$HOME/.aztec/current}"

echo ">>> Running test"
exec node --no-warnings "${script_dir}/full-dev-path.ts"
exec node --no-warnings "${script_dir}/aztec-cli-acceptance-test.ts"
4 changes: 2 additions & 2 deletions noir-projects/aztec-nr/aztec/src/authwit/entrypoint/app.nr
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{authwit::entrypoint::FunctionCall, context::PrivateContext};
use crate::protocol::{
constants::DOM_SEP__SIGNATURE_PAYLOAD,
hash::poseidon2_hash_with_separator,
traits::{Hash, Serialize},
traits::{Deserialize, Hash, Serialize},
};
use std::meta::derive;

Expand All @@ -12,7 +12,7 @@ global ACCOUNT_MAX_CALLS: u32 = 5;
// - default_entrypoint.ts
// - account_entrypoint.ts (specifically `getEntrypointAbi()`)
// - default_multi_call_entrypoint.ts (specifically `getEntrypointAbi()`)
#[derive(Serialize)]
#[derive(Deserialize, Serialize)]
pub struct AppPayload {
function_calls: [FunctionCall; ACCOUNT_MAX_CALLS],
// A nonce that enables transaction cancellation. When the cancellable flag is enabled, this nonce is used to
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
use crate::protocol::{abis::function_selector::FunctionSelector, address::AztecAddress, traits::Serialize};
use crate::protocol::{
abis::function_selector::FunctionSelector,
address::AztecAddress,
traits::{Deserialize, Serialize},
};
use std::meta::derive;

// @dev: If you change the following struct you have to update:
// - account_entrypoint.ts (specifically `getEntrypointAbi()`)
// - default_multi_call_entrypoint.ts (specifically `getEntrypointAbi()`)
// - function_call.ts
// - encoding.ts
#[derive(Serialize)]
#[derive(Deserialize, Serialize)]
pub struct FunctionCall {
// args_hash is calldata_hash if is_public is true
pub args_hash: Field,
Expand Down
5 changes: 4 additions & 1 deletion noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub(crate) mod auth_registry;

use crate::macros::{
internals_functions_generation::{external_functions_registry, internal_functions_registry},
utils::{is_fn_external, module_has_initializer},
utils::{assert_params_serializable, assert_return_type_serializable, is_fn_external, module_has_initializer},
};
use super::utils::{fn_has_allow_phase_change, fn_has_noinitcheck, is_fn_initializer, is_fn_only_self, is_fn_view};
use auth_registry::AUTHORIZE_ONCE_REGISTRY;
Expand Down Expand Up @@ -342,6 +342,9 @@ pub comptime fn external(f: FunctionDefinition, f_type: CtString) {
// In this function we perform basic checks on the function to ensure it is valid and then we add the function to
// the external functions registry for later processing by the `#[aztec]` macro.

assert_return_type_serializable(f);
assert_params_serializable(f);

if f_type.eq("private") {
assert_valid_private(f);
external_functions_registry::add_private(f);
Expand Down
40 changes: 39 additions & 1 deletion noir-projects/aztec-nr/aztec/src/macros/utils.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::protocol::meta::derive_serialize;
use std::meta::{ctstring::AsCtString, unquote};
use std::meta::{ctstring::AsCtString, type_of, unquote};

pub(crate) comptime fn is_fn_external(f: FunctionDefinition) -> bool {
f.has_named_attribute("external")
Expand Down Expand Up @@ -153,6 +153,44 @@ pub(crate) comptime fn get_trait_impl_method(typ: Type, target_trait: Quoted, ta
.as_typed_expr()
}

/// Asserts that the return type of a contract function implements both `Serialize` and `Deserialize`.
/// Functions with a `()` return type are skipped since unit values are not serialized.
pub(crate) comptime fn assert_return_type_serializable(f: FunctionDefinition) {
let return_type = f.return_type();
if return_type != type_of(()) {
let fn_name = f.name();
let serialize_constraint = quote { crate::protocol::traits::Serialize }.as_trait_constraint();
assert(
return_type.implements(serialize_constraint),
f"Contract function '{fn_name}' returns a value of type {return_type}, which does not implement the Serialize trait. Add #[derive(Serialize)] to {return_type}'s declaration.",
);
let deserialize_constraint = quote { crate::protocol::traits::Deserialize }.as_trait_constraint();
assert(
return_type.implements(deserialize_constraint),
f"Contract function '{fn_name}' returns a value of type {return_type}, which does not implement the Deserialize trait. Add #[derive(Deserialize)] to {return_type}'s declaration.",
);
}
}

/// Asserts that all parameters of a contract function implement both `Serialize` and `Deserialize`.
pub(crate) comptime fn assert_params_serializable(f: FunctionDefinition) {
let fn_name = f.name();

for param in f.parameters() {
let (name, param_type) = param;
let serialize_constraint = quote { crate::protocol::traits::Serialize }.as_trait_constraint();
assert(
param_type.implements(serialize_constraint),
f"Parameter '{name}' of contract function '{fn_name}' has type {param_type}, which does not implement the Serialize trait. Add #[derive(Serialize)] to {param_type}'s declaration.",
);
let deserialize_constraint = quote { crate::protocol::traits::Deserialize }.as_trait_constraint();
assert(
param_type.implements(deserialize_constraint),
f"Parameter '{name}' of contract function '{fn_name}' has type {param_type}, which does not implement the Deserialize trait. Add #[derive(Deserialize)] to {param_type}'s declaration.",
);
}
}

/// Generates a quote that implements `Serialize` for a given struct `s`. If the struct already implements `Serialize`,
/// we return an empty quote.
pub comptime fn derive_serialize_if_not_implemented(s: TypeDefinition) -> Quoted {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "non_deserializable"
authors = [""]
compiler_version = ">=0.25.0"
type = "contract"

[dependencies]
aztec = { path = "../../../aztec-nr/aztec" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Contract function 'bad_private_return' returns a value of type NotDeserializable, which does not implement the Deserialize trait. Add #[derive(Deserialize)] to NotDeserializable's declaration.
Parameter '_p' of contract function 'bad_private_param' has type NotDeserializable, which does not implement the Deserialize trait. Add #[derive(Deserialize)] to NotDeserializable's declaration.
Contract function 'bad_public_return' returns a value of type NotDeserializable, which does not implement the Deserialize trait. Add #[derive(Deserialize)] to NotDeserializable's declaration.
Parameter '_p' of contract function 'bad_public_param' has type NotDeserializable, which does not implement the Deserialize trait. Add #[derive(Deserialize)] to NotDeserializable's declaration.
Contract function 'bad_utility_return' returns a value of type NotDeserializable, which does not implement the Deserialize trait. Add #[derive(Deserialize)] to NotDeserializable's declaration.
Parameter '_p' of contract function 'bad_utility_param' has type NotDeserializable, which does not implement the Deserialize trait. Add #[derive(Deserialize)] to NotDeserializable's declaration.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Tests that contract functions using a type that does not implement Deserialize fail to compile, for all function types
// (private, public, utility) and both return types and parameters.
use aztec::macros::aztec;

#[aztec]
pub contract NonDeserializable {
use aztec::macros::functions::external;
use aztec::protocol::traits::Serialize;

#[derive(Serialize)]
pub struct NotDeserializable {
x: Field,
}

#[external("private")]
fn bad_private_return() -> pub NotDeserializable {
NotDeserializable { x: 1 }
}

#[external("private")]
fn bad_private_param(_p: NotDeserializable) {}

#[external("public")]
fn bad_public_return() -> pub NotDeserializable {
NotDeserializable { x: 1 }
}

#[external("public")]
fn bad_public_param(_p: NotDeserializable) {}

#[external("utility")]
unconstrained fn bad_utility_return() -> pub NotDeserializable {
NotDeserializable { x: 1 }
}

#[external("utility")]
unconstrained fn bad_utility_param(_p: NotDeserializable) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "non_serializable"
authors = [""]
compiler_version = ">=0.25.0"
type = "contract"

[dependencies]
aztec = { path = "../../../aztec-nr/aztec" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Contract function 'bad_private_return' returns a value of type NotSerializable, which does not implement the Serialize trait. Add #[derive(Serialize)] to NotSerializable's declaration.
Parameter '_p' of contract function 'bad_private_param' has type NotSerializable, which does not implement the Serialize trait. Add #[derive(Serialize)] to NotSerializable's declaration.
Contract function 'bad_public_return' returns a value of type NotSerializable, which does not implement the Serialize trait. Add #[derive(Serialize)] to NotSerializable's declaration.
Parameter '_p' of contract function 'bad_public_param' has type NotSerializable, which does not implement the Serialize trait. Add #[derive(Serialize)] to NotSerializable's declaration.
Contract function 'bad_utility_return' returns a value of type NotSerializable, which does not implement the Serialize trait. Add #[derive(Serialize)] to NotSerializable's declaration.
Parameter '_p' of contract function 'bad_utility_param' has type NotSerializable, which does not implement the Serialize trait. Add #[derive(Serialize)] to NotSerializable's declaration.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Tests that contract functions using a type that does not implement Serialize fail to compile, for all function types
// (private, public, utility) and both return types and parameters.
use aztec::macros::aztec;

#[aztec]
pub contract NonSerializable {
use aztec::macros::functions::external;
use aztec::protocol::traits::Deserialize;

#[derive(Deserialize)]
pub struct NotSerializable {
x: Field,
}

#[external("private")]
fn bad_private_return() -> pub NotSerializable {
NotSerializable { x: 1 }
}

#[external("private")]
fn bad_private_param(_p: NotSerializable) {}

#[external("public")]
fn bad_public_return() -> pub NotSerializable {
NotSerializable { x: 1 }
}

#[external("public")]
fn bad_public_param(_p: NotSerializable) {}

#[external("utility")]
unconstrained fn bad_utility_return() -> pub NotSerializable {
NotSerializable { x: 1 }
}

#[external("utility")]
unconstrained fn bad_utility_param(_p: NotSerializable) {}
}
1 change: 1 addition & 0 deletions noir-projects/noir-contracts/Nargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ members = [
"contracts/test/storage_proof_test_contract",
"contracts/test/test_contract",
"contracts/test/test_log_contract",
"contracts/test/unit_return_type_contract",
"contracts/test/updatable_contract",
"contracts/test/updated_contract"
]
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ use aztec::{
address::AztecAddress,
constants::DOM_SEP__SIGNATURE_PAYLOAD,
hash::poseidon2_hash_with_separator,
traits::{Hash, Serialize},
traits::{Deserialize, Hash, Serialize},
},
};
use std::meta::derive;

global DAPP_MAX_CALLS: u32 = 1;

// @dev: If you change the following struct you have to update default_entrypoint.ts
#[derive(Serialize)]
#[derive(Deserialize, Serialize)]
pub struct DAppPayload {
function_calls: [FunctionCall; DAPP_MAX_CALLS],
// A nonce that enables transaction cancellation. When the cancellable flag is enabled, this nonce is used to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ use aztec::{
protocol::{
address::AztecAddress,
constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL,
traits::{FromField, Hash, Packable, Serialize, ToField},
traits::{Deserialize, FromField, Hash, Packable, Serialize, ToField},
},
state_vars::{Owned, PrivateSet, StateVariable},
};
use field_note::FieldNote;
use std::meta::derive;

#[derive(Serialize)]
#[derive(Serialize, Deserialize)]
pub struct Card {
// We use u32s since u16s are unsupported
pub strength: u32,
Expand Down
Loading
Loading