1use byteorder::{ByteOrder, LittleEndian};
4use ckb_chain_spec::consensus::Consensus;
5use ckb_dao_utils::{DaoError, extract_dao_data, pack_dao_data};
6use ckb_traits::{CellDataProvider, EpochProvider, HeaderProvider};
7use ckb_types::{
8 bytes::Bytes,
9 core::{
10 Capacity, CapacityResult, EpochExt, HeaderView, ScriptHashType,
11 cell::{CellMeta, ResolvedTransaction},
12 },
13 packed::{Byte32, CellOutput, Script, WitnessArgs},
14 prelude::*,
15};
16use std::collections::HashSet;
17
18#[cfg(test)]
19mod tests;
20
21pub struct DaoCalculator<'a, DL> {
24 consensus: &'a Consensus,
25 data_loader: &'a DL,
26}
27
28impl<'a, DL: CellDataProvider + HeaderProvider> DaoCalculator<'a, DL> {
29 pub fn transaction_fee(&self, rtx: &ResolvedTransaction) -> Result<Capacity, DaoError> {
31 let maximum_withdraw = self.transaction_maximum_withdraw(rtx)?;
32 rtx.transaction
33 .outputs_capacity()
34 .and_then(|y| maximum_withdraw.safe_sub(y))
35 .map_err(Into::into)
36 }
37 fn transaction_maximum_withdraw(
38 &self,
39 rtx: &ResolvedTransaction,
40 ) -> Result<Capacity, DaoError> {
41 let header_deps: HashSet<Byte32> = rtx.transaction.header_deps_iter().collect();
42 rtx.resolved_inputs.iter().enumerate().try_fold(
43 Capacity::zero(),
44 |capacities, (i, cell_meta)| {
45 let capacity: Result<Capacity, DaoError> = {
46 let output = &cell_meta.cell_output;
47 let is_dao_type_script = |type_script: Script| {
48 Into::<u8>::into(type_script.hash_type())
49 == Into::<u8>::into(ScriptHashType::Type)
50 && type_script.code_hash() == self.consensus.dao_type_hash()
51 };
52 let is_withdrawing_input =
53 |cell_meta: &CellMeta| match self.data_loader.load_cell_data(cell_meta) {
54 Some(data) => data.len() == 8 && LittleEndian::read_u64(&data) > 0,
55 None => false,
56 };
57 if output
58 .type_()
59 .to_opt()
60 .map(is_dao_type_script)
61 .unwrap_or(false)
62 && is_withdrawing_input(cell_meta)
63 {
64 let withdrawing_header_hash = cell_meta
65 .transaction_info
66 .as_ref()
67 .map(|info| &info.block_hash)
68 .filter(|hash| header_deps.contains(hash))
69 .ok_or(DaoError::InvalidOutPoint)?;
70 let deposit_header_hash = rtx
71 .transaction
72 .witnesses()
73 .get(i)
74 .ok_or(DaoError::InvalidOutPoint)
75 .and_then(|witness_data| {
76 let witness =
78 WitnessArgs::from_slice(&Into::<Bytes>::into(witness_data))
79 .map_err(|_| DaoError::InvalidDaoFormat)?;
80 let header_deps_index_data: Option<Bytes> =
81 witness.input_type().to_opt().map(|witness| witness.into());
82 if header_deps_index_data.is_none()
83 || header_deps_index_data.clone().map(|data| data.len())
84 != Some(8)
85 {
86 return Err(DaoError::InvalidDaoFormat);
87 }
88 Ok(LittleEndian::read_u64(&header_deps_index_data.unwrap()))
89 })
90 .and_then(|header_dep_index| {
91 rtx.transaction
92 .header_deps()
93 .get(header_dep_index as usize)
94 .and_then(|hash| header_deps.get(&hash))
95 .ok_or(DaoError::InvalidOutPoint)
96 })?;
97 self.calculate_maximum_withdraw(
98 output,
99 Capacity::bytes(cell_meta.data_bytes as usize)?,
100 deposit_header_hash,
101 withdrawing_header_hash,
102 )
103 } else {
104 Ok(output.capacity().into())
105 }
106 };
107 capacity.and_then(|c| c.safe_add(capacities).map_err(Into::into))
108 },
109 )
110 }
111
112 pub fn calculate_maximum_withdraw(
114 &self,
115 output: &CellOutput,
116 output_data_capacity: Capacity,
117 deposit_header_hash: &Byte32,
118 withdrawing_header_hash: &Byte32,
119 ) -> Result<Capacity, DaoError> {
120 let deposit_header = self
121 .data_loader
122 .get_header(deposit_header_hash)
123 .ok_or(DaoError::InvalidHeader)?;
124 let withdrawing_header = self
125 .data_loader
126 .get_header(withdrawing_header_hash)
127 .ok_or(DaoError::InvalidHeader)?;
128 if deposit_header.number() >= withdrawing_header.number() {
129 return Err(DaoError::InvalidOutPoint);
130 }
131
132 let (deposit_ar, _, _, _) = extract_dao_data(deposit_header.dao());
133 let (withdrawing_ar, _, _, _) = extract_dao_data(withdrawing_header.dao());
134
135 let occupied_capacity = output.occupied_capacity(output_data_capacity)?;
136 let output_capacity: Capacity = output.capacity().into();
137 let counted_capacity = output_capacity.safe_sub(occupied_capacity)?;
138 let withdraw_counted_capacity = u128::from(counted_capacity.as_u64())
139 * u128::from(withdrawing_ar)
140 / u128::from(deposit_ar);
141 let withdraw_capacity =
142 Capacity::shannons(withdraw_counted_capacity as u64).safe_add(occupied_capacity)?;
143
144 Ok(withdraw_capacity)
145 }
146 pub fn new(consensus: &'a Consensus, data_loader: &'a DL) -> Self {
148 DaoCalculator {
149 consensus,
150 data_loader,
151 }
152 }
153}
154
155impl<'a, DL: CellDataProvider + EpochProvider + HeaderProvider> DaoCalculator<'a, DL> {
156 pub fn primary_block_reward(&self, target: &HeaderView) -> Result<Capacity, DaoError> {
158 let target_epoch = self
159 .data_loader
160 .get_epoch_ext(target)
161 .ok_or(DaoError::InvalidHeader)?;
162
163 target_epoch
164 .block_reward(target.number())
165 .map_err(Into::into)
166 }
167
168 pub fn secondary_block_reward(&self, target: &HeaderView) -> Result<Capacity, DaoError> {
170 if target.number() == 0 {
171 return Ok(Capacity::zero());
172 }
173
174 let target_parent_hash = target.data().raw().parent_hash();
175 let target_parent = self
176 .data_loader
177 .get_header(&target_parent_hash)
178 .ok_or(DaoError::InvalidHeader)?;
179 let target_epoch = self
180 .data_loader
181 .get_epoch_ext(target)
182 .ok_or(DaoError::InvalidHeader)?;
183
184 let target_g2 = target_epoch
185 .secondary_block_issuance(target.number(), self.consensus.secondary_epoch_reward())?;
186 let (_, target_parent_c, _, target_parent_u) = extract_dao_data(target_parent.dao());
187 let reward128 = u128::from(target_g2.as_u64()) * u128::from(target_parent_u.as_u64())
188 / u128::from(target_parent_c.as_u64());
189 let reward = u64::try_from(reward128).map_err(|_| DaoError::Overflow)?;
190 Ok(Capacity::shannons(reward))
191 }
192
193 pub fn dao_field_with_current_epoch(
195 &self,
196 rtxs: impl Iterator<Item = &'a ResolvedTransaction> + Clone,
197 parent: &HeaderView,
198 current_block_epoch: &EpochExt,
199 ) -> Result<Byte32, DaoError> {
200 let freed_occupied_capacities =
202 rtxs.clone().try_fold(Capacity::zero(), |capacities, rtx| {
203 self.input_occupied_capacities(rtx)
204 .and_then(|c| capacities.safe_add(c))
205 })?;
206 let added_occupied_capacities = self.added_occupied_capacities(rtxs.clone())?;
207 let withdrawed_interests = self.withdrawed_interests(rtxs)?;
208
209 let (parent_ar, parent_c, parent_s, parent_u) = extract_dao_data(parent.dao());
210
211 let current_block_number = parent.number() + 1;
219 let current_g2 = current_block_epoch.secondary_block_issuance(
220 current_block_number,
221 self.consensus.secondary_epoch_reward(),
222 )?;
223 let current_g = current_block_epoch
224 .block_reward(current_block_number)
225 .and_then(|c| c.safe_add(current_g2))?;
226
227 let miner_issuance128 = u128::from(current_g2.as_u64()) * u128::from(parent_u.as_u64())
228 / u128::from(parent_c.as_u64());
229 let miner_issuance =
230 Capacity::shannons(u64::try_from(miner_issuance128).map_err(|_| DaoError::Overflow)?);
231 let nervosdao_issuance = current_g2.safe_sub(miner_issuance)?;
232
233 let current_c = parent_c.safe_add(current_g)?;
234 let current_u = parent_u
235 .safe_add(added_occupied_capacities)
236 .and_then(|u| u.safe_sub(freed_occupied_capacities))?;
237 let current_s = parent_s
238 .safe_add(nervosdao_issuance)
239 .and_then(|s| s.safe_sub(withdrawed_interests))?;
240
241 let ar_increase128 =
242 u128::from(parent_ar) * u128::from(current_g2.as_u64()) / u128::from(parent_c.as_u64());
243 let ar_increase = u64::try_from(ar_increase128).map_err(|_| DaoError::Overflow)?;
244 let current_ar = parent_ar
245 .checked_add(ar_increase)
246 .ok_or(DaoError::Overflow)?;
247
248 Ok(pack_dao_data(current_ar, current_c, current_s, current_u))
249 }
250
251 pub fn dao_field(
256 &self,
257 rtxs: impl Iterator<Item = &'a ResolvedTransaction> + Clone,
258 parent: &HeaderView,
259 ) -> Result<Byte32, DaoError> {
260 let current_block_epoch = self
261 .consensus
262 .next_epoch_ext(parent, self.data_loader)
263 .ok_or(DaoError::InvalidHeader)?
264 .epoch();
265 self.dao_field_with_current_epoch(rtxs, parent, ¤t_block_epoch)
266 }
267
268 fn added_occupied_capacities(
269 &self,
270 mut rtxs: impl Iterator<Item = &'a ResolvedTransaction>,
271 ) -> CapacityResult<Capacity> {
272 let added_occupied_capacities = rtxs.try_fold(Capacity::zero(), |capacities, rtx| {
274 rtx.transaction
275 .outputs_with_data_iter()
276 .enumerate()
277 .try_fold(Capacity::zero(), |tx_capacities, (_, (output, data))| {
278 Capacity::bytes(data.len())
279 .and_then(|c| output.occupied_capacity(c))
280 .and_then(|c| tx_capacities.safe_add(c))
281 })
282 .and_then(|c| capacities.safe_add(c))
283 })?;
284
285 Ok(added_occupied_capacities)
286 }
287
288 fn input_occupied_capacities(&self, rtx: &ResolvedTransaction) -> CapacityResult<Capacity> {
289 rtx.resolved_inputs
290 .iter()
291 .try_fold(Capacity::zero(), |capacities, cell_meta| {
292 let current_capacity = modified_occupied_capacity(cell_meta, self.consensus);
293 current_capacity.and_then(|c| capacities.safe_add(c))
294 })
295 }
296
297 fn withdrawed_interests(
298 &self,
299 mut rtxs: impl Iterator<Item = &'a ResolvedTransaction> + Clone,
300 ) -> Result<Capacity, DaoError> {
301 let maximum_withdraws = rtxs.clone().try_fold(Capacity::zero(), |capacities, rtx| {
302 self.transaction_maximum_withdraw(rtx)
303 .and_then(|c| capacities.safe_add(c).map_err(Into::into))
304 })?;
305 let input_capacities = rtxs.try_fold(Capacity::zero(), |capacities, rtx| {
306 let tx_input_capacities = rtx.resolved_inputs.iter().try_fold(
307 Capacity::zero(),
308 |tx_capacities, cell_meta| {
309 let output_capacity: Capacity = cell_meta.cell_output.capacity().into();
310 tx_capacities.safe_add(output_capacity)
311 },
312 )?;
313 capacities.safe_add(tx_input_capacities)
314 })?;
315 maximum_withdraws
316 .safe_sub(input_capacities)
317 .map_err(Into::into)
318 }
319}
320
321pub fn modified_occupied_capacity(
324 cell_meta: &CellMeta,
325 consensus: &Consensus,
326) -> CapacityResult<Capacity> {
327 if let Some(tx_info) = &cell_meta.transaction_info {
328 if tx_info.is_genesis()
329 && tx_info.is_cellbase()
330 && cell_meta.cell_output.lock().args().raw_data() == consensus.satoshi_pubkey_hash.0[..]
331 {
332 return Into::<Capacity>::into(cell_meta.cell_output.capacity())
333 .safe_mul_ratio(consensus.satoshi_cell_occupied_ratio);
334 }
335 }
336 cell_meta.occupied_capacity()
337}