Skip to content

Commit 25a470f

Browse files
author
Liu Chuankai
committed
feat: 🎸 support dao deposit script simple transaction builder
1 parent 0d6cbb3 commit 25a470f

File tree

5 files changed

+357
-1
lines changed

5 files changed

+357
-1
lines changed

‎examples/dao_deposit_example.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use ckb_sdk::{
2+
transaction::{
3+
builder::{CkbTransactionBuilder, SimpleTransactionBuilder},
4+
handler::{dao, HandlerContexts},
5+
input::InputIterator,
6+
signer::{SignContexts, TransactionSigner},
7+
TransactionBuilderConfiguration,
8+
},
9+
Address, CkbRpcClient, NetworkInfo,
10+
};
11+
use ckb_types::h256;
12+
use std::{error::Error as StdErr, str::FromStr};
13+
14+
fn main() -> Result<(), Box<dyn StdErr>> {
15+
let network_info = NetworkInfo::testnet();
16+
let sender = Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r")?;
17+
let receiver=Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqv5dsed9par23x4g58seaw58j3ym5ml2hs8ztche")?;
18+
19+
let configuration = TransactionBuilderConfiguration::new_with_network(network_info.clone())?;
20+
21+
let iterator = InputIterator::new(vec![(&sender).into()], configuration.network_info());
22+
let mut builder = SimpleTransactionBuilder::new(configuration, iterator);
23+
24+
let context = dao::DepositContext::new((&receiver).into(), 510_0000_0000u64);
25+
let mut contexts = HandlerContexts::default();
26+
contexts.add_context(Box::new(context) as Box<_>);
27+
28+
builder.set_change_lock((&sender).into());
29+
let mut tx_with_groups = builder.build(&contexts)?;
30+
31+
let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone());
32+
println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap());
33+
34+
let private_keys = vec![h256!(
35+
"0x6c9ed03816e3111e49384b8d180174ad08e29feb1393ea1b51cef1c505d4e36a"
36+
)];
37+
TransactionSigner::new(&network_info).sign_transaction(
38+
&mut tx_with_groups,
39+
&SignContexts::new_sighash_h256(private_keys)?,
40+
)?;
41+
42+
let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone());
43+
println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap());
44+
45+
let tx_hash = CkbRpcClient::new(network_info.url.as_str())
46+
.send_transaction(json_tx.inner, None)
47+
.expect("send transaction");
48+
// example tx: 8b7ab7770c821fa8dc70738d5d6ef43da46706541c258b9a02edf66948039798
49+
println!(">>> tx {} sent! <<<", tx_hash);
50+
51+
Ok(())
52+
}

