Skip to content

Commit

Permalink
✨ Verify dao action data schema
Browse files Browse the repository at this point in the history
The action data is encoded using molecule.
  • Loading branch information
doitian committed Feb 9, 2024
1 parent b0c30b8 commit e0775b2
Show file tree
Hide file tree
Showing 10 changed files with 437 additions and 51 deletions.
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions contracts/__tests__/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ edition = "2021"
[dependencies]
ckb-testtool = "0.10.1"
serde_json = "1.0"
ckb-transaction-cobuild = { git = "https://github.com/cryptape/ckb-transaction-cobuild-poc.git" }
ckb-dao-cobuild-schemas = { path = "../../crates/ckb-dao-cobuild-schemas" }
hex-literal = "0.4.1"
327 changes: 322 additions & 5 deletions contracts/__tests__/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
use ckb_dao_cobuild_schemas::{
Capacity, Claim, ClaimVec, DaoActionData, Deposit, DepositVec, Withdraw, WithdrawVec,
};
use ckb_testtool::{
builtin::ALWAYS_SUCCESS,
ckb_error::Error,
ckb_hash::blake2b_256,
ckb_types::{
bytes::Bytes,
core::{Cycle, TransactionView},
core::{Cycle, TransactionBuilder, TransactionView},
packed,
prelude::*,
},
context::Context,
};
use std::env;
use std::fs;
use std::path::PathBuf;
use std::str::FromStr;
use ckb_transaction_cobuild::schemas::{
basic::{Action, ActionVec, Message, SighashAll},
top_level::{WitnessLayout, WitnessLayoutUnion},
};
use hex_literal::hex;
use std::{env, fs, path::PathBuf, str::FromStr};

include!("../../dao-action-verifier/src/error_code.rs");

#[cfg(test)]
mod tests;
Expand Down Expand Up @@ -110,3 +121,309 @@ pub fn verify_and_dump_failed_tx(
}
result
}

pub fn assert_tx_error(
context: &Context,
tx: &TransactionView,
err_code: ErrorCode,
max_cycles: u64,
) {
match context.verify_tx(tx, max_cycles) {
Ok(_) => panic!(
"expect error code {:?}({}), got success",
err_code, err_code as i8
),
Err(err) => {
let error_string = err.to_string();
assert!(
error_string.contains(format!("error code {} ", err_code as i8).as_str()),
"expect error code {:?}({}), got {}",
err_code,
err_code as i8,
error_string,
);
}
}
}

pub struct CellSpec {
output: packed::CellOutputBuilder,
data: Bytes,
since: packed::Uint64,
}

pub trait TxSpec {
fn new_dao_input_spec(&mut self) -> CellSpec;
fn new_dao_output_spec(&mut self) -> CellSpec;
fn new_tx_builder(
&mut self,
dao_input_spec: CellSpec,
dao_output_spec: CellSpec,
) -> TransactionBuilder;
fn complete_tx(&mut self, tx: TransactionView) -> TransactionView;
}

pub fn build_tx<T: TxSpec>(spec: &mut T) -> TransactionView {
let dao_input_spec = spec.new_dao_input_spec();
let dao_output_spec = spec.new_dao_output_spec();
let tx = spec.new_tx_builder(dao_input_spec, dao_output_spec).build();

spec.complete_tx(tx)
}

pub struct DefaultTxSpec {
context: Context,

alice_lock_script: packed::Script,

verifier_type_script: packed::Script,
dao_type_script: packed::Script,
}

impl DefaultTxSpec {
fn new() -> Self {
let mut context = Context::default();
let loader = Loader::default();

// use always success as lock
let always_success_out_point = context.deploy_cell((*ALWAYS_SUCCESS).clone());
let alice_lock_script = context
.build_script(&always_success_out_point, Bytes::from(vec![0xa]))
.expect("script");

// use always success as dao
let dao_type_script = packed::Script::new_builder()
.code_hash(
hex!("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e").pack(),
)
.hash_type(1.into())
.build();
let dao = packed::CellOutput::new_builder()
.capacity(1000u64.pack())
.lock(alice_lock_script.clone())
.type_(
Some(
packed::Script::new_builder()
.code_hash(
hex!(
"00000000000000000000000000000000000000000000000000545950455f4944"
)
.pack(),
)
.hash_type(1.into())
.args(
hex!(
"b2a8500929d6a1294bf9bf1bf565f549fa4a5f1316a3306ad3d4783e64bcf626"
)
.to_vec()
.pack(),
)
.build(),
)
.pack(),
)
.build();
context.create_cell(dao, (*ALWAYS_SUCCESS).clone());

// verifier cell
let verifier_out_point = context.deploy_cell(loader.load_binary("dao-action-verifier"));
let verifier_type_script = context
.build_script(&verifier_out_point, Default::default())
.expect("script");

Self {
context,
alice_lock_script,
verifier_type_script,
dao_type_script,
}
}

pub fn pack_dao_data(&self, action_data: Bytes) -> Bytes {
self.pack_dao_data_vec(vec![action_data])
}

pub fn pack_dao_operations(
&self,
deposits: Vec<Deposit>,
withdraws: Vec<Withdraw>,
claims: Vec<Claim>,
) -> Bytes {
self.pack_dao_data(
DaoActionData::new_builder()
.deposits(DepositVec::new_builder().set(deposits).build())
.withdraws(WithdrawVec::new_builder().set(withdraws).build())
.claims(ClaimVec::new_builder().set(claims).build())
.build()
.as_bytes(),
)
}

pub fn pack_dao_data_vec(&self, action_data_vec: Vec<Bytes>) -> Bytes {
let script_hash = blake2b_256(self.dao_type_script.as_slice());
let actions = ActionVec::new_builder()
.set(
action_data_vec
.into_iter()
.map(|data| {
Action::new_builder()
.script_hash(script_hash.pack())
.data(data.pack())
.build()
})
.collect(),
)
.build();
let message = Message::new_builder().actions(actions).build();
WitnessLayout::new_builder()
.set(WitnessLayoutUnion::SighashAll(
SighashAll::new_builder().message(message).build(),
))
.build()
.as_bytes()
}
}

