1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
//! Defines a set of serializable types required for the Fuel VM ABI.
//!
//! We declare these in a dedicated, minimal crate in order to allow for downstream projects to
//! consume or generate these ABI-compatible types without needing to pull in the rest of the SDK.

use std::collections::HashMap;

use serde::{Deserialize, Serialize};
use strum_macros::ToString;
use utils::{has_array_format, has_tuple_format};

pub mod bech32;
pub mod constants;
pub mod errors;
pub mod function_selector;
pub mod param_types;
pub mod parse_param;
pub mod utils;

/// Fuel ABI representation in JSON, originally specified here:
///
/// https://github.com/FuelLabs/fuel-specs/blob/master/specs/protocol/abi.md
///
/// This type may be used by compilers (e.g. Sway) and related tooling to convert an ABI
/// representation into native Rust structs and vice-versa.
pub type JsonABI = Vec<Function>;

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Function {
    #[serde(rename = "type")]
    pub type_field: String,
    pub inputs: Vec<Property>,
    pub name: String,
    pub outputs: Vec<Property>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Property {
    pub name: String,
    #[serde(rename = "type")]
    pub type_field: String,
    pub components: Option<Vec<Property>>, // Used for custom types
}

// Both those constants are used to determine if a type field represents an `Enum` or a `Struct`.
// Since it would have the format `struct foo` or `enum bar`, there is a whitespace.
pub const STRUCT_KEYWORD: &str = "struct ";
pub const ENUM_KEYWORD: &str = "enum ";

impl Property {
    pub fn is_enum_type(&self) -> bool {
        self.type_field.starts_with(ENUM_KEYWORD)
    }
    pub fn is_struct_type(&self) -> bool {
        self.type_field.starts_with(STRUCT_KEYWORD)
    }
    pub fn is_custom_type(&self) -> bool {
        self.is_enum_type()
            || self.is_struct_type()
            || self.has_custom_type_in_array()
            || self.has_custom_type_in_tuple()
    }

    pub fn has_custom_type_in_array(&self) -> bool {
        if self.type_field.starts_with('[') && self.type_field.ends_with(']') {
            return self.get_custom_type().is_some();
        }
        false
    }

    pub fn has_custom_type_in_tuple(&self) -> bool {
        if self.type_field.starts_with('(') && self.type_field.ends_with(')') {
            return self.get_custom_type().is_some();
        }
        false
    }

    pub fn get_custom_type(&self) -> Option<CustomType> {
        if self.type_field.contains(STRUCT_KEYWORD) {
            Some(CustomType::Struct)
        } else if self.type_field.contains(ENUM_KEYWORD) {
            Some(CustomType::Enum)
        } else {
            None
        }
    }
}

#[derive(Debug, Clone, Copy, ToString, PartialEq, Eq)]
#[strum(serialize_all = "lowercase")]
pub enum CustomType {
    Struct,
    Enum,
}

// New ABI file format. For now, we'll keep both this `ProgramABI` and the old
// `JsonABI` around, but eventually we'll want to remove the old one.

/// Flat JSON ABI
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProgramABI {
    pub types: Vec<TypeDeclaration>,
    pub functions: Vec<ABIFunction>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ABIFunction {
    pub inputs: Vec<TypeApplication>,
    pub name: String,
    pub output: TypeApplication,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TypeDeclaration {
    pub type_id: usize,
    #[serde(rename = "type")]
    pub type_field: String,
    pub components: Option<Vec<TypeApplication>>, // Used for custom types
    pub type_parameters: Option<Vec<usize>>,
}

#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TypeApplication {
    pub name: String,
    #[serde(rename = "type")]
    pub type_field: usize,
    pub type_arguments: Option<Vec<usize>>,
}

impl TypeDeclaration {
    pub fn is_enum_type(&self) -> bool {
        self.type_field.starts_with(ENUM_KEYWORD)
    }
    pub fn is_struct_type(&self) -> bool {
        self.type_field.starts_with(STRUCT_KEYWORD)
    }
    pub fn is_custom_type(&self, types: &HashMap<usize, TypeDeclaration>) -> bool {
        self.is_enum_type()
            || self.is_struct_type()
            || self.has_custom_type_in_array(types)
            || self.has_custom_type_in_tuple(types)
    }

    pub fn has_custom_type_in_array(&self, types: &HashMap<usize, TypeDeclaration>) -> bool {
        if has_array_format(&self.type_field) {
            // For each component in the tuple, check if it is a custom type
            for component in self.components.as_ref().unwrap() {
                let component_type = types.get(&component.type_field).unwrap();
                if component_type.is_custom_type(types) {
                    return true;
                }
            }

            return self.get_custom_type().is_some();
        }
        false
    }

    pub fn has_custom_type_in_tuple(&self, types: &HashMap<usize, TypeDeclaration>) -> bool {
        if has_tuple_format(&self.type_field) {
            // For each component in the tuple, check if it is a custom type
            for component in self.components.as_ref().unwrap() {
                let component_type = types.get(&component.type_field).unwrap();
                if component_type.is_custom_type(types) {
                    return true;
                }
            }
        }
        false
    }

    pub fn get_custom_type(&self) -> Option<CustomType> {
        if self.type_field.contains(STRUCT_KEYWORD) {
            Some(CustomType::Struct)
        } else if self.type_field.contains(ENUM_KEYWORD) {
            Some(CustomType::Enum)
        } else {
            None
        }
    }
}