Skip to content

Commit 0e899b8

Browse files
author
AztecBot
committed
Merge branch 'next' into merge-train/avm
2 parents 1fa3bd6 + 2fb567f commit 0e899b8

15 files changed

Lines changed: 240 additions & 75 deletions

File tree

docs/docs-developers/docs/foundational-topics/pxe/index.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,25 @@ The keystore securely stores cryptographic keys for registered accounts, includi
8787

8888
Oracles are pieces of data that are injected into a smart contract function from the client side. Learn more about [how oracles work](../../aztec-nr/framework-description/advanced/protocol_oracles.md).
8989

90+
## Oracle versioning
91+
92+
The set of oracles that the PXE exposes to private and utility functions is versioned, so that contracts can declare which oracles they expect to be available. Every contract compiled with `Aztec.nr` records the oracle version it was built against, and the PXE checks this version before executing any oracle call.
93+
94+
The version uses two components, `major.minor`, with the following compatibility rules:
95+
96+
- **`major`** must match exactly. A major bump is a breaking change — oracles were removed or their signatures changed — and a PXE on a different major cannot safely run the contract.
97+
- **`minor`** indicates additive changes (new oracles). The PXE uses a best-effort approach here: a contract compiled against a higher `minor` than the PXE supports is still allowed to run, and an error is only thrown if the contract actually invokes an oracle the PXE does not know about. In practice, a contract built with a newer Aztec.nr may not use any of the newly added oracles at all, in which case it runs fine on an older PXE.
98+
99+
The canonical version constants live in the PXE (`ORACLE_VERSION_MAJOR` / `ORACLE_VERSION_MINOR` in `yarn-project/pxe/src/oracle_version.ts`) and in Aztec.nr (`noir-projects/aztec-nr/aztec/src/oracle/version.nr`). The two are kept in lockstep as part of each release.
100+
101+
### Resolving a version mismatch
102+
103+
If you see an error like _"Oracle '…' not found. … The contract was compiled with Aztec.nr oracle version X.Y, but this private execution environment only supports up to A.B"_, the contract uses one or more oracles from a newer Aztec.nr than your PXE supports.
104+
105+
To fix it, upgrade the software that ships the PXE (sandbox, wallet, or whatever embeds `@aztec/pxe`) to a release whose Aztec.nr version is at least as new as the one the contract was compiled with.
106+
107+
If the PXE reports a version that _should_ include every oracle the contract needs but an oracle is still missing, that is a contract bug rather than a version problem and you should likely report it to the app developer.
108+
90109
## For developers
91110

92111
To learn how to develop on top of the PXE, refer to these guides:

docs/netlify.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,7 @@
802802
[[redirects]]
803803
# PXE: incompatible oracle version between contract and PXE
804804
from = "/errors/8"
805-
to = "/developers/docs/foundational-topics/pxe"
805+
to = "/developers/docs/foundational-topics/pxe#oracle-versioning"
806806

807807
[[redirects]]
808808
# CLI: aztec dep version in Nargo.toml does not match the CLI version
Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1-
/// The ORACLE_VERSION constant is used to check that the oracle interface is in sync between PXE and Aztec.nr. We need
2-
/// to version the oracle interface to ensure that developers get a reasonable error message if they use incompatible
3-
/// versions of Aztec.nr and PXE. The TypeScript counterpart is in `oracle_version.ts`.
1+
/// The oracle version constants are used to check that the oracle interface is in sync between PXE and Aztec.nr.
2+
/// We version the oracle interface as `major.minor` where:
3+
/// - `major` = backward-breaking changes (must match exactly between PXE and Aztec.nr)
4+
/// - `minor` = oracle additions (non-breaking; PXE minor >= contract minor)
5+
///
6+
/// The TypeScript counterparts are in `oracle_version.ts`.
47
///
58
/// @dev Whenever a contract function or Noir test is run, the `aztec_utl_assertCompatibleOracleVersion` oracle is
6-
/// called and if the oracle version is incompatible an error is thrown.
7-
pub global ORACLE_VERSION: Field = 22;
9+
/// called. If the major version is incompatible, an error is thrown immediately. The minor version is recorded by
10+
/// the PXE and used to provide helpful error messages if a contract calls an oracle that doesn't exist. We don't throw
11+
/// immediately if AZTEC_NR_MINOR > PXE_MINOR because if a contract is updated to use a newer Aztec.nr dependency
12+
/// without actually using any of the new oracles then there is no reason to throw.
13+
pub global ORACLE_VERSION_MAJOR: Field = 22;
14+
pub global ORACLE_VERSION_MINOR: Field = 0;
815

