Skip to content

Commit 2fac271

Browse files
authored
Merge branch 'merge-train/fairies' into gj/embedded_wallet_improvements_and_regressions
2 parents 444bd1b + 50a6a6c commit 2fac271

19 files changed

Lines changed: 632 additions & 88 deletions

File tree

.github/ci3_labels_to_env.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ function main {
3737
echo "NO_FAIL_FAST=1" >> $GITHUB_ENV
3838
fi
3939

40+
# Handle skip-compat-e2e label (escape hatch for backwards compat test failures on release PRs)
41+
if has_label "ci-skip-compat-e2e"; then
42+
echo "SKIP_COMPAT_E2E=1" >> $GITHUB_ENV
43+
fi
44+
4045
# Determine CI mode based on event, labels, and target branch
4146
local ci_mode
4247
if [ "${GITHUB_EVENT_NAME:-}" == "merge_group" ] || has_label "ci-merge-queue"; then

.github/workflows/ci3.yml

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,3 +445,108 @@ jobs:
445445
AWS_SHUTDOWN_TIME: 180
446446
run: |
447447
./.github/ci3.sh network-tests-kind
448+
449+
# Backwards compatibility e2e tests.
450+
# Runs e2e tests with contract artifacts from every prior stable release to validate
451+
# that new client code works with old contract artifacts ("new pxe / old contracts").
452+
# Blocking for stable/RC releases: ci-release-publish requires this job to pass before
453+
# publishing. Observational for nightlies: runs, but continue-on-error keeps the workflow
454+
# green and ci-release-publish's condition publishes nightlies regardless of the result.
455+
# Escape hatch: ci-skip-compat-e2e label makes failures non-blocking on release PRs.
456+
ci-compat-e2e:
457+
runs-on: ubuntu-latest
458+
permissions:
459+
id-token: write
460+
contents: read
461+
needs: [ci]
462+
if: |
463+
always()
464+
&& (needs.ci.result == 'success' || needs.ci.result == 'skipped')
465+
&& github.event.pull_request.head.repo.fork != true
466+
&& github.event.pull_request.draft == false
467+
&& (
468+
(startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-commit.'))
469+
|| contains(github.event.pull_request.labels.*.name, 'ci-compat-e2e')
470+
|| contains(github.event.pull_request.labels.*.name, 'ci-release-pr')
471+
)
472+
# Non-blocking for nightlies and when ci-skip-compat-e2e escape hatch is applied.
473+
continue-on-error: ${{ contains(github.ref_name, '-nightly.') || contains(github.event.pull_request.labels.*.name, 'ci-skip-compat-e2e') }}
474+
steps:
475+
- name: Checkout
476+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
477+
with:
478+
ref: ${{ github.event.pull_request.head.sha || github.sha }}
479+
480+
- name: Configure AWS credentials (OIDC)
481+
uses: aws-actions/configure-aws-credentials@v4
482+
with:
483+
role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }}
484+
aws-region: us-east-2
485+
role-session-name: ci3-compat-e2e-${{ github.run_id }}
486+
role-duration-seconds: 21600 # 6h – covers AWS_SHUTDOWN_TIME (300 min) + 60 min buffer
487+
488+
- name: Run Backwards Compatibility E2E Tests
489+
timeout-minutes: 330
490+
env:
491+
GITHUB_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }}
492+
BUILD_INSTANCE_SSH_KEY: ${{ secrets.BUILD_INSTANCE_SSH_KEY }}
493+
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
494+
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
495+
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
496+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
497+
CI3_INSTANCE_PROFILE_NAME: ${{ secrets.CI3_INSTANCE_PROFILE_NAME }}
498+
CI3_SECURITY_GROUP_ID: ${{ secrets.CI3_SECURITY_GROUP_ID }}
499+
RUN_ID: ${{ github.run_id }}
500+
AWS_SHUTDOWN_TIME: 300
501+
run: ./.github/ci3.sh compat-e2e
502+
503+
# Publishes the release (npm, Docker, GitHub release, aztec-up scripts, etc.).
504+
# Gated on ci-compat-e2e: a compat regression blocks stable/RC publishing. Nightlies
505+
# publish regardless — compat-e2e runs there observationally. Dev `-commit.` tags from
506+
# the ci-release-pr flow never reach this job (they are not real releases).
507+
ci-release-publish:
508+
runs-on: ubuntu-latest
509+
environment: master
510+
permissions:
511+
id-token: write
512+
contents: read
513+
needs: [ci, ci-compat-e2e]
514+
if: |
515+
startsWith(github.ref, 'refs/tags/v')
516+
&& !contains(github.ref_name, '-commit.')
517+
&& needs.ci.result == 'success'
518+
&& (
519+
contains(github.ref_name, '-nightly.')
520+
|| needs.ci-compat-e2e.result == 'success'
521+
)
522+
steps:
523+
- name: Checkout
524+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
525+
with:
526+
ref: ${{ github.sha }}
527+
528+
- name: Configure AWS credentials (OIDC)
529+
uses: aws-actions/configure-aws-credentials@v4
530+
with:
531+
role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }}
532+
aws-region: us-east-2
533+
role-session-name: ci3-release-publish-${{ github.run_id }}
534+
role-duration-seconds: 21600
535+
536+
- name: Run Release Publish
537+
env:
538+
GITHUB_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }}
539+
BUILD_INSTANCE_SSH_KEY: ${{ secrets.BUILD_INSTANCE_SSH_KEY }}
540+
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
541+
GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
542+
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
543+
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
544+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
545+
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
546+
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
547+
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
548+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
549+
CI3_INSTANCE_PROFILE_NAME: ${{ secrets.CI3_INSTANCE_PROFILE_NAME }}
550+
CI3_SECURITY_GROUP_ID: ${{ secrets.CI3_SECURITY_GROUP_ID }}
551+
RUN_ID: ${{ github.run_id }}
552+
run: ./.github/ci3.sh release-publish

