1- use std:: collections:: BTreeMap ;
2-
31use arkade_compiler:: ContractJson ;
42
53/// Wire-encoding descriptor, matching the compiler's `WitnessElement.encoding` field.
@@ -41,8 +39,8 @@ impl Encoding {
4139 }
4240 }
4341
44- /// Infer encoding from an Arkade Script type string.
45- /// Used as fallback when `witnessSchema` is absent .
42+ /// Infer encoding from an Arkade Script type string. Constructor parameters
43+ /// and covenant inputs carry only a type, not an explicit encoding .
4644 pub fn from_ark_type ( type_str : & str ) -> Self {
4745 match type_str {
4846 "pubkey" => Encoding :: Compressed33 ,
@@ -72,7 +70,7 @@ impl Encoding {
7270 }
7371}
7472
75- /// A typed field in the IR (constructor param or witness element).
73+ /// A typed field in the IR (constructor param, covenant input, or witness element).
7674#[ derive( Debug , Clone ) ]
7775pub struct Field {
7876 /// Field name as it appears in the artifact (camelCase).
@@ -81,34 +79,49 @@ pub struct Field {
8179 pub ark_type : String ,
8280 /// Wire encoding descriptor.
8381 pub encoding : Encoding ,
84- /// True if this field is injected by the Arkade server (e.g., "serverSig" ).
85- pub is_server_injected : bool ,
82+ /// True when Arkade infrastructure supplies this field (e.g., co-signer sigs ).
83+ pub is_injected : bool ,
8684}
8785
88- /// One variant of a function (cooperative or exit) .
86+ /// One L1 tapleaf spend path within a group .
8987#[ derive( Debug , Clone ) ]
90- pub struct VariantIR {
91- /// Fields the caller must supply (excludes server-injected ).
92- pub user_fields : Vec < Field > ,
93- /// All fields including server-injected .
94- pub all_fields : Vec < Field > ,
95- /// Raw assembly instructions .
88+ pub struct LeafIR {
89+ /// Leaf name (source tapscript name, or covenant name for a synthesized default ).
90+ pub name : String ,
91+ /// Witness stack the caller supplies, in order. Injected fields are marked .
92+ pub witness_fields : Vec < Field > ,
93+ /// Tapleaf assembly.
9694 pub asm : Vec < String > ,
97- /// Human-readable requirement descriptions for doc comments.
98- pub requirements : Vec < String > ,
99- /// Whether this is an N-of-N multisig fallback exit path.
100- pub is_nofn_fallback : bool ,
10195}
10296
103- /// A paired function with cooperative and exit variants.
97+ impl LeafIR {
98+ /// Fields the caller must supply (excludes infrastructure-injected ones).
99+ pub fn user_fields ( & self ) -> Vec < & Field > {
100+ self . witness_fields
101+ . iter ( )
102+ . filter ( |f| !f. is_injected )
103+ . collect ( )
104+ }
105+ }
106+
107+ /// The emulator-run covenant attached to a function-backed group.
104108#[ derive( Debug , Clone ) ]
105- pub struct FunctionIR {
106- /// Function name as it appears in the artifact.
109+ pub struct CovenantIR {
110+ /// Covenant inputs (function parameters, array-expanded).
111+ pub inputs : Vec < Field > ,
112+ /// Covenant assembly.
113+ pub asm : Vec < String > ,
114+ }
115+
116+ /// A spend group: an optional covenant plus its L1 leaves.
117+ #[ derive( Debug , Clone ) ]
118+ pub struct GroupIR {
119+ /// Group name (covenant function name, or a standalone leaf's own name).
107120 pub name : String ,
108- /// Cooperative path (serverVariant: true) .
109- pub cooperative : VariantIR ,
110- /// Exit path (serverVariant: false) .
111- pub exit : VariantIR ,
121+ /// Emulator covenant; absent for groups of standalone leaves .
122+ pub covenant : Option < CovenantIR > ,
123+ /// L1 tapleaves grouped under this entry .
124+ pub leaves : Vec < LeafIR > ,
112125}
113126
114127/// The complete intermediate representation of a contract.
@@ -118,164 +131,70 @@ pub struct ContractIR {
118131 pub name : String ,
119132 /// Typed constructor parameters.
120133 pub constructor_fields : Vec < Field > ,
121- /// Paired functions (cooperative + exit) .
122- pub functions : Vec < FunctionIR > ,
134+ /// Spend groups in artifact order .
135+ pub groups : Vec < GroupIR > ,
123136 /// Original .ark source code, if embedded.
124137 pub source : Option < String > ,
125138 /// Compiler version string.
126139 pub compiler_version : Option < String > ,
127140}
128141
142+ fn field_from_ark_type ( name : & str , ark_type : & str ) -> Field {
143+ Field {
144+ name : name. to_string ( ) ,
145+ ark_type : ark_type. to_string ( ) ,
146+ encoding : Encoding :: from_ark_type ( ark_type) ,
147+ is_injected : false ,
148+ }
149+ }
150+
129151/// Build an IR from a compiled contract artifact.
130152pub fn build_ir ( artifact : & ContractJson ) -> Result < ContractIR , String > {
131153 let constructor_fields = artifact
132154 . parameters
133155 . iter ( )
134- . map ( |p| Field {
135- name : p. name . clone ( ) ,
136- ark_type : p. param_type . clone ( ) ,
137- encoding : Encoding :: from_ark_type ( & p. param_type ) ,
138- is_server_injected : false ,
139- } )
156+ . map ( |p| field_from_ark_type ( & p. name , & p. param_type ) )
140157 . collect ( ) ;
141158
142- let functions = pair_functions ( artifact) ?;
159+ let groups = artifact
160+ . functions
161+ . iter ( )
162+ . map ( |group| GroupIR {
163+ name : group. name . clone ( ) ,
164+ covenant : group. arkade . as_ref ( ) . map ( |cov| CovenantIR {
165+ inputs : cov
166+ . inputs
167+ . iter ( )
168+ . map ( |i| field_from_ark_type ( & i. name , & i. param_type ) )
169+ . collect ( ) ,
170+ asm : cov. asm . clone ( ) ,
171+ } ) ,
172+ leaves : group
173+ . leaves
174+ . iter ( )
175+ . map ( |leaf| LeafIR {
176+ name : leaf. name . clone ( ) ,
177+ witness_fields : leaf
178+ . witness
179+ . iter ( )
180+ . map ( |w| Field {
181+ name : w. name . clone ( ) ,
182+ ark_type : w. elem_type . clone ( ) ,
183+ encoding : Encoding :: parse ( & w. encoding ) ,
184+ is_injected : w. injected ,
185+ } )
186+ . collect ( ) ,
187+ asm : leaf. asm . clone ( ) ,
188+ } )
189+ . collect ( ) ,
190+ } )
191+ . collect ( ) ;
143192
144193 Ok ( ContractIR {
145194 name : artifact. name . clone ( ) ,
146195 constructor_fields,
147- functions ,
196+ groups ,
148197 source : artifact. source . clone ( ) ,
149198 compiler_version : artifact. compiler . as_ref ( ) . map ( |c| c. version . clone ( ) ) ,
150199 } )
151200}
152-
153- /// Group artifact functions by name and pair cooperative + exit variants.
154- fn pair_functions ( artifact : & ContractJson ) -> Result < Vec < FunctionIR > , String > {
155- // Use BTreeMap to preserve insertion order (sorted, but stable).
156- // We'll re-sort by first-appearance order afterwards.
157- let mut groups: BTreeMap < String , ( Option < usize > , Vec < & arkade_compiler:: models:: AbiFunction > ) > =
158- BTreeMap :: new ( ) ;
159-
160- for ( idx, func) in artifact. functions . iter ( ) . enumerate ( ) {
161- let entry = groups
162- . entry ( func. name . clone ( ) )
163- . or_insert_with ( || ( Some ( idx) , Vec :: new ( ) ) ) ;
164- entry. 1 . push ( func) ;
165- }
166-
167- // Sort by first appearance order
168- let mut ordered: Vec < _ > = groups. into_iter ( ) . collect ( ) ;
169- ordered. sort_by_key ( |( _, ( first_idx, _) ) | first_idx. unwrap_or ( usize:: MAX ) ) ;
170-
171- let mut result = Vec :: new ( ) ;
172-
173- for ( name, ( _, variants) ) in ordered {
174- let mut cooperative = None ;
175- let mut exit = None ;
176-
177- for func in & variants {
178- if func. server_variant {
179- if cooperative. is_some ( ) {
180- return Err ( format ! (
181- "Function '{}' has multiple cooperative variants" ,
182- name
183- ) ) ;
184- }
185- cooperative = Some ( * func) ;
186- } else {
187- if exit. is_some ( ) {
188- return Err ( format ! ( "Function '{}' has multiple exit variants" , name) ) ;
189- }
190- exit = Some ( * func) ;
191- }
192- }
193-
194- let cooperative_func = cooperative
195- . ok_or_else ( || format ! ( "Function '{}' missing cooperative variant" , name) ) ?;
196- let exit_func = exit. ok_or_else ( || format ! ( "Function '{}' missing exit variant" , name) ) ?;
197-
198- result. push ( FunctionIR {
199- name : name. clone ( ) ,
200- cooperative : build_variant ( cooperative_func) ,
201- exit : build_variant ( exit_func) ,
202- } ) ;
203- }
204-
205- Ok ( result)
206- }
207-
208- /// Build a VariantIR from a single AbiFunction.
209- fn build_variant ( func : & arkade_compiler:: models:: AbiFunction ) -> VariantIR {
210- let has_witness_schema = !func. witness_schema . is_empty ( ) ;
211-
212- // Check if "serverSig" is a user-declared input (not protocol-injected)
213- let user_declared_server_sig = func. function_inputs . iter ( ) . any ( |fi| fi. name == "serverSig" ) ;
214-
215- let all_fields: Vec < Field > = if has_witness_schema {
216- // Primary path: use witnessSchema with explicit encodings
217- func. witness_schema
218- . iter ( )
219- . map ( |w| Field {
220- name : w. name . clone ( ) ,
221- ark_type : w. elem_type . clone ( ) ,
222- encoding : Encoding :: parse ( & w. encoding ) ,
223- is_server_injected : func. server_variant
224- && w. name == "serverSig"
225- && !user_declared_server_sig,
226- } )
227- . collect ( )
228- } else {
229- // Fallback: infer encodings from functionInputs type strings
230- let mut fields: Vec < Field > = func
231- . function_inputs
232- . iter ( )
233- . map ( |fi| Field {
234- name : fi. name . clone ( ) ,
235- ark_type : fi. param_type . clone ( ) ,
236- encoding : Encoding :: from_ark_type ( & fi. param_type ) ,
237- is_server_injected : false ,
238- } )
239- . collect ( ) ;
240-
241- // If this is a cooperative variant and serverSig isn't user-declared, add it
242- if func. server_variant && !user_declared_server_sig {
243- fields. push ( Field {
244- name : "serverSig" . to_string ( ) ,
245- ark_type : "signature" . to_string ( ) ,
246- encoding : Encoding :: Schnorr64 ,
247- is_server_injected : true ,
248- } ) ;
249- }
250-
251- fields
252- } ;
253-
254- let user_fields = all_fields
255- . iter ( )
256- . filter ( |f| !f. is_server_injected )
257- . cloned ( )
258- . collect ( ) ;
259-
260- let requirements = func
261- . require
262- . iter ( )
263- . map ( |r| {
264- if let Some ( ref msg) = r. message {
265- format ! ( "{}: {}" , r. req_type, msg)
266- } else {
267- r. req_type . clone ( )
268- }
269- } )
270- . collect ( ) ;
271-
272- let is_nofn_fallback = func. require . iter ( ) . any ( |r| r. req_type == "nOfNMultisig" ) ;
273-
274- VariantIR {
275- user_fields,
276- all_fields,
277- asm : func. asm . clone ( ) ,
278- requirements,
279- is_nofn_fallback,
280- }
281- }
0 commit comments