ckb_sdk/tx_builder/udt/
mod.rs

1mod sudt;
2
3use anyhow::anyhow;
4use ckb_types::{
5    bytes::{BufMut, Bytes, BytesMut},
6    core::{Capacity, TransactionBuilder, TransactionView},
7    packed::{Byte32, CellDep, CellInput, CellOutput, Script},
8    prelude::*,
9};
10use std::collections::HashSet;
11
12use super::{TransferAction, TxBuilder, TxBuilderError};
13use crate::traits::{
14    CellCollector, CellDepResolver, CellQueryOptions, HeaderDepResolver,
15    TransactionDependencyProvider, ValueRangeOption,
16};
17use crate::types::ScriptId;
18
19/// The udt type
20#[derive(Debug, Eq, PartialEq, Hash, Clone)]
21pub enum UdtType {
22    Sudt,
23    /// The parameter is <xudt args> (NOTE: xudt is current not supported, this variant is for future support)
24    Xudt(Bytes),
25}
26
27impl UdtType {
28    pub fn build_script(&self, script_id: &ScriptId, owner_lock_hash: &Byte32) -> Script {
29        let type_script_args = match self {
30            UdtType::Sudt => owner_lock_hash.as_bytes(),
31            UdtType::Xudt(extra_args) => {
32                let mut data = BytesMut::with_capacity(32 + extra_args.len());
33                data.put(owner_lock_hash.as_slice());
34                data.put(extra_args.as_ref());
35                data.freeze()
36            }
37        };
38        Script::new_builder()
39            .code_hash(script_id.code_hash.pack())
40            .hash_type(script_id.hash_type)
41            .args(type_script_args.pack())
42            .build()
43    }
44}
45
46/// The udt issue/transfer receiver
47#[derive(Debug, Eq, PartialEq, Hash, Clone)]
48pub struct UdtTargetReceiver {
49    pub action: TransferAction,
50
51    /// The lock script set to this udt cell, if `action` is `Update` will query
52    /// input cell by this lock script.
53    pub lock_script: Script,
54
55    /// The capacity set to this udt cell when `action` is TransferAction::Create
56    pub capacity: Option<u64>,
57
58    /// The amount to issue/transfer
59    pub amount: u128,
60
61    /// Only for <xudt data> and only used when action == TransferAction::Create
62    pub extra_data: Option<Bytes>,
63}
64
65pub struct ReceiverBuildOutput {
66    pub input: Option<(CellInput, CellDep)>,
67    pub output: CellOutput,
68    pub output_data: Bytes,
69}
70
71impl UdtTargetReceiver {
72    pub fn new(action: TransferAction, lock_script: Script, amount: u128) -> UdtTargetReceiver {
73        UdtTargetReceiver {
74            action,
75            lock_script,
76            capacity: None,
77            amount,
78            extra_data: None,
79        }
80    }
81    #[cfg(not(target_arch = "wasm32"))]
82    pub fn build(
83        &self,
84        type_script: &Script,
85        cell_collector: &mut dyn CellCollector,
86        cell_dep_resolver: &dyn CellDepResolver,
87    ) -> Result<ReceiverBuildOutput, TxBuilderError> {
88        crate::rpc::block_on(self.build_async(type_script, cell_collector, cell_dep_resolver))
89    }
90
91    pub async fn build_async(
92        &self,
93        type_script: &Script,
94        cell_collector: &mut dyn CellCollector,
95        cell_dep_resolver: &dyn CellDepResolver,
96    ) -> Result<ReceiverBuildOutput, TxBuilderError> {
97        match self.action {
98            TransferAction::Create => {
99                let data_len = self
100                    .extra_data
101                    .as_ref()
102                    .map(|data| data.len())
103                    .unwrap_or_default()
104                    + 16;
105                let mut data = BytesMut::with_capacity(data_len);
106                data.put(&self.amount.to_le_bytes()[..]);
107                if let Some(extra_data) = self.extra_data.as_ref() {
108                    data.put(extra_data.as_ref());
109                }
110
111                let base_output = CellOutput::new_builder()
112                    .lock(self.lock_script.clone())
113                    .type_(Some(type_script.clone()).pack())
114                    .build();
115                let base_occupied_capacity = base_output
116                    .occupied_capacity(Capacity::bytes(data_len).unwrap())
117                    .unwrap()
118                    .as_u64();
119                let final_capacity = if let Some(capacity) = self.capacity.as_ref() {
120                    if *capacity >= base_occupied_capacity {
121                        *capacity
122                    } else {
123                        return Err(TxBuilderError::Other(anyhow!(
124                            "Not enough capacity to hold a receiver cell, min: {}, actual: {}",
125                            base_occupied_capacity,
126                            *capacity,
127                        )));
128                    }
129                } else {
130                    base_occupied_capacity
131                };
132                let output = base_output.as_builder().capacity(final_capacity).build();
133                Ok(ReceiverBuildOutput {
134                    input: None,
135                    output,
136                    output_data: data.freeze(),
137                })
138            }
139            TransferAction::Update => {
140                let receiver_query = {
141                    let mut query = CellQueryOptions::new_lock(self.lock_script.clone());
142                    query.secondary_script = Some(type_script.clone());
143                    query.data_len_range = Some(ValueRangeOption::new_min(16));
144                    query
145                };
146                let (receiver_cells, _) = cell_collector
147                    .collect_live_cells_async(&receiver_query, true)
148                    .await?;
149                if receiver_cells.is_empty() {
150                    return Err(TxBuilderError::Other(anyhow!(
151                        "update receiver cell failed, cell not found, lock={:?}",
152                        self.lock_script
153                    )));
154                }
155
156                let receiver_cell_dep =
157                    cell_dep_resolver
158                        .resolve(&self.lock_script)
159                        .ok_or_else(|| {
160                            TxBuilderError::ResolveCellDepFailed(self.lock_script.clone())
161                        })?;
162
163                let mut amount_bytes = [0u8; 16];
164                let receiver_cell = &receiver_cells[0];
165                amount_bytes.copy_from_slice(&receiver_cell.output_data.as_ref()[0..16]);
166                let old_amount = u128::from_le_bytes(amount_bytes);
167                let new_amount = old_amount + self.amount;
168                let mut new_data = receiver_cell.output_data.as_ref().to_vec();
169                new_data[0..16].copy_from_slice(&new_amount.to_le_bytes()[..]);
170                let output_data = Bytes::from(new_data);
171
172                let input = CellInput::new(receiver_cell.out_point.clone(), 0);
173                Ok(ReceiverBuildOutput {
174                    input: Some((input, receiver_cell_dep)),
175                    output: receiver_cell.output.clone(),
176                    output_data,
177                })
178            }
179        }
180    }
181}
182
183/// The udt issue transaction builder
184pub struct UdtIssueBuilder {
185    /// The udt type (sudt/xudt)
186    pub udt_type: UdtType,
187
188    /// The sudt/xudt script id
189    pub script_id: ScriptId,
190
191    /// We will collect a cell from owner, there must exists a cell that:
192    ///   * type script is None
193    ///   * data field is empty
194    ///   * is mature
195    pub owner: Script,
196
197    /// The receivers
198    pub receivers: Vec<UdtTargetReceiver>,
199}
200
201#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
202#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
203impl TxBuilder for UdtIssueBuilder {
204    async fn build_base_async(
205        &self,
206        cell_collector: &mut dyn CellCollector,
207        cell_dep_resolver: &dyn CellDepResolver,
208        _header_dep_resolver: &dyn HeaderDepResolver,
209        _tx_dep_provider: &dyn TransactionDependencyProvider,
210    ) -> Result<TransactionView, TxBuilderError> {
211        // Build inputs
212        let owner_query = {
213            let mut query = CellQueryOptions::new_lock(self.owner.clone());
214            query.secondary_script_len_range = Some(ValueRangeOption::new_exact(0));
215            query.data_len_range = Some(ValueRangeOption::new_exact(0));
216            query
217        };
218
219        let (owner_cells, _) = cell_collector
220            .collect_live_cells_async(&owner_query, true)
221            .await?;
222        if owner_cells.is_empty() {
223            return Err(TxBuilderError::Other(anyhow!("owner cell not found")));
224        }
225        let mut inputs = vec![CellInput::new(owner_cells[0].out_point.clone(), 0)];
226
227        // Build output type script
228        let owner_lock_hash = self.owner.calc_script_hash();
229        let type_script = self
230            .udt_type
231            .build_script(&self.script_id, &owner_lock_hash);
232
233        let owner_cell_dep = cell_dep_resolver
234            .resolve(&self.owner)
235            .ok_or_else(|| TxBuilderError::ResolveCellDepFailed(self.owner.clone()))?;
236        let udt_cell_dep = cell_dep_resolver
237            .resolve(&type_script)
238            .ok_or_else(|| TxBuilderError::ResolveCellDepFailed(type_script.clone()))?;
239        #[allow(clippy::mutable_key_type)]
240        let mut cell_deps = HashSet::new();
241        cell_deps.insert(owner_cell_dep);
242        cell_deps.insert(udt_cell_dep);
243
244        // Build outputs, outputs_data, cell_deps
245        let mut outputs = Vec::new();
246        let mut outputs_data = Vec::new();
247        for receiver in &self.receivers {
248            let ReceiverBuildOutput {
249                input,
250                output,
251                output_data,
252            } = receiver
253                .build_async(&type_script, cell_collector, cell_dep_resolver)
254                .await?;
255            if let Some((input, input_lock_cell_dep)) = input {
256                inputs.push(input);
257                cell_deps.insert(input_lock_cell_dep);
258            }
259            outputs.push(output);
260            outputs_data.push(output_data.pack());
261        }
262        Ok(TransactionBuilder::default()
263            .set_cell_deps(cell_deps.into_iter().collect())
264            .set_inputs(inputs)
265            .set_outputs(outputs)
266            .set_outputs_data(outputs_data)
267            .build())
268    }
269}
270
271pub struct UdtTransferBuilder {
272    /// The udt type script
273    pub type_script: Script,
274
275    /// Sender's lock script (we will asume there is only one udt cell identify
276    /// by `type_script` and `sender`)
277    pub sender: Script,
278
279    /// The transfer receivers
280    pub receivers: Vec<UdtTargetReceiver>,
281}
282
283#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
284#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
285impl TxBuilder for UdtTransferBuilder {
286    async fn build_base_async(
287        &self,
288        cell_collector: &mut dyn CellCollector,
289        cell_dep_resolver: &dyn CellDepResolver,
290        _header_dep_resolver: &dyn HeaderDepResolver,
291        _tx_dep_provider: &dyn TransactionDependencyProvider,
292    ) -> Result<TransactionView, TxBuilderError> {
293        let sender_query = {
294            let mut query = CellQueryOptions::new_lock(self.sender.clone());
295            query.secondary_script = Some(self.type_script.clone());
296            query.data_len_range = Some(ValueRangeOption::new_min(16));
297            query
298        };
299        let (sender_cells, _) = cell_collector
300            .collect_live_cells_async(&sender_query, true)
301            .await?;
302        if sender_cells.is_empty() {
303            return Err(TxBuilderError::Other(anyhow!("sender cell not found")));
304        }
305        let sender_cell = &sender_cells[0];
306
307        let sender_cell_dep = cell_dep_resolver
308            .resolve(&self.sender)
309            .ok_or_else(|| TxBuilderError::ResolveCellDepFailed(self.sender.clone()))?;
310        let udt_cell_dep = cell_dep_resolver
311            .resolve(&self.type_script)
312            .ok_or_else(|| TxBuilderError::ResolveCellDepFailed(self.type_script.clone()))?;
313        #[allow(clippy::mutable_key_type)]
314        let mut cell_deps = HashSet::new();
315        cell_deps.insert(sender_cell_dep);
316        cell_deps.insert(udt_cell_dep);
317
318        let mut amount_bytes = [0u8; 16];
319        amount_bytes.copy_from_slice(&sender_cell.output_data.as_ref()[0..16]);
320        let input_total = u128::from_le_bytes(amount_bytes);
321        let output_total: u128 = self.receivers.iter().map(|receiver| receiver.amount).sum();
322        if input_total < output_total {
323            return Err(TxBuilderError::Other(anyhow!(
324                "sender udt amount not enough, expected at least: {}, actual: {}",
325                output_total,
326                input_total
327            )));
328        }
329
330        let sender_output_data = {
331            let new_amount = input_total - output_total;
332            let mut new_data = sender_cell.output_data.as_ref().to_vec();
333            new_data[0..16].copy_from_slice(&new_amount.to_le_bytes()[..]);
334            Bytes::from(new_data)
335        };
336
337        let mut inputs = vec![CellInput::new(sender_cell.out_point.clone(), 0)];
338        let mut outputs = vec![sender_cell.output.clone()];
339        let mut outputs_data = vec![sender_output_data.pack()];
340
341        for receiver in &self.receivers {
342            let ReceiverBuildOutput {
343                input,
344                output,
345                output_data,
346            } = receiver
347                .build_async(&self.type_script, cell_collector, cell_dep_resolver)
348                .await?;
349            if let Some((input, input_lock_cell_dep)) = input {
350                inputs.push(input);
351                cell_deps.insert(input_lock_cell_dep);
352            }
353            outputs.push(output);
354            outputs_data.push(output_data.pack());
355        }
356
357        Ok(TransactionBuilder::default()
358            .set_cell_deps(cell_deps.into_iter().collect())
359            .set_inputs(inputs)
360            .set_outputs(outputs)
361            .set_outputs_data(outputs_data)
362            .build())
363    }
364}