@@ -2,7 +2,8 @@ import type { ACIRCallback, ACVMField } from '@aztec/simulator/client';
22
33import { ORACLE_VERSION_MAJOR , ORACLE_VERSION_MINOR } from '../../oracle_version.js' ;
44import type { IMiscOracle , IPrivateExecutionOracle , IUtilityExecutionOracle } from './interfaces.js' ;
5- import { ORACLE_REGISTRY } from './oracle_registry.js' ;
5+ import { LEGACY_ORACLE_REGISTRY , type LegacyOracleEntry } from './legacy_oracle_registry.js' ;
6+ import { type NamedValue , ORACLE_REGISTRY , type OracleRegistryEntry , makeEntry } from './oracle_registry.js' ;
67
78export class UnavailableOracleError extends Error {
89 constructor ( oracleName : string ) {
@@ -17,26 +18,71 @@ export class UnavailableOracleError extends Error {
1718 * `aztec_{scope}_{methodName}` convention to resolve the handler, and calling the method directly. Unknown oracle
1819 * names produce enhanced error messages based on the contract's oracle version.
1920 */
20- export function buildACIRCallback ( handler : OracleHandler ) : ACIRCallback {
21+ export function buildACIRCallback (
22+ handler : OracleHandler ,
23+ registries : {
24+ real ?: Record < string , OracleRegistryEntry > ;
25+ legacy ?: Record < string , LegacyOracleEntry > ;
26+ } = { } ,
27+ ) : ACIRCallback {
28+ const { real = ORACLE_REGISTRY , legacy : legacyRegistry = LEGACY_ORACLE_REGISTRY } = registries ;
2129 const target = { } as ACIRCallback ;
22- for ( const [ oracleKey , entry ] of Object . entries ( ORACLE_REGISTRY ) ) {
23- const match = oracleKey . match ( / ^ a z t e c _ ( \w + ?) _ ( .+ ) $ / ) ;
24- if ( ! match ) {
25- throw new Error ( `Oracle "${ oracleKey } " does not follow the aztec_{scope}_{method} convention` ) ;
26- }
27- const [ , scope , methodName ] = match ;
30+ for ( const [ oracleKey , entry ] of Object . entries ( real ) ) {
31+ const { scope, methodName } = parseOracleName ( oracleKey , 'Oracle' ) ;
2832 target [ oracleKey ] = async ( ...inputs : ACVMField [ ] [ ] ) => {
2933 assertHandlerSupportsScope ( handler , scope ) ;
3034 const named = entry . deserializeParams ( inputs ) ;
31- const positional = named . map ( p => p . value ) ;
35+ const positional = named . map ( ( p : NamedValue ) => p . value ) ;
3236 const result = await ( handler as any ) [ methodName ] ( ...positional ) ;
3337 return entry . serializeReturn ( result ) ;
3438 } ;
3539 }
3640
37- return new Proxy ( target , {
41+ // Legacy oracle names: served for contracts compiled against a retired oracle version. Each reuses the current
42+ // handler of its `modernOracle` and reshapes the wire (params and/or return) back to what the old bytecode expects.
43+ for ( const [ legacyKey , legacy ] of Object . entries ( legacyRegistry ) ) {
44+ const { scope } = parseOracleName ( legacyKey , 'Legacy oracle' ) ;
45+ if ( legacyKey in target ) {
46+ throw new Error ( `Legacy oracle "${ legacyKey } " collides with a live oracle of the same name in the registry` ) ;
47+ }
48+ const modernEntry = real [ legacy . modernOracle ] ;
49+ const { methodName } = parseOracleName ( legacy . modernOracle , 'Oracle' ) ;
50+ // Override only the side whose wire changed; inherit the other from the modern entry.
51+ const paramOverride = legacy . params ;
52+ const paramSource = paramOverride ? makeEntry ( { params : [ ...paramOverride . legacyType ] } ) : modernEntry ;
53+ const returnOverride = legacy . returnType ;
54+ const returnSource = returnOverride ? makeEntry ( { returnType : returnOverride . legacyType } ) : modernEntry ;
55+ target [ legacyKey ] = async ( ...inputs : ACVMField [ ] [ ] ) => {
56+ assertHandlerSupportsScope ( handler , scope ) ;
57+ const legacyArgs = paramSource . deserializeParams ( inputs ) . map ( p => p . value ) ;
58+ const positional = paramOverride ? paramOverride . mapping ( legacyArgs ) : legacyArgs ;
59+ const result = await ( handler as any ) [ methodName ] ( ...positional ) ;
60+ return returnSource . serializeReturn ( returnOverride ? returnOverride . mapping ( result ) : result ) ;
61+ } ;
62+ }
63+
64+ return new Proxy ( target , makeUnknownOracleTrap ( handler ) ) ;
65+ }
66+
67+ /** Parses an `aztec_{scope}_{method}` oracle name into its parts, throwing if it doesn't follow the convention. */
68+ function parseOracleName ( key : string , label : string ) : { scope : string ; methodName : string } {
69+ const match = key . match ( / ^ a z t e c _ ( \w + ?) _ ( .+ ) $ / ) ;
70+ if ( ! match ) {
71+ throw new Error ( `${ label } "${ key } " does not follow the aztec_{scope}_{method} convention` ) ;
72+ }
73+ return { scope : match [ 1 ] , methodName : match [ 2 ] } ;
74+ }
75+
76+ /**
77+ * Proxy trap for the callback table: a known oracle name passes through; an unknown one throws a diagnostic keyed on
78+ * the contract's oracle version (version unknown, contract newer than this environment, or a same-version mismatch).
79+ */
80+ function makeUnknownOracleTrap ( handler : OracleHandler ) : ProxyHandler < ACIRCallback > {
81+ return {
3882 get ( obj , prop : string ) {
39- if ( prop in obj ) {
83+ // Own-property check only: `in` would match inherited `Object.prototype` keys (e.g. `constructor`, `toString`)
84+ // and return the built-in instead of falling through to the unknown-oracle diagnostic below.
85+ if ( Object . hasOwn ( obj , prop ) ) {
4086 return ( obj as Record < string , unknown > ) [ prop ] ;
4187 }
4288
@@ -76,7 +122,7 @@ export function buildACIRCallback(handler: OracleHandler): ACIRCallback {
76122 }
77123 } ;
78124 } ,
79- } ) ;
125+ } ;
80126}
81127
82128type OracleHandler = IMiscOracle & ( IUtilityExecutionOracle | IPrivateExecutionOracle ) ;
0 commit comments