ethbind_gen/
lib.rs

1//! This crate provide core types of ethbind generation system
2//!
3//!
4//!
5
6use std::{
7    collections::HashMap,
8    fs::{self, read_to_string},
9    path::Path,
10    str::FromStr,
11};
12
13use ethbind_json::{
14    AbiField, Array, ArrayM, Constructor, Error, Event, FixedMN, Function, HardhatArtifact,
15    IntegerM, Type,
16};
17use serde::{Deserialize, Serialize};
18use thiserror::Error;
19
20/// Typed **bind** error
21#[derive(Debug, Error)]
22pub enum BindError {
23    /// This error is returned by [`RuntimeBinder`] to indicate
24    /// that a runtime type binding for the contract type was not found.
25    #[error("Runtime binder didn't found mapping runtime type for {0}")]
26    UnknownType(String),
27}
28
29/// ABI data structure that can be generated into arbitrary programming language supported by `Ethbind`.
30///
31/// `Ethbind` provides `Generatable` implementations for types of crate [`ethbind-json`](https://docs.rs/ethbind-json)
32pub trait Generatable {
33    /// Generate abi data to `Target` programming language code.
34    fn generate<C: Context>(&self, context: &mut C) -> anyhow::Result<()>;
35}
36
37/// `Ethbind` code generation system `Context` instance
38pub trait Context {
39    /// Target programming language runtime/strongly typed binder
40    type Runtime: RuntimeBinder;
41
42    /// `Target` programming language code generator
43    type Language: Generator;
44
45    /// Get context binding programming language [`Generator`] and [`runtime binder`](RuntimeBinder)
46    fn get_mut(&mut self) -> (&mut Self::Language, &mut Self::Runtime);
47
48    fn finalize(self) -> (Self::Language, Self::Runtime);
49}
50
51/// Binder for mapping contract type system to `target` programming language runtime type system.
52pub trait RuntimeBinder {
53    /// Convert contract [`abi type`](Type) to `runtime` type string
54    ///
55    /// If the parameter `type` is tuple or array of tuple returns [`None`]
56    fn to_runtime_type(&mut self, r#type: &Type) -> anyhow::Result<Option<&str>>;
57
58    /// Get runtime type by metadata `name`, If not found the implementation must return [`Err(BindError::UnknownType)`]
59    fn get(&mut self, name: &str) -> anyhow::Result<&str>;
60}
61
62/// Programming language code generator supported by `Ethbind`.
63///
64/// The implementation must support multi-round generation process.
65pub trait Generator {
66    /// [`Generatable`] or `Executor` call this fn to start a new contract generation round.
67    fn begin<R: RuntimeBinder>(&mut self, runtime_binder: &mut R, name: &str)
68        -> anyhow::Result<()>;
69
70    /// Close current generation contract round.
71    fn end<R: RuntimeBinder>(&mut self, runtime_binder: &mut R, name: &str) -> anyhow::Result<()>;
72
73    /// Generate contract method ,call this fn after call [`begin`](Generator::begin) at least once.
74    fn generate_fn<R: RuntimeBinder>(
75        &mut self,
76        runtime_binder: &mut R,
77        r#fn: &Function,
78    ) -> anyhow::Result<()>;
79
80    /// Generate contract deploy method ,call this fn after call [`begin`](Generator::begin) at least once.
81    fn generate_deploy<R: RuntimeBinder>(
82        &mut self,
83        runtime_binder: &mut R,
84        contructor: &Constructor,
85        deploy_bytes: &str,
86    ) -> anyhow::Result<()>;
87
88    /// Generate event handle interface ,call this fn after call [`begin`](Generator::begin) at least once.
89    fn generate_event<R: RuntimeBinder>(
90        &mut self,
91        runtime_binder: &mut R,
92        event: &Event,
93    ) -> anyhow::Result<()>;
94
95    /// Generate error handle interface ,call this fn after call [`begin`](Generator::begin) at least once.
96    fn generate_error<R: RuntimeBinder>(
97        &mut self,
98        runtime_binder: &mut R,
99        error: &Error,
100    ) -> anyhow::Result<()>;
101
102    /// Close generator and return generated contract codes.
103    fn finalize<R: RuntimeBinder>(self, runtime_binder: &mut R) -> anyhow::Result<Vec<Contract>>;
104}
105
106/// Generated contract files archive
107pub struct Contract {
108    pub files: Vec<File>,
109}
110
111/// Generated codes and file name
112pub struct File {
113    pub name: String,
114    pub data: String,
115}
116
117pub trait SaveTo {
118    fn save_to<P: AsRef<Path>>(&self, output_dir: P) -> anyhow::Result<()>;
119}
120
121impl SaveTo for Contract {
122    /// Write generated contract codes to file system.
123    fn save_to<P: AsRef<Path>>(&self, output_dir: P) -> anyhow::Result<()> {
124        if !output_dir.as_ref().exists() {
125            fs::create_dir_all(&output_dir)?;
126        }
127
128        for file in &self.files {
129            let path = output_dir.as_ref().join(&file.name);
130
131            fs::write(path, &file.data)?;
132        }
133
134        Ok(())
135    }
136}
137
138impl SaveTo for Vec<Contract> {
139    fn save_to<P: AsRef<Path>>(&self, output_dir: P) -> anyhow::Result<()> {
140        for c in self {
141            c.save_to(&output_dir)?;
142        }
143
144        Ok(())
145    }
146}
147
148/// A [`RuntimeBinder`] implementation which load runtime types mapping metadata from json.
149#[derive(Debug, Serialize, Deserialize)]
150pub struct JsonRuntimeBinder {
151    #[serde(flatten)]
152    runtime_types: HashMap<String, String>,
153
154    #[serde(skip)]
155    components: HashMap<String, String>,
156}
157
158impl FromStr for JsonRuntimeBinder {
159    type Err = serde_json::Error;
160
161    fn from_str(s: &str) -> Result<Self, Self::Err> {
162        serde_json::from_str(s)
163    }
164}
165
166impl TryFrom<&str> for JsonRuntimeBinder {
167    type Error = serde_json::Error;
168
169    fn try_from(value: &str) -> Result<Self, Self::Error> {
170        serde_json::from_str(value.as_ref())
171    }
172}
173
174impl TryFrom<String> for JsonRuntimeBinder {
175    type Error = serde_json::Error;
176
177    fn try_from(value: String) -> Result<Self, Self::Error> {
178        serde_json::from_str(value.as_ref())
179    }
180}
181
182impl TryFrom<&[u8]> for JsonRuntimeBinder {
183    type Error = serde_json::Error;
184
185    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
186        serde_json::from_slice(value)
187    }
188}
189
190impl JsonRuntimeBinder {
191    /// Create `JsonRuntimeBinder` from [`Reader`](std::io::Read)
192    pub fn from_reader<R>(reader: R) -> anyhow::Result<Self>
193    where
194        R: std::io::Read,
195    {
196        Ok(serde_json::from_reader(reader)?)
197    }
198
199    /// Try load `JsonRuntimeBinder` from json file with `path` parameter
200    pub fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
201        Ok(fs::read_to_string(path)?.try_into()?)
202    }
203
204    /// Try map contract abi basic types's `type_name` to [`runtime type`](JsonRuntimeType), if not found returns `Err(EthBindError::UnknownType)`
205    fn search_basic_type<T: AsRef<str>>(&self, type_name: T) -> anyhow::Result<&str> {
206        self.runtime_types
207            .get(type_name.as_ref())
208            .map(|c| c.as_str())
209            .ok_or(BindError::UnknownType(type_name.as_ref().to_owned()).into())
210    }
211
212    fn to_array_m(&mut self, element: &ArrayM) -> anyhow::Result<Option<&str>> {
213        let tag = element.to_string();
214
215        // Returns exists runtime type
216        if self.components.contains_key(&tag) {
217            return Ok(self.components.get(&tag).map(|c| c.as_str()));
218        }
219
220        let el_type = { self.to_runtime_type(&element.element)?.map(|c| c.clone()) };
221
222        if el_type.is_none() {
223            return Ok(None);
224        }
225
226        let el_type = el_type.unwrap().to_owned();
227
228        let runtime_type = self.search_basic_type("array_m")?;
229
230        let m = element.m;
231
232        let declare_type = runtime_type
233            .replace("$el", &el_type)
234            .replace("$m", &m.to_string());
235
236        self.components.insert(tag.clone(), declare_type);
237
238        Ok(self.components.get(&tag).map(|c| c.as_str()))
239    }
240
241    fn to_bytes_m(&mut self, m: usize) -> anyhow::Result<&str> {
242        let tag = format!("array{}", m);
243
244        // Returns exists runtime type
245        if self.components.contains_key(&tag) {
246            return Ok(self.components.get(&tag).unwrap());
247        }
248
249        let runtime_type = self.search_basic_type("bytes_m")?;
250
251        let declare_type = runtime_type.replace("$m", &m.to_string());
252
253        self.components.insert(tag.clone(), declare_type);
254
255        Ok(self.components.get(&tag).unwrap())
256    }
257
258    fn to_array(&mut self, element: &Array) -> anyhow::Result<Option<&str>> {
259        let tag = element.to_string();
260
261        // Returns exists runtime type
262        if self.components.contains_key(&tag) {
263            return Ok(self.components.get(&tag).map(|c| c.as_str()));
264        }
265
266        let el_type = { self.to_runtime_type(&element.element)?.map(|c| c.clone()) };
267
268        if el_type.is_none() {
269            return Ok(None);
270        }
271
272        let el_type = el_type.unwrap().to_owned();
273
274        let runtime_type = { self.search_basic_type("array")? };
275
276        let declare_type = runtime_type.replace("$el", &el_type);
277
278        self.components.insert(tag.clone(), declare_type);
279
280        Ok(self.components.get(&tag).map(|c| c.as_str()))
281    }
282
283    fn to_fixed_m_n(&mut self, fixed: &FixedMN) -> anyhow::Result<&str> {
284        let tag = fixed.to_string();
285
286        // Returns exists runtime type
287        if self.components.contains_key(&tag) {
288            return Ok(self.components.get(&tag).unwrap());
289        }
290
291        let runtime_type = self.search_basic_type("fixed_m_n")?;
292
293        let declare_type = runtime_type
294            .replace("$m", &fixed.m.to_string())
295            .replace("$n", &fixed.n.to_string());
296
297        self.components.insert(tag.clone(), declare_type);
298
299        Ok(self.components.get(&tag).unwrap())
300    }
301
302    fn to_integer_m(&mut self, integer_m: &IntegerM) -> anyhow::Result<&str> {
303        let tag = integer_m.to_string();
304
305        // Returns exists runtime type
306        if self.components.contains_key(&tag) {
307            return Ok(self.components.get(&tag).unwrap());
308        }
309
310        let runtime_type = if integer_m.signed {
311            self.search_basic_type("int_m")?
312        } else {
313            self.search_basic_type("uint_m")?
314        };
315
316        let declare_type = runtime_type.replace("$m", &integer_m.m.to_string());
317
318        self.components.insert(tag.clone(), declare_type);
319
320        Ok(self.components.get(&tag).unwrap())
321    }
322}
323
324impl RuntimeBinder for JsonRuntimeBinder {
325    fn to_runtime_type(&mut self, r#type: &Type) -> anyhow::Result<Option<&str>> {
326        match r#type {
327            Type::Simple(element) if element.is_tuple() => Ok(None),
328            Type::Simple(element) => self.search_basic_type(element.to_string()).map(|c| Some(c)),
329            Type::ArrayM(element) => self.to_array_m(&element),
330            Type::Array(element) => self.to_array(&element),
331            Type::BytesM(element) => self.to_bytes_m(element.m).map(|c| Some(c)),
332            Type::FixedMN(element) => self.to_fixed_m_n(element).map(|c| Some(c)),
333            Type::IntegerM(element) => self.to_integer_m(element).map(|c| Some(c)),
334        }
335    }
336
337    fn get(&mut self, name: &str) -> anyhow::Result<&str> {
338        Ok(self
339            .runtime_types
340            .get(name)
341            .ok_or(BindError::UnknownType(name.to_string()))?)
342    }
343}
344
345/// `Ethbind` executor
346pub struct Executor<L, R> {
347    generator: L,
348    runtime_binder: R,
349}
350
351impl<L, R> Context for Executor<L, R>
352where
353    R: RuntimeBinder,
354    L: Generator,
355{
356    type Language = L;
357    type Runtime = R;
358
359    fn get_mut(&mut self) -> (&mut Self::Language, &mut Self::Runtime) {
360        (&mut self.generator, &mut self.runtime_binder)
361    }
362
363    fn finalize(self) -> (Self::Language, Self::Runtime) {
364        (self.generator, self.runtime_binder)
365    }
366}
367
368impl<L, R> From<(L, R)> for Executor<L, R>
369where
370    R: RuntimeBinder,
371    L: Generator,
372{
373    fn from(value: (L, R)) -> Self {
374        Executor {
375            generator: value.0,
376            runtime_binder: value.1,
377        }
378    }
379}
380
381pub struct BindingBuilder<C: Context> {
382    context: C,
383    builders: Vec<Box<dyn Fn(&mut C) -> anyhow::Result<()>>>,
384}
385
386impl<C: Context + Default> Default for BindingBuilder<C> {
387    fn default() -> Self {
388        Self {
389            context: Default::default(),
390            builders: Default::default(),
391        }
392    }
393}
394
395impl<C: Context> BindingBuilder<C> {
396    /// Create new binder builder with providing [`generator`](Generator)
397    pub fn new<C1: Into<C>>(context: C1) -> Self {
398        Self {
399            context: context.into(),
400            builders: Default::default(),
401        }
402    }
403
404    /// Generate binding codes with contract/abi data
405    pub fn bind<S: AsRef<str> + 'static, CN: AsRef<str>>(
406        mut self,
407        contract_name: CN,
408        contract: S,
409    ) -> Self {
410        let contract_name = contract_name.as_ref().to_string();
411
412        self.builders.push(Box::new(move |c| {
413            let fields: Vec<AbiField> = serde_json::from_str(contract.as_ref())?;
414
415            {
416                let (generator, runtime_binder) = c.get_mut();
417
418                generator.begin(runtime_binder, &contract_name)?;
419            }
420
421            fields.generate(c)?;
422
423            Ok(())
424        }));
425
426        self
427    }
428
429    /// Generate binding codes with hardhat artifact data
430    pub fn bind_hardhat<S: AsRef<str> + 'static>(mut self, contract: S) -> Self {
431        self.builders.push(Box::new(move |c| {
432            let fields: HardhatArtifact = serde_json::from_str(contract.as_ref())?;
433
434            {
435                let (generator, runtime_binder) = c.get_mut();
436
437                generator.begin(runtime_binder, &fields.contract_name)?;
438            }
439
440            fields.generate(c)?;
441
442            Ok(())
443        }));
444
445        self
446    }
447
448    /// Generate binding codes with contract/abi file path
449    pub fn bind_file<P: AsRef<Path> + 'static, CN: AsRef<str>>(
450        mut self,
451        contract_name: CN,
452        path: P,
453    ) -> Self {
454        let contract_name = contract_name.as_ref().to_string();
455
456        self.builders.push(Box::new(move |c| {
457            let contract = read_to_string(&path)?;
458
459            let fields: Vec<AbiField> = serde_json::from_str(contract.as_ref())?;
460
461            {
462                let (generator, runtime_binder) = c.get_mut();
463
464                generator.begin(runtime_binder, &contract_name)?;
465            }
466
467            fields.generate(c)?;
468
469            Ok(())
470        }));
471
472        self
473    }
474
475    /// Generate binding codes with hardhat artifact file path
476    pub fn bind_hardhat_file<P: AsRef<Path> + 'static>(mut self, path: P) -> Self {
477        self.builders.push(Box::new(move |c| {
478            let contract = read_to_string(&path)?;
479
480            let fields: HardhatArtifact = serde_json::from_str(contract.as_ref())?;
481
482            {
483                let (generator, runtime_binder) = c.get_mut();
484
485                generator.begin(runtime_binder, &fields.contract_name)?;
486            }
487
488            fields.generate(c)?;
489
490            Ok(())
491        }));
492
493        self
494    }
495
496    /// Retrieve [`result`](Generator) and consume binding builder instance.
497    pub fn finalize(mut self) -> anyhow::Result<Vec<Contract>> {
498        for builder in self.builders {
499            builder(&mut self.context)?;
500        }
501
502        let (generator, mut runtime_binder) = self.context.finalize();
503
504        Ok(generator.finalize(&mut runtime_binder)?)
505    }
506}
507
508impl Generatable for HardhatArtifact {
509    fn generate<C: Context>(&self, context: &mut C) -> anyhow::Result<()> {
510        self.abi.generate(context)?;
511
512        for abi in &self.abi {
513            match abi {
514                AbiField::Constructor(contructor) => {
515                    // Generate deploy fn
516                    let (generator, runtime_binder) = context.get_mut();
517
518                    generator.generate_deploy(runtime_binder, contructor, &self.bytecode)?;
519                }
520                _ => {}
521            }
522        }
523
524        Ok(())
525    }
526}
527
528impl Generatable for Vec<AbiField> {
529    fn generate<C: Context>(&self, context: &mut C) -> anyhow::Result<()> {
530        let (generator, runtime_binder) = context.get_mut();
531
532        for abi in self {
533            match abi {
534                AbiField::Function(function) => generator.generate_fn(runtime_binder, function)?,
535                AbiField::Event(event) => generator.generate_event(runtime_binder, event)?,
536                AbiField::Error(error) => generator.generate_error(runtime_binder, error)?,
537                _ => {
538                    // Skip generate codes for constructor/receive/fallback.
539                    // - Call `Generator::generate_deploy` for [`HardhatArtifact`]'s trait `Generate` to generate the constructor's binding code.
540                    // - It seems that end users do not directly call receive/fallback api, so we skip their generation step.
541                }
542            }
543        }
544
545        Ok(())
546    }
547}
548
549#[cfg(test)]
550mod tests {
551    use ethbind_json::Type;
552
553    use crate::{JsonRuntimeBinder, RuntimeBinder};
554
555    #[test]
556    fn test_json_runtime_binder() {
557        let mut runtime_binder: JsonRuntimeBinder =
558            include_str!("../../rust/macros/tests/mapping.json")
559                .parse()
560                .expect("Load abi");
561
562        let t: Type = "uint256".parse().expect("Parse type string");
563
564        let runtime_type = runtime_binder
565            .to_runtime_type(&t)
566            .expect("Get runtime type")
567            .expect("Is not tuple type");
568
569        assert_eq!(runtime_type, "mock::Int<false,256>");
570
571        let t: Type = "fixed128x8".parse().expect("Parse type string");
572
573        let runtime_type = runtime_binder
574            .to_runtime_type(&t)
575            .expect("Get runtime type")
576            .expect("Is not tuple type");
577
578        assert_eq!(runtime_type, "mock::Fixed<true,128,8>");
579
580        let t: Type = "uint256[20]".parse().expect("Parse type string");
581
582        let runtime_type = runtime_binder
583            .to_runtime_type(&t)
584            .expect("Get runtime type")
585            .expect("Is not tuple type");
586
587        assert_eq!(runtime_type, "[mock::Int<false,256>;20]");
588
589        let t: Type = "uint256[]".parse().expect("Parse type string");
590
591        let runtime_type = runtime_binder
592            .to_runtime_type(&t)
593            .expect("Get runtime type")
594            .expect("Is not tuple type");
595
596        assert_eq!(runtime_type, "Vec<mock::Int<false,256>>");
597
598        let t: Type = "bytes24".parse().expect("Parse type string");
599
600        let runtime_type = runtime_binder
601            .to_runtime_type(&t)
602            .expect("Get runtime type")
603            .expect("Is not tuple type");
604
605        assert_eq!(runtime_type, "[u8;24]");
606
607        let t: Type = "address".parse().expect("Parse type string");
608
609        let runtime_type = runtime_binder
610            .to_runtime_type(&t)
611            .expect("Get runtime type")
612            .expect("Is not tuple type");
613
614        assert_eq!(runtime_type, "mock::Address");
615    }
616}