diff --git a/Makefile b/Makefile index 5b767b7..ccb8f10 100644 --- a/Makefile +++ b/Makefile @@ -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 @@ -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 diff --git a/contracts/__tests__/src/lib.rs b/contracts/__tests__/src/lib.rs index 9e0ff17..91b889c 100644 --- a/contracts/__tests__/src/lib.rs +++ b/contracts/__tests__/src/lib.rs @@ -181,6 +181,7 @@ pub struct DefaultTxSpec { alice_lock_script: packed::Script, + verifier_lock_script: packed::Script, verifier_type_script: packed::Script, dao_type_script: packed::Script, } @@ -233,6 +234,9 @@ impl DefaultTxSpec { // verifier cell let verifier_out_point = context.deploy_cell(loader.load_binary("dao-action-verifier")); + let verifier_lock_script = context + .build_script(&always_success_out_point, Default::default()) + .expect("script"); let verifier_type_script = context .build_script(&verifier_out_point, Default::default()) .expect("script"); @@ -241,6 +245,7 @@ impl DefaultTxSpec { context, dao_input_out_point, alice_lock_script, + verifier_lock_script, verifier_type_script, dao_type_script, } @@ -321,6 +326,12 @@ pub fn pack_ar(ar: u64) -> packed::Byte32 { packed::Byte32::new_unchecked(Bytes::from(dao_buf)) } +pub fn unpack_ar(dao: packed::Byte32) -> u64 { + let mut ar_buf = [0u8; 8]; + ar_buf.copy_from_slice(&dao.as_slice()[8..16]); + u64::from_le_bytes(ar_buf) +} + impl TxSpec for DefaultTxSpec { fn new_dao_input_spec(&mut self) -> CellSpec { CellSpec { @@ -362,7 +373,7 @@ impl TxSpec for DefaultTxSpec { let verifier_cell = self.context.create_cell( packed::CellOutput::new_builder() .capacity(DEFAULT_CAPACITY.pack()) - .lock(self.alice_lock_script.clone()) + .lock(self.verifier_lock_script.clone()) .type_(Some(self.verifier_type_script.clone()).pack()) .build(), Bytes::new(), @@ -372,7 +383,7 @@ impl TxSpec for DefaultTxSpec { .build(); let verifier_output = packed::CellOutput::new_builder() .capacity(DEFAULT_CAPACITY.pack()) - .lock(self.alice_lock_script.clone()) + .lock(self.verifier_lock_script.clone()) .build(); TransactionBuilder::default() @@ -528,6 +539,14 @@ where let mut spec = CustomTxSpec::default(); let claim = claim_builder(&mut spec); + let deposit_ar = unpack_ar(deposit_header.dao()); + let withdraw_ar = unpack_ar(withdraw_header.dao()); + let componsation = if deposit_ar != 0 { + (DEFAULT_CAPACITY - DAO_INPUT_OCCUPIED_CAPACITY) * (withdraw_ar - deposit_ar) / deposit_ar + } else { + 0 + }; + let deposit_header_hash = deposit_header.hash(); let deposit_block_number = pack_uint64(deposit_header.number()); let withdraw_header_hash = withdraw_header.hash(); @@ -556,8 +575,11 @@ where data: deposit_block_number.as_bytes(), ..cell }); - spec.on_new_output_spec(|cell| CellSpec { - output: cell.output.type_(packed::ScriptOpt::default()), + spec.on_new_output_spec(move |cell| CellSpec { + output: cell + .output + .capacity((DEFAULT_CAPACITY + componsation).pack()) + .type_(packed::ScriptOpt::default()), data: Bytes::new(), ..cell }); diff --git a/contracts/__tests__/src/tests.rs b/contracts/__tests__/src/tests.rs index df15a80..6c5fcdd 100644 --- a/contracts/__tests__/src/tests.rs +++ b/contracts/__tests__/src/tests.rs @@ -971,6 +971,7 @@ fn test_dao_claim_componsation_amount_not_matched() { #[test] fn test_dao_claim_pass() { + let componsation = (DEFAULT_CAPACITY - DAO_INPUT_OCCUPIED_CAPACITY) / 10; let mut spec = create_claim_spec( new_header_builder(1, 100).dao(pack_ar(100)).build(), new_header_builder(2, 100).dao(pack_ar(110)).build(), @@ -994,15 +995,311 @@ fn test_dao_claim_pass() { .withdraw_info( WithdrawInfo::new_builder() .withdraw_block_number(pack_uint64(2)) - .componsation_amount(pack_capacity( - (DEFAULT_CAPACITY - DAO_INPUT_OCCUPIED_CAPACITY) / 10, - )) + .componsation_amount(pack_capacity(componsation)) + .build(), + ) + .build() + }, + ); + + let tx = build_tx(&mut spec); + verify_and_dump_failed_tx(&spec.inner.context, &tx, MAX_CYCLES).expect("pass"); +} + +#[test] +fn test_dao_insufficient_deposit_from() { + 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_input_spec(|cell| CellSpec { + output: cell.output.capacity((DEFAULT_CAPACITY - 1).pack()), + ..cell + }); + 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::InsufficientDepositFrom, + MAX_CYCLES, + ); +} + +#[test] +fn test_dao_insufficient_claim_to() { + let componsation = (DEFAULT_CAPACITY - DAO_INPUT_OCCUPIED_CAPACITY) / 10; + let mut spec = create_claim_spec( + new_header_builder(1, 100).dao(pack_ar(100)).build(), + new_header_builder(2, 100).dao(pack_ar(110)).build(), + |spec| { + Claim::new_builder() + .cell_pointer(OutPoint::new_unchecked( + spec.inner.dao_input_out_point.as_bytes(), + )) + .from(Address::new_unchecked( + spec.inner.alice_lock_script.as_bytes(), + )) + .to(Address::new_unchecked( + spec.inner.alice_lock_script.as_bytes(), + )) + .deposit_info( + DepositInfo::new_builder() + .deposit_block_number(pack_uint64(1)) + .amount(pack_capacity(DEFAULT_CAPACITY)) + .build(), + ) + .withdraw_info( + WithdrawInfo::new_builder() + .withdraw_block_number(pack_uint64(2)) + .componsation_amount(pack_capacity(componsation)) .build(), ) .build() }, ); + spec.on_new_output_spec(move |cell| CellSpec { + output: cell + .output + .capacity((DEFAULT_CAPACITY + componsation - 1).pack()) + .type_(packed::ScriptOpt::default()), + data: Bytes::new(), + ..cell + }); + + let tx = build_tx(&mut spec); + assert_tx_error( + &spec.inner.context, + &tx, + ErrorCode::InsufficientClaimTo, + MAX_CYCLES, + ); +} + +#[test] +fn test_dao_claim_to_deposit() { + let componsation = (DEFAULT_CAPACITY - DAO_INPUT_OCCUPIED_CAPACITY) / 10; + + let mut spec = create_claim_spec( + new_header_builder(1, 100).dao(pack_ar(100)).build(), + new_header_builder(2, 100).dao(pack_ar(110)).build(), + |_spec| Claim::default(), + ); + spec.on_new_output_spec(move |cell| CellSpec { + output: cell + .output + .capacity((DEFAULT_CAPACITY + componsation).pack()), + ..cell + }); + + let claim = Claim::new_builder() + .cell_pointer(OutPoint::new_unchecked( + spec.inner.dao_input_out_point.as_bytes(), + )) + .from(Address::new_unchecked( + spec.inner.alice_lock_script.as_bytes(), + )) + .to(Address::new_unchecked( + spec.inner.alice_lock_script.as_bytes(), + )) + .deposit_info( + DepositInfo::new_builder() + .deposit_block_number(pack_uint64(1)) + .amount(pack_capacity(DEFAULT_CAPACITY)) + .build(), + ) + .withdraw_info( + WithdrawInfo::new_builder() + .withdraw_block_number(pack_uint64(2)) + .componsation_amount(pack_capacity(componsation)) + .build(), + ) + .build(); + let deposit = 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 + componsation)) + .build(); + + let witnesses = vec![ + packed::WitnessArgs::new_builder() + .input_type(Some(Bytes::from(0u64.to_le_bytes().to_vec())).pack()) + .build() + .as_bytes(), + Bytes::new(), + spec.inner + .pack_dao_operations(vec![deposit], vec![], vec![claim]), + ]; + let remember_on_new_tx_builder = spec.on_new_tx_builder.take().unwrap(); + spec.on_new_tx_builder(move |b| { + remember_on_new_tx_builder(b) + .set_witnesses(vec![]) + .witnesses(witnesses.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_claim_to_deposit_insufficient_claim_to() { + let componsation = (DEFAULT_CAPACITY - DAO_INPUT_OCCUPIED_CAPACITY) / 10; + + let mut spec = create_claim_spec( + new_header_builder(1, 100).dao(pack_ar(100)).build(), + new_header_builder(2, 100).dao(pack_ar(110)).build(), + |_spec| Claim::default(), + ); + spec.on_new_output_spec(move |cell| CellSpec { + output: cell + .output + .capacity((DEFAULT_CAPACITY + componsation - 1).pack()), + ..cell + }); + + let claim = Claim::new_builder() + .cell_pointer(OutPoint::new_unchecked( + spec.inner.dao_input_out_point.as_bytes(), + )) + .from(Address::new_unchecked( + spec.inner.alice_lock_script.as_bytes(), + )) + .to(Address::new_unchecked( + spec.inner.alice_lock_script.as_bytes(), + )) + .deposit_info( + DepositInfo::new_builder() + .deposit_block_number(pack_uint64(1)) + .amount(pack_capacity(DEFAULT_CAPACITY)) + .build(), + ) + .withdraw_info( + WithdrawInfo::new_builder() + .withdraw_block_number(pack_uint64(2)) + .componsation_amount(pack_capacity(componsation)) + .build(), + ) + .build(); + let deposit = 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 + componsation - 1)) + .build(); + + let witnesses = vec![ + packed::WitnessArgs::new_builder() + .input_type(Some(Bytes::from(0u64.to_le_bytes().to_vec())).pack()) + .build() + .as_bytes(), + Bytes::new(), + spec.inner + .pack_dao_operations(vec![deposit], vec![], vec![claim]), + ]; + let remember_on_new_tx_builder = spec.on_new_tx_builder.take().unwrap(); + spec.on_new_tx_builder(move |b| { + remember_on_new_tx_builder(b) + .set_witnesses(vec![]) + .witnesses(witnesses.clone().pack()) + }); + + let tx = build_tx(&mut spec); + assert_tx_error( + &spec.inner.context, + &tx, + ErrorCode::InsufficientClaimTo, + MAX_CYCLES, + ); +} + +#[test] +fn test_dao_claim_to_deposit_insufficient_deposit_from() { + let componsation = (DEFAULT_CAPACITY - DAO_INPUT_OCCUPIED_CAPACITY) / 10; + + let mut spec = create_claim_spec( + new_header_builder(1, 100).dao(pack_ar(100)).build(), + new_header_builder(2, 100).dao(pack_ar(110)).build(), + |_spec| Claim::default(), + ); + spec.on_new_output_spec(move |cell| CellSpec { + output: cell + .output + .capacity((DEFAULT_CAPACITY + componsation + 1).pack()), + ..cell + }); + + let claim = Claim::new_builder() + .cell_pointer(OutPoint::new_unchecked( + spec.inner.dao_input_out_point.as_bytes(), + )) + .from(Address::new_unchecked( + spec.inner.alice_lock_script.as_bytes(), + )) + .to(Address::new_unchecked( + spec.inner.alice_lock_script.as_bytes(), + )) + .deposit_info( + DepositInfo::new_builder() + .deposit_block_number(pack_uint64(1)) + .amount(pack_capacity(DEFAULT_CAPACITY)) + .build(), + ) + .withdraw_info( + WithdrawInfo::new_builder() + .withdraw_block_number(pack_uint64(2)) + .componsation_amount(pack_capacity(componsation)) + .build(), + ) + .build(); + let deposit = 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 + componsation + 1)) + .build(); + + let witnesses = vec![ + packed::WitnessArgs::new_builder() + .input_type(Some(Bytes::from(0u64.to_le_bytes().to_vec())).pack()) + .build() + .as_bytes(), + Bytes::new(), + spec.inner + .pack_dao_operations(vec![deposit], vec![], vec![claim]), + ]; + let remember_on_new_tx_builder = spec.on_new_tx_builder.take().unwrap(); + spec.on_new_tx_builder(move |b| { + remember_on_new_tx_builder(b) + .set_witnesses(vec![]) + .witnesses(witnesses.clone().pack()) + }); + + let tx = build_tx(&mut spec); + assert_tx_error( + &spec.inner.context, + &tx, + ErrorCode::InsufficientDepositFrom, + MAX_CYCLES, + ); +} diff --git a/contracts/dao-action-verifier/src/dao.rs b/contracts/dao-action-verifier/src/dao.rs new file mode 100644 index 0000000..3ba506a --- /dev/null +++ b/contracts/dao-action-verifier/src/dao.rs @@ -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) +} diff --git a/contracts/dao-action-verifier/src/derived_ckb_accounting.rs b/contracts/dao-action-verifier/src/derived_ckb_accounting.rs new file mode 100644 index 0000000..9c30682 --- /dev/null +++ b/contracts/dao-action-verifier/src/derived_ckb_accounting.rs @@ -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, + outputs: BTreeMap, + // cache componsations for deposit from + componsations: BTreeMap, +} + +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) -> 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(()) + } +} diff --git a/contracts/dao-action-verifier/src/derived_dao_action_data.rs b/contracts/dao-action-verifier/src/derived_dao_action_data.rs index 9ba3729..b2a57d6 100644 --- a/contracts/dao-action-verifier/src/derived_dao_action_data.rs +++ b/contracts/dao-action-verifier/src/derived_dao_action_data.rs @@ -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)] @@ -48,12 +36,6 @@ pub struct DerivedDaoActionData { claims: BTreeMap, } -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 { if witness.len() != 8 { return Err(trace_error!( @@ -196,7 +178,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)?; } @@ -503,64 +485,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 { - 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 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 { - Some(self.cmp(other)) - } -} - -impl Ord for WithdrawKey { - fn cmp(&self, other: &Self) -> cmp::Ordering { - self.cell_pointer.cmp(&other.cell_pointer) - } -} diff --git a/contracts/dao-action-verifier/src/error_code.rs b/contracts/dao-action-verifier/src/error_code.rs index f80f7b7..a6f3487 100644 --- a/contracts/dao-action-verifier/src/error_code.rs +++ b/contracts/dao-action-verifier/src/error_code.rs @@ -15,4 +15,6 @@ pub enum ErrorCode { NotFound = 67, NotMatched = 68, InvalidHeaderDepIndex = 69, + InsufficientDepositFrom = 70, + InsufficientClaimTo = 71, } diff --git a/contracts/dao-action-verifier/src/keys.rs b/contracts/dao-action-verifier/src/keys.rs new file mode 100644 index 0000000..683036b --- /dev/null +++ b/contracts/dao-action-verifier/src/keys.rs @@ -0,0 +1,110 @@ +use core::cmp; + +use ckb_dao_cobuild_schemas::{Address, Deposit, OutPoint as DaoActionOutPoint}; +use ckb_std::ckb_types::{bytes::Bytes, packed, prelude::*}; + +#[derive(Debug, Eq, PartialEq)] +pub struct DepositKey { + to: Bytes, + amount: Bytes, +} + +#[derive(Debug, Eq, PartialEq)] +pub struct CellPointerKey { + cell_pointer: Bytes, +} + +pub type WithdrawKey = CellPointerKey; +pub type ClaimKey = CellPointerKey; + +#[derive(Debug, Eq, PartialEq)] +pub struct AccountingKey { + address: Bytes, +} + +impl AccountingKey { + pub fn as_slice(&self) -> &[u8] { + self.address.as_ref() + } +} + +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 { + 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 for CellPointerKey { + fn from(value: packed::OutPoint) -> Self { + Self { + cell_pointer: value.as_bytes(), + } + } +} + +impl From<&DaoActionOutPoint> for CellPointerKey { + fn from(value: &DaoActionOutPoint) -> Self { + Self { + cell_pointer: value.as_bytes(), + } + } +} + +impl PartialOrd for CellPointerKey { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for CellPointerKey { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.cell_pointer.cmp(&other.cell_pointer) + } +} + +impl From
for AccountingKey { + fn from(value: Address) -> Self { + Self { + address: value.as_bytes(), + } + } +} + +impl PartialOrd for AccountingKey { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for AccountingKey { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.address.cmp(&other.address) + } +} diff --git a/contracts/dao-action-verifier/src/main.rs b/contracts/dao-action-verifier/src/main.rs index fc5e897..4207c09 100644 --- a/contracts/dao-action-verifier/src/main.rs +++ b/contracts/dao-action-verifier/src/main.rs @@ -16,12 +16,16 @@ use ckb_std::ckb_types::{bytes::Bytes, prelude::*}; use ckb_transaction_cobuild::fetch_message; mod constants; +mod dao; +mod derived_ckb_accounting; mod derived_dao_action_data; mod error; mod error_code; +mod keys; use crate::{ - constants::DAO_SCRIPT_HASH, derived_dao_action_data::DerivedDaoActionData, error::Error, + constants::DAO_SCRIPT_HASH, derived_ckb_accounting::DerivedCkbAccounting, + derived_dao_action_data::DerivedDaoActionData, error::Error, }; pub fn program_entry() -> i8 { @@ -34,18 +38,21 @@ pub fn program_entry() -> i8 { fn verify() -> Result<(), Error> { if let Ok(Some(message)) = fetch_message() { let mut derived_dao_action_data = DerivedDaoActionData::derive(); + let mut derived_ckb_accounting = DerivedCkbAccounting::default(); for action in message.actions().into_iter() { if action.script_hash().as_slice() == DAO_SCRIPT_HASH { let dao_action_data = decode_dao_action_data(action.data().raw_data())?; - derived_dao_action_data.verify(dao_action_data)?; + derived_dao_action_data.verify(&dao_action_data)?; + derived_ckb_accounting.derive(&dao_action_data); } } - derived_dao_action_data.complete() - } else { - Ok(()) + derived_dao_action_data.complete()?; + derived_ckb_accounting.verify()?; } + + Ok(()) } fn decode_dao_action_data(data: Bytes) -> Result {