1use crate::rpc::{
6 errors::{invalid_params, invalid_params_check},
7 types::{
8 address::RpcAddress,
9 cfx::{
10 check_rpc_address_network, check_two_rpc_address_network_match,
11 to_primitive_access_list, CfxAccessList,
12 },
13 Bytes,
14 },
15 CoreResult,
16};
17use cfx_addr::Network;
18use cfx_parameters::{
19 block::{
20 CIP1559_CORE_TRANSACTION_GAS_RATIO, DEFAULT_TARGET_BLOCK_GAS_LIMIT,
21 },
22 RATIO_BASE_TEN,
23};
24use cfx_types::{Address, AddressSpaceUtil, U256, U64};
25use cfx_util_macros::bail;
26use cfxcore_accounts::AccountProvider;
27use cfxkey::Password;
28use primitives::{
29 transaction::{
30 Action, Cip1559Transaction, Cip2930Transaction, NativeTransaction,
31 TypedNativeTransaction::*, CIP1559_TYPE, CIP2930_TYPE, LEGACY_TX_TYPE,
32 },
33 SignedTransaction, Transaction, TransactionWithSignature,
34};
35use serde::{Deserialize, Serialize};
36use std::{convert::Into, sync::Arc};
37
38pub const DEFAULT_CFX_GAS_CALL_REQUEST: u64 = DEFAULT_TARGET_BLOCK_GAS_LIMIT
40 * CIP1559_CORE_TRANSACTION_GAS_RATIO
41 / RATIO_BASE_TEN;
42
43#[derive(Debug, Default, Deserialize, PartialEq, Serialize, Clone)]
44#[serde(rename_all = "camelCase")]
45pub struct TransactionRequest {
46 pub from: Option<RpcAddress>,
48 pub to: Option<RpcAddress>,
50 pub gas_price: Option<U256>,
52 pub gas: Option<U256>,
54 pub value: Option<U256>,
56 pub data: Option<Bytes>,
58 pub nonce: Option<U256>,
60 pub storage_limit: Option<U64>,
62 pub access_list: Option<CfxAccessList>,
64 pub max_fee_per_gas: Option<U256>,
65 pub max_priority_fee_per_gas: Option<U256>,
66 #[serde(rename = "type")]
67 pub transaction_type: Option<U64>,
68 pub chain_id: Option<U256>,
70 pub epoch_height: Option<U256>,
72}
73
74#[derive(Debug, Default, PartialEq, Deserialize, Serialize)]
75#[serde(rename_all = "camelCase")]
76pub struct EstimateGasAndCollateralResponse {
77 pub gas_limit: U256,
79 pub gas_used: U256,
81 pub storage_collateralized: U64,
83}
84
85#[derive(Debug, Default, PartialEq, Deserialize, Serialize)]
86#[serde(rename_all = "camelCase")]
87pub struct CheckBalanceAgainstTransactionResponse {
88 pub will_pay_tx_fee: bool,
90 pub will_pay_collateral: bool,
92 pub is_balance_enough: bool,
94}
95
96impl TransactionRequest {
97 pub fn check_rpc_address_network(
98 &self, param_name: &str, expected: &Network,
99 ) -> CoreResult<()> {
100 let rpc_request_network = invalid_params_check(
101 param_name,
102 check_two_rpc_address_network_match(
103 self.from.as_ref(),
104 self.to.as_ref(),
105 ),
106 )?;
107 invalid_params_check(
108 param_name,
109 check_rpc_address_network(rpc_request_network, expected),
110 )
111 .map_err(|e| e.into())
112 }
113
114 pub fn transaction_type(&self) -> u8 {
115 if let Some(tx_type) = self.transaction_type {
116 tx_type.as_usize() as u8
117 } else {
118 if self.max_fee_per_gas.is_some()
119 || self.max_priority_fee_per_gas.is_some()
120 {
121 CIP1559_TYPE
122 } else if self.access_list.is_some() {
123 CIP2930_TYPE
124 } else {
125 LEGACY_TX_TYPE
126 }
127 }
128 }
129
130 pub fn has_gas_price(&self) -> bool {
131 self.gas_price.is_some()
132 || self.max_fee_per_gas.is_some()
133 || self.max_priority_fee_per_gas.is_some()
134 }
135
136 pub fn sign_with(
137 self, epoch_height: u64, chain_id: u32, password: Option<String>,
138 accounts: Arc<AccountProvider>,
139 ) -> CoreResult<TransactionWithSignature> {
140 let gas = self.gas.ok_or("should have gas")?;
141 let nonce = self.nonce.ok_or("should have nonce")?;
142 let transaction_type = self.transaction_type();
143 let action = self.to.map_or(Action::Create, |rpc_addr| {
144 Action::Call(rpc_addr.hex_address)
145 });
146
147 let value = self.value.unwrap_or_default();
148 let storage_limit = self
149 .storage_limit
150 .map(|v| v.as_u64())
151 .ok_or("should have storage_limit")?;
152 let data = self.data.unwrap_or_default().into_vec();
153
154 let access_list = self.access_list.unwrap_or(vec![]);
155
156 let typed_native_tx = match transaction_type {
157 LEGACY_TX_TYPE => {
158 let gas_price =
159 self.gas_price.ok_or("should have gas_price")?;
160 Cip155(NativeTransaction {
161 nonce,
162 action,
163 gas,
164 gas_price,
165 value,
166 storage_limit,
167 epoch_height,
168 chain_id,
169 data,
170 })
171 }
172 CIP2930_TYPE => {
173 let gas_price =
174 self.gas_price.ok_or("should have gas_price")?;
175 Cip2930(Cip2930Transaction {
176 nonce,
177 gas_price,
178 gas,
179 action,
180 value,
181 storage_limit,
182 epoch_height,
183 chain_id,
184 data,
185 access_list: to_primitive_access_list(access_list),
186 })
187 }
188 CIP1559_TYPE => {
189 let max_fee_per_gas = self
190 .max_fee_per_gas
191 .ok_or("should have max_fee_per_gas")?;
192 let max_priority_fee_per_gas = self
193 .max_priority_fee_per_gas
194 .ok_or("should have max_priority_fee_per_gas")?;
195 Cip1559(Cip1559Transaction {
196 nonce,
197 action,
198 gas,
199 value,
200 max_fee_per_gas,
201 max_priority_fee_per_gas,
202 storage_limit,
203 epoch_height,
204 chain_id,
205 data,
206 access_list: to_primitive_access_list(access_list),
207 })
208 }
209 x => {
211 return Err(
212 invalid_params("Unrecognized transaction type", x).into()
213 );
214 }
215 };
216
217 let tx = Transaction::Native(typed_native_tx);
218 let password = password.map(Password::from);
219 let sig = accounts
220 .sign(
221 self.from.unwrap().into(),
222 password,
223 tx.hash_for_compute_signature(),
224 )
225 .map_err(|e| format!("failed to sign transaction: {:?}", e))?;
227
228 Ok(tx.with_signature(sig))
229 }
230
231 pub fn sign_call(
232 self, epoch_height: u64, chain_id: u32, max_gas: Option<U256>,
233 ) -> CoreResult<SignedTransaction> {
234 let max_gas = max_gas.unwrap_or(DEFAULT_CFX_GAS_CALL_REQUEST.into());
235 let gas = self.gas.unwrap_or(max_gas);
236 if gas > max_gas {
237 bail!(invalid_params(
238 "gas",
239 format!("specified gas is larger than max gas {:?}", max_gas)
240 ))
241 }
242 let transaction_type = self.transaction_type();
243 let nonce = self.nonce.unwrap_or_default();
244 let action = self.to.map_or(Action::Create, |rpc_addr| {
245 Action::Call(rpc_addr.hex_address)
246 });
247
248 let value = self.value.unwrap_or_default();
249 let storage_limit = self
250 .storage_limit
251 .map(|v| v.as_u64())
252 .unwrap_or(std::u64::MAX);
253 let data = self.data.unwrap_or_default().into_vec();
254
255 let gas_price = self.gas_price.unwrap_or(1.into());
256 let max_fee_per_gas = self
257 .max_fee_per_gas
258 .or(self.max_priority_fee_per_gas)
259 .unwrap_or(gas_price);
260 let max_priority_fee_per_gas =
261 self.max_priority_fee_per_gas.unwrap_or(U256::zero());
262 let access_list = self.access_list.unwrap_or(vec![]);
263
264 let transaction = match transaction_type {
265 LEGACY_TX_TYPE => Cip155(NativeTransaction {
266 nonce,
267 action,
268 gas,
269 gas_price,
270 value,
271 storage_limit,
272 epoch_height,
273 chain_id,
274 data,
275 }),
276 CIP2930_TYPE => Cip2930(Cip2930Transaction {
277 nonce,
278 gas_price,
279 gas,
280 action,
281 value,
282 storage_limit,
283 epoch_height,
284 chain_id,
285 data,
286 access_list: to_primitive_access_list(access_list),
287 }),
288 CIP1559_TYPE => Cip1559(Cip1559Transaction {
289 nonce,
290 action,
291 gas,
292 value,
293 max_fee_per_gas,
294 max_priority_fee_per_gas,
295 storage_limit,
296 epoch_height,
297 chain_id,
298 data,
299 access_list: to_primitive_access_list(access_list),
300 }),
301 x => {
303 return Err(
304 invalid_params("Unrecognized transaction type", x).into()
305 );
306 }
307 };
308
309 let from = self
310 .from
311 .map_or_else(|| Address::zero(), |rpc_addr| rpc_addr.hex_address);
312
313 Ok(transaction.fake_sign_rpc(from.with_native_space()))
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use super::TransactionRequest;
320
321 use crate::rpc::types::address::RpcAddress;
322 use cfx_addr::Network;
323 use cfx_types::{H160, U256, U64};
324 use rustc_hex::FromHex;
325 use serde_json;
326 use std::str::FromStr;
327
328 #[test]
329 fn call_request_deserialize() {
330 let expected = TransactionRequest {
331 from: Some(
332 RpcAddress::try_from_h160(
333 H160::from_low_u64_be(1),
334 Network::Main,
335 )
336 .unwrap(),
337 ),
338 to: Some(
339 RpcAddress::try_from_h160(
340 H160::from_low_u64_be(2),
341 Network::Main,
342 )
343 .unwrap(),
344 ),
345 gas_price: Some(U256::from(1)),
346 gas: Some(U256::from(2)),
347 value: Some(U256::from(3)),
348 data: Some(vec![0x12, 0x34, 0x56].into()),
349 storage_limit: Some(U64::from_str("7b").unwrap()),
350 nonce: Some(U256::from(4)),
351 ..Default::default()
352 };
353
354 let s = r#"{
355 "from":"CFX:TYPE.BUILTIN:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEJC4EYEY6",
356 "to":"CFX:TYPE.BUILTIN:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJD0WN6U9U",
357 "gasPrice":"0x1",
358 "gas":"0x2",
359 "value":"0x3",
360 "data":"0x123456",
361 "storageLimit":"0x7b",
362 "nonce":"0x4"
363 }"#;
364 let deserialized_result = serde_json::from_str::<TransactionRequest>(s);
365 assert!(
366 deserialized_result.is_ok(),
367 "serialized str should look like {}",
368 serde_json::to_string(&expected).unwrap()
369 );
370 assert_eq!(deserialized_result.unwrap(), expected);
371 }
372
373 #[test]
374 fn call_request_deserialize2() {
375 let expected = TransactionRequest {
376 from: Some(RpcAddress::try_from_h160(H160::from_str("160e8dd61c5d32be8058bb8eb970870f07233155").unwrap(), Network::Main ).unwrap()),
377 to: Some(RpcAddress::try_from_h160(H160::from_str("846e8dd67c5d32be8058bb8eb970870f07244567").unwrap(), Network::Main).unwrap()),
378 gas_price: Some(U256::from_str("9184e72a000").unwrap()),
379 gas: Some(U256::from_str("76c0").unwrap()),
380 value: Some(U256::from_str("9184e72a").unwrap()),
381 storage_limit: Some(U64::from_str("3344adf").unwrap()),
382 data: Some("d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675".from_hex::<Vec<u8>>().unwrap().into()),
383 nonce: None,
384 ..Default::default()
385 };
386
387 let s = r#"{
388 "from": "CFX:TYPE.USER:AANA7DS0DVSXFTYANC727SNUU6HUSJ3VMYC3F1AY93",
389 "to": "CFX:TYPE.CONTRACT:ACCG7DS0TVSXFTYANC727SNUU6HUSKCFP6KB3NFJ02",
390 "gas": "0x76c0",
391 "gasPrice": "0x9184e72a000",
392 "value": "0x9184e72a",
393 "storageLimit":"0x3344adf",
394 "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"
395 }"#;
396 let deserialized_result = serde_json::from_str::<TransactionRequest>(s);
397 assert!(
398 deserialized_result.is_ok(),
399 "serialized str should look like {}",
400 serde_json::to_string(&expected).unwrap()
401 );
402 assert_eq!(deserialized_result.unwrap(), expected);
403 }
404
405 #[test]
406 fn call_request_deserialize_empty() {
407 let expected = TransactionRequest {
408 from: Some(
409 RpcAddress::try_from_h160(
410 H160::from_low_u64_be(1),
411 Network::Main,
412 )
413 .unwrap(),
414 ),
415 to: None,
416 gas_price: None,
417 gas: None,
418 value: None,
419 data: None,
420 storage_limit: None,
421 nonce: None,
422 ..Default::default()
423 };
424
425 let s = r#"{"from":"CFX:TYPE.BUILTIN:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEJC4EYEY6"}"#;
426 let deserialized_result = serde_json::from_str::<TransactionRequest>(s);
427 assert!(
428 deserialized_result.is_ok(),
429 "serialized str should look like {}",
430 serde_json::to_string(&expected).unwrap()
431 );
432 assert_eq!(deserialized_result.unwrap(), expected);
433 }
434}