waves_rust/model/transaction/
ethereum_transaction.rs

1use crate::error::Error::UnsupportedOperation;
2use crate::error::{Error, Result};
3use crate::model::{Address, Amount, AssetId, ByteString, Function, StateChanges};
4use crate::util::JsonDeserializer;
5use serde_json::Value;
6use std::borrow::Borrow;
7use std::fmt;
8
9const TYPE: u8 = 18;
10
11#[derive(Clone, Eq, PartialEq, Debug)]
12pub struct EthereumTransactionInfo {
13    bytes: HexString,
14    payload: Payload,
15}
16
17impl EthereumTransactionInfo {
18    pub fn new(bytes: HexString, payload: Payload) -> Self {
19        Self { bytes, payload }
20    }
21
22    pub fn bytes(&self) -> HexString {
23        self.bytes.clone()
24    }
25
26    pub fn payload(&self) -> Payload {
27        self.payload.clone()
28    }
29
30    pub fn tx_type() -> u8 {
31        TYPE
32    }
33}
34
35impl TryFrom<&Value> for EthereumTransactionInfo {
36    type Error = Error;
37
38    fn try_from(value: &Value) -> Result<Self> {
39        let payload = value["payload"].borrow().try_into()?;
40        let bytes = HexString::new(hex::decode(
41            &JsonDeserializer::safe_to_string_from_field(value, "bytes")?[2..],
42        )?);
43        Ok(EthereumTransactionInfo { bytes, payload })
44    }
45}
46
47#[derive(Clone, Eq, PartialEq, Debug)]
48pub struct EthereumTransaction {
49    bytes: HexString,
50}
51
52impl EthereumTransaction {
53    pub fn new(bytes: HexString) -> Self {
54        Self { bytes }
55    }
56
57    pub fn bytes(&self) -> HexString {
58        self.bytes.clone()
59    }
60
61    pub fn tx_type() -> u8 {
62        TYPE
63    }
64}
65
66impl TryFrom<&Value> for EthereumTransaction {
67    type Error = Error;
68
69    fn try_from(value: &Value) -> Result<Self> {
70        let bytes = HexString::new(hex::decode(
71            &JsonDeserializer::safe_to_string_from_field(value, "bytes")?[2..],
72        )?);
73        Ok(EthereumTransaction { bytes })
74    }
75}
76
77#[derive(Clone, Eq, PartialEq, Debug)]
78//todo fix it
79#[allow(clippy::large_enum_variant)]
80pub enum Payload {
81    Invoke(InvokePayload),
82    Transfer(TransferPayload),
83}
84
85impl TryFrom<&Value> for Payload {
86    type Error = Error;
87
88    fn try_from(value: &Value) -> Result<Self> {
89        match JsonDeserializer::safe_to_string_from_field(value, "type")?.as_str() {
90            "invocation" => {
91                let invoke: InvokePayload = value.try_into()?;
92                Ok(Payload::Invoke(invoke))
93            }
94            "transfer" => {
95                let transfer: TransferPayload = value.try_into()?;
96                Ok(Payload::Transfer(transfer))
97            }
98            _ => Err(UnsupportedOperation("unknown payload type".to_owned())),
99        }
100    }
101}
102
103#[derive(Clone, Eq, PartialEq, Debug)]
104pub struct TransferPayload {
105    recipient: Address,
106    amount: Amount,
107}
108
109impl TransferPayload {
110    pub fn new(recipient: Address, amount: Amount) -> Self {
111        Self { recipient, amount }
112    }
113
114    pub fn recipient(&self) -> Address {
115        self.recipient.clone()
116    }
117
118    pub fn amount(&self) -> Amount {
119        self.amount.clone()
120    }
121}
122
123impl TryFrom<&Value> for TransferPayload {
124    type Error = Error;
125
126    fn try_from(value: &Value) -> Result<Self> {
127        let recipient = JsonDeserializer::safe_to_string_from_field(value, "recipient")?;
128        let asset = match value["asset"].as_str() {
129            Some(asset_id) => Some(AssetId::from_string(asset_id)?),
130            None => None,
131        };
132        let amount = JsonDeserializer::safe_to_int_from_field(value, "amount")?;
133
134        Ok(TransferPayload {
135            recipient: Address::from_string(&recipient)?,
136            amount: Amount::new(amount as u64, asset),
137        })
138    }
139}
140
141#[derive(Clone, Eq, PartialEq, Debug)]
142pub struct InvokePayload {
143    dapp: Address,
144    function: Function,
145    payments: Vec<Amount>,
146    state_changes: StateChanges,
147}
148
149impl InvokePayload {
150    pub fn new(
151        dapp: Address,
152        function: Function,
153        payments: Vec<Amount>,
154        state_changes: StateChanges,
155    ) -> Self {
156        Self {
157            dapp,
158            function,
159            payments,
160            state_changes,
161        }
162    }
163
164    pub fn dapp(&self) -> Address {
165        self.dapp.clone()
166    }
167
168    pub fn function(&self) -> Function {
169        self.function.clone()
170    }
171
172    pub fn payments(&self) -> Vec<Amount> {
173        self.payments.clone()
174    }
175
176    pub fn state_changes(&self) -> StateChanges {
177        self.state_changes.clone()
178    }
179}
180
181impl TryFrom<&Value> for InvokePayload {
182    type Error = Error;
183
184    fn try_from(value: &Value) -> Result<Self> {
185        let dapp = JsonDeserializer::safe_to_string_from_field(value, "dApp")?;
186        let function: Function = value.try_into()?;
187        let payments = map_payment(value)?;
188        let state_changes: StateChanges = value["stateChanges"].borrow().try_into()?;
189
190        Ok(InvokePayload {
191            dapp: Address::from_string(&dapp)?,
192            function,
193            payments,
194            state_changes,
195        })
196    }
197}
198
199#[derive(Clone, Eq, PartialEq, Hash)]
200pub struct HexString {
201    bytes: Vec<u8>,
202}
203
204impl fmt::Debug for HexString {
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        write!(f, "HexString {{ {} }}", self.encoded())
207    }
208}
209
210impl HexString {
211    pub fn new(bytes: Vec<u8>) -> Self {
212        Self { bytes }
213    }
214
215    pub fn encoded(&self) -> String {
216        hex::encode(self.bytes.clone())
217    }
218
219    pub fn bytes(&self) -> Vec<u8> {
220        self.bytes.clone()
221    }
222}
223
224impl ByteString for HexString {
225    fn bytes(&self) -> Vec<u8> {
226        self.bytes.clone()
227    }
228
229    fn encoded(&self) -> String {
230        hex::encode(self.bytes.clone())
231    }
232
233    fn encoded_with_prefix(&self) -> String {
234        format!("0x{}", self.encoded())
235    }
236}
237
238//todo rm duplicate
239fn map_payment(value: &Value) -> Result<Vec<Amount>> {
240    JsonDeserializer::safe_to_array_from_field(value, "payment")?
241        .iter()
242        .map(|payment| {
243            let value = JsonDeserializer::safe_to_int_from_field(payment, "amount")?;
244            let asset_id = match payment["assetId"].as_str() {
245                Some(asset) => Some(AssetId::from_string(asset)?),
246                None => None,
247            };
248            Ok(Amount::new(value as u64, asset_id))
249        })
250        .collect::<Result<Vec<Amount>>>()
251}
252
253#[cfg(test)]
254mod tests {
255    use crate::model::data_entry::DataEntry;
256    use crate::model::{Arg, ByteString, EthereumTransactionInfo, Payload};
257    use serde_json::Value;
258    use std::borrow::Borrow;
259    use std::fs;
260
261    const INVOKE_BYTES: &str = "0xf9011186017cac99be168502540be4008307a120940ea8e14f313237aac31995f9c19a7e0f78c1cc2b80b8a409abf90e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000e74657374206d6574616d61736b32000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000081c9a0dcc682194d46cd3a763b352ca77a4317e9d89f10e5213379b55563cbc03619f3a02a2f26c580ab9f3d83db801bf7d556dd50d37cd69b19df8ee4a3488a6c5140c8";
262
263    #[test]
264    fn test_json_to_eth_invoke_transaction() {
265        let data = fs::read_to_string("./tests/resources/ethereum_transaction_invoke_rs.json")
266            .expect("Unable to read file");
267        let json: Value = serde_json::from_str(&data).expect("failed to generate json from str");
268
269        let eth_invoke_from_json: EthereumTransactionInfo = json.borrow().try_into().unwrap();
270
271        assert_eq!(
272            INVOKE_BYTES[2..],
273            hex::encode(eth_invoke_from_json.bytes().bytes())
274        );
275
276        let invoke = match eth_invoke_from_json.payload() {
277            Payload::Invoke(invoke) => invoke,
278            Payload::Transfer(_) => panic!("expected invoke but was transfer"),
279        };
280
281        assert_eq!(
282            "3MRuzZVauiiX2DGwNyP8Tv7idDGUy1VG5bJ",
283            invoke.dapp().encoded()
284        );
285        assert_eq!("saveString", invoke.function().name());
286
287        match &invoke.function().args()[0] {
288            Arg::String(value) => {
289                assert_eq!("test metamask2", value)
290            }
291            _ => panic!("expected string arg"),
292        }
293
294        assert_eq!(1, invoke.payments().len());
295        let payment = &invoke.payments()[0];
296        assert_eq!(26434954086, payment.value());
297        assert_eq!(
298            "97zHFp1C3cB7qfvx8Xv5f2rWp9nUSG5UnAamfPcW6txf",
299            payment.asset_id().expect("should not be empty").encoded()
300        );
301
302        match &invoke.state_changes().data()[0] {
303            DataEntry::StringEntry { key, value } => {
304                assert_eq!("str_1043725", key);
305                assert_eq!("test metamask2", value);
306            }
307            _ => panic!("expected string entry"),
308        }
309    }
310
311    #[test]
312    fn test_json_to_eth_transfer_transaction() {
313        let data = fs::read_to_string("./tests/resources/ethereum_transaction_transfer_rs.json")
314            .expect("Unable to read file");
315        let json: Value = serde_json::from_str(&data).expect("failed to generate json from str");
316
317        let eth_invoke_from_json: EthereumTransactionInfo = json.borrow().try_into().unwrap();
318
319        let transfer = match eth_invoke_from_json.payload() {
320            Payload::Transfer(transfer) => transfer,
321            Payload::Invoke(_) => panic!("expected transfer but was invoke"),
322        };
323
324        assert_eq!(
325            "3MVeY7NhZciZLsnwb4E47moXVd9y4gKw8S7",
326            transfer.recipient().encoded()
327        );
328        assert_eq!(10000000, transfer.amount().value());
329        assert_eq!(None, transfer.amount().asset_id());
330    }
331}