Skip to content

Commit

Permalink
✨ Verify deposit from and claim to
Browse files Browse the repository at this point in the history
  • Loading branch information
doitian committed Feb 2, 2024
1 parent 8d05e83 commit c4bcbab
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 92 deletions.
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ contract: schemas
make -f contracts.mk build
make CARGO_ARGS="${CARGO_TEST_ARGS}" -f contracts.mk test

contract-linters:
make -f contracts.mk fmt
make -f contracts.mk check
make -f contracts.mk clippy

SCHEMA_MOL_FILES := $(wildcard schemas/*.mol)
SCHEMA_RUST_FILES := $(patsubst %.mol,crates/ckb-dao-cobuild-schemas/src/%.rs,$(SCHEMA_MOL_FILES))
crates/ckb-dao-cobuild-schemas/src/%.rs: %.mol
Expand All @@ -20,4 +25,4 @@ schemas: $(SCHEMA_RUST_FILES)
clean-schemas:
rm -f $(SCHEMA_RUST_FILES)

.PHONY: all web contract schemas clean-schemas
.PHONY: all web contract contract-linters schemas clean-schemas
9 changes: 9 additions & 0 deletions contracts/dao-action-verifier/src/dao.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use ckb_std::{ckb_constants::Source, high_level::load_cell_data};

use crate::constants::DAO_DEPOSIT_DATA;

pub 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)
}
119 changes: 119 additions & 0 deletions contracts/dao-action-verifier/src/derived_ckb_accounting.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use alloc::collections::BTreeMap;
use ckb_dao_cobuild_schemas::{Capacity, DaoActionData};
use ckb_std::{
ckb_constants::Source,
ckb_types::prelude::*,
high_level::{load_cell, load_input_out_point, QueryIter},
};

use crate::{
constants::DAO_TYPE_SCRIPT,
dao::is_deposit_cell,
error::Error,
error_code::ErrorCode,
keys::{AccountingKey, CellPointerKey},
trace_error,
};

#[derive(Default, Debug)]
pub struct DerivedCkbAccounting {
inputs: BTreeMap<AccountingKey, u128>,
outputs: BTreeMap<AccountingKey, u128>,
// cache componsations for deposit from
componsations: BTreeMap<CellPointerKey, u64>,
}

fn unpack_capacity(capacity: Capacity) -> u64 {
let mut buf = [0u8; 8];
buf.copy_from_slice(capacity.as_slice());
u64::from_le_bytes(buf)
}

#[allow(clippy::mutable_key_type)]
fn tally_input(key: &AccountingKey, componsations: &BTreeMap<CellPointerKey, u64>) -> u128 {
// Exclude deposit cells, which must go to the corresponding withdraw cell.
QueryIter::new(load_cell, Source::Input)
.enumerate()
.filter(|(index, cell_output)| {
cell_output.lock().as_slice() == key.as_slice()
&& !(cell_output.type_().as_slice() == DAO_TYPE_SCRIPT
&& is_deposit_cell(*index, Source::Input))
})
.fold(0u128, |acc, (index, cell_output)| {
let componsation = if cell_output.type_().as_slice() == DAO_TYPE_SCRIPT {
let key = load_input_out_point(index, Source::Input)
.expect("load input")
.into();
componsations
.get(&key)
.copied()
.expect("componsation exists")
} else {
0
};
acc + cell_output.capacity().unpack() as u128 + componsation as u128
})
}

fn tally_output(key: &AccountingKey) -> u128 {
// Exclude withdraw cells, which must come from the corresponding deposit cell.
QueryIter::new(load_cell, Source::Output)
.enumerate()
.filter(|(index, cell_output)| {
cell_output.lock().as_slice() == key.as_slice()
&& (cell_output.type_().as_slice() != DAO_TYPE_SCRIPT
|| is_deposit_cell(*index, Source::Output))
})
.fold(0u128, |acc, (_index, cell_output)| {
acc + cell_output.capacity().unpack() as u128
})
}

impl DerivedCkbAccounting {
pub fn derive(&mut self, data: &DaoActionData) {
for deposit in data.deposits().into_iter() {
let total = self.inputs.entry(deposit.from().into()).or_default();
*total += unpack_capacity(deposit.amount()) as u128;
}
for claim in data.claims().into_iter() {
let componsation = unpack_capacity(claim.withdraw_info().componsation_amount());

let total = self.outputs.entry(claim.to().into()).or_default();
*total += unpack_capacity(claim.deposit_info().amount()) as u128 + componsation as u128;

self.componsations
.insert((&claim.cell_pointer()).into(), componsation);
}
}

pub fn verify(self) -> Result<(), Error> {
#[allow(clippy::mutable_key_type)]
let componsations = self.componsations;
for (key, expected) in self.inputs.into_iter() {
let actual = tally_input(&key, &componsations);
if actual < expected {
return Err(trace_error!(
ErrorCode::InsufficientDepositFrom,
"expect deposit {} from {:?}, got {}",
expected,
key,
actual
));
}
}
for (key, expected) in self.outputs.into_iter() {
let actual = tally_output(&key);
if actual < expected {
return Err(trace_error!(
ErrorCode::InsufficientClaimTo,
"expect claim {} to {:?}, got {}",
expected,
key,
actual
));
}
}

Ok(())
}
}
93 changes: 7 additions & 86 deletions contracts/dao-action-verifier/src/derived_dao_action_data.rs
Original file line number Diff line number Diff line change
@@ -1,44 +1,32 @@
//! Derive dao action from tx.
use core::cmp;

use alloc::collections::BTreeMap;
use ckb_dao_cobuild_schemas::{Claim, DaoActionData, Deposit, OutPoint, Withdraw};
use ckb_dao_cobuild_schemas::{Claim, DaoActionData, Deposit, Withdraw};
use ckb_std::{
ckb_constants::Source,
ckb_types::{bytes::Bytes, packed, prelude::*},
ckb_types::{packed, prelude::*},
error::SysError,
high_level::{
load_cell, load_cell_data, load_cell_occupied_capacity, load_header, load_input_out_point,
load_cell, load_cell_occupied_capacity, load_header, load_input_out_point,
load_witness_args, QueryIter,
},
since::EpochNumberWithFraction,
};

use crate::{
constants::{DAO_DEPOSIT_DATA, DAO_TYPE_SCRIPT},
constants::DAO_TYPE_SCRIPT,
dao::is_deposit_cell,
error::Error,
error_code::ErrorCode,
keys::{ClaimKey, DepositKey, WithdrawKey},
trace_error,
};

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

#[derive(Debug, Eq, PartialEq)]
pub struct WithdrawKey {
cell_pointer: Bytes,
}

#[derive(Debug)]
pub struct WithdrawValue {
index: usize,
input_cell_output: packed::CellOutput,
}

pub type ClaimKey = WithdrawKey;
pub type ClaimValue = WithdrawValue;

#[derive(Default, Debug)]
Expand All @@ -48,12 +36,6 @@ pub struct DerivedDaoActionData {
claims: BTreeMap<ClaimKey, ClaimValue>,
}

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)
}

fn load_header_from_witness(witness: &[u8]) -> Result<packed::RawHeader, Error> {
if witness.len() != 8 {
return Err(trace_error!(
Expand Down Expand Up @@ -195,7 +177,7 @@ impl DerivedDaoActionData {
Ok(())
}

pub fn verify(&mut self, dao_action_data: DaoActionData) -> Result<(), Error> {
pub fn verify(&mut self, dao_action_data: &DaoActionData) -> Result<(), Error> {
for deposit in dao_action_data.deposits().into_iter() {
self.verify_deposit(deposit)?;
}
Expand Down Expand Up @@ -502,64 +484,3 @@ impl DerivedDaoActionData {
}
}
}

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> {
Some(self.cmp(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,
}
}
}

impl From<packed::OutPoint> for WithdrawKey {
fn from(value: packed::OutPoint) -> Self {
Self {
cell_pointer: value.as_bytes(),
}
}
}

impl From<&OutPoint> for WithdrawKey {
fn from(value: &OutPoint) -> Self {
Self {
cell_pointer: value.as_bytes(),
}
}
}

impl PartialOrd for WithdrawKey {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}

impl Ord for WithdrawKey {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.cell_pointer.cmp(&other.cell_pointer)
}
}
2 changes: 2 additions & 0 deletions contracts/dao-action-verifier/src/error_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ pub enum ErrorCode {
NotFound = 67,
NotMatched = 68,
InvalidHeaderDepIndex = 69,
InsufficientDepositFrom = 70,
InsufficientClaimTo = 71,
}
Loading

0 comments on commit c4bcbab

Please sign in to comment.