Skip to content

Commit

Permalink
✨ Verify deposit to
Browse files Browse the repository at this point in the history
  • Loading branch information
doitian committed Feb 9, 2024
1 parent 6c87c26 commit 3ecbe54
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 12 deletions.
55 changes: 54 additions & 1 deletion contracts/__tests__/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// See https://github.com/xxuejie/ckb-native-build-sample/blob/main/tests/src/tests.rs for examples

use crate::*;
use ckb_dao_cobuild_schemas::DaoActionData;
use ckb_dao_cobuild_schemas::{Address, DaoActionData};

const MAX_CYCLES: u64 = 10_000_000;

Expand Down Expand Up @@ -63,3 +63,56 @@ fn test_multiple_dao_actions() {
let tx = build_tx(&mut spec);
verify_and_dump_failed_tx(&spec.inner.context, &tx, MAX_CYCLES).expect("pass");
}

#[test]
fn test_dao_deposit_single() {
let mut spec = CustomTxSpec::default();
let witness = spec.inner.pack_dao_operations(
vec![Deposit::new_builder()
.from(Address::new_unchecked(
spec.inner.alice_lock_script.as_bytes(),
))
.to(Address::new_unchecked(
spec.inner.alice_lock_script.as_bytes(),
))
.amount(pack_capacity(DEFAULT_CAPACITY))
.build()],
vec![],
vec![],
);
spec.on_new_tx_builder(move |b| b.witness(witness.clone().pack()));

let tx = build_tx(&mut spec);
verify_and_dump_failed_tx(&spec.inner.context, &tx, MAX_CYCLES).expect("pass");
}

#[test]
fn test_dao_deposit_not_found() {
let mut spec = CustomTxSpec::default();
let witness = spec.inner.pack_dao_operations(
vec![Deposit::new_builder()
.from(Address::new_unchecked(
spec.inner.alice_lock_script.as_bytes(),
))
.to(Address::new_unchecked(
spec.inner.alice_lock_script.as_bytes(),
))
.build()],
vec![],
vec![],
);
spec.on_new_tx_builder(move |b| b.witness(witness.clone().pack()));

let tx = build_tx(&mut spec);
assert_tx_error(&spec.inner.context, &tx, ErrorCode::NotFound, MAX_CYCLES);
}

#[test]
fn test_dao_deposit_not_coverred() {
let mut spec = CustomTxSpec::default();
let witness = spec.inner.pack_dao_operations(vec![], vec![], vec![]);
spec.on_new_tx_builder(move |b| b.witness(witness.clone().pack()));

let tx = build_tx(&mut spec);
assert_tx_error(&spec.inner.context, &tx, ErrorCode::NotCoverred, MAX_CYCLES);
}
13 changes: 13 additions & 0 deletions contracts/dao-action-verifier/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,16 @@ pub const DAO_SCRIPT_HASH: [u8; 32] = [
204, 119, 196, 222, 172, 5, 214, 138, 181, 178, 104, 40, 240, 191, 69, 101, 168, 215, 49, 19,
215, 187, 126, 146, 184, 54, 43, 138, 116, 229, 142, 88,
];

// molecule encoding of the dao type script
// - code_hash: "0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"
// - hash_type: "type"
// - args: "0x"
pub const DAO_TYPE_SCRIPT: [u8; 53] = [
0x35, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00,
0x82, 0xd7, 0x6d, 0x1b, 0x75, 0xfe, 0x2f, 0xd9, 0xa2, 0x7d, 0xfb, 0xaa, 0x65, 0xa0, 0x39, 0x22,
0x1a, 0x38, 0x0d, 0x76, 0xc9, 0x26, 0xf3, 0x78, 0xd3, 0xf8, 0x1c, 0xf3, 0xe7, 0xe1, 0x3f, 0x2e,
0x01, 0x00, 0x00, 0x00, 0x00,
];

pub const DAO_DEPOSIT_DATA: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0];
118 changes: 118 additions & 0 deletions contracts/dao-action-verifier/src/derived_dao_action_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//! Derive dao action from tx.
use core::cmp;

use alloc::collections::BTreeMap;
use ckb_dao_cobuild_schemas::{DaoActionData, Deposit};
use ckb_std::{
ckb_constants::Source,
ckb_types::{bytes::Bytes, packed, prelude::*},
high_level::{load_cell, load_cell_data, QueryIter},
};

use crate::{
constants::{DAO_DEPOSIT_DATA, DAO_TYPE_SCRIPT},
error::Error,
error_code::ErrorCode,
trace_error,
};

#[derive(Debug, Eq, PartialEq)]
pub struct DepositKey {
to: Bytes,
amount: Bytes,
}

#[derive(Default, Debug)]
pub struct DerivedDaoActionData {
deposits: BTreeMap<DepositKey, usize>,
}

fn is_deposit_cell(index: usize, source: Source) -> bool {
load_cell_data(index, source)
.map(|data| data.as_ref() == DAO_DEPOSIT_DATA)
.unwrap_or(false)
}

