1use crate::{
6 access_list::{from_primitive_access_list, CfxAccessList},
7 receipt::Receipt,
8 RpcAddress,
9};
10use cfx_addr::Network;
11use cfx_rpc_primitives::Bytes;
12use cfx_types::{Space, H256, U256, U64};
13use cfxkey::Error;
14use primitives::{
15 transaction::{
16 eth_transaction::Eip155Transaction,
17 native_transaction::NativeTransaction, Action,
18 },
19 SignedTransaction, Transaction as PrimitiveTransaction, TransactionIndex,
20 TransactionWithSignature, TransactionWithSignatureSerializePart,
21};
22use serde::{Deserialize, Serialize};
23
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct Transaction {
27 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
28 pub transaction_type: Option<U64>,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub space: Option<Space>,
31 pub hash: H256,
32 pub nonce: U256,
33 pub block_hash: Option<H256>,
34 pub transaction_index: Option<U64>,
35 pub from: RpcAddress,
36 pub to: Option<RpcAddress>,
37 pub value: U256,
38 pub gas_price: U256,
39 pub gas: U256,
40 pub contract_created: Option<RpcAddress>,
41 pub data: Bytes,
42 pub storage_limit: U256,
43 pub epoch_height: U256,
44 pub chain_id: Option<U256>,
45 pub status: Option<U64>,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub access_list: Option<CfxAccessList>,
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub max_priority_fee_per_gas: Option<U256>,
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub max_fee_per_gas: Option<U256>,
55 pub v: U256,
57 pub r: U256,
59 pub s: U256,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub y_parity: Option<U64>,
63}
64
65pub enum PackedOrExecuted {
66 Packed(TransactionIndex),
67 Executed(Receipt),
68}
69
70impl Transaction {
71 pub fn default(network: Network) -> Result<Transaction, String> {
72 Ok(Transaction {
73 space: None,
74 hash: Default::default(),
75 nonce: Default::default(),
76 block_hash: Default::default(),
77 transaction_index: Default::default(),
78 from: RpcAddress::null(network)?,
79 to: Default::default(),
80 value: Default::default(),
81 gas_price: Default::default(),
82 gas: Default::default(),
83 contract_created: Default::default(),
84 data: Default::default(),
85 storage_limit: Default::default(),
86 epoch_height: Default::default(),
87 chain_id: Some(U256::one()),
88 status: Default::default(),
89 v: Default::default(),
90 r: Default::default(),
91 s: Default::default(),
92 access_list: Default::default(),
93 max_priority_fee_per_gas: Default::default(),
94 max_fee_per_gas: Default::default(),
95 y_parity: Default::default(),
96 transaction_type: Default::default(),
97 })
98 }
99
100 pub fn from_signed(
101 t: &SignedTransaction,
102 maybe_packed_or_executed: Option<PackedOrExecuted>, network: Network,
103 ) -> Result<Transaction, String> {
104 let mut contract_created = None;
105 let mut status: Option<U64> = None;
106 let mut block_hash = None;
107 let mut transaction_index = None;
108 match maybe_packed_or_executed {
109 None => {}
110 Some(PackedOrExecuted::Packed(tx_index)) => {
111 block_hash = Some(tx_index.block_hash);
112 transaction_index = Some(
113 tx_index.rpc_index.unwrap_or(tx_index.real_index).into(),
114 );
115 }
116 Some(PackedOrExecuted::Executed(receipt)) => {
117 block_hash = Some(receipt.block_hash);
118 transaction_index = Some(receipt.index.into());
119 if let Some(ref address) = receipt.contract_created {
120 contract_created = Some(address.clone());
121 }
122 status = Some(receipt.outcome_status);
123 }
124 }
125 let (storage_limit, epoch_height) =
126 if let PrimitiveTransaction::Native(ref tx) = t.unsigned {
127 (*tx.storage_limit(), *tx.epoch_height())
128 } else {
129 (0, 0)
130 };
131 let space = match t.space() {
132 Space::Native => None,
133 Space::Ethereum => Some(Space::Ethereum),
134 };
135 Ok(Transaction {
136 space,
137 hash: t.transaction.hash().into(),
138 nonce: t.nonce().into(),
139 block_hash,
140 transaction_index,
141 status,
142 contract_created,
143 from: RpcAddress::try_from_h160(t.sender().address, network)?,
144 to: match t.action() {
145 Action::Create => None,
146 Action::Call(ref address) => {
147 Some(RpcAddress::try_from_h160(address.clone(), network)?)
148 }
149 },
150 value: t.value().into(),
151 gas_price: t.gas_price().into(),
152 gas: t.gas().into(),
153 data: t.data().clone().into(),
154 storage_limit: storage_limit.into(),
155 epoch_height: epoch_height.into(),
156 chain_id: t.chain_id().map(|x| U256::from(x as u64)),
157 access_list: t
158 .access_list()
159 .cloned()
160 .map(|list| from_primitive_access_list(list, network)),
161 max_fee_per_gas: t.after_1559().then_some(*t.gas_price()),
162 max_priority_fee_per_gas: t
163 .after_1559()
164 .then_some(*t.max_priority_gas_price()),
165 y_parity: t.is_2718().then_some(t.transaction.v.into()),
166 transaction_type: Some(U64::from(t.type_id())),
167 v: t.transaction.v.into(),
168 r: t.transaction.r.into(),
169 s: t.transaction.s.into(),
170 })
171 }
172
173 pub fn into_signed(self) -> Result<SignedTransaction, Error> {
174 let tx_with_sig = TransactionWithSignature {
175 transaction: TransactionWithSignatureSerializePart {
176 unsigned: if self.space == Some(Space::Ethereum) {
177 Eip155Transaction {
178 nonce: self.nonce.into(),
179 gas_price: self.gas_price.into(),
180 gas: self.gas.into(),
181 action: match self.to {
182 None => Action::Create,
183 Some(address) => Action::Call(address.into()),
184 },
185 value: self.value.into(),
186 chain_id: self.chain_id.map(|x| x.as_u32()),
187 data: self.data.into(),
188 }
189 .into()
190 } else {
191 NativeTransaction {
192 nonce: self.nonce.into(),
193 gas_price: self.gas_price.into(),
194 gas: self.gas.into(),
195 action: match self.to {
196 None => Action::Create,
197 Some(address) => Action::Call(address.into()),
198 },
199 value: self.value.into(),
200 storage_limit: self.storage_limit.as_u64(),
201 epoch_height: self.epoch_height.as_u64(),
202 chain_id: self
203 .chain_id
204 .ok_or(Error::Custom(
205 "Native transaction must have chain_id".into(),
206 ))?
207 .as_u32(),
208 data: self.data.into(),
209 }
210 .into()
211 },
212 v: self.v.as_usize() as u8,
213 r: self.r.into(),
214 s: self.s.into(),
215 },
216 hash: self.hash.into(),
217 rlp_size: None,
218 };
219 let public = tx_with_sig.recover_public()?;
220 Ok(SignedTransaction::new(public, tx_with_sig))
221 }
222}