‎src/transaction/builder/mod.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,25 @@ impl SimpleTransactionBuilder {
132132
.capacity((capacity + delta_capacity).pack())
133133
.build();
134134
tx_builder.set_output(idx, output);
135+
136+
Ok(())
137+
}
138+
139+
fn prepare_transaction(
140+
transaction_inputs: &mut Vec<TransactionInput>,
141+
tx_builder: &mut crate::core::TransactionBuilder,
142+
configuration: &TransactionBuilderConfiguration,
143+
contexts: &HandlerContexts,
144+
) -> Result<(), TxBuilderError> {
145+
for handler in configuration.get_script_handlers() {
146+
for context in &contexts.contexts {
147+
if let Ok(true) =
148+
handler.prepare_transaction(transaction_inputs, tx_builder, context.as_ref())
149+
{
150+
break;
151+
}
152+
}
153+
}
135154
Ok(())
136155
}
137156
}
@@ -148,6 +167,12 @@ impl CkbTransactionBuilder for SimpleTransactionBuilder {
148167
&mut self,
149168
contexts: &HandlerContexts,
150169
) -> Result<TransactionWithScriptGroups, TxBuilderError> {
170+
Self::prepare_transaction(
171+
&mut self.transaction_inputs,
172+
&mut self.tx,
173+
&self.configuration,
174+
contexts,
175+
)?;
151176
let mut lock_groups: HashMap<Byte32, ScriptGroup> = HashMap::default();
152177
let mut type_groups: HashMap<Byte32, ScriptGroup> = HashMap::default();
153178
let mut outputs_capacity = 0u64;

‎src/transaction/handler/dao.rs

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
use anyhow::anyhow;
2+
use ckb_types::{
3+
core::{DepType, ScriptHashType},
4+
h256,
5+
packed::{CellDep, CellInput, CellOutput, OutPoint, Script, WitnessArgs},
6+
prelude::{Builder, Entity, Pack},
7+
};
8+
use lazy_static::lazy_static;
9+
10+
use crate::{
11+
constants,
12+
traits::{
13+
DefaultHeaderDepResolver, DefaultTransactionDependencyProvider, HeaderDepResolver, LiveCell,
14+
},
15+
transaction::input::TransactionInput,
16+
tx_builder::{
17+
dao::{DaoDepositReceiver, DaoPrepareItem},
18+
TxBuilderError,
19+
},
20+
NetworkInfo, NetworkType, ScriptGroup,
21+
};
22+
23+
use super::{HandlerContext, ScriptHandler};
24+
25+
lazy_static! {
26+
static ref DAO_TYPE_SCRIPT: Script = Script::new_builder()
27+
.code_hash(constants::DAO_TYPE_HASH.pack())
28+
.hash_type(ScriptHashType::Type.into())
29+
.build();
30+
}
31+
32+
pub struct DaoScriptHandler {
33+
cell_deps: Vec<CellDep>,
34+
}
35+
36+
#[derive(Clone, Debug, Default)]
37+
pub struct DepositContext {
38+
// lock script, capacity list
39+
pub receivers: Vec<DaoDepositReceiver>,
40+
}
41+
42+
impl DepositContext {
43+
pub fn new(lock_script: Script, capacity: u64) -> Self {
44+
let mut ret = Self::default();
45+
ret.add_output(lock_script, capacity);
46+
ret
47+
}
48+
49+
pub fn add_output(&mut self, lock_script: Script, capacity: u64) {
50+
self.receivers
51+
.push(DaoDepositReceiver::new(lock_script, capacity));
52+
}
53+
}
54+
55+
impl HandlerContext for DepositContext {}
56+
57+
#[derive(Default)]
58+
pub struct WithdrawPhrase1Context {
59+
pub items: Vec<DaoPrepareItem>,
60+
pub rpc_url: String,
61+
}
62+
63+
impl WithdrawPhrase1Context {
64+
/// add input.
65+
/// If `lock_script` is `None` copy the lock script from input with same
66+
/// index, otherwise replace the lock script with the given script.
67+
pub fn add_input(&mut self, input: CellInput, lock_script: Option<Script>) {
68+
self.items.push(DaoPrepareItem { input, lock_script });
69+
}
70+
71+
pub fn new(rpc_url: String) -> Self {
72+
Self {
73+
rpc_url,
74+
..Default::default()
75+
}
76+
}
77+
}
78+
79+
impl HandlerContext for WithdrawPhrase1Context {}
80+
81+
impl DaoScriptHandler {
82+
pub fn is_match(&self, script: &Script) -> bool {
83+
script.code_hash() == constants::DAO_TYPE_HASH.pack()
84+
}
85+
pub fn new_with_network(network: &NetworkInfo) -> Result<Self, TxBuilderError> {
86+
let mut ret = Self { cell_deps: vec![] };
87+
ret.init(network)?;
88+
Ok(ret)
89+
}
90+
91+
pub fn build_phrase1_base(
92+
transaction_inputs: &mut Vec<TransactionInput>,
93+
tx_data: &mut crate::core::TransactionBuilder,
94+
context: &WithdrawPhrase1Context,
95+
) -> Result<(), TxBuilderError> {
96+
if context.items.is_empty() {
97+
return Err(TxBuilderError::InvalidParameter(anyhow!(
98+
"No cell to prepare"
99+
)));
100+
}
101+
102+
let header_dep_resolver = DefaultHeaderDepResolver::new(&context.rpc_url);
103+
let tx_dep_provider = DefaultTransactionDependencyProvider::new(&context.rpc_url, 10);
104+
105+
for DaoPrepareItem { input, lock_script } in &context.items {
106+
let out_point = input.previous_output();
107+
let tx_hash = out_point.tx_hash();
108+
let deposit_header = header_dep_resolver
109+
.resolve_by_tx(&tx_hash)
110+
.map_err(TxBuilderError::Other)?
111+
.ok_or_else(|| TxBuilderError::ResolveHeaderDepByTxHashFailed(tx_hash.clone()))?;
112+
let (input_cell, data) = tx_dep_provider.get_cell_with_data(&out_point)?;
113+
if input_cell.type_().to_opt().as_ref() != Some(&DAO_TYPE_SCRIPT) {
114+
return Err(TxBuilderError::InvalidParameter(anyhow!(
115+
"the input cell has invalid type script"
116+
)));
117+
}
118+
119+
let output = {
120+
let mut builder = input_cell.clone().as_builder();
121+
if let Some(script) = lock_script {
122+
builder = builder.lock(script.clone());
123+
}
124+
builder.build()
125+
};
126+
let output_data = bytes::Bytes::from(deposit_header.number().to_le_bytes().to_vec());
127+
128+
let live_cell = LiveCell {
129+
output: input_cell,
130+
output_data: data,
131+
out_point,
132+
block_number: deposit_header.number(),
133+
tx_index: u32::MAX, // TODO set correct tx_index
134+
};
135+
let transaction_input = TransactionInput::new(live_cell, 0);
136+
transaction_inputs.push(transaction_input);
137+
138+
tx_data.dedup_header_dep(deposit_header.hash());
139+
tx_data.input(input.clone());
140+
tx_data.output(output);
141+
tx_data.output_data(output_data.pack());
142+
}
143+
Ok(())
144+
}
145+
146+
pub fn build_deposit(
147+
_transaction_inputs: &mut [TransactionInput],
148+
tx_data: &mut crate::core::TransactionBuilder,
149+
context: &DepositContext,
150+
) -> Result<(), TxBuilderError> {
151+
if context.receivers.is_empty() {
152+
return Err(TxBuilderError::InvalidParameter(anyhow!(
153+
"empty dao receivers"
154+
)));
155+
}
156+
let dao_type_script = Script::new_builder()
157+
.code_hash(constants::DAO_TYPE_HASH.pack())
158+
.hash_type(ScriptHashType::Type.into())
159+
.build();
160+
161+
for receiver in &context.receivers {
162+
let output = CellOutput::new_builder()
163+
.capacity(receiver.capacity.pack())
164+
.lock(receiver.lock_script.clone())
165+
.type_(Some(dao_type_script.clone()).pack())
166+
.build();
167+
tx_data.output(output);
168+
tx_data.output_data(bytes::Bytes::from(vec![0u8; 8]).pack());
169+
}
170+
171+
Ok(())
172+
}
173+
}
174+
175+
impl ScriptHandler for DaoScriptHandler {
176+
fn prepare_transaction(
177+
&self,
178+
transaction_inputs: &mut Vec<TransactionInput>,
179+
tx_data: &mut crate::core::TransactionBuilder,
180+
context: &dyn HandlerContext,
181+
) -> Result<bool, TxBuilderError> {
182+
if let Some(args) = context.as_any().downcast_ref::<DepositContext>() {
183+
Self::build_deposit(transaction_inputs, tx_data, args)?;
184+
Ok(true)
185+
} else if let Some(args) = context.as_any().downcast_ref::<WithdrawPhrase1Context>() {
186+
Self::build_phrase1_base(transaction_inputs, tx_data, args)?;
187+
Ok(true)
188+
} else {
189+
Ok(false)
190+
}
191+
}
192+
fn build_transaction(
193+
&self,
194+
tx_data: &mut crate::core::TransactionBuilder,
195+
script_group: &ScriptGroup,
196+
context: &dyn HandlerContext,
197+
) -> Result<bool, TxBuilderError> {
198+
if !self.is_match(&script_group.script) {
199+
return Ok(false);
200+
}
201+
if let Some(_args) = context.as_any().downcast_ref::<DepositContext>() {
202+
tx_data.dedup_cell_deps(self.cell_deps.clone());
203+
if !script_group.input_indices.is_empty() {
204+
let index = script_group.input_indices.first().unwrap();
205+
let witness = WitnessArgs::new_builder()
206+
.lock(Some(bytes::Bytes::from(vec![0u8; 65])).pack())
207+
.build();
208+
tx_data.set_witness(*index, witness.as_bytes().pack());
209+
}
210+
Ok(true)
211+
} else {
212+
Ok(false)
213+
}
214+
}
215+
216+
fn init(&mut self, network: &NetworkInfo) -> Result<(), TxBuilderError> {
217+
let out_point = if network.network_type == NetworkType::Mainnet {
218+
OutPoint::new_builder()
219+
.tx_hash(
220+
h256!("0xe2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c")
221+
.pack(),
222+
)
223+
.index(2u32.pack())
224+
.build()
225+
} else if network.network_type == NetworkType::Testnet {
226+
OutPoint::new_builder()
227+
.tx_hash(
228+
h256!("0x8f8c79eb6671709633fe6a46de93c0fedc9c1b8a6527a18d3983879542635c9f")
229+
.pack(),
230+
)
231+
.index(2u32.pack())
232+
.build()
233+
} else {
234+
return Err(TxBuilderError::UnsupportedNetworkType(network.network_type));
235+
};
236+
237+
let cell_dep = CellDep::new_builder()
238+
.out_point(out_point)
239+
.dep_type(DepType::Code.into())
240+
.build();
241+
self.cell_deps.push(cell_dep);
242+
Ok(())
243+
}
244+
}

‎src/transaction/handler/mod.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,22 @@ use crate::{
77

88
use self::sighash::Secp256k1Blake160SighashAllScriptContext;
99

10+
use super::input::TransactionInput;
11+
12+
pub mod dao;
1013
pub mod multisig;
1114
pub mod sighash;
1215

1316
pub trait ScriptHandler {
17+
fn prepare_transaction(
18+
&self,
19+
_transaction_inputs: &mut Vec<TransactionInput>,
20+
_tx_data: &mut TransactionBuilder,
21+
_context: &dyn HandlerContext,
22+
) -> Result<bool, TxBuilderError> {
23+
Ok(false)
24+
}
25+
1426
/// Try to build transaction with the given script_group and context.
1527
///
1628
/// Return true if script_group and context are matched, otherwise return false.
@@ -49,6 +61,11 @@ impl Default for HandlerContexts {
4961
}
5062

5163
impl HandlerContexts {
64+
pub fn new(context: Box<dyn HandlerContext>) -> Self {
65+
Self {
66+
contexts: vec![context],
67+
}
68+
}
5269
pub fn new_sighash() -> Self {
5370
Self {
5471
contexts: vec![Box::new(Secp256k1Blake160SighashAllScriptContext {})],
@@ -63,7 +80,24 @@ impl HandlerContexts {
6380
}
6481
}
6582

66-
pub fn add_context(mut self, context: Box<dyn HandlerContext>) {
83+
pub fn new_dao_withdraw_phrase1() -> Self {
84+
Self {
85+
contexts: vec![Box::<dao::WithdrawPhrase1Context>::default()],
86+
}
87+
}
88+
89+
pub fn new_dao_withdraw_phrase2() -> Self {
90+
Self {
91+
contexts: vec![Box::<dao::WithdrawPhrase1Context>::default()],
92+
}
93+
}
94+
95+
pub fn add_context(&mut self, context: Box<dyn HandlerContext>) {
6796
self.contexts.push(context);
6897
}
98+
99+
/// extend this context with other contexts
100+
pub fn extend_contexts(&mut self, contexts: HandlerContexts) {
101+
self.contexts.extend(contexts.contexts);
102+
}
69103
}

0 commit comments

Comments
 (0)