impl DerivedDaoActionData {
/// Derive dao action from the tx.
pub fn derive() -> Self {
#[allow(clippy::mutable_key_type)]
let deposits = QueryIter::new(load_cell, Source::Output)
.enumerate()
.filter(|(index, cell_output)| {
cell_output.type_().as_slice() == DAO_TYPE_SCRIPT
&& is_deposit_cell(*index, Source::Output)
})
.map(|(index, cell_output)| ((&cell_output).into(), index))
.collect();

Self { deposits }
}

/// Ensure all derived operations have been found in tx.
pub fn complete(self) -> Result<(), Error> {
match self.deposits.into_values().next() {
Some(index) => Err(trace_error!(
ErrorCode::NotCoverred,
"tx output {} not coverred by dao action",
index
)),
None => Ok(()),
}
}

pub fn verify(&mut self, dao_action_data: DaoActionData) -> Result<(), Error> {
for deposit in dao_action_data.deposits().into_iter() {
self.verify_deposit(deposit)?;
}

Ok(())
}

fn verify_deposit(&mut self, deposit: Deposit) -> Result<(), Error> {
match self.deposits.remove(&(&deposit).into()) {
Some(_) => Ok(()),
None => Err(trace_error!(
ErrorCode::NotFound,
"deposit not found in tx: {}",
deposit
)),
}
}
}

impl From<&packed::CellOutput> for DepositKey {
fn from(value: &packed::CellOutput) -> Self {
Self {
to: value.lock().as_bytes(),
amount: value.capacity().as_bytes(),
}
}
}

impl From<&Deposit> for DepositKey {
fn from(value: &Deposit) -> Self {
Self {
to: value.to().as_bytes(),
amount: value.amount().as_bytes(),
}
}
}

impl PartialOrd for DepositKey {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
match self.to.partial_cmp(&other.to) {
Some(cmp::Ordering::Equal) => self.amount.partial_cmp(&other.amount),
other => other,
}
}
}

impl Ord for DepositKey {
fn cmp(&self, other: &Self) -> cmp::Ordering {
match self.to.cmp(&other.to) {
cmp::Ordering::Equal => self.amount.cmp(&other.amount),
other => other,
}
}
}
14 changes: 14 additions & 0 deletions contracts/dao-action-verifier/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use ckb_std::error::SysError;
pub enum Error {
CkbStd(SysError),
InvalidActionDataSchema(molecule::error::VerificationError),
Custom(ErrorCode),
}

impl From<Error> for ErrorCode {
Expand All @@ -18,6 +19,7 @@ impl From<Error> for ErrorCode {
SysError::Unknown(_) => ErrorCode::Unknown,
},
Error::InvalidActionDataSchema(_) => ErrorCode::InvalidActionDataSchema,
Error::Custom(code) => code,
}
}
}
Expand All @@ -40,6 +42,12 @@ impl From<molecule::error::VerificationError> for Error {
}
}

impl From<ErrorCode> for Error {
fn from(code: ErrorCode) -> Self {
Error::Custom(code)
}
}

#[macro_export]
macro_rules! trace_error {
($err:expr) => {{
Expand All @@ -54,4 +62,10 @@ macro_rules! trace_error {
ckb_std::debug!("{}:{} {:?} {}", file!(), line!(), err, $message);
err
}};
($err:expr, $format:literal, $($args:expr),+) => {{
let err = $crate::error::Error::from($err);
#[cfg(debug_assertions)]
ckb_std::debug!("{}:{} {:?} {}", file!(), line!(), err, core::format_args!($format, $($args), +));
err
}};
}
5 changes: 4 additions & 1 deletion contracts/dao-action-verifier/src/error_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@ pub enum ErrorCode {
Unknown = 20,

// custom errors
InvalidActionDataSchema = 64,
DuplicatedActionData = 64,
InvalidActionDataSchema = 65,
NotCoverred = 66,
NotFound = 67,
}
27 changes: 17 additions & 10 deletions contracts/dao-action-verifier/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@ ckb_std::entry!(program_entry);
#[cfg(all(target_arch = "riscv64", not(test)))]
default_alloc!();

use ckb_dao_cobuild_schemas::DaoActionDataReader;
use ckb_std::ckb_types::prelude::*;
use ckb_dao_cobuild_schemas::{DaoActionData, DaoActionDataReader};
use ckb_std::ckb_types::{bytes::Bytes, prelude::*};
use ckb_transaction_cobuild::fetch_message;

mod constants;
mod derived_dao_action_data;
mod error;
mod error_code;

use crate::{constants::DAO_SCRIPT_HASH, error::Error};
use crate::{
constants::DAO_SCRIPT_HASH, derived_dao_action_data::DerivedDaoActionData, error::Error,
};

pub fn program_entry() -> i8 {
match verify() {
Expand All @@ -30,18 +33,22 @@ pub fn program_entry() -> i8 {

fn verify() -> Result<(), Error> {
if let Ok(Some(message)) = fetch_message() {
let mut derived_dao_action_data = DerivedDaoActionData::derive();

for action in message.actions().into_iter() {
if action.script_hash().as_slice() == DAO_SCRIPT_HASH {
verify_action_data(&action.data().raw_data())?;
let dao_action_data = decode_dao_action_data(action.data().raw_data())?;
derived_dao_action_data.verify(dao_action_data)?;
}
}
}

// It's OK to not include DAO action data
Ok(())
derived_dao_action_data.complete()
} else {
Ok(())
}
}

fn verify_action_data(data: &[u8]) -> Result<(), Error> {
DaoActionDataReader::from_slice(data).map_err(|err| trace_error!(err))?;
Ok(())
fn decode_dao_action_data(data: Bytes) -> Result<DaoActionData, Error> {
DaoActionDataReader::from_slice(&data).map_err(|err| trace_error!(err))?;
Ok(DaoActionData::new_unchecked(data))
}

0 comments on commit 3ecbe54

Please sign in to comment.