916
/// Asserts that the version of the oracle is compatible with the version expected by the contract.
1017
pub fn assert_compatible_oracle_version() {
@@ -16,23 +23,23 @@ pub fn assert_compatible_oracle_version() {
1623
}
1724

1825
unconstrained fn assert_compatible_oracle_version_wrapper() {
19-
assert_compatible_oracle_version_oracle(ORACLE_VERSION);
26+
assert_compatible_oracle_version_oracle(ORACLE_VERSION_MAJOR, ORACLE_VERSION_MINOR);
2027
}
2128

22-
#[oracle(aztec_utl_assertCompatibleOracleVersion)]
23-
unconstrained fn assert_compatible_oracle_version_oracle(version: Field) {}
29+
#[oracle(aztec_utl_assertCompatibleOracleVersionV2)]
30+
unconstrained fn assert_compatible_oracle_version_oracle(major: Field, minor: Field) {}
2431

2532
mod test {
26-
use super::{assert_compatible_oracle_version_oracle, ORACLE_VERSION};
33+
use super::{assert_compatible_oracle_version_oracle, ORACLE_VERSION_MAJOR, ORACLE_VERSION_MINOR};
2734

2835
#[test]
2936
unconstrained fn compatible_oracle_version() {
30-
assert_compatible_oracle_version_oracle(ORACLE_VERSION);
37+
assert_compatible_oracle_version_oracle(ORACLE_VERSION_MAJOR, ORACLE_VERSION_MINOR);
3138
}
3239

3340
#[test(should_fail_with = "Incompatible aztec cli version:")]
34-
unconstrained fn incompatible_oracle_version() {
35-
let arbitrary_incorrect_version = 318183437;
36-
assert_compatible_oracle_version_oracle(arbitrary_incorrect_version);
41+
unconstrained fn incompatible_oracle_version_major() {
42+
let arbitrary_incorrect_major = 318183437;
43+
assert_compatible_oracle_version_oracle(arbitrary_incorrect_major, ORACLE_VERSION_MINOR);
3744
}
3845
}

noir-projects/aztec-nr/aztec/src/test/helpers/test_environment/test/misc.nr

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use crate::{oracle::version::ORACLE_VERSION, test::helpers::test_environment::TestEnvironment};
1+
use crate::{
2+
oracle::version::{ORACLE_VERSION_MAJOR, ORACLE_VERSION_MINOR},
3+
test::helpers::test_environment::TestEnvironment,
4+
};
25
use std::test::OracleMock;
36

47
#[test]
@@ -39,10 +42,10 @@ unconstrained fn private_public_and_utility_context_share_default_chain_id() {
3942

4043
#[test]
4144
unconstrained fn oracle_version_is_checked_upon_env_creation() {
42-
let mock = OracleMock::mock("aztec_utl_assertCompatibleOracleVersion");
45+
let mock = OracleMock::mock("aztec_utl_assertCompatibleOracleVersionV2");
4346

4447
let _env = TestEnvironment::new();
4548

4649
assert_eq(mock.times_called(), 1);
47-
assert_eq(mock.get_last_params::<Field>(), ORACLE_VERSION);
50+
assert_eq(mock.get_last_params::<(Field, Field)>(), (ORACLE_VERSION_MAJOR, ORACLE_VERSION_MINOR));
4851
}
Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1-
/// The ORACLE_VERSION constant is used to check that the oracle interface is in sync between PXE and Aztec.nr. We need
2-
/// to version the oracle interface to ensure that developers get a reasonable error message if they use incompatible
3-
/// versions of Aztec.nr and PXE. The TypeScript counterpart is in `oracle_version.ts`.
1+
/// The oracle version constants are used to check that the oracle interface is in sync between PXE and Aztec.nr.
2+
/// We version the oracle interface as `major.minor` where:
3+
/// - `major` = backward-breaking changes (must match exactly between PXE and Aztec.nr)
4+
/// - `minor` = oracle additions (non-breaking; PXE minor >= contract minor)
5+
///
6+
/// The TypeScript counterparts are in `oracle_version.ts`.
47
///
58
/// @dev Whenever a contract function or Noir test is run, the `aztec_utl_assertCompatibleOracleVersion` oracle is
6-
/// called and if the oracle version is incompatible an error is thrown.
7-
pub global ORACLE_VERSION: Field = 22;
9+
/// called. If the major version is incompatible, an error is thrown immediately. The minor version is recorded by
10+
/// the PXE and used to provide helpful error messages if a contract calls an oracle that doesn't exist. We don't throw
11+
/// immediately if AZTEC_NR_MINOR > PXE_MINOR because if a contract is updated to use a newer Aztec.nr dependency
12+
/// without actually using any of the new oracles then there is no reason to throw.
13+
pub global ORACLE_VERSION_MAJOR: Field = 22;
14+
pub global ORACLE_VERSION_MINOR: Field = 0;
815

916
/// Asserts that the version of the oracle is compatible with the version expected by the contract.
1017
pub fn assert_compatible_oracle_version() {
@@ -16,23 +23,23 @@ pub fn assert_compatible_oracle_version() {
1623
}
1724

1825
unconstrained fn assert_compatible_oracle_version_wrapper() {
19-
assert_compatible_oracle_version_oracle(ORACLE_VERSION);
26+
assert_compatible_oracle_version_oracle(ORACLE_VERSION_MAJOR, ORACLE_VERSION_MINOR);
2027
}
2128

22-
#[oracle(aztec_utl_assertCompatibleOracleVersion)]
23-
unconstrained fn assert_compatible_oracle_version_oracle(version: Field) {}
29+
#[oracle(aztec_utl_assertCompatibleOracleVersionV2)]
30+
unconstrained fn assert_compatible_oracle_version_oracle(major: Field, minor: Field) {}
2431

2532
mod test {
26-
use super::{assert_compatible_oracle_version_oracle, ORACLE_VERSION};
33+
use super::{assert_compatible_oracle_version_oracle, ORACLE_VERSION_MAJOR, ORACLE_VERSION_MINOR};
2734

2835
#[test]
2936
unconstrained fn compatible_oracle_version() {
30-
assert_compatible_oracle_version_oracle(ORACLE_VERSION);
37+
assert_compatible_oracle_version_oracle(ORACLE_VERSION_MAJOR, ORACLE_VERSION_MINOR);
3138
}
3239

3340
#[test(should_fail_with = "Incompatible aztec cli version:")]
34-
unconstrained fn incompatible_oracle_version() {
35-
let arbitrary_incorrect_version = 318183437;
36-
assert_compatible_oracle_version_oracle(arbitrary_incorrect_version);
41+
unconstrained fn incompatible_oracle_version_major() {
42+
let arbitrary_incorrect_major = 318183437;
43+
assert_compatible_oracle_version_oracle(arbitrary_incorrect_major, ORACLE_VERSION_MINOR);
3744
}
3845
}

yarn-project/pxe/src/bin/check_oracle_version.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ import { ORACLE_INTERFACE_HASH } from '../oracle_version.js';
1313
*
1414
* The Oracle interface needs to be versioned to ensure compatibility between Aztec.nr and PXE. This function computes
1515
* a hash of the Oracle interface and compares it against a known hash. If they don't match, it means the interface has
16-
* changed and the ORACLE_VERSION constant needs to be incremented and the ORACLE_INTERFACE_HASH constant needs to be
17-
* updated.
16+
* changed and the oracle version needs to be bumped:
17+
* - If the change is backward-breaking (e.g. removing/renaming an oracle), bump ORACLE_VERSION_MAJOR.
18+
* - If the change is an oracle addition (non-breaking), bump ORACLE_VERSION_MINOR.
1819
*
1920
* TODO(#16581): The following only takes into consideration changes to the oracles defined in Oracle.ts and omits TXE
2021
* oracles. Ensure this checks TXE oracles as well. This hasn't been implemented yet since we don't have a clean TXE
@@ -26,9 +27,8 @@ function assertOracleInterfaceMatches(): void {
2627
// We use keccak256 here just because we already have it in the dependencies.
2728
const oracleInterfaceHash = keccak256String(oracleInterfaceSignature);
2829
if (oracleInterfaceHash !== ORACLE_INTERFACE_HASH) {
29-
// This check exists only to notify you when you need to update the ORACLE_VERSION constant.
3030
throw new Error(
31-
`The Oracle interface has changed, which implies a breaking change in the aztec.nr/PXE oracle interface. Update ORACLE_INTERFACE_HASH to ${oracleInterfaceHash} and bump ORACLE_VERSION in pxe/src/oracle_version.ts.`,
31+
`The Oracle interface has changed. Update ORACLE_INTERFACE_HASH to ${oracleInterfaceHash} in pxe/src/oracle_version.ts and bump the oracle version (ORACLE_VERSION_MAJOR for breaking changes, ORACLE_VERSION_MINOR for oracle additions).`,
3232
);
3333
}
3434
}

yarn-project/pxe/src/contract_function_simulator/oracle/interfaces.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export interface IMiscOracle {
5454
isMisc: true;
5555

5656
getRandomField(): Fr;
57-
assertCompatibleOracleVersion(version: number): void;
57+
assertCompatibleOracleVersion(major: number, minor: number): void;
5858
log(level: number, message: string, fields: Fr[]): Promise<void>;
5959
}
6060

yarn-project/pxe/src/contract_function_simulator/oracle/legacy_oracle_mappings.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import type { Oracle } from './oracle.js';
1010
* TODO(F-416): Remove these aliases on v5 when protocol contracts are redeployed.
1111
*/
1212
export function buildLegacyOracleCallbacks(oracle: Oracle): ACIRCallback {
13+
// If you are about to add a new mapping ensure that the old oracle name is different from the new one - this can
14+
// commonly occur when only the args are getting modified.
1315
return {
1416
// Simple prefix renames (privateXxx/utilityXxx → aztec_prv_/aztec_utl_)
1517
utilityLog: (
@@ -19,7 +21,11 @@ export function buildLegacyOracleCallbacks(oracle: Oracle): ACIRCallback {
1921
fields: ACVMField[],
2022
): Promise<ACVMField[]> => oracle.aztec_utl_log(level, message, _ignoredFieldsSize, fields),
2123
utilityAssertCompatibleOracleVersion: (version: ACVMField[]): Promise<ACVMField[]> =>
22-
oracle.aztec_utl_assertCompatibleOracleVersion(version),
24+
oracle.aztec_utl_assertCompatibleOracleVersionV2(version, [toACVMField(0)]),
25+
// Old 1-arg oracle before minor/major split. Maps to V2 with minor=0.
26+
// eslint-disable-next-line camelcase
27+
aztec_utl_assertCompatibleOracleVersion: (version: ACVMField[]): Promise<ACVMField[]> =>
28+
oracle.aztec_utl_assertCompatibleOracleVersionV2(version, [toACVMField(0)]),
2329
utilityLoadCapsule: (
2430
contractAddress: ACVMField[],
2531
slot: ACVMField[],

yarn-project/pxe/src/contract_function_simulator/oracle/oracle.ts

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { AztecAddress } from '@aztec/stdlib/aztec-address';
1515
import { BlockHash } from '@aztec/stdlib/block';
1616
import { ContractClassLog, ContractClassLogFields } from '@aztec/stdlib/logs';
1717

18+
import { ORACLE_VERSION_MAJOR, ORACLE_VERSION_MINOR } from '../../oracle_version.js';
1819
import type { IMiscOracle, IPrivateExecutionOracle, IUtilityExecutionOracle } from './interfaces.js';
1920
import { buildLegacyOracleCallbacks } from './legacy_oracle_mappings.js';
2021
import { packAsHintedNote } from './note_packing_utils.js';
@@ -111,12 +112,67 @@ export class Oracle {
111112
return acc;
112113
}, {} as ACIRCallback);
113114

114-
return { ...callback, ...buildLegacyOracleCallbacks(this) };
115+
const allCallbacks = { ...callback, ...buildLegacyOracleCallbacks(this) };
116+
117+
// Wrap in a Proxy to intercept access to missing oracle names and provide enhanced error messages when the
118+
// contract's minor version is higher than the PXE's (i.e. the contract expects oracles that were added in a newer
119+
// minor version).
120+
const handler = this.handler;
121+
return new Proxy(allCallbacks, {
122+
get(target, prop: string) {
123+
if (prop in target) {
124+
return target[prop];
125+
}
126+
// Return a function that throws with an enhanced error message if applicable
127+
return () => {
128+
type NonOracleFunctionGetContractOracleVersion = {
129+
nonOracleFunctionGetContractOracleVersion(): { major: number; minor: number } | undefined;
130+
};
131+
132+
let contractVersion = undefined;
133+
if ('nonOracleFunctionGetContractOracleVersion' in handler) {
134+
contractVersion = (
135+
handler as unknown as NonOracleFunctionGetContractOracleVersion
136+
).nonOracleFunctionGetContractOracleVersion();
137+
}
138+
if (!contractVersion) {
139+
throw new Error(
140+
`Oracle '${prop}' not found and the contract's oracle version is unknown (the version check oracle ` +
141+
`was not called before '${prop}'). This usually means the contract was not compiled with the ` +
142+
`#[aztec] macro, which injects the version check as the first oracle call in every private/utility ` +
143+
`external function. If you're using a custom entry point, ensure assert_compatible_oracle_version() ` +
144+
`is called before any other oracle calls. See https://docs.aztec.network/errors/8`,
145+
);
146+
} else if (contractVersion.minor > ORACLE_VERSION_MINOR) {
147+
throw new Error(
148+
`Oracle '${prop}' not found.` +
149+
` This usually means the contract requires a newer private execution environment than you have.` +
150+
` Upgrade your private execution environment to a compatible version. The contract was compiled with` +
151+
` Aztec.nr oracle version ${contractVersion.major}.${contractVersion.minor}, but this private` +
152+
` execution environment only supports up to ${ORACLE_VERSION_MAJOR}.${ORACLE_VERSION_MINOR}.` +
153+
` See https://docs.aztec.network/errors/8`,
154+
);
155+
} else {
156+
throw new Error(
157+
`Oracle '${prop}' not found.` +
158+
` The contract's oracle version (${contractVersion.major}.${contractVersion.minor}) is compatible` +
159+
` with this private execution environment (${ORACLE_VERSION_MAJOR}.${ORACLE_VERSION_MINOR}), so all` +
160+
` standard oracles should be available. This could mean the contract was compiled against a modified` +
161+
` version of Aztec.nr, or that it references an oracle that does not exist.` +
162+
` See https://docs.aztec.network/errors/8`,
163+
);
164+
}
165+
};
166+
},
167+
});
115168
}
116169

117170
// eslint-disable-next-line camelcase
118-
aztec_utl_assertCompatibleOracleVersion([version]: ACVMField[]) {
119-
this.handlerAsMisc().assertCompatibleOracleVersion(Fr.fromString(version).toNumber());
171+
aztec_utl_assertCompatibleOracleVersionV2([major]: ACVMField[], [minor]: ACVMField[]) {
172+
this.handlerAsMisc().assertCompatibleOracleVersion(
173+
Fr.fromString(major).toNumber(),
174+
Fr.fromString(minor).toNumber(),
175+
);
120176
return Promise.resolve([]);
121177
}
122178

0 commit comments

Comments
 (0)