waves_rust/model/transaction/
ethereum_transaction.rs1use 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#[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
238fn 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}