bootstrap.sh

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,17 @@ case "$cmd" in
823823
# RELEASES #
824824
############
825825
"ci-release")
826+
# Verification build for a release tag. Does NOT publish — publishing happens in
827+
# ci-release-publish, gated on ci-compat-e2e so a compat regression blocks the release.
828+
export CI=1
829+
export USE_TEST_CACHE=1
830+
if ! semver check $REF_NAME; then
831+
exit 1
832+
fi
833+
build
834+
;;
835+
"ci-release-publish")
836+
# Actual publish step. `build` cache-hits against ci-release's build of the same commit.
826837
export CI=1
827838
export USE_TEST_CACHE=1
828839
if ! semver check $REF_NAME; then
@@ -902,6 +913,82 @@ case "$cmd" in
902913
build
903914
yarn-project/end-to-end/bootstrap.sh avm_check_circuit
904915
;;
916+
#############################################
917+
# BACKWARDS COMPATIBILITY E2E TESTS #
918+
#############################################
919+
"ci-compat-e2e")
920+
# Runs e2e tests with contract artifacts from every prior stable release since 4.2.0 (version where we committed to
921+
# backwards compatibility). This Validates that old contract artifacts work on current release.
922+
export CI=1
923+
export USE_TEST_CACHE=0
924+
export CI_FULL=0
925+
export NO_FAIL_FAST=1
926+
927+
build
928+
929+
# TODO: bump when v5 commits to backwards-compatible contract artifacts.
930+
# compat_major: major version that has compat guarantees today.
931+
# compat_min_version: earliest stable tag of that major to test against
932+
# (artifacts before this are incompatible due to oracle interface changes).
933+
compat_major="4"
934+
compat_min_version="4.2.0"
935+
936+
# Get current major version.
937+
current_version=$(jq -r '."."' .release-please-manifest.json)
938+
major=$(semver major "$current_version")
939+
if [ "$major" != "$compat_major" ]; then
940+
echo "Compat e2e tests only apply to v${compat_major}. Current major: v${major}. Skipping."
941+
exit 0
942+
fi
943+
min_version="$compat_min_version"
944+
945+
# Fetch tags (EC2 clone may not have them). Fail loud: a silent fetch failure plus an empty
946+
# tag list would publish a real release with zero compat coverage.
947+
if ! git fetch origin 'refs/tags/v*:refs/tags/v*'; then
948+
echo "ERROR: failed to fetch release tags." >&2
949+
exit 1
950+
fi
951+
952+
# Discover stable tags for this major version (no prerelease suffixes).
953+
versions=()
954+
while IFS= read -r tag; do
955+
ver=${tag#v}
956+
# Include only versions >= min_version (sort -V puts smaller first).
957+
if [ "$(printf '%s\n%s' "$min_version" "$ver" | sort -V | head -1)" = "$min_version" ]; then
958+
versions+=("$ver")
959+
fi
960+
done < <(git tag -l "v${major}.*" | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$" | sort -V)
961+
962+
# Exclude the current tag when running on a release tag push.
963+
if [[ "${REF_NAME:-}" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
964+
current_tag="${BASH_REMATCH[1]}"
965+
filtered=()
966+
for v in "${versions[@]}"; do
967+
[ "$v" != "$current_tag" ] && filtered+=("$v")
968+
done
969+
versions=("${filtered[@]}")
970+
fi
971+
972+
if [ ${#versions[@]} -eq 0 ]; then
973+
echo "No prior stable versions found for v${major}.x (>= $min_version). Skipping compat tests."
974+
exit 0
975+
fi
976+
977+
echo_header "Backwards compatibility e2e tests"
978+
echo "Testing against ${#versions[@]} prior stable version(s): ${versions[*]}"
979+
980+
# Pre-populate the legacy contract cache on the host. Test containers run with --net=none, so the
981+
# jest resolver's on-demand npm install would fail with EAI_AGAIN. Install here where we have network.
982+
for ver in "${versions[@]}"; do
983+
node yarn-project/end-to-end/src/install_legacy_contracts.cjs "$ver"
984+
done
985+
986+
# Generate compat test commands for all versions and run them in parallel.
987+
for ver in "${versions[@]}"; do
988+
yarn-project/end-to-end/bootstrap.sh compat_test_cmds "$ver"
989+
done | filter_test_cmds | parallelize
990+
;;
991+
905992
##########################################
906993
# ROLLUP UPGRADE DEPLOYMENT #
907994
##########################################

ci.sh

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ function print_usage {
3535
echo_cmd "network-teardown" "Spin up an EC2 instance to teardown a network deployment."
3636
echo_cmd "network-tests-kind" "Spin up an EC2 instance to run a KIND-based spartan test."
3737
echo_cmd "deploy-rollup-upgrade" "Spin up an EC2 instance to deploy a rollup upgrade."
38+
echo_cmd "compat-e2e" "Spin up an EC2 instance and run backwards compat e2e tests."
3839
echo_cmd "release" "Spin up an EC2 instance and run bootstrap release."
3940
echo_cmd "shell-new" "Spin up an EC2 instance, clone the repo, and drop into a shell."
4041
echo_cmd "shell" "Drop into a shell in the current running build instance container."
@@ -302,16 +303,42 @@ case "$cmd" in
302303
bootstrap_ec2 "./bootstrap.sh ci-deploy-rollup-upgrade $*"
303304
;;
304305

306+
##############################
307+
# BACKWARDS COMPATIBILITY #
308+
##############################
309+
compat-e2e)
310+
# Spin up an EC2 instance and run backwards compatibility e2e tests
311+
# against contract artifacts from prior stable releases.
312+
export CI_DASHBOARD="releases"
313+
export JOB_ID="x-compat-e2e"
314+
export AWS_SHUTDOWN_TIME=300
315+
bootstrap_ec2 "./bootstrap.sh ci-compat-e2e"
316+
;;
317+
305318
############
306319
# RELEASES #
307320
############
308321
release)
309-
# Spin up ec2 instance and run the release flow.
322+
# Spin up ec2 instance and run the release-tag verification build (no publish).
310323
export CI_DASHBOARD="releases"
311324
multi_job_run \
312325
'x-release amd64 ci-release' \
313326
'a-release arm64 ci-release'
314327
;;
328+
release-publish)
329+
# Spin up ec2 instance and run the actual publish flow. Gated in ci3.yml on ci + ci-compat-e2e.
330+
export CI_DASHBOARD="releases"
331+
export DENOISE=1
332+
export DENOISE_WIDTH=32
333+
run() {
334+
PARENT_LOG_ID=$RUN_ID JOB_ID=$1 INSTANCE_POSTFIX=$1 ARCH=$2 exec denoise "bootstrap_ec2 './bootstrap.sh ci-release-publish'"
335+
}
336+
export -f run
337+
338+
parallel --termseq 'TERM,10000' --tagstring '{= $_=~s/run (\w+).*/$1/; =}' --line-buffered --halt now,fail=1 ::: \
339+
'run x-release-publish amd64' \
340+
'run a-release-publish arm64' | DUP=1 cache_log "Release Publish CI run" $RUN_ID
341+
;;
315342

316343
##################
317344
# SHELL SESSIONS #

noir-projects/aztec-nr/aztec/src/contract_self/contract_self_private.nr

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ use crate::protocol::{address::AztecAddress, traits::{Deserialize, Serialize}};
4848
/// - `CallSelfStatic`: Macro-generated type for calling contract's own view functions
4949
/// - `EnqueueSelfStatic`: Macro-generated type for enqueuing calls to the contract's own view functions
5050
/// - `CallInternal`: Macro-generated type for calling internal functions
51-
pub struct ContractSelfPrivate<Storage, CallSelf, EnqueueSelf, CallSelfStatic, EnqueueSelfStatic, CallInternal> {
51+
/// - `CallSelfUtility`: Macro-generated type for calling the contract's own utility functions
52+
pub struct ContractSelfPrivate<Storage, CallSelf, EnqueueSelf, CallSelfStatic, EnqueueSelfStatic, CallInternal, CallSelfUtility> {
5253
/// The address of this contract
5354
pub address: AztecAddress,
5455

@@ -110,17 +111,17 @@ pub struct ContractSelfPrivate<Storage, CallSelf, EnqueueSelf, CallSelfStatic, E
110111
/// ```
111112
pub internal: CallInternal,
112113

113-
/// Gateway for calling utility functions from this private context.
114+
/// A struct that allows for ergonomic calling of utility functions from this private context.
114115
///
115116
/// Example API:
116117
/// ```noir
117118
/// // Safety: result is unconstrained
118119
/// unsafe { self.utility.call(MyContract::at(address).my_utility_function(args)) }
119120
/// ```
120-
pub utility: PrivateUtility,
121+
pub utility: PrivateUtilityCalls<CallSelfUtility>,
121122
}
122123

123-
impl<Storage, CallSelf, EnqueueSelf, CallSelfStatic, EnqueueSelfStatic, CallInternal> ContractSelfPrivate<Storage, CallSelf, EnqueueSelf, CallSelfStatic, EnqueueSelfStatic, CallInternal> {
124+
impl<Storage, CallSelf, EnqueueSelf, CallSelfStatic, EnqueueSelfStatic, CallInternal, CallSelfUtility> ContractSelfPrivate<Storage, CallSelf, EnqueueSelf, CallSelfStatic, EnqueueSelfStatic, CallInternal, CallSelfUtility> {
124125
/// Creates a new `ContractSelfPrivate` instance for a private function.
125126
///
126127
/// This constructor is called automatically by the macro system and should not be called directly.
@@ -132,6 +133,7 @@ impl<Storage, CallSelf, EnqueueSelf, CallSelfStatic, EnqueueSelfStatic, CallInte
132133
call_self_static: CallSelfStatic,
133134
enqueue_self_static: EnqueueSelfStatic,
134135
internal: CallInternal,
136+
utility: PrivateUtilityCalls<CallSelfUtility>,
135137
) -> Self {
136138
Self {
137139
context,
@@ -142,7 +144,7 @@ impl<Storage, CallSelf, EnqueueSelf, CallSelfStatic, EnqueueSelfStatic, CallInte
142144
call_self_static,
143145
enqueue_self_static,
144146
internal,
145-
utility: PrivateUtility {},
147+
utility,
146148
}
147149
}
148150

@@ -407,16 +409,31 @@ impl<Storage, CallSelf, EnqueueSelf, CallSelfStatic, EnqueueSelfStatic, CallInte
407409
}
408410
}
409411

410-
/// Gateway for calling utility functions from a private context.
412+
/// A struct that allows for ergonomic calling of utility functions from a private context.
411413
///
412414
/// Accessible via `self.utility` in private functions. Results are not part of any circuit
413415
/// proof; use them to inform logic, not as inputs to constrained assertions.
414-
// Implemented as a separate empty struct because Noir does not allow passing `&mut PrivateContext`
415-
// (held by `ContractSelfPrivate`) into unconstrained code. This struct holds no mutable references,
416-
// so it can be passed freely into `unconstrained` functions.
417-
pub struct PrivateUtility {}
416+
///
417+
/// ## Type Parameters
418+
///
419+
/// - `CallSelf`: Macro-generated type for calling the contract's own utility functions (same type
420+
/// as `CallSelf` in \[`ContractSelfUtility`\])
421+
// Implemented as a separate struct (rather than inlined in ContractSelfPrivate) because Noir does
422+
// not allow passing `&mut PrivateContext` (held by `ContractSelfPrivate`) into unconstrained code.
423+
// This struct holds no mutable references, so it can be passed freely into `unconstrained`
424+
// functions.
425+
pub struct PrivateUtilityCalls<CallSelf> {
426+
/// Provides type-safe methods for calling this contract's own utility functions.
427+
///
428+
/// Example API:
429+
/// ```noir
430+
/// // Safety: result is unconstrained
431+
/// unsafe { self.utility.call_self.some_utility_function(args) }
432+
/// ```
433+
pub call_self: CallSelf,
434+
}
418435

419-
impl PrivateUtility {
436+
impl<CallSelf> PrivateUtilityCalls<CallSelf> {
420437
/// Makes a utility contract call from a private function.
421438
///
422439
/// Note: only same-contract utility calls are currently supported. See TODO(F-29).

noir-projects/aztec-nr/aztec/src/contract_self/contract_self_utility.nr

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ use crate::protocol::{address::AztecAddress, traits::Deserialize};
1313
///
1414
/// - `Storage`: The contract's storage struct (defined with [`storage`](crate::macros::storage::storage), or `()` if
1515
/// the contract has no storage
16-
pub struct ContractSelfUtility<Storage> {
16+
/// - `CallSelf`: Macro-generated type for calling the contract's own utility functions (same type
17+
/// as `CallSelf` in \[`PrivateUtilityCalls`\])
18+
pub struct ContractSelfUtility<Storage, CallSelf> {
1719
/// The address of this contract
1820
pub address: AztecAddress,
1921

@@ -34,14 +36,22 @@ pub struct ContractSelfUtility<Storage> {
3436

3537
/// The utility execution context.
3638
pub context: UtilityContext,
39+
40+
/// Provides type-safe methods for calling this contract's own utility functions.
41+
///
42+
/// Example API:
43+
/// ```noir
44+
/// self.call_self.some_utility_function(args)
45+
/// ```
46+
pub call_self: CallSelf,
3747
}
3848

39-
impl<Storage> ContractSelfUtility<Storage> {
49+
impl<Storage, CallSelf> ContractSelfUtility<Storage, CallSelf> {
4050
/// Creates a new `ContractSelfUtility` instance for a utility function.
4151
///
4252
/// This constructor is called automatically by the macro system and should not be called directly.
43-
pub fn new(context: UtilityContext, storage: Storage) -> Self {
44-
Self { context, storage, address: context.this_address() }
53+
pub fn new(context: UtilityContext, storage: Storage, call_self: CallSelf) -> Self {
54+
Self { context, storage, address: context.this_address(), call_self }
4555
}
4656

4757
/// Makes a utility contract call from another utility function.

0 commit comments

Comments
 (0)