From 719f22f3f2bdf5073d2f29140648d22c695cd1af Mon Sep 17 00:00:00 2001 From: Xuejie Xiao Date: Thu, 24 Oct 2019 07:25:43 +0000 Subject: [PATCH 1/2] feat: Use 2 phase commit to solve DAO's lock period issue --- Cargo.lock | 2 +- Cargo.toml | 2 +- build.rs | 2 +- c/dao.c | 276 ++++++++++---- src/tests/dao.rs | 949 ++++++++++++++++++++++++++++++++++++++++++----- 5 files changed, 1066 insertions(+), 165 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8f23d85..1deeed1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -394,7 +394,7 @@ dependencies = [ [[package]] name = "ckb-system-scripts" -version = "0.4.0-alpha.13+nonstrict-witnesses-length2" +version = "0.4.0-alpha.14+2-phase-dao" dependencies = [ "blake2b-rs 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index e8a6a46..3cb228b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ckb-system-scripts" -version = "0.4.0-alpha.13+nonstrict-witnesses-length2" +version = "0.4.0-alpha.14+2-phase-dao" authors = ["Nervos Core Dev "] edition = "2018" build = "build.rs" diff --git a/build.rs b/build.rs index 05f6022..14482da 100644 --- a/build.rs +++ b/build.rs @@ -23,7 +23,7 @@ const BINARIES: &[(&str, &str)] = &[ ), ( "dao", - "007c21b2593e645f390b068a14f6941aa3382ec88edf594e8bffbbb4bbf444f5", + "516be0333273bbe12a723f3be583c524f0b6089326f89c49fc61e24d1f56be21", ), ( "secp256k1_blake160_multisig_all", diff --git a/c/dao.c b/c/dao.c index 909173c..79fc9d8 100644 --- a/c/dao.c +++ b/c/dao.c @@ -1,56 +1,6 @@ /* - * This file provides the type script for NervosDAO logic. To deposit - * to NervosDAO, one simply needs to create a new cell with this script - * as the type script(for the exact code hash to use please refer to - * the most current CKB version). While you can certainly used the official - * secp256k1-blake160 lock script to guard your cell, you are also free - * to use any other lock script, as long as the lock script satisfies the - * following conditions: - * - * 1. The lock script won't affected by the last witness argument. - * 2. The lock script ensures the last witness argument won't be tampered, - * one example to ensure this, is that the lock script can include this argument - * in signature calculation steps. - * - * No further actions are needed to keep your capacities locked in NervosDAO. - * - * To withdraw from NervosDAO, one needs to create a new transaction with - * the NervosDAO cell as one of the inputs. The OutPoint used to reference - * the NervosDAO cell should have block_hash correctly set, so this script - * can load the header of the deposit block which contains the locked NervosDAO - * cell. He/she should also specify an existing header denoted as the withdraw - * block. This script will calculate the interest from the deposit block to - * this withdraw block. - * The withdraw block should be included in one of the transaction deps using - * block_hash field. The index of the withdraw block in the deps field, should - * be serialized into 64-bit unsigned little endian integer, and append as the - * witness argument at the last index in the corresponding witness for the - * locked NervosDAO input. - * - * If the above steps feel confusing to you, you can also refer to one of our - * official CKB SDK to learn how to deposit to and withdraw from NervosDAO. - * - * NervosDAO relies on a special field in the block header named dao to provide - * needed statistic data to calculate NervosDAO interests. Specifically, 3 - * fields will be kept in DAO field in the block header: - * - * * AR: accumulated rate of NervosDAO - * * C: All issued capacities in CKB (not including current block) - * * U: All occupied capacities in CKB (including current block) - * - * Please refer to CKB implementation for how to calculate AR, C and U. - * - * To calculate the interest of NervosDAO, we first separate the capacities in - * the deposit cell as +free_capacity+ and +occupied_capacity+: Free capacity is - * calculated as the total capacity minus occupied capacity. Then the maximum - * capacity one can withdraw for the NervosDAO input is calculated as: - * - * occupied_capacity + free_capacity * AR_withdraw / AR_deposit - * - * Notice one is free to include normal inputs in a transaction containing - * NervosDAO inputs, he/she is also free to include multiple NervosDAO inputs - * in one transaction. This type script will calculate the correct total - * capacities in all cases. + * This file provides the type script for NervosDAO logic. Please refer to the + * Nervos DAO RFC on how to use this script. */ #include "ckb_syscalls.h" #include "protocol.h" @@ -66,6 +16,9 @@ #define ERROR_INCORRECT_CAPACITY -15 #define ERROR_INCORRECT_EPOCH -16 #define ERROR_INCORRECT_SINCE -17 +#define ERROR_TOO_MANY_OUTPUT_CELLS -18 +#define ERROR_NEWLY_CREATED_CELL -19 +#define ERROR_INVALID_WITHDRAWING_CELL -20 #define HASH_SIZE 32 #define HEADER_SIZE 4096 @@ -73,10 +26,12 @@ #define MAX_WITNESS_SIZE 32768 #define SCRIPT_SIZE 32768 -#define LOCK_PERIOD_BLOCKS 10 -#define MATURITY_BLOCKS 5 - -#define MAX(a, b) (((a) < (b)) ? (b) : (a)) +/* + * For simplicity, a transaction containing Nervos DAO script is limited to + * 64 output cells so we can simplify processing. Later we might upgrade this + * script to relax this limitation. + */ +#define MAX_OUTPUT_LENGTH 64 #define LOCK_PERIOD_EPOCHES 180 @@ -91,11 +46,11 @@ #define EPOCH_LENGTH_MASK ((1 << EPOCH_LENGTH_BITS) - 1) /* - * Fetch withdraw header hash from the last 8 bytes + * Fetch deposit header hash from the last 8 bytes * of witness. Kept as a separate function so witness buffer * can be cleaned as soon as it is not needed. */ -static int extract_withdraw_header_index(size_t input_index, size_t *index) { +static int extract_deposit_header_index(size_t input_index, size_t *index) { int ret; uint64_t len = 0; unsigned char witness[MAX_WITNESS_SIZE]; @@ -110,7 +65,7 @@ static int extract_withdraw_header_index(size_t input_index, size_t *index) { } mol_seg_t witness_seg; - witness_seg.ptr = (uint8_t*)witness; + witness_seg.ptr = (uint8_t *)witness; witness_seg.size = len; if (MolReader_WitnessArgs_verify(&witness_seg, false) != MOL_OK) { @@ -155,6 +110,7 @@ static int extract_epoch_info(uint64_t epoch, int allow_zero_epoch_length, } typedef struct { + uint64_t block_number; uint64_t epoch_number; uint64_t epoch_index; uint64_t epoch_length; @@ -184,7 +140,9 @@ static int load_dao_header_data(size_t index, size_t source, mol_seg_t raw_seg = MolReader_Header_get_raw(&header_seg); mol_seg_t dao_seg = MolReader_RawHeader_get_dao(&raw_seg); mol_seg_t epoch_seg = MolReader_RawHeader_get_epoch(&raw_seg); + mol_seg_t block_number_seg = MolReader_RawHeader_get_number(&raw_seg); + data->block_number = *((uint64_t *)block_number_seg.ptr); memcpy(data->dao, dao_seg.ptr, 32); return extract_epoch_info(*((uint64_t *)epoch_seg.ptr), 0, &(data->epoch_number), &(data->epoch_index), @@ -192,26 +150,30 @@ static int load_dao_header_data(size_t index, size_t source, } static int calculate_dao_input_capacity(size_t input_index, + uint64_t deposited_block_number, uint64_t original_capacity, uint64_t *calculated_capacity) { uint64_t len = 0; - size_t withdraw_index = 0; + size_t deposit_index = 0; - int ret = extract_withdraw_header_index(input_index, &withdraw_index); + int ret = extract_deposit_header_index(input_index, &deposit_index); if (ret != CKB_SUCCESS) { return ret; } - dao_header_data_t withdraw_data; - ret = load_dao_header_data(withdraw_index, CKB_SOURCE_HEADER_DEP, - &withdraw_data); + dao_header_data_t deposit_data; + ret = + load_dao_header_data(deposit_index, CKB_SOURCE_HEADER_DEP, &deposit_data); if (ret != CKB_SUCCESS) { return ret; } + /* deposited_block_number must match actual deposit block */ + if (deposited_block_number != deposit_data.block_number) { + return ERROR_INVALID_WITHDRAW_BLOCK; + } - dao_header_data_t deposit_data; - memset(&deposit_data, 0, sizeof(deposit_data)); // avoid mem2reg - ret = load_dao_header_data(input_index, CKB_SOURCE_INPUT, &deposit_data); + dao_header_data_t withdraw_data; + ret = load_dao_header_data(input_index, CKB_SOURCE_INPUT, &withdraw_data); if (ret != CKB_SUCCESS) { return ret; } @@ -328,6 +290,92 @@ static int calculate_dao_input_capacity(size_t input_index, return CKB_SUCCESS; } +/* + * For a newly generated withdrawing cell, the following conditions should + * be met: + * + * * withdrawing cell uses the same lock script as deposited cell + * * withdrawing cell uses Nervos DAO type script + * * withdrawing cell has the same capacity as the input deposited cell + * * withdrawing cell has an 8-byte long cell data, the content is the + * block number containing deposited cell in 64-bit little endian unsigned + * integer format. + */ +static int validate_withdrawing_cell(size_t index, uint64_t input_capacity, + unsigned char *dao_script_hash) { + unsigned char hash1[HASH_SIZE], hash2[HASH_SIZE]; + uint64_t len = HASH_SIZE; + /* Check lock script */ + int ret = ckb_load_cell_by_field(hash1, &len, 0, index, CKB_SOURCE_INPUT, + CKB_CELL_FIELD_LOCK_HASH); + if (ret != CKB_SUCCESS) { + return ret; + } + if (len != HASH_SIZE) { + return ERROR_SYSCALL; + } + len = HASH_SIZE; + ret = ckb_load_cell_by_field(hash2, &len, 0, index, CKB_SOURCE_OUTPUT, + CKB_CELL_FIELD_LOCK_HASH); + if (ret != CKB_SUCCESS) { + return ret; + } + if (len != HASH_SIZE) { + return ERROR_SYSCALL; + } + if (memcmp(hash1, hash2, HASH_SIZE) != 0) { + return ERROR_INVALID_WITHDRAWING_CELL; + } + /* Check type script */ + len = HASH_SIZE; + ret = ckb_load_cell_by_field(hash1, &len, 0, index, CKB_SOURCE_OUTPUT, + CKB_CELL_FIELD_TYPE_HASH); + if (ret != CKB_SUCCESS) { + return ret; + } + if (len != HASH_SIZE) { + return ERROR_SYSCALL; + } + if (memcmp(hash1, dao_script_hash, HASH_SIZE) != 0) { + return ERROR_INVALID_WITHDRAWING_CELL; + } + /* Check capacity */ + uint64_t output_capacity = 0; + len = 8; + ret = + ckb_load_cell_by_field((unsigned char *)&output_capacity, &len, 0, index, + CKB_SOURCE_OUTPUT, CKB_CELL_FIELD_CAPACITY); + if (ret != CKB_SUCCESS) { + return ret; + } + if (len != 8) { + return ERROR_SYSCALL; + } + if (output_capacity != input_capacity) { + return ERROR_INVALID_WITHDRAWING_CELL; + } + /* Check cell data */ + dao_header_data_t deposit_header; + ret = load_dao_header_data(index, CKB_SOURCE_INPUT, &deposit_header); + if (ret != CKB_SUCCESS) { + return ret; + } + uint64_t stored_block_number = 0; + len = 8; + ret = ckb_load_cell_data((unsigned char *)&stored_block_number, &len, 0, + index, CKB_SOURCE_OUTPUT); + if (ret != CKB_SUCCESS) { + return ret; + } + if (len != 8) { + return ERROR_SYSCALL; + } + if (stored_block_number != deposit_header.block_number) { + return ERROR_INVALID_WITHDRAWING_CELL; + } + return CKB_SUCCESS; +} + int main() { int ret; unsigned char script_hash[HASH_SIZE]; @@ -346,7 +394,7 @@ int main() { if (ret != CKB_SUCCESS) { return ERROR_SYSCALL; } - script_seg.ptr = (uint8_t*)script; + script_seg.ptr = (uint8_t *)script; script_seg.size = len; if (MolReader_Script_verify(&script_seg, false) != MOL_OK) { return ERROR_ENCODING; @@ -365,6 +413,10 @@ int main() { size_t index = 0; uint64_t input_capacities = 0; +#if MAX_OUTPUT_LENGTH > 64 +#error "Masking solutioin can only work with 64 outputs at most!" +#endif + uint64_t output_withdrawing_mask = 0; while (1) { int dao_input = 0; uint64_t capacity = 0; @@ -378,7 +430,7 @@ int main() { len = HASH_SIZE; ret = ckb_load_cell_by_field(current_script_hash, &len, 0, index, CKB_SOURCE_INPUT, CKB_CELL_FIELD_TYPE_HASH); - if ((ret == CKB_SUCCESS) && + if ((ret == CKB_SUCCESS) && len == HASH_SIZE && (memcmp(script_hash, current_script_hash, HASH_SIZE) == 0)) { dao_input = 1; } @@ -395,16 +447,53 @@ int main() { return ERROR_OVERFLOW; } } else { - /* DAO input, calculate its capacity */ - uint64_t dao_capacity = 0; - ret = calculate_dao_input_capacity(index, capacity, &dao_capacity); + /* + * First check whether current DAO input is deposited cell, + * or withdrawing cell. + */ + uint64_t block_number = 0; + len = 8; + ret = ckb_load_cell_data((unsigned char *)&block_number, &len, 0, index, + CKB_SOURCE_INPUT); if (ret != CKB_SUCCESS) { return ret; } + if (len != 8) { + return ERROR_SYSCALL; + } - if (__builtin_uaddl_overflow(input_capacities, dao_capacity, - &input_capacities)) { - return ERROR_OVERFLOW; + if (block_number > 0) { + /* + * Withdrawing cell, this DAO cell is at phase 2, where we can calculate + * and issue the extra tokens. + */ + uint64_t dao_capacity = 0; + ret = calculate_dao_input_capacity(index, block_number, capacity, + &dao_capacity); + if (ret != CKB_SUCCESS) { + return ret; + } + if (__builtin_uaddl_overflow(input_capacities, dao_capacity, + &input_capacities)) { + return ERROR_OVERFLOW; + } + } else { + /* + * Deposited cell, this DAO cell is at phase 1, we only need to check + * a withdrawing cell for current one is generated. For simplicity, we + * are limiting the code so the withdrawing cell must at the same index + * with the deposited cell. Due to the fact that one deposited cell is + * mapped to exactly one withdrawing cell, this would work fine here. + */ + ret = validate_withdrawing_cell(index, capacity, script_hash); + if (ret != CKB_SUCCESS) { + return ret; + } + output_withdrawing_mask |= (1 << index); + if (__builtin_uaddl_overflow(input_capacities, capacity, + &input_capacities)) { + return ERROR_OVERFLOW; + } } } @@ -424,12 +513,47 @@ int main() { if (ret != CKB_SUCCESS) { return ERROR_SYSCALL; } - + if (index >= MAX_OUTPUT_LENGTH) { + return ERROR_TOO_MANY_OUTPUT_CELLS; + } if (__builtin_uaddl_overflow(output_capacities, capacity, &output_capacities)) { return ERROR_OVERFLOW; } + unsigned char current_script_hash[HASH_SIZE]; + len = HASH_SIZE; + ret = ckb_load_cell_by_field(current_script_hash, &len, 0, index, + CKB_SOURCE_OUTPUT, CKB_CELL_FIELD_TYPE_HASH); + if ((ret == CKB_SUCCESS) && len == HASH_SIZE && + (memcmp(script_hash, current_script_hash, HASH_SIZE) == 0)) { + /* + * There are 2 types of cells in the transaction output cells with + * Nervos DAO type script: + * + * * Withdrawing DAO cells created in current transaction, those cells + * are marked via output_withdrawing_mask, they have already passed all + * validations, no further action is needed here. + * * Newly deposited DAO cells, for those cells, we need to validate the + * cell data part contains 8-byte data filled with 0. + */ + if ((output_withdrawing_mask & (1 << index)) == 0) { + uint64_t block_number = 0; + len = 8; + ret = ckb_load_cell_data((unsigned char *)&block_number, &len, 0, index, + CKB_SOURCE_OUTPUT); + if (ret != CKB_SUCCESS) { + return ret; + } + if (len != 8) { + return ERROR_SYSCALL; + } + if (block_number != 0) { + return ERROR_NEWLY_CREATED_CELL; + } + } + } + index += 1; } diff --git a/src/tests/dao.rs b/src/tests/dao.rs index fdbbc52..624e2cb 100644 --- a/src/tests/dao.rs +++ b/src/tests/dao.rs @@ -17,9 +17,13 @@ use ckb_types::{ }; use rand::{thread_rng, Rng}; +const ERROR_SYSCALL: i8 = -4; const ERROR_INVALID_WITHDRAW_BLOCK: i8 = -14; const ERROR_INCORRECT_CAPACITY: i8 = -15; const ERROR_INCORRECT_SINCE: i8 = -17; +const ERROR_TOO_MANY_OUTPUT_CELLS: i8 = -18; +const ERROR_NEWLY_CREATED_CELL: i8 = -19; +const ERROR_INVALID_WITHDRAWING_CELL: i8 = -20; fn cell_output_with_only_capacity(shannons: u64) -> CellOutput { CellOutput::new_builder() @@ -27,14 +31,18 @@ fn cell_output_with_only_capacity(shannons: u64) -> CellOutput { .build() } -fn script_cell(script_data: &Bytes) -> (CellOutput, OutPoint) { +fn generate_random_out_point() -> OutPoint { let tx_hash = { let mut rng = thread_rng(); let mut buf = [0u8; 32]; rng.fill(&mut buf); buf.pack() }; - let out_point = OutPoint::new(tx_hash, 0); + OutPoint::new(tx_hash, 0) +} + +fn script_cell(script_data: &Bytes) -> (CellOutput, OutPoint) { + let out_point = generate_random_out_point(); let cell = CellOutput::new_builder() .capacity( @@ -55,18 +63,35 @@ fn dao_code_hash() -> Byte32 { CellOutput::calc_data_hash(&DAO_BIN) } +fn gen_normal_cell( + dummy: &mut DummyDataLoader, + capacity: Capacity, + lock_args: Bytes, +) -> (CellOutput, OutPoint) { + let out_point = generate_random_out_point(); + + let lock = Script::new_builder() + .args(lock_args.pack()) + .code_hash(secp_code_hash()) + .hash_type(ScriptHashType::Data.into()) + .build(); + let cell = CellOutput::new_builder() + .capacity(capacity.pack()) + .lock(lock) + .build(); + dummy + .cells + .insert(out_point.clone(), (cell.clone(), Bytes::new())); + + (cell, out_point) +} + fn gen_dao_cell( dummy: &mut DummyDataLoader, capacity: Capacity, lock_args: Bytes, ) -> (CellOutput, OutPoint) { - let tx_hash = { - let mut rng = thread_rng(); - let mut buf = [0u8; 32]; - rng.fill(&mut buf); - buf.pack() - }; - let out_point = OutPoint::new(tx_hash, 0); + let out_point = generate_random_out_point(); let lock = Script::new_builder() .args(lock_args.pack()) @@ -213,12 +238,14 @@ fn test_dao_single_cell() { .epoches .insert(withdraw_header.hash(), withdraw_epoch.clone()); - let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::new()) + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1554); + let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::from(&b[..])) .out_point(previous_out_point.clone()) .transaction_info(TransactionInfo { - block_hash: deposit_header.hash(), - block_number: deposit_header.number(), - block_epoch: EpochNumberWithFraction::new(35, 554, 1000), + block_hash: withdraw_header.hash(), + block_number: withdraw_header.number(), + block_epoch: EpochNumberWithFraction::new(575, 610, 1100), index: 0, }) .build(); @@ -227,13 +254,13 @@ fn test_dao_single_cell() { let mut resolved_cell_deps = vec![]; let mut b = [0; 8]; - LittleEndian::write_u64(&mut b, 0); + LittleEndian::write_u64(&mut b, 1); let witness = WitnessArgs::new_builder() .type_(Bytes::from(&b[..]).pack()) .build(); let builder = TransactionBuilder::default() .input(CellInput::new(previous_out_point, 0x2003e8022a0002f3)) - .output(cell_output_with_only_capacity(123468045678)) + .output(cell_output_with_only_capacity(123468105678)) .output_data(Bytes::new().pack()) .header_dep(withdraw_header.hash()) .header_dep(deposit_header.hash()) @@ -280,12 +307,14 @@ fn test_dao_single_cell_epoch_edge() { .epoches .insert(withdraw_header.hash(), withdraw_epoch.clone()); - let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::new()) + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1554); + let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::from(&b[..])) .out_point(previous_out_point.clone()) .transaction_info(TransactionInfo { - block_hash: deposit_header.hash(), - block_number: deposit_header.number(), - block_epoch: EpochNumberWithFraction::new(35, 554, 1000), + block_hash: withdraw_header.hash(), + block_number: withdraw_header.number(), + block_epoch: EpochNumberWithFraction::new(575, 555, 1000), index: 0, }) .build(); @@ -294,13 +323,13 @@ fn test_dao_single_cell_epoch_edge() { let mut resolved_cell_deps = vec![]; let mut b = [0; 8]; - LittleEndian::write_u64(&mut b, 0); + LittleEndian::write_u64(&mut b, 1); let witness = WitnessArgs::new_builder() .type_(Bytes::from(&b[..]).pack()) .build(); let builder = TransactionBuilder::default() .input(CellInput::new(previous_out_point, 0x2003e8022a0002f3)) - .output(cell_output_with_only_capacity(123468045678)) + .output(cell_output_with_only_capacity(123468105678)) .output_data(Bytes::new().pack()) .header_dep(withdraw_header.hash()) .header_dep(deposit_header.hash()) @@ -347,12 +376,14 @@ fn test_dao_single_cell_start_of_epoch() { .epoches .insert(withdraw_header.hash(), withdraw_epoch.clone()); - let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::new()) + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1000); + let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::from(&b[..])) .out_point(previous_out_point.clone()) .transaction_info(TransactionInfo { - block_hash: deposit_header.hash(), - block_number: deposit_header.number(), - block_epoch: EpochNumberWithFraction::new(35, 0, 1000), + block_hash: withdraw_header.hash(), + block_number: withdraw_header.number(), + block_epoch: EpochNumberWithFraction::new(575, 1, 1100), index: 0, }) .build(); @@ -361,13 +392,13 @@ fn test_dao_single_cell_start_of_epoch() { let mut resolved_cell_deps = vec![]; let mut b = [0; 8]; - LittleEndian::write_u64(&mut b, 0); + LittleEndian::write_u64(&mut b, 1); let witness = WitnessArgs::new_builder() .type_(Bytes::from(&b[..]).pack()) .build(); let builder = TransactionBuilder::default() .input(CellInput::new(previous_out_point, 0x2003e800000002f3)) - .output(cell_output_with_only_capacity(123468045678)) + .output(cell_output_with_only_capacity(123468105678)) .output_data(Bytes::new().pack()) .header_dep(withdraw_header.hash()) .header_dep(deposit_header.hash()) @@ -414,12 +445,14 @@ fn test_dao_single_cell_end_of_epoch() { .epoches .insert(withdraw_header.hash(), withdraw_epoch.clone()); - let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::new()) + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1999); + let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::from(&b[..])) .out_point(previous_out_point.clone()) .transaction_info(TransactionInfo { - block_hash: deposit_header.hash(), - block_number: deposit_header.number(), - block_epoch: EpochNumberWithFraction::new(35, 999, 1000), + block_hash: withdraw_header.hash(), + block_number: withdraw_header.number(), + block_epoch: EpochNumberWithFraction::new(576, 0, 1100), index: 0, }) .build(); @@ -428,13 +461,13 @@ fn test_dao_single_cell_end_of_epoch() { let mut resolved_cell_deps = vec![]; let mut b = [0; 8]; - LittleEndian::write_u64(&mut b, 0); + LittleEndian::write_u64(&mut b, 1); let witness = WitnessArgs::new_builder() .type_(Bytes::from(&b[..]).pack()) .build(); let builder = TransactionBuilder::default() .input(CellInput::new(previous_out_point, 0x2003e803e70002f3)) - .output(cell_output_with_only_capacity(123468045678)) + .output(cell_output_with_only_capacity(123468105678)) .output_data(Bytes::new().pack()) .header_dep(withdraw_header.hash()) .header_dep(deposit_header.hash()) @@ -481,12 +514,14 @@ fn test_dao_single_cell_with_fees() { .epoches .insert(withdraw_header.hash(), withdraw_epoch.clone()); - let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::new()) + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1554); + let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::from(&b[..])) .out_point(previous_out_point.clone()) .transaction_info(TransactionInfo { - block_hash: deposit_header.hash(), - block_number: deposit_header.number(), - block_epoch: EpochNumberWithFraction::new(35, 554, 1000), + block_hash: withdraw_header.hash(), + block_number: withdraw_header.number(), + block_epoch: EpochNumberWithFraction::new(575, 610, 1100), index: 0, }) .build(); @@ -495,7 +530,7 @@ fn test_dao_single_cell_with_fees() { let mut resolved_cell_deps = vec![]; let mut b = [0; 8]; - LittleEndian::write_u64(&mut b, 0); + LittleEndian::write_u64(&mut b, 1); let witness = WitnessArgs::new_builder() .type_(Bytes::from(&b[..]).pack()) .build(); @@ -548,12 +583,14 @@ fn test_dao_single_cell_with_dao_output_cell() { .epoches .insert(withdraw_header.hash(), withdraw_epoch.clone()); - let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::new()) + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1554); + let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::from(&b[..])) .out_point(previous_out_point.clone()) .transaction_info(TransactionInfo { - block_hash: deposit_header.hash(), - block_number: deposit_header.number(), - block_epoch: EpochNumberWithFraction::new(35, 554, 1000), + block_hash: withdraw_header.hash(), + block_number: withdraw_header.number(), + block_epoch: EpochNumberWithFraction::new(575, 610, 1100), index: 0, }) .build(); @@ -562,7 +599,7 @@ fn test_dao_single_cell_with_dao_output_cell() { let mut resolved_cell_deps = vec![]; let mut b = [0; 8]; - LittleEndian::write_u64(&mut b, 0); + LittleEndian::write_u64(&mut b, 1); let witness = WitnessArgs::new_builder() .type_(Bytes::from(&b[..]).pack()) .build(); @@ -578,7 +615,7 @@ fn test_dao_single_cell_with_dao_output_cell() { .type_(Some(type_).pack()) .build(), ) - .output_data(Bytes::new().pack()) + .output_data(Bytes::from(&[0u8; 8][..]).pack()) .header_dep(withdraw_header.hash()) .header_dep(deposit_header.hash()) .witness(witness.as_bytes().pack()); @@ -643,21 +680,25 @@ fn test_dao_multiple_cells() { .epoches .insert(withdraw_header2.hash(), withdraw_epoch2.clone()); - let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::new()) + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1554); + let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::from(&b[..])) .out_point(previous_out_point.clone()) .transaction_info(TransactionInfo { - block_hash: deposit_header.hash(), - block_number: deposit_header.number(), - block_epoch: EpochNumberWithFraction::new(35, 554, 1000), + block_hash: withdraw_header.hash(), + block_number: withdraw_header.number(), + block_epoch: EpochNumberWithFraction::new(575, 610, 1100), index: 0, }) .build(); - let input_cell_meta2 = CellMetaBuilder::from_cell_output(cell2, Bytes::new()) + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1564); + let input_cell_meta2 = CellMetaBuilder::from_cell_output(cell2, Bytes::from(&b[..])) .out_point(previous_out_point2.clone()) .transaction_info(TransactionInfo { - block_hash: deposit_header2.hash(), - block_number: deposit_header2.number(), - block_epoch: EpochNumberWithFraction::new(35, 564, 1000), + block_hash: withdraw_header2.hash(), + block_number: withdraw_header2.number(), + block_epoch: EpochNumberWithFraction::new(575, 621, 1100), index: 0, }) .build(); @@ -666,20 +707,20 @@ fn test_dao_multiple_cells() { let mut resolved_cell_deps = vec![]; let mut b = [0; 8]; - LittleEndian::write_u64(&mut b, 0); + LittleEndian::write_u64(&mut b, 2); let witness = WitnessArgs::new_builder() .type_(Bytes::from(&b[..]).pack()) .build(); let mut b2 = [0; 8]; - LittleEndian::write_u64(&mut b2, 1); + LittleEndian::write_u64(&mut b2, 3); let witness2 = WitnessArgs::new_builder() .type_(Bytes::from(&b2[..]).pack()) .build(); let builder = TransactionBuilder::default() .input(CellInput::new(previous_out_point, 0x2003e800000002f4)) .input(CellInput::new(previous_out_point2, 0x2003e802340002f3)) - .output(cell_output_with_only_capacity(123468185678)) - .output(cell_output_with_only_capacity(123468186678)) + .output(cell_output_with_only_capacity(123468106670)) + .output(cell_output_with_only_capacity(123468105686)) .outputs_data(vec![Bytes::new(); 2].pack()) .header_dep(withdraw_header.hash()) .header_dep(withdraw_header2.hash()) @@ -708,7 +749,6 @@ fn test_dao_missing_deposit_header() { let mut data_loader = DummyDataLoader::new(); let (privkey, lock_args) = gen_lock(); - let deposit_header = gen_header(1554, 10000000, 35, 1000, 1000).0; let (withdraw_header, withdraw_epoch) = gen_header(2000610, 10001000, 575, 2000000, 1100); let (cell, previous_out_point) = gen_dao_cell( &mut data_loader, @@ -728,12 +768,14 @@ fn test_dao_missing_deposit_header() { .index(cell_out_point.index()) .build(); - let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::new()) + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1554); + let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::from(&b[..])) .out_point(previous_out_point.clone()) .transaction_info(TransactionInfo { - block_hash: deposit_header.hash(), - block_number: deposit_header.number(), - block_epoch: EpochNumberWithFraction::new(35, 554, 1000), + block_hash: withdraw_header.hash(), + block_number: withdraw_header.number(), + block_epoch: EpochNumberWithFraction::new(575, 610, 1100), index: 0, }) .build(); @@ -742,7 +784,7 @@ fn test_dao_missing_deposit_header() { let mut resolved_cell_deps = vec![]; let mut b = [0; 8]; - LittleEndian::write_u64(&mut b, 0); + LittleEndian::write_u64(&mut b, 1); let witness = WitnessArgs::new_builder() .type_(Bytes::from(&b[..]).pack()) .build(); @@ -767,7 +809,7 @@ fn test_dao_missing_deposit_header() { let verify_result = TransactionScriptsVerifier::new(&rtx, &data_loader).verify(MAX_CYCLES); assert_error_eq!( verify_result.unwrap_err(), - ScriptError::ValidationFailure(2), + ScriptError::ValidationFailure(1), ); } @@ -777,6 +819,7 @@ fn test_dao_missing_withdraw_header() { let (privkey, lock_args) = gen_lock(); let (deposit_header, deposit_epoch) = gen_header(1554, 10000000, 35, 1000, 1000); + let (withdraw_header, _withdraw_epoch) = gen_header(2000610, 10001000, 575, 2000000, 1100); let (cell, previous_out_point) = gen_dao_cell( &mut data_loader, Capacity::shannons(123456780000), @@ -790,12 +833,14 @@ fn test_dao_missing_withdraw_header() { .epoches .insert(deposit_header.hash(), deposit_epoch.clone()); - let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::new()) + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1554); + let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::from(&b[..])) .out_point(previous_out_point.clone()) .transaction_info(TransactionInfo { - block_hash: deposit_header.hash(), - block_number: deposit_header.number(), - block_epoch: EpochNumberWithFraction::new(35, 554, 1000), + block_hash: withdraw_header.hash(), + block_number: withdraw_header.number(), + block_epoch: EpochNumberWithFraction::new(575, 610, 1100), index: 0, }) .build(); @@ -804,7 +849,7 @@ fn test_dao_missing_withdraw_header() { let mut resolved_cell_deps = vec![]; let mut b = [0; 8]; - LittleEndian::write_u64(&mut b, 1); + LittleEndian::write_u64(&mut b, 0); let witness = WitnessArgs::new_builder() .type_(Bytes::from(&b[..]).pack()) .build(); @@ -829,12 +874,12 @@ fn test_dao_missing_withdraw_header() { let verify_result = TransactionScriptsVerifier::new(&rtx, &data_loader).verify(MAX_CYCLES); assert_error_eq!( verify_result.unwrap_err(), - ScriptError::ValidationFailure(1), + ScriptError::ValidationFailure(2), ); } #[test] -fn test_dao_invalid_withdraw_header() { +fn test_dao_invalid_deposit_header() { let mut data_loader = DummyDataLoader::new(); let (privkey, lock_args) = gen_lock(); @@ -859,12 +904,14 @@ fn test_dao_invalid_withdraw_header() { .epoches .insert(withdraw_header.hash(), withdraw_epoch.clone()); - let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::new()) + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1554); + let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::from(&b[..])) .out_point(previous_out_point.clone()) .transaction_info(TransactionInfo { - block_hash: deposit_header.hash(), - block_number: deposit_header.number(), - block_epoch: EpochNumberWithFraction::new(35, 554, 1000), + block_hash: withdraw_header.hash(), + block_number: withdraw_header.number(), + block_epoch: EpochNumberWithFraction::new(575, 610, 1100), index: 0, }) .build(); @@ -881,8 +928,7 @@ fn test_dao_invalid_withdraw_header() { .input(CellInput::new(previous_out_point, 0x2003e80000000320)) .output(cell_output_with_only_capacity(123468045678)) .output_data(Bytes::new().pack()) - .header_dep(deposit_header.hash()) - .header_dep(deposit_header.hash()) + .header_dep(withdraw_header.hash()) .witness(witness.as_bytes().pack()); let (tx, mut resolved_cell_deps2) = complete_tx(&mut data_loader, builder); let tx = sign_tx(tx, &privkey); @@ -929,12 +975,14 @@ fn test_dao_invalid_withdraw_amount() { .epoches .insert(withdraw_header.hash(), withdraw_epoch.clone()); - let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::new()) + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1554); + let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::from(&b[..])) .out_point(previous_out_point.clone()) .transaction_info(TransactionInfo { - block_hash: deposit_header.hash(), - block_number: deposit_header.number(), - block_epoch: EpochNumberWithFraction::new(35, 554, 1000), + block_hash: withdraw_header.hash(), + block_number: withdraw_header.number(), + block_epoch: EpochNumberWithFraction::new(575, 610, 1100), index: 0, }) .build(); @@ -943,7 +991,7 @@ fn test_dao_invalid_withdraw_amount() { let mut resolved_cell_deps = vec![]; let mut b = [0; 8]; - LittleEndian::write_u64(&mut b, 0); + LittleEndian::write_u64(&mut b, 1); let witness = WitnessArgs::new_builder() .type_(Bytes::from(&b[..]).pack()) .build(); @@ -999,12 +1047,14 @@ fn test_dao_invalid_since() { .epoches .insert(withdraw_header.hash(), withdraw_epoch.clone()); - let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::new()) + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1554); + let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::from(&b[..])) .out_point(previous_out_point.clone()) .transaction_info(TransactionInfo { - block_hash: deposit_header.hash(), - block_number: deposit_header.number(), - block_epoch: EpochNumberWithFraction::new(35, 554, 1000), + block_hash: withdraw_header.hash(), + block_number: withdraw_header.number(), + block_epoch: EpochNumberWithFraction::new(575, 610, 1100), index: 0, }) .build(); @@ -1013,7 +1063,7 @@ fn test_dao_invalid_since() { let mut resolved_cell_deps = vec![]; let mut b = [0; 8]; - LittleEndian::write_u64(&mut b, 0); + LittleEndian::write_u64(&mut b, 1); let witness = WitnessArgs::new_builder() .type_(Bytes::from(&b[..]).pack()) .build(); @@ -1043,3 +1093,730 @@ fn test_dao_invalid_since() { ScriptError::ValidationFailure(ERROR_INCORRECT_SINCE), ); } + +#[test] +fn test_dao_invalid_withdraw_from_deposited_cell() { + let mut data_loader = DummyDataLoader::new(); + let (privkey, lock_args) = gen_lock(); + + let (deposit_header, deposit_epoch) = gen_header(1554, 10000000, 35, 1000, 1000); + let (withdraw_header, withdraw_epoch) = gen_header(2000610, 10001000, 575, 2000000, 1100); + let (cell, previous_out_point) = gen_dao_cell( + &mut data_loader, + Capacity::shannons(123456780000), + lock_args, + ); + + data_loader + .headers + .insert(deposit_header.hash(), deposit_header.clone()); + data_loader + .headers + .insert(withdraw_header.hash(), withdraw_header.clone()); + data_loader + .epoches + .insert(deposit_header.hash(), deposit_epoch.clone()); + data_loader + .epoches + .insert(withdraw_header.hash(), withdraw_epoch.clone()); + + let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::from(&[0; 8][..])) + .out_point(previous_out_point.clone()) + .transaction_info(TransactionInfo { + block_hash: withdraw_header.hash(), + block_number: withdraw_header.number(), + block_epoch: EpochNumberWithFraction::new(575, 610, 1100), + index: 0, + }) + .build(); + + let resolved_inputs = vec![input_cell_meta]; + let mut resolved_cell_deps = vec![]; + + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1); + let witness = WitnessArgs::new_builder() + .type_(Bytes::from(&b[..]).pack()) + .build(); + let builder = TransactionBuilder::default() + .input(CellInput::new(previous_out_point, 0x2003e8022a0002f3)) + .output(cell_output_with_only_capacity(123468105678)) + .output_data(Bytes::new().pack()) + .header_dep(withdraw_header.hash()) + .header_dep(deposit_header.hash()) + .witness(witness.as_bytes().pack()); + let (tx, mut resolved_cell_deps2) = complete_tx(&mut data_loader, builder); + let tx = sign_tx(tx, &privkey); + for dep in resolved_cell_deps2.drain(..) { + resolved_cell_deps.push(dep); + } + let rtx = ResolvedTransaction { + transaction: tx, + resolved_inputs, + resolved_cell_deps, + resolved_dep_groups: vec![], + }; + + let verify_result = TransactionScriptsVerifier::new(&rtx, &data_loader).verify(MAX_CYCLES); + assert_error_eq!( + verify_result.unwrap_err(), + ScriptError::ValidationFailure(ERROR_INVALID_WITHDRAWING_CELL), + ); +} + +#[test] +fn test_dao_deposit_cell() { + let mut data_loader = DummyDataLoader::new(); + let (privkey, lock_args) = gen_lock(); + + let (input_cell, previous_out_point) = gen_normal_cell( + &mut data_loader, + Capacity::shannons(1234567890), + lock_args.clone(), + ); + let (withdraw_header, _) = gen_header(2000610, 10001000, 575, 2000000, 1100); + let input_cell_meta = CellMetaBuilder::from_cell_output(input_cell, Bytes::new()) + .out_point(previous_out_point.clone()) + .transaction_info(TransactionInfo { + block_hash: withdraw_header.hash(), + block_number: withdraw_header.number(), + block_epoch: EpochNumberWithFraction::new(575, 610, 1100), + index: 0, + }) + .build(); + + let resolved_inputs = vec![input_cell_meta]; + let mut resolved_cell_deps = vec![]; + + let (output_cell, _) = + gen_dao_cell(&mut data_loader, Capacity::shannons(1234567890), lock_args); + + let witness = WitnessArgs::new_builder().build(); + let builder = TransactionBuilder::default() + .input(CellInput::new(previous_out_point, 0)) + .output(output_cell) + .output_data(Bytes::from(&[0; 8][..]).pack()) + .witness(witness.as_bytes().pack()); + let (tx, mut resolved_cell_deps2) = complete_tx(&mut data_loader, builder); + let tx = sign_tx(tx, &privkey); + for dep in resolved_cell_deps2.drain(..) { + resolved_cell_deps.push(dep); + } + let rtx = ResolvedTransaction { + transaction: tx, + resolved_inputs, + resolved_cell_deps, + resolved_dep_groups: vec![], + }; + + let verify_result = TransactionScriptsVerifier::new(&rtx, &data_loader).verify(MAX_CYCLES); + verify_result.expect("pass verification"); +} + +#[test] +fn test_dao_deposit_invalid_cell() { + let mut data_loader = DummyDataLoader::new(); + let (privkey, lock_args) = gen_lock(); + + let (input_cell, previous_out_point) = gen_normal_cell( + &mut data_loader, + Capacity::shannons(1234567890), + lock_args.clone(), + ); + let (withdraw_header, _) = gen_header(2000610, 10001000, 575, 2000000, 1100); + let input_cell_meta = CellMetaBuilder::from_cell_output(input_cell, Bytes::new()) + .out_point(previous_out_point.clone()) + .transaction_info(TransactionInfo { + block_hash: withdraw_header.hash(), + block_number: withdraw_header.number(), + block_epoch: EpochNumberWithFraction::new(575, 610, 1100), + index: 0, + }) + .build(); + + let resolved_inputs = vec![input_cell_meta]; + let mut resolved_cell_deps = vec![]; + + let (output_cell, _) = + gen_dao_cell(&mut data_loader, Capacity::shannons(1234567890), lock_args); + + let witness = WitnessArgs::new_builder().build(); + let builder = TransactionBuilder::default() + .input(CellInput::new(previous_out_point, 0)) + .output(output_cell) + .output_data(Bytes::from(&[1; 8][..]).pack()) + .witness(witness.as_bytes().pack()); + let (tx, mut resolved_cell_deps2) = complete_tx(&mut data_loader, builder); + let tx = sign_tx(tx, &privkey); + for dep in resolved_cell_deps2.drain(..) { + resolved_cell_deps.push(dep); + } + let rtx = ResolvedTransaction { + transaction: tx, + resolved_inputs, + resolved_cell_deps, + resolved_dep_groups: vec![], + }; + + let verify_result = TransactionScriptsVerifier::new(&rtx, &data_loader).verify(MAX_CYCLES); + assert_error_eq!( + verify_result.unwrap_err(), + ScriptError::ValidationFailure(ERROR_NEWLY_CREATED_CELL), + ); +} + +#[test] +fn test_dao_deposit_cell_missing_data() { + let mut data_loader = DummyDataLoader::new(); + let (privkey, lock_args) = gen_lock(); + + let (input_cell, previous_out_point) = gen_normal_cell( + &mut data_loader, + Capacity::shannons(1234567890), + lock_args.clone(), + ); + let (withdraw_header, _) = gen_header(2000610, 10001000, 575, 2000000, 1100); + let input_cell_meta = CellMetaBuilder::from_cell_output(input_cell, Bytes::new()) + .out_point(previous_out_point.clone()) + .transaction_info(TransactionInfo { + block_hash: withdraw_header.hash(), + block_number: withdraw_header.number(), + block_epoch: EpochNumberWithFraction::new(575, 610, 1100), + index: 0, + }) + .build(); + + let resolved_inputs = vec![input_cell_meta]; + let mut resolved_cell_deps = vec![]; + + let (output_cell, _) = + gen_dao_cell(&mut data_loader, Capacity::shannons(1234567890), lock_args); + + let witness = WitnessArgs::new_builder().build(); + let builder = TransactionBuilder::default() + .input(CellInput::new(previous_out_point, 0)) + .output(output_cell) + .output_data(Bytes::new().pack()) + .witness(witness.as_bytes().pack()); + let (tx, mut resolved_cell_deps2) = complete_tx(&mut data_loader, builder); + let tx = sign_tx(tx, &privkey); + for dep in resolved_cell_deps2.drain(..) { + resolved_cell_deps.push(dep); + } + let rtx = ResolvedTransaction { + transaction: tx, + resolved_inputs, + resolved_cell_deps, + resolved_dep_groups: vec![], + }; + + let verify_result = TransactionScriptsVerifier::new(&rtx, &data_loader).verify(MAX_CYCLES); + assert_error_eq!( + verify_result.unwrap_err(), + ScriptError::ValidationFailure(ERROR_SYSCALL), + ); +} + +#[test] +fn test_dao_create_withdrawing_cell() { + let mut data_loader = DummyDataLoader::new(); + let (privkey, lock_args) = gen_lock(); + + let (deposit_header, deposit_epoch) = gen_header(1554, 10000000, 35, 1000, 1000); + let (cell, previous_out_point) = gen_dao_cell( + &mut data_loader, + Capacity::shannons(123456780000), + lock_args.clone(), + ); + + data_loader + .headers + .insert(deposit_header.hash(), deposit_header.clone()); + data_loader + .epoches + .insert(deposit_header.hash(), deposit_epoch.clone()); + + let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::from(&[0; 8][..])) + .out_point(previous_out_point.clone()) + .transaction_info(TransactionInfo { + block_hash: deposit_header.hash(), + block_number: deposit_header.number(), + block_epoch: EpochNumberWithFraction::new(35, 554, 1000), + index: 0, + }) + .build(); + + let resolved_inputs = vec![input_cell_meta]; + let mut resolved_cell_deps = vec![]; + + let (output_cell, _) = gen_dao_cell( + &mut data_loader, + Capacity::shannons(123456780000), + lock_args, + ); + + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1554); + let witness = WitnessArgs::new_builder().build(); + let builder = TransactionBuilder::default() + .input(CellInput::new(previous_out_point, 0)) + .output(output_cell) + .output_data(Bytes::from(&b[..]).pack()) + .header_dep(deposit_header.hash()) + .witness(witness.as_bytes().pack()); + let (tx, mut resolved_cell_deps2) = complete_tx(&mut data_loader, builder); + let tx = sign_tx(tx, &privkey); + for dep in resolved_cell_deps2.drain(..) { + resolved_cell_deps.push(dep); + } + let rtx = ResolvedTransaction { + transaction: tx, + resolved_inputs, + resolved_cell_deps, + resolved_dep_groups: vec![], + }; + + let verify_result = TransactionScriptsVerifier::new(&rtx, &data_loader).verify(MAX_CYCLES); + verify_result.expect("pass verification"); +} + +#[test] +fn test_dao_create_withdrawing_cell_with_invalid_lock() { + let mut data_loader = DummyDataLoader::new(); + let (privkey, lock_args) = gen_lock(); + let (_, lock_args2) = gen_lock(); + + let (deposit_header, deposit_epoch) = gen_header(1554, 10000000, 35, 1000, 1000); + let (cell, previous_out_point) = gen_dao_cell( + &mut data_loader, + Capacity::shannons(123456780000), + lock_args.clone(), + ); + + data_loader + .headers + .insert(deposit_header.hash(), deposit_header.clone()); + data_loader + .epoches + .insert(deposit_header.hash(), deposit_epoch.clone()); + + let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::from(&[0; 8][..])) + .out_point(previous_out_point.clone()) + .transaction_info(TransactionInfo { + block_hash: deposit_header.hash(), + block_number: deposit_header.number(), + block_epoch: EpochNumberWithFraction::new(35, 554, 1000), + index: 0, + }) + .build(); + + let resolved_inputs = vec![input_cell_meta]; + let mut resolved_cell_deps = vec![]; + + let (output_cell, _) = gen_dao_cell( + &mut data_loader, + Capacity::shannons(123456780000), + lock_args2, + ); + + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1554); + let witness = WitnessArgs::new_builder().build(); + let builder = TransactionBuilder::default() + .input(CellInput::new(previous_out_point, 0)) + .output(output_cell) + .output_data(Bytes::from(&b[..]).pack()) + .header_dep(deposit_header.hash()) + .witness(witness.as_bytes().pack()); + let (tx, mut resolved_cell_deps2) = complete_tx(&mut data_loader, builder); + let tx = sign_tx(tx, &privkey); + for dep in resolved_cell_deps2.drain(..) { + resolved_cell_deps.push(dep); + } + let rtx = ResolvedTransaction { + transaction: tx, + resolved_inputs, + resolved_cell_deps, + resolved_dep_groups: vec![], + }; + + let verify_result = TransactionScriptsVerifier::new(&rtx, &data_loader).verify(MAX_CYCLES); + assert_error_eq!( + verify_result.unwrap_err(), + ScriptError::ValidationFailure(ERROR_INVALID_WITHDRAWING_CELL), + ); +} + +#[test] +fn test_dao_create_withdrawing_cell_with_invalid_type() { + let mut data_loader = DummyDataLoader::new(); + let (privkey, lock_args) = gen_lock(); + + let (deposit_header, deposit_epoch) = gen_header(1554, 10000000, 35, 1000, 1000); + let (cell, previous_out_point) = gen_dao_cell( + &mut data_loader, + Capacity::shannons(123456780000), + lock_args.clone(), + ); + + data_loader + .headers + .insert(deposit_header.hash(), deposit_header.clone()); + data_loader + .epoches + .insert(deposit_header.hash(), deposit_epoch.clone()); + + let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::from(&[0; 8][..])) + .out_point(previous_out_point.clone()) + .transaction_info(TransactionInfo { + block_hash: deposit_header.hash(), + block_number: deposit_header.number(), + block_epoch: EpochNumberWithFraction::new(35, 554, 1000), + index: 0, + }) + .build(); + + let resolved_inputs = vec![input_cell_meta]; + let mut resolved_cell_deps = vec![]; + + let (output_cell, _) = gen_normal_cell( + &mut data_loader, + Capacity::shannons(123456780000), + lock_args, + ); + + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1554); + let witness = WitnessArgs::new_builder().build(); + let builder = TransactionBuilder::default() + .input(CellInput::new(previous_out_point, 0)) + .output(output_cell) + .output_data(Bytes::from(&b[..]).pack()) + .header_dep(deposit_header.hash()) + .witness(witness.as_bytes().pack()); + let (tx, mut resolved_cell_deps2) = complete_tx(&mut data_loader, builder); + let tx = sign_tx(tx, &privkey); + for dep in resolved_cell_deps2.drain(..) { + resolved_cell_deps.push(dep); + } + let rtx = ResolvedTransaction { + transaction: tx, + resolved_inputs, + resolved_cell_deps, + resolved_dep_groups: vec![], + }; + + let verify_result = TransactionScriptsVerifier::new(&rtx, &data_loader).verify(MAX_CYCLES); + assert_error_eq!( + verify_result.unwrap_err(), + ScriptError::ValidationFailure(2), + ); +} + +#[test] +fn test_dao_create_withdrawing_cell_with_invalid_data() { + let mut data_loader = DummyDataLoader::new(); + let (privkey, lock_args) = gen_lock(); + + let (deposit_header, deposit_epoch) = gen_header(1554, 10000000, 35, 1000, 1000); + let (cell, previous_out_point) = gen_dao_cell( + &mut data_loader, + Capacity::shannons(123456780000), + lock_args.clone(), + ); + + data_loader + .headers + .insert(deposit_header.hash(), deposit_header.clone()); + data_loader + .epoches + .insert(deposit_header.hash(), deposit_epoch.clone()); + + let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::from(&[0; 8][..])) + .out_point(previous_out_point.clone()) + .transaction_info(TransactionInfo { + block_hash: deposit_header.hash(), + block_number: deposit_header.number(), + block_epoch: EpochNumberWithFraction::new(35, 554, 1000), + index: 0, + }) + .build(); + + let resolved_inputs = vec![input_cell_meta]; + let mut resolved_cell_deps = vec![]; + + let (output_cell, _) = gen_dao_cell( + &mut data_loader, + Capacity::shannons(123456780000), + lock_args, + ); + + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1590); + let witness = WitnessArgs::new_builder().build(); + let builder = TransactionBuilder::default() + .input(CellInput::new(previous_out_point, 0)) + .output(output_cell) + .output_data(Bytes::from(&b[..]).pack()) + .header_dep(deposit_header.hash()) + .witness(witness.as_bytes().pack()); + let (tx, mut resolved_cell_deps2) = complete_tx(&mut data_loader, builder); + let tx = sign_tx(tx, &privkey); + for dep in resolved_cell_deps2.drain(..) { + resolved_cell_deps.push(dep); + } + let rtx = ResolvedTransaction { + transaction: tx, + resolved_inputs, + resolved_cell_deps, + resolved_dep_groups: vec![], + }; + + let verify_result = TransactionScriptsVerifier::new(&rtx, &data_loader).verify(MAX_CYCLES); + assert_error_eq!( + verify_result.unwrap_err(), + ScriptError::ValidationFailure(ERROR_INVALID_WITHDRAWING_CELL), + ); +} + +#[test] +fn test_dao_create_withdrawing_cell_with_invalid_capacity() { + let mut data_loader = DummyDataLoader::new(); + let (privkey, lock_args) = gen_lock(); + + let (deposit_header, deposit_epoch) = gen_header(1554, 10000000, 35, 1000, 1000); + let (cell, previous_out_point) = gen_dao_cell( + &mut data_loader, + Capacity::shannons(123456780000), + lock_args.clone(), + ); + + data_loader + .headers + .insert(deposit_header.hash(), deposit_header.clone()); + data_loader + .epoches + .insert(deposit_header.hash(), deposit_epoch.clone()); + + let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::from(&[0; 8][..])) + .out_point(previous_out_point.clone()) + .transaction_info(TransactionInfo { + block_hash: deposit_header.hash(), + block_number: deposit_header.number(), + block_epoch: EpochNumberWithFraction::new(35, 554, 1000), + index: 0, + }) + .build(); + + let resolved_inputs = vec![input_cell_meta]; + let mut resolved_cell_deps = vec![]; + + let (output_cell, _) = + gen_dao_cell(&mut data_loader, Capacity::shannons(1234567800), lock_args); + + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1554); + let witness = WitnessArgs::new_builder().build(); + let builder = TransactionBuilder::default() + .input(CellInput::new(previous_out_point, 0)) + .output(output_cell) + .output_data(Bytes::from(&b[..]).pack()) + .header_dep(deposit_header.hash()) + .witness(witness.as_bytes().pack()); + let (tx, mut resolved_cell_deps2) = complete_tx(&mut data_loader, builder); + let tx = sign_tx(tx, &privkey); + for dep in resolved_cell_deps2.drain(..) { + resolved_cell_deps.push(dep); + } + let rtx = ResolvedTransaction { + transaction: tx, + resolved_inputs, + resolved_cell_deps, + resolved_dep_groups: vec![], + }; + + let verify_result = TransactionScriptsVerifier::new(&rtx, &data_loader).verify(MAX_CYCLES); + assert_error_eq!( + verify_result.unwrap_err(), + ScriptError::ValidationFailure(ERROR_INVALID_WITHDRAWING_CELL), + ); +} + +#[test] +fn test_dao_too_many_output_cells() { + let mut data_loader = DummyDataLoader::new(); + let (privkey, lock_args) = gen_lock(); + + let (deposit_header, deposit_epoch) = gen_header(1554, 10000000, 35, 1000, 1000); + let (withdraw_header, withdraw_epoch) = gen_header(2000610, 10001000, 575, 2000000, 1100); + let (cell, previous_out_point) = gen_dao_cell( + &mut data_loader, + Capacity::shannons(1234567800 * 65), + lock_args, + ); + + data_loader + .headers + .insert(deposit_header.hash(), deposit_header.clone()); + data_loader + .headers + .insert(withdraw_header.hash(), withdraw_header.clone()); + data_loader + .epoches + .insert(deposit_header.hash(), deposit_epoch.clone()); + data_loader + .epoches + .insert(withdraw_header.hash(), withdraw_epoch.clone()); + + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1554); + let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::from(&b[..])) + .out_point(previous_out_point.clone()) + .transaction_info(TransactionInfo { + block_hash: withdraw_header.hash(), + block_number: withdraw_header.number(), + block_epoch: EpochNumberWithFraction::new(575, 610, 1100), + index: 0, + }) + .build(); + + let resolved_inputs = vec![input_cell_meta]; + let mut resolved_cell_deps = vec![]; + + let outputs = vec![cell_output_with_only_capacity(123468105678); 65]; + let outputs_data = vec![Bytes::new().pack(); 65]; + + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1); + let witness = WitnessArgs::new_builder() + .type_(Bytes::from(&b[..]).pack()) + .build(); + let builder = TransactionBuilder::default() + .input(CellInput::new(previous_out_point, 0x2003e8022a0002f3)) + .outputs(outputs) + .outputs_data(outputs_data) + .header_dep(withdraw_header.hash()) + .header_dep(deposit_header.hash()) + .witness(witness.as_bytes().pack()); + let (tx, mut resolved_cell_deps2) = complete_tx(&mut data_loader, builder); + let tx = sign_tx(tx, &privkey); + for dep in resolved_cell_deps2.drain(..) { + resolved_cell_deps.push(dep); + } + let rtx = ResolvedTransaction { + transaction: tx, + resolved_inputs, + resolved_cell_deps, + resolved_dep_groups: vec![], + }; + + let verify_result = TransactionScriptsVerifier::new(&rtx, &data_loader).verify(MAX_CYCLES); + assert_error_eq!( + verify_result.unwrap_err(), + ScriptError::ValidationFailure(ERROR_TOO_MANY_OUTPUT_CELLS), + ); +} + +#[test] +fn test_dao_all_dao_actions() { + let mut data_loader = DummyDataLoader::new(); + let (privkey, lock_args) = gen_lock(); + + let (deposit_header, deposit_epoch) = gen_header(1554, 10000000, 35, 1000, 1000); + let (withdraw_header, withdraw_epoch) = gen_header(2000610, 10001000, 575, 2000000, 1100); + let (cell, previous_out_point) = gen_dao_cell( + &mut data_loader, + Capacity::shannons(123456780000), + lock_args.clone(), + ); + let (cell2, previous_out_point2) = gen_dao_cell( + &mut data_loader, + Capacity::shannons(1234567890), + lock_args.clone(), + ); + + data_loader + .headers + .insert(deposit_header.hash(), deposit_header.clone()); + data_loader + .headers + .insert(withdraw_header.hash(), withdraw_header.clone()); + data_loader + .epoches + .insert(deposit_header.hash(), deposit_epoch.clone()); + data_loader + .epoches + .insert(withdraw_header.hash(), withdraw_epoch.clone()); + + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1554); + let input_cell_meta = CellMetaBuilder::from_cell_output(cell, Bytes::from(&b[..])) + .out_point(previous_out_point.clone()) + .transaction_info(TransactionInfo { + block_hash: withdraw_header.hash(), + block_number: withdraw_header.number(), + block_epoch: EpochNumberWithFraction::new(575, 610, 1100), + index: 0, + }) + .build(); + let input_cell_meta2 = CellMetaBuilder::from_cell_output(cell2, Bytes::from(&[0; 8][..])) + .out_point(previous_out_point2.clone()) + .transaction_info(TransactionInfo { + block_hash: deposit_header.hash(), + block_number: deposit_header.number(), + block_epoch: EpochNumberWithFraction::new(35, 554, 1000), + index: 0, + }) + .build(); + + let resolved_inputs = vec![input_cell_meta, input_cell_meta2]; + let mut resolved_cell_deps = vec![]; + + let (withdrawing_cell, _) = + gen_dao_cell(&mut data_loader, Capacity::shannons(1234567890), lock_args); + + let mut b = [0; 8]; + LittleEndian::write_u64(&mut b, 1); + let mut b2 = [0; 8]; + LittleEndian::write_u64(&mut b2, 1554); + let witness = WitnessArgs::new_builder() + .type_(Bytes::from(&b[..]).pack()) + .build(); + let type_ = Script::new_builder() + .code_hash(dao_code_hash()) + .hash_type(ScriptHashType::Data.into()) + .build(); + let builder = TransactionBuilder::default() + .input(CellInput::new(previous_out_point, 0x2003e8022a0002f3)) + .input(CellInput::new(previous_out_point2, 0)) + .output(cell_output_with_only_capacity(61734052839)) + .output(withdrawing_cell) + .output( + cell_output_with_only_capacity(61734052839) + .as_builder() + .type_(Some(type_).pack()) + .build(), + ) + .output_data(Bytes::new().pack()) + .output_data(Bytes::from(&b2[..]).pack()) + .output_data(Bytes::from(&[0u8; 8][..]).pack()) + .header_dep(withdraw_header.hash()) + .header_dep(deposit_header.hash()) + .witness(witness.as_bytes().pack()) + .witness(WitnessArgs::new_builder().build().as_bytes().pack()) + .witness(WitnessArgs::new_builder().build().as_bytes().pack()); + let (tx, mut resolved_cell_deps2) = complete_tx(&mut data_loader, builder); + let tx = sign_tx(tx, &privkey); + for dep in resolved_cell_deps2.drain(..) { + resolved_cell_deps.push(dep); + } + let rtx = ResolvedTransaction { + transaction: tx, + resolved_inputs, + resolved_cell_deps, + resolved_dep_groups: vec![], + }; + + let verify_result = TransactionScriptsVerifier::new(&rtx, &data_loader).verify(MAX_CYCLES); + verify_result.expect("pass verification"); +} From bd4131a1498942ecd5065d75a9da55c31cf71075 Mon Sep 17 00:00:00 2001 From: Xuejie Xiao Date: Mon, 28 Oct 2019 02:35:49 +0000 Subject: [PATCH 2/2] fix: Comment --- c/dao.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/c/dao.c b/c/dao.c index 79fc9d8..e5beac4 100644 --- a/c/dao.c +++ b/c/dao.c @@ -46,8 +46,8 @@ #define EPOCH_LENGTH_MASK ((1 << EPOCH_LENGTH_BITS) - 1) /* - * Fetch deposit header hash from the last 8 bytes - * of witness. Kept as a separate function so witness buffer + * Fetch deposit header hash from the input type part in witness, it should be + * exactly 8 bytes long. Kept as a separate function so witness buffer * can be cleaned as soon as it is not needed. */ static int extract_deposit_header_index(size_t input_index, size_t *index) {