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
//! Defines a set of serializable types required for the Fuel VM ABI.

use serde::{Deserialize, Serialize};

/// FuelVM 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 and related tooling to convert an ABI
/// representation into native Rust structs and vice-versa.
#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProgramABI {
    pub program_type: String,
    pub spec_version: Version,
    pub encoding_version: Version,
    pub concrete_types: Vec<TypeConcreteDeclaration>,
    pub metadata_types: Vec<TypeMetadataDeclaration>,
    pub functions: Vec<ABIFunction>,
    pub logged_types: Option<Vec<LoggedType>>,
    pub messages_types: Option<Vec<MessageType>>,
    pub configurables: Option<Vec<Configurable>>,
}

#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Version(pub String);

impl From<&str> for Version {
    fn from(value: &str) -> Self {
        Version(value.into())
    }
}

impl Version {
    pub fn major(&self) -> Option<&str> {
        let s = self.0.split('.').next().map(|x| x.trim());
        match s {
            Some("") => None,
            s => s,
        }
    }

    pub fn minor(&self) -> Option<&str> {
        let s = self.0.split('.').nth(1).map(|x| x.trim());
        match s {
            Some("") => None,
            s => s,
        }
    }
}

#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub struct ConcreteTypeId(pub String);

impl From<&str> for ConcreteTypeId {
    fn from(value: &str) -> Self {
        ConcreteTypeId(value.into())
    }
}

#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub struct MetadataTypeId(pub usize);

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[serde(untagged)]
pub enum TypeId {
    Concrete(ConcreteTypeId),
    Metadata(MetadataTypeId),
}

impl Default for TypeId {
    fn default() -> Self {
        TypeId::Metadata(MetadataTypeId(usize::MAX))
    }
}

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

impl ABIFunction {
    pub fn is_payable(&self) -> bool {
        self.attributes
            .iter()
            .flatten()
            .any(|attr| attr.name == "payable")
    }
}

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

#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TypeConcreteDeclaration {
    #[serde(rename = "type")]
    pub type_field: String,
    pub concrete_type_id: ConcreteTypeId,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata_type_id: Option<MetadataTypeId>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub type_arguments: Option<Vec<ConcreteTypeId>>,
}

#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TypeConcreteParameter {
    pub name: String,
    pub concrete_type_id: ConcreteTypeId,
}

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

#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LoggedType {
    pub log_id: String,
    pub concrete_type_id: ConcreteTypeId,
}

#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MessageType {
    pub message_id: String,
    pub concrete_type_id: ConcreteTypeId,
}

#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Configurable {
    pub name: String,
    pub concrete_type_id: ConcreteTypeId,
    pub offset: u64,
}

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

#[test]
fn version_extraction_test() {
    let v = Version("1.2".to_string());
    assert_eq!(v.major(), Some("1"));
    assert_eq!(v.minor(), Some("2"));

    let v = Version("1".to_string());
    assert_eq!(v.major(), Some("1"));
    assert_eq!(v.minor(), None);

    let v = Version("".to_string());
    assert_eq!(v.major(), None);
    assert_eq!(v.minor(), None);
}