impl Default for DefaultTxSpec {
fn default() -> Self {
Self::new()
}
}

pub const DEFAULT_CAPACITY: u64 = 1000;
pub const DAO_DEPOSIT_DATA: [u8; 8] = hex!("0000000000000000");

pub fn pack_capacity(shannons: u64) -> Capacity {
Capacity::new_unchecked(Bytes::from(shannons.to_le_bytes().to_vec()))
}

impl TxSpec for DefaultTxSpec {
fn new_dao_input_spec(&mut self) -> CellSpec {
CellSpec {
output: packed::CellOutput::new_builder()
.capacity(DEFAULT_CAPACITY.pack())
.lock(self.alice_lock_script.clone()),
data: Bytes::new(),
since: 0.pack(),
}
}

fn new_dao_output_spec(&mut self) -> CellSpec {
CellSpec {
output: packed::CellOutput::new_builder()
.capacity(DEFAULT_CAPACITY.pack())
.type_(Some(self.dao_type_script.clone()).pack())
.lock(self.alice_lock_script.clone()),
data: Bytes::from(DAO_DEPOSIT_DATA.to_vec()),
since: 0.pack(),
}
}

fn new_tx_builder(
&mut self,
dao_input_spec: CellSpec,
dao_output_spec: CellSpec,
) -> TransactionBuilder {
let dao_cell = self
.context
.create_cell(dao_input_spec.output.build(), dao_input_spec.data);
let dao_input = packed::CellInput::new_builder()
.previous_output(dao_cell)
.since(dao_input_spec.since)
.build();
let dao_output = dao_output_spec.output.build();

let verifier_cell = self.context.create_cell(
packed::CellOutput::new_builder()
.capacity(DEFAULT_CAPACITY.pack())
.lock(self.alice_lock_script.clone())
.type_(Some(self.verifier_type_script.clone()).pack())
.build(),
Bytes::new(),
);
let verifier_input = packed::CellInput::new_builder()
.previous_output(verifier_cell)
.build();
let verifier_output = packed::CellOutput::new_builder()
.capacity(DEFAULT_CAPACITY.pack())
.lock(self.alice_lock_script.clone())
.build();

TransactionBuilder::default()
.inputs(vec![dao_input, verifier_input])
.outputs(vec![dao_output, verifier_output])
.outputs_data([dao_output_spec.data, Bytes::new()].pack())
.witnesses([Bytes::new(), Bytes::new()].pack())
}

fn complete_tx(&mut self, tx: TransactionView) -> TransactionView {
self.context.complete_tx(tx)
}
}

#[derive(Default)]
pub struct CustomTxSpec {
inner: DefaultTxSpec,
on_new_input_spec: Option<Box<dyn Fn(CellSpec) -> CellSpec>>,
on_new_output_spec: Option<Box<dyn Fn(CellSpec) -> CellSpec>>,
on_new_tx_builder: Option<Box<dyn Fn(TransactionBuilder) -> TransactionBuilder>>,
}

impl TxSpec for CustomTxSpec {
fn new_dao_input_spec(&mut self) -> CellSpec {
let cell_spec = self.inner.new_dao_input_spec();
match &self.on_new_input_spec {
Some(cb) => cb(cell_spec),
None => cell_spec,
}
}
fn new_dao_output_spec(&mut self) -> CellSpec {
let cell_spec = self.inner.new_dao_output_spec();
match &self.on_new_output_spec {
Some(cb) => cb(cell_spec),
None => cell_spec,
}
}

fn new_tx_builder(
&mut self,
dao_input_spec: CellSpec,
dao_output_spec: CellSpec,
) -> TransactionBuilder {
let tx_builder = self.inner.new_tx_builder(dao_input_spec, dao_output_spec);
match &self.on_new_tx_builder {
Some(cb) => cb(tx_builder),
None => tx_builder,
}
}

fn complete_tx(&mut self, tx: TransactionView) -> TransactionView {
self.inner.complete_tx(tx)
}
}

impl CustomTxSpec {
pub fn on_new_tx_builder<F>(&mut self, cb: F) -> &mut Self
where
F: Fn(TransactionBuilder) -> TransactionBuilder + 'static,
{
self.on_new_tx_builder = Some(Box::new(cb));
self
}

pub fn on_new_input_spec<F>(&mut self, cb: F) -> &mut Self
where
F: Fn(CellSpec) -> CellSpec + 'static,
{
self.on_new_input_spec = Some(Box::new(cb));
self
}

pub fn on_new_output_spec<F>(&mut self, cb: F) -> &mut Self
where
F: Fn(CellSpec) -> CellSpec + 'static,
{
self.on_new_output_spec = Some(Box::new(cb));
self
}
}
Loading

0 comments on commit e0775b2

Please sign